/*!
 * 后台主题皮肤（视觉层，用来覆盖 Tabler）
 *
 * ============================================================================
 * 怎么加载
 * ============================================================================
 * 顺序见 layouts/_head.blade.php：
 *
 *   tabler.min.css → rfi-icons.css → rageframe.css → 【本文件】 → 主题内联 :root
 *
 * 必须排在 Tabler 与 rageframe.css **之后**：本文件靠「同权重后置即胜出」覆盖，
 * 只有少数必须压过 Bootstrap 工具类（.bg-white / .text-bg-* 这些自带 !important 的）
 * 的地方才用 !important，用到的地方都逐条注明了原因。
 * 也必须排在主题内联 <style> 之前：那个 <style> 里只有三行主色源（--rf-accent
 * 基色 / 悬浮 / 点击三档），
 * 排它前面才能被它覆盖 —— 用户在主题面板换主色（蓝/绿/青/紫/橙/红）时，
 * 主色仍然以用户选择为准。
 *
 * ============================================================================
 * 职责边界
 * ============================================================================
 *   归属判定（theme.css 只写主题相关、rageframe.css 只写全局外壳、其他一律回
 *   组件 / 页面自己的地方）见 docs/Guidelines.md §七.2。
 *   本文件自己的纪律：不改任何布局尺寸 —— 唯一碰到的几处内边距都是为了
 *   配合新圆角，都在规则上方注明了。
 *
 * ============================================================================
 * 两套皮：浅色（默认）/ 深色（同一套关系的夜航版本）
 * ============================================================================
 *   浅色  页面纯色 #f5f7f9 / 卡片纯白 + 8px 圆角 + 极淡中性影 /
 *         文字 #4e5969（Arco gray-8，正文与标题同色）/ 次要 #6b7785 /
 *         提示 #86909c / 描边 #e5e6eb（文字与描边整条链都是 Arco 中性灰阶，不带色相）
 *   深色  墨蓝夜航 —— 页面底 / 栅栏直接取 iView 深色真身（#191a23 / #101117），
 *         同一套关系的夜里版本（主色两套皮都取用户选的那个，不在这里另定）
 *
 * 深浅切换走既有的主题系统（<html data-bs-theme>）。注意外壳里还挂着**两个子树级**
 * 的 data-bs-theme：侧栏与栅栏各自带一份，所以「浅色页面 + 深色侧栏」这种组合下，
 * 侧栏那块拿到的是深色令牌 —— 本文件把令牌定义在 [data-bs-theme] 上而不是只挂在
 * :root，正是为了让这种组合自动成立，不必为侧栏再写一套规则。
 *
 * ============================================================================
 * 三条改色纪律
 * ============================================================================
 *   --rf-accent     **主色的唯一来源，三档**：基色 / 悬浮 / 点击
 *                   （--rf-accent / -hover / -active）。值取 Arco 色板的 6 / 5 / 7 号，
 *                   全项目只有两处定义它们：这里给一组兜底默认值、
 *                   服务端在 <style> 里写用户当前选择（后置 → 用户值胜出）。
 *                   ⚠️ 三档必须**一起换**（前端切换、服务端注入、演示页工具条三处同理）：
 *                      只换基色的话，实心按钮悬停 / 按住还是默认蓝。
 *   --tblr-primary  主色的**落点**（两套皮各自从 --rf-accent 派生，将来要给深色
 *                   单独提亮就只改这两行）。组件规则里**不要**把它写死成某个蓝：
 *                   用户换主色时靠它变；需要浅底 / 淡描边的地方一律用 color-mix()
 *                   从它派生（color-mix 是本项目 Tabler 1.6.1 自身就在用的语法 ——
 *                   它的 --tblr-primary-lt 就是 color-mix(in oklab, var(--tblr-primary) 10%, transparent)，
 *                   所以这里用它不引入额外的浏览器要求）。
 *                   ⚠️ **例外**：按钮的 hover / active 不用 color-mix，读上面那两档原色
 *                      （Arco 的 5 / 7 号是独立调过的色，掺黑掺白都凑不出来）。
 *   --rf-*          结构性颜色（页面底 / 卡片底 / 描边 / 文字）。两套皮各定义一份，
 *                   组件规则只引用令牌、不写死色值 —— 换皮只改令牌区。
 *
 * 主色为什么要绕一层 --rf-accent（而不是直接在各处用 --tblr-primary）：
 * 外壳里的「栅栏」（固定 dark）与「侧栏」（跟随 sidebar 轴）是**子树级的
 * data-bs-theme**，本文件按明暗给这类子树定义令牌。若主色直接写在 --tblr-primary
 * 上，这两个子树就被本文件的规则**直接命中**，而服务端写在 <html> 上的用户色
 * 对它们只是**继承** —— 直接命中永远压过继承，结果就是「切主色时页面主体变了、
 * 侧栏与栅栏还是默认蓝」。--rf-accent 只在 :root 落一次，
 * 子树继承它、再按自己的明暗派生 --tblr-primary，问题消失。
 */

/* ============================================================
   1. 设计令牌
   ============================================================ */

/* ── 西文字体 Inter（本地化，零外网依赖）──────────────────────
   只带 latin 子集、三个字重（400 / 500 / 600 = 正文 / 结构标签 / 强调）。
   中文由字体栈里的 PingFang SC / 微软雅黑 承担 —— Inter 没有中文字形，
   不配中文回退会掉到浏览器默认字体。
   路径相对本文件（本文件在 assets/rageframe/1.0.0/）；font-display: swap
   让文字先用回退字体显示、字体到位再换，不出现首屏空字。 */
@font-face {
    font-family: Inter;
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url("../../inter/5.3.0/inter-latin-400-normal.woff2") format("woff2");
}
@font-face {
    font-family: Inter;
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url("../../inter/5.3.0/inter-latin-500-normal.woff2") format("woff2");
}
@font-face {
    font-family: Inter;
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url("../../inter/5.3.0/inter-latin-600-normal.woff2") format("woff2");
}

/* ── 与明暗无关的刻度 + 主色源（唯一来源，三档）───────────────
   注意：--rf-accent 系（基色 / 悬浮 / 点击）只挂 `:root`，**绝不能**写进下面
   `[data-bs-theme]` 那一组选择器里：侧栏与栅栏是带 data-bs-theme 的子树，
   一旦它们被这条规则直接命中，就会拿到这里的默认值、把用户选择挡在外面
   （切绿后侧栏仍是 #165dff）。
   服务端会在 <html> 的 :root 里覆盖它们（App\Support\Theme::cssVariables），
   两处都在 <html> 上 → 子树一路继承，跟着用户走。
   默认值必须与 Theme::COLORS['blue'] 的三档 hex 一致。 */
:root {
    /* 圆角刻度：卡片 8 / 内嵌块 6 / 控件 6 / 小件 4 / 胶囊 20。
       卡片 8 是基准档，其余各档以它为准按同一比例落位 ——
       保持「卡片 > 内嵌 = 控件 > 小件」的递减关系，整体是一套收敛的小圆角。 */
    --rf-r-lg: 8px;
    --rf-r: 6px;
    --rf-r-sm: 6px;
    --rf-r-xs: 4px;
    --rf-r-pill: 20px;
    /* ⚠️ 全站「全圆」其实是三种语义，别把它们当成同一件事去合并（2026-10-16 审计后拍板保留）：
       1) --rf-r-pill(20px) = 固定胶囊档：用在 .badge / .progress / .rf-tag 这类**高度已知**的胶囊，
          换个尺寸改这里一处即可；
       2) 50% = 「不管元素多大都是正圆」（尺寸无关）：.rf-image-round（任意尺寸的图）、
          .rf-lightbox-nav(44px)、.rf-pick-flag(20px)……大过 40px 的元素换成 20px 就变圆角方块；
       3) 999px = 「不管文字多宽都是胶囊」（尺寸无关）：.rf-upload-tile-change / .rf-upload-act /
          .rf-cropper-mask-act 这些带文字的胶囊按钮，文字一长 20px 就撑不住。
       （CSS 会把超过边长一半的圆角钳回半边，所以小圆钮写 20px 恰好也是正圆 —— 但那是「凑巧」，不是语义。） */

    /* 字体栈：Inter 管西文与数字（本地 @font-face，见 §1 开头），
       PingFang / 微软雅黑管中文 —— PingFang SC / Hiragino Sans GB 只在
       macOS 命中，Windows 上中文实际由 Microsoft YaHei（微软雅黑）渲染。 */
    --rf-font: Inter, "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB",
               "Microsoft YaHei", "微软雅黑", Arial, sans-serif;

    /* ── 字号阶层（全站排版刻度）──────────────────────────────
       全站所有长度的单位**一律 px，不用 rem**（直接可读、不受根字号影响；
       第三方库自己的 rem 不动，见下面 vendor 一条）。
       档位按「不同位置用不同字号」的规则落到具体位置上：

         26  指标数字：卡片里的大数字（.fs-3），比标题更抢眼
         24  h1 / .page-title   页面标题
         19  h2                 区块标题
         16  h3 / h4 / .card-title   小节标题、卡片标题、区块内的标题
         14  正文档（交互与阅读主体）：body / 按钮 / 表单控件 / **表格**（表头 th 与
             数据行同号 —— 行高才严格一致）/ **所有导航项**（侧栏菜单、子菜单、顶栏导航项、
             栅栏分组项）/ 下拉项 / h5
         13  次要档（辅助信息）：页签 / 面包屑 / code / .subheader /
             主题菜单的分组标签
         12  提示档（最小号）：徽标 / 状态点（.status）/ 侧栏分组标题 / 帮助与提示文字 / .small / h6 / btn-sm

       ⚠️ 位置归属按**角色**判断，不按组件：导航项哪怕再小也是「交互主体」→ 14；
          只有纯辅助信息才往 13 / 12 走 —— 顶栏项与栅栏项都算交互主体（14），
          分组标题是纯辅助（12）。

       ⚠️ 改档位**只改这一组令牌** —— 下面的 Tabler 变量映射与组件规则全部引用它们。
       ⚠️ 上表是**本项目自己的**口径（px）。Tabler vendor 自己的样式仍写 rem
          （不改动它）—— Tabler 未设 html font-size，即浏览器默认值，1rem = 16px。 */
    /* 展示档：唯一的「非正文」档位，只服务独立页的错误码这种
       「一个巨大数字撑住整张卡」的场景（登录页 / 错误页见 rageframe.css 末节）。
       它不属于标题阶梯（h1 才是），所以单列一档而不是拿 h1 放大。 */
    --rf-fs-display: 46px;
    --rf-fs-metric: 26px;
    --rf-fs-h1: 24px;
    --rf-fs-h2: 19px;
    --rf-fs-h3: 16px;
    --rf-fs-h4: 16px;
    --rf-fs-h5: 14px;
    --rf-fs-h6: 12px;
    --rf-fs-body: 14px;
    --rf-fs-sm: 13px;
    --rf-fs-xs: 12px;

    /* ── 图标尺寸：一把尺（默认 14 / 小控件 12 / 极小 10 / 特大按需）────
       图标统一走 `<i class="rfi rfi-…">`（RF Icons mask 雪碧图），
       尺寸只有几档，别再各处散写 px：

         · 默认档 --rf-icon-size 14px = 正文档（--rf-fs-body）。
           图标**跟随所在文本**（见 §2 那条全局 :where() 规则）—— 正文上下文
           本来就是 14px，所以「默认 14」是自动成立的，不用逐处写死。
         · 小控件档 --rf-icon-size-sm 12px —— 小圆钮里的 ✕ / 拖拽柄、菜单
           展开箭头、与小字号文字同排的图标。
         · 极小档 --rf-icon-size-xs 10px —— 只用在「12 已经顶满载体」的窄小
           位置：标签页关闭键（× 坐在 16px 方钮里）、表头排序箭头（列名旁的
           辅助符号，不必与 13px 列名等量齐观）。**它同时是全站地板**：全局
           规则把任何比它还小的旧值托到 10px，不再出现更细的「针尖」。
         · 导航档 --rf-icon-size-nav 16px —— **所有一级导航图标**：侧栏菜单
           （.nav-link-icon / .rf-submenu-icon）、顶栏一级导航（.rf-topnav-item i）、
           顶栏右侧图标钮（.rf-topbar-right .nav-link .rfi）、图标分栏的栅栏
           （.rf-fence-item > i）。这几处同屏出现或互为布局替身，**必须同档**，
           否则换布局时整排图标会「跳一下」。
           为什么不复用默认档：菜单图标是「扫读索引」，要和 14px 菜单文字
           同时被一眼认出 —— 等大（14）时偏轻、落在 12 时像装饰，16 才在
           40px 行高里立得住。**这一档故意比正文大一档**，不是尺子错位。
         · 特大档不属于这把尺：品牌标（16 / 24）、页头图标片（20）、统计卡 /
           应用图标片（18）、空态大图标（17 / 24 / 30）、加载转圈（20 / 22）、
           媒体播放角标（18）—— 各自显式写死，互不干涉。

       ⚠️ 改图标大小：优先改这四枚令牌，或给容器挂 --rf-icon-size；
          不要再去写 `.xxx i { font-size: 15px }` 这种游离值。 */
    --rf-icon-size: 14px;
    --rf-icon-size-sm: 12px;
    --rf-icon-size-xs: 10px;
    --rf-icon-size-nav: 16px;

    /* ── 动效：时长与缓动分成两个刻度 ──────────────────────────
       时长管「多快」、缓动管「什么曲线」，分开才调得动 —— 混成一个值之后
       想「全站快一点」要改二十几处。三条时长按用途分工：

         fast   .12s  微交互：图标 / 角标出现、图片缩放、拖拽跟手
         默认   .15s  标准：按钮 / 控件 / 链接 / 卡片的颜色与描边过渡
         slow   .2s   大块位移：抽屉滑出、面板展开

       ⚠️ 组件规则写 `var(--rf-dur) var(--rf-ease-curve)`（或 fast / slow），
          **不要**再写裸的 `.15s ease-in-out` —— 这样「减少动态效果」只需把
          三个时长令牌压到 .01ms，全站一次性生效（见下方同名媒体查询一节）。
       ⚠️ --rf-ease 是「时长 + 曲线」的合并别名，只为兼容存量引用保留；
          新写的规则请用 --rf-dur* / --rf-ease-curve 两个令牌。 */
    --rf-dur-fast: .12s;
    --rf-dur: .15s;
    --rf-dur-slow: .2s;
    --rf-ease-curve: ease;
    --rf-ease-curve-out: ease-in-out;      /* 淡入淡出：蒙层 / 进度这类 */
    --rf-ease: var(--rf-dur) var(--rf-ease-curve);

    /* ── 焦点环（键盘可达）────────────────────────────────────
       只有两种形态，取色同源（--tblr-primary —— 它在两套皮里都等于用户选的
       那个主色，所以这里定义一次就够，深浅自动一致）：

         ① 柔光环 --rf-focus-ring-shadow（box-shadow）：用在会被相邻单元格 /
            滚动容器裁掉描边的元素上（表格里的图片框、贴边的可点元素）。
         ② 描边四件套（outline）：用在浮层里的独立小控件上（开关 / 返回键 /
            分页 / 复制）。贴控件内缘画（偏移为负）时改用半透明的
            --rf-focus-ring-color-soft，不然 2px 实心会糊住小控件本身。

       偏移四档各有归属，别再随手写 1px / 3px：
         inner  -2px  贴内缘（下拉项、小控件内部）
         tight   1px  紧跟按钮边
         默认     2px  落在页面底色上
         wide    3px  蓝底按钮上 —— 环必须离开主色底才看得见 */
    --rf-focus-ring-shadow: 0 0 0 3px color-mix(in oklab, var(--tblr-primary) 22%, transparent);
    --rf-focus-ring-width: 2px;
    --rf-focus-ring-color: var(--tblr-primary);
    --rf-focus-ring-color-soft: color-mix(in oklab, var(--tblr-primary) 45%, transparent);
    --rf-focus-ring-offset-inner: -2px;
    --rf-focus-ring-offset-tight: 1px;
    --rf-focus-ring-offset: 2px;
    --rf-focus-ring-offset-wide: 3px;

    /* 表单控件的**编辑态光晕**（:focus）不是键盘焦点环 —— 它表示「正在这里输入」，
       鼠标点进去也要亮，所以不走 :focus-visible，透明度也更高（18%，键盘环 22%）。
       校验失败的红晕同理（16%），跟着 --rf-danger 走。 */
    --rf-field-glow: 0 0 0 3px color-mix(in oklab, var(--tblr-primary) 18%, transparent);
    --rf-field-glow-danger: 0 0 0 3px color-mix(in oklab, var(--rf-danger) 16%, transparent);

    /* ── z-index 刻度 ────────────────────────────────────────
       只管「我们自己的」层叠层；Tabler 的弹层（dropdown 1000 / backdrop 1050 /
       modal 1055）不在刻度里、也不去改它。刻度把「谁盖住谁」写进名字，
       组件里别再写裸数字。低速段是组件内，高速段是外壳与浮层：

         under 0 / base 1 / raised 2 / front 3   一个容器内的四层
         high 5 / overlay 20 / toolbar 50        页面级：整区加载遮罩 / 组件浮层 / 吸顶条
         drawer-mask 1029 / drawer 1030 / topbar 1031
                                                 外壳：遮罩 < 抽屉（＝悬浮返回键）< 顶栏
         dropdown 1060 / toast 1080 / lightbox 1090
                                                 弹层：组件下拉 < 全局提示 < 全屏浮层

       ⚠️ dropdown 必须**大于 Tabler 的 modal 1055**，不是 1050。这套后台的表单
       常在弹层里承载（panel.js 的 data-ui-panel），而弹层就是一个 modal：控件
       自带的下拉（x-ui.cascader / x-ui.icon-picker / Tom Select）只要 z 低于
       1055，展开那一刻就被弹层本体压住 —— 实测级联只剩 30% 可见、图标选择器
       只剩 12%（一行搜索框）。1050 那个值当初是按「页面上没有弹层」排的。 */
    --rf-z-under: 0;
    --rf-z-base: 1;
    --rf-z-raised: 2;
    --rf-z-front: 3;
    --rf-z-high: 5;
    --rf-z-overlay: 20;
    --rf-z-toolbar: 50;
    --rf-z-drawer-mask: 1029;
    --rf-z-drawer: 1030;
    --rf-z-topbar: 1031;
    --rf-z-dropdown: 1060;
    --rf-z-toast: 1080;
    --rf-z-lightbox: 1090;

    /* ── 间距刻度（4px 网格）──────────────────────────────────
       只给**新写的**规则用。已有规则里的 6px / 10px / 14px 是逐处对过眼的
       光学微调，不在网格上 —— 别为了凑刻度去改它们（改了真的会挪位）。 */
    --rf-space-1: 4px;
    --rf-space-2: 8px;
    --rf-space-3: 12px;
    --rf-space-4: 16px;
    --rf-space-5: 20px;
    --rf-space-6: 24px;

    /* ── 控件高度四档（小 24 / 中 28 / 默认 32 / 大 36）─────────
       .btn-{sm,md,lg} 与 .form-control-{sm,md,lg} / .form-select-{sm,md,lg}
       都挂这里的档，高度一律由 min-height 定、不在规则里现算。
       四档的取法与 Arco 一致（mini 24 / small 28 / default 32 / large 36）：
       sm 是行内小控件（表格行内 / 菜单项），md 是区块工具带（卡片头 / 筛选行 /
       工具条 / 页脚 / 浮层内工具），默认档是表单层（字段 / 页头动作 / 弹层页脚），
       lg 只给**整屏唯一的主行动**（登录、空状态 CTA）—— 弹层主操作走默认档，
       与它上面的字段同高。分配表见 docs/UI.md「控件尺寸分配」。
       （2026-10-13 整体收紧：28/34/40/48 → 24/30/36/44，治「傻大粗」；
       2026-10-14 lg 由 44 再收到 42；2026-10-16 全面对照 Arco，整体再压一档
       成 24/28/32/36 —— 与 Arco 四档同值，步长 4。）

       ⚠️ 高度与**内边距**是配套的一对，必须一起换：
          天然高度 = padding-y×2 + 行高 + 描边，而上面这组 min-height 只是「下限」。
          默认档的天然高度（5+5+20+2）恰好就等于令牌值 32 —— 所以只把令牌改小，
          min-height 压不动它（实测过：旧档时代把令牌压到 34px，40px 的按钮纹丝不动）。
          下面 --rf-ctl-py-* 四档就是「让天然高度落在令牌上」的那份内边距。
       ⚠️ 默认档 --rf-ctl-h 会被 .rf-toolbar **局部覆盖**成 28px ——
          工具条里的控件要再矮一档，是有意的局部级联，不是笔误。
       ⚠️ 这两组会被「紧凑」密度成批换掉（见下面 [data-rf-density="compact"]）——
          组件规则只引用令牌，所以换挡不必改任何组件。 */
    --rf-ctl-h-sm: 24px;      /* 24 —— 紧凑表格里的控件（btn-sm 等） */
    --rf-ctl-h-md: 28px;     /* 28 —— 中号档：表头筛选行 / 夹在 sm 与默认之间 */
    --rf-ctl-h: 32px;           /* 32 —— 默认档 */
    --rf-ctl-h-lg: 36px;     /* 36 —— 整屏唯一主行动（登录 / 空状态 CTA） */

    --rf-ctl-py-sm: 3px;   /* 3 → 3+3+16(行高)+2 = 24 */
    --rf-ctl-py-md: 3px;   /* 3 → 3+3+20(行高)+2 = 28（.btn-md 与表头筛选控件） */
    --rf-ctl-py: 5px;      /* 5 → 5+5+20+2 = 32 */
    --rf-ctl-py-lg: 5px;   /* 5 → 5+5+24(行高)+2 = 36 */

    /* ── 内边距刻度（卡片与表格共用）──────────────────────────
       卡片头/体/尾的横向内边距与表格单元格的横向内边距**必须同值** ——
       首列左缘才能和卡片标题左缘对上一条竖线。两处挂同一个令牌：
       改一处两处一起走，密度换挡也只动这一个数。
       卡片体是四边都用它，所以名字不带方向。 */
    --rf-pad: 16px;               /* 16 —— 卡片头/体/尾 + 表格单元格的左右内边距 */
    --rf-card-cap-y: 14px;        /* 14 —— 卡片头的**纵向**内边距。与 --rf-pad 分开：横向跟着
                                     密度换挡，纵向不跟（紧凑档只收横向，卡片头高不变）。
                                     单独成令牌是为了让 .card-header-tabs 的负 margin
                                     `calc(-1 * var(--rf-card-cap-y))` 有唯一取值源 ——
                                     以前两处各写一个 14，改一处就静默错位。 */
    --rf-cell-pad-y: 9px;    /* 9 —— 表格数据行的上下内边距（Arco large：9+9+22 = **40**） */
    --rf-cell-pad-y-sm: 7px; /* 7 —— .table-sm（紧凑表）的上下内边距（Arco middle：7+7+22 = **36**） */
    --rf-cell-lh: 1.5715;         /* 表格单元格行高（Arco 真身）：14 × 1.5715 = **22**，行高才落在整数档上。
                                     不继承全局正文的 1.6（22.4）—— 差 0.4px 会让 9+9 凑成 40.4 而不是 40。
                                     只作用于单元格（th / td），不影响页面正文的排版松弛度。 */

    /* ── 状态层（主色派生阶）──────────────────────────────────
       hover 底 / 选中底 / 强调描边一律取这里的档，组件规则里
       **不再现算百分比**：换主色整组自动跟随；调手感（深浅跨度）只改这一组。
       ⚠️ 实心按钮的 hover / active **不在这条派生链上**：它们读色板原档
          --rf-accent-hover / --rf-accent-active（Arco 的 5 / 7 号，PHP 注入），
          因为那两档算不出来，只能照抄（见文件顶部与 Theme::cssVariables()）。
       --tblr-primary 在浅/深两套皮各自派生，这里定义在 :root、值在**使用点**
       解析，所以深浅皮 + 侧栏/栅栏子树都自动拿到正确的主色。 */
    --rf-accent-soft:   color-mix(in oklab, var(--tblr-primary) 10%, transparent); /* 最淡：深底 hover、弹层高亮 */
    --rf-accent-bg:     color-mix(in oklab, var(--tblr-primary) 12%, transparent); /* 标准淡底：选中 / hover 底纹 */
    --rf-accent-strong: color-mix(in oklab, var(--tblr-primary) 18%, transparent); /* 加重：深底选中、命中高亮 */
    --rf-accent-line:   color-mix(in oklab, var(--tblr-primary) 40%, transparent); /* 强调描边：outline 按钮静止 */

    /* ── 语义色状态阶（与主色那组 accent-* 同构）────────────────
       每个语义色补三档，组件规则里不再现算百分比：
         -line-soft  描边按钮（.btn-outline-*）的静止描边，与 --rf-accent-line 同档
         -hover      实心按钮 hover 底（Arco 5 号）
         -active     实心按钮 active 底（Arco 7 号）
       ⚠️ hover / active 是**逐档写死的 Arco 色板值**，不走 color-mix ——
          Arco 的 5 / 7 号在色板里独立调过，掺黑掺白都凑不出来（旧的
          color-mix 88% 掺黑版本颜色偏、与 Arco 对不上，2026-10-16 换掉）。
          两皮的档位**不同**：浅皮基色是 6 号（hover 5 / active 7），
          深皮基色是 5 号（hover 4 / active 6）—— 各自色块里写自己那套。 */
    --rf-ok-line-soft:     color-mix(in oklab, var(--rf-ok) 40%, transparent);
    --rf-warn-line-soft:   color-mix(in oklab, var(--rf-warn) 40%, transparent);
    --rf-danger-line-soft: color-mix(in oklab, var(--rf-danger) 40%, transparent);
    --rf-info-line-soft:   color-mix(in oklab, var(--rf-info) 40%, transparent);

    --rf-ok-hover:     #23c343;   /* green-5 */
    --rf-ok-active:    #009a29;   /* green-7 */
    --rf-warn-hover:   #ff9a2e;   /* orange-5 */
    --rf-warn-active:  #d25f00;   /* orange-7 */
    --rf-danger-hover: #f76560;   /* red-5 */
    --rf-danger-active:#cb272d;   /* red-7 */
    --rf-info-hover:   #8d4eda;   /* purple-5 */
    --rf-info-active:  #551db0;   /* purple-7 */

    /* 强调色的两处特例：百分比与上面那组都不同，所以各立一条，
       规则里不再写死百分比 —— 选区底色要压得住选中文字，比 accent-strong(18%)
       再重一点；链接下划线要淡到不抢字，又要看得见。 */
    --rf-selection-bg: color-mix(in oklab, var(--tblr-primary) 22%, transparent);
    --rf-link-underline: color-mix(in oklab, var(--tblr-primary) 35%, transparent);

    /* 禁用态透明度：统一一个值，别各处写 .4 / .5 / .55 */
    --rf-state-disabled-opacity: .55;

    /* on-color（语义实心底 / 深色浮层上的前景）：主色块、地图与媒体浮标、
       上传蒙层上的白字共用它 —— 换皮时只改这一处。 */
    --rf-on-strong: #ffffff;

    /* 弹层遮罩（modal / offcanvas）：墨色 45%，取自正文色 —— 浅皮是深灰、
       深皮是近白，两皮的遮罩都跟着自己的 --rf-ink 走。 */
    --rf-scrim: color-mix(in oklab, var(--rf-ink) 45%, transparent);

    /* ── 组件私有令牌的命名纪律 ───────────────────────────────
       组件自己的尺寸 / 参数用 `--rf-<组件>-<属性>`，且**必须带兜底值**
       （如 `var(--rf-upload-w, 128px)`）—— 组件 CSS 可能被单独拷到别处用，
       宿主没加载时也得成立。已有：--rf-upload-w/-h、--rf-cropper-w/-h、
       --rf-map-h、--rf-image-h、--rf-media-state-bg、--rf-ctl-h。
       新组件照这条写；老的按需迁移，不强制。 */

    /* 主色源（三档）：基色 + 悬浮 + 点击。
       值由 Theme::cssVariables() 逐档注入（用户选的那根轴的 Arco 6 / 5 / 7 号），
       这里只是「PHP 没输出时」的兜底 —— 取默认蓝那一轴，与 Theme::COLORS 同源。
       ⚠️ 三档**不能**从基色算：Arco 的 5 / 7 号是色板里独立调过的色，
          color-mix / oklch 都推不出来（旧实现拿 color-mix 掺黑当 hover，
          得到的是另一支颜色，2026-10-16 换成逐档照抄）。
       ⚠️ 只在 :root 落一次、子树靠继承 —— 理由见文件顶部「三条改色纪律」。 */
    --rf-accent: #165dff;
    --rf-accent-hover: #4080ff;
    --rf-accent-active: #0e42d2;
}

/* ── 密度换挡（界面密度轴，见 App\Support\Theme::DENSITIES）─────────
   实现方式只有一条：整批换掉一组令牌值，**组件规则一个都不动** ——
   控件、表格行、卡片内边距引用的就是这些令牌，于是整页一起收放。
   挂在 <html data-rf-density>：normal 那一档就是上面 :root 的原值，
   这里只写 compact，将来要加第三档（如 loose）再补一段覆盖即可。

   紧凑档是**逐项定值**而不是等比缩放：控件降一档（32→28 / 28→26 / 24→22
   / 36→32），行高与内边距跟着收 —— 数字都落在 2px 网格上，不会出现半像素。 */
[data-rf-density="compact"] {
    --rf-ctl-h-sm: 22px;      /* 24 → 22 */
    --rf-ctl-h-md: 26px;      /* 28 → 26 */
    --rf-ctl-h: 28px;          /* 32 → 28 */
    --rf-ctl-h-lg: 32px;          /* 36 → 32 */

    /* 内边距必须跟着降，天然高度才落得到新令牌上（算式见 :root 那组令牌） */
    --rf-ctl-py-sm: 2px;      /* 3 → 2：2+2+16+2 = 22 */
    --rf-ctl-py-md: 2px;      /* 3 → 2：2+2+20+2 = 26 */
    --rf-ctl-py: 3px;        /* 5 → 3：3+3+20+2 = 28 */
    --rf-ctl-py-lg: 3px;     /* 5 → 3：3+3+24+2 = 32 */

    --rf-pad: 14px;            /* 16 → 14（卡片与表格一起收，仍互相对齐） */
    --rf-cell-pad-y: 7px;    /* 9 → 7（Arco middle：40 → **36**） */
    --rf-cell-pad-y-sm: 5px; /* 7 → 5（Arco small：36 → **32**） */
}

/* 工具条把默认档局部压到 28px（rageframe.css 的工具条一节），比 :root 更靠内，
   所以换挡要**再命中它一次**才压得住 —— 否则紧凑档下工具条里的控件纹丝不动。
   py 也要一起换：工具条里的默认档按钮靠 py 撑出天然高度，只压 h 压不动。 */
[data-rf-density="compact"] .rf-toolbar {
    --rf-ctl-h: 26px;         /* 28 → 26 */
    --rf-ctl-py: 2px;         /* 3 → 2：2+2+20+2 = 26 */
}

/* ── 栅栏底：外壳里唯一「与明暗、与侧栏配色轴都无关」的一处 ──────
   栅栏（.rf-fence，仅「图标分栏」风格）在模板里就写死了 data-bs-theme="dark"，
   两套皮、侧栏选浅选深都不影响它，所以值只在这里定一次、不写进浅/深两块
   （写两遍就会有「改一处漏一处」的风险）。

   #101117 = iView Admin Plus 的**激活项 / logo 分割线**实测色（rgb(16,17,23)），
   是它整套里最深的一档；栅栏取它，侧栏取它的侧栏底 #191a23（见下面深色块的
   --rf-sidebar-bg）—— 两栏差一档才分得开。

   ⚠️ 侧栏**不能**跟着写在这里：侧栏的配色是独立轴（浅色页面下也可能选 light 轴），
      浅色轴下的侧栏必须是白的，所以它的值只能放在浅/深两块里各定一份。 */
:root {
    --rf-fence: #101117;
}

/* ── Bootstrap 变量命名空间兼容层 ────────────────────────────
   Tabler 是独立重写的样式体系，**一个 --bs-* 都不定义**（实测 0 处）。
   但有一类消费者按 Bootstrap 的变量名取值：第三方增强库的官方皮肤 ——
   Tom Select 的 tom-select.bootstrap5.css 整份皮肤全是 var(--bs-*)，
   一个都没有回退值（实测：body-bg 6 / border-radius 6 / border-width 5 /
   body-color 2 / border-color 2 / border-color-translucent 2 /
   form-invalid-color 3 / form-valid-color 3 / secondary-bg 2 / secondary-color 2 /
   tertiary-bg 1 / border-radius-sm 1 / border-radius-lg 1 / box-shadow-inset 1）。

   ⚠️ 本项目**自己的**规则一律不读这一层：本文件的组件规则直接引用 --rf-* /
      --tblr-*。早先有 18 处绕道这里（border-radius 7 / border-width 6 /
      danger 5），等于给自己的样式多套一层「为别人准备的别名」，已全部改直。
      这一层从今往后只服务第三方皮肤，改它的值不会牵动我们自己的组件。

   不补的后果是**静默失效**（不报错、不影响其它规则）：CSS 变量未定义时
   `background: var(--bs-body-bg)` 这类声明会在 computed-value 阶段整条作废、
   回退到 initial —— 实测现象：Tom Select 下拉面板背景全透明、边框消失、
   高度塌成 10px；编辑器外壳与上传拖拽区没有边框。

   值统一指向 --rf-* 令牌，因此深浅色**自动跟随** <html data-bs-theme>
   （令牌本身在下面浅/深两块各定义一次，声明处就地解析）。
   子树级换主题（侧栏自己带 data-bs-theme）不会让这份兼容层跟着变 ——
   增强库弹层都挂在主内容区，无实际影响。

   ⚠️ 本项目从不引入 Bootstrap 原生 CSS，所以 --bs-* 这个名字空间是空的、
      借用它不会与任何东西冲突。若将来真引入 bootstrap.min.css，这层要重评。 */
:root {
    /* 边框与圆角 */
    --bs-border-width: var(--tblr-border-width, 1px);
    --bs-border-radius: var(--rf-r-sm);
    --bs-border-radius-sm: var(--rf-r-xs);
    --bs-border-radius-lg: var(--rf-r);
    --bs-border-radius-xl: var(--rf-r-lg);
    --bs-border-radius-pill: var(--rf-r-pill);
    --bs-border-color: var(--rf-line);
    --bs-border-color-translucent: var(--rf-line);

    /* 表面与文字 */
    --bs-body-bg: var(--rf-field-bg);
    --bs-body-color: var(--rf-ink);
    --bs-secondary-bg: var(--rf-surface-2);
    --bs-secondary-color: var(--rf-muted);
    --bs-tertiary-bg: var(--rf-surface-2);

    /* 状态色与阴影 */
    --bs-danger: var(--rf-danger);
    --bs-form-invalid-color: var(--rf-danger);
    --bs-form-valid-color: var(--rf-ok);
    --bs-box-shadow: var(--rf-shadow);
    --bs-box-shadow-inset: var(--tblr-box-shadow-inset, inset 0 1px 2px rgba(0, 0, 0, .075));
}

/* ── 浅色（默认）─────────────────────────────────────────── */
:root,
[data-bs-theme="light"] {
    /* 三级底：页面 → 卡片 → 内嵌块（页面底一律**纯色**，不做渐变 —— 2026-10-16 拍板）。
       三级底取 iView Admin Plus 真身色，不自己推导 —— 布局底 #f5f7f9、卡片纯白
       （它内容区就是白卡浮在 #f5f7f9 上）。⚠️ 只有底走 iView：文字与描边是
       **中性灰阶**（见下两节，2026-10-16 用户拍板回滚），别按「整套 iView」改。 */
    --rf-page: #f5f7f9;
    --rf-surface: #ffffff;
    --rf-surface-2: #f5f7f9;
    --rf-surface-3: #fafafa;
    --rf-field-bg: #ffffff;

    /* 文字：正文（= 强调标题）/ 次要 / 提示
       （**只有一支正文色** --rf-ink：#4e5969 = Arco gray-8。正文段落、表格、
          下拉项、按钮字，以及 h1~h6 / .card-title / strong / label 全用它 ——
          层级靠字号与字重，不靠再压深一档。
          （2026-10-16 用户拍板：原先与正文并列的强调令牌 --rf-ink-strong 全局
            移除、改读 --rf-ink —— 浅皮两档本来同值（#4e5969），深皮那档
            （#f4f5fa）比正文亮一档的差别也一并取消；令牌少一支，规则里
            「正文还是强调」这个选择就不存在了。）
          次要 #6b7785 = gray-7 / 提示 #86909c = gray-6 —— 整条文字链**全落
          Arco 中性灰阶**，不带色相，自浓到淡一档一阶。
          （次要原取 gray-8 #4e5969，与正文几乎同色（明度只差 2 度），
            表头 / 页签 / 面包屑的「次要感」全压在字重上；2026-10-16 升到 gray-7，
            与正文拉开一档，字重回归常态）

       ⚠️ 正文色同时喂 --tblr-body-color(-rgb) 与 --rf-scrim（弹层遮罩 = 该色的 45%）
          —— 改这一处，遮罩跟着变，这是有意的：遮罩是「字色的影」，文字一沉它一起沉。
          侧栏文字另有自己的三档链，见下面的 --rf-sidebar-*。 */
    --rf-ink: #4e5969;
    --rf-muted: #6b7785;
    --rf-faint: #86909c;

    /* 描边三档：面板/浮层 / **控件**（输入框、下拉、按钮、勾选框、分页…）/ 极浅分隔线
       （⚠️ 卡片**不在这条链上**：卡片已去掉边框、只靠影分层，见 §6；
       --rf-line 这一档留给仍需要边界的容器 —— 下拉 / 日期弹层 / pre / 选项卡 / 窄屏行卡）
       控件这一档 = #dcdee2：**全站描边统一**（2026-10-16 用户拍板）。
       原先控件比面板重一档（#c9cdd4），输入框面积大、还常常成排出现，那个重量
       落在它身上就「太重了」；降到 #dcdee2（也真是 iView 输入框的描边色）之后，
       输入框 / 按钮 / 勾选框 / 分页的边同色，不再各说各话。
       ⚠️ 这一档同时喂 --tblr-btn-border-color，所以它一变**按钮族一起变**，这是有意的。
       （中性灰阶：#e5e6eb 面板 / #dcdee2 控件 / #f2f3f5 分割线） */
    --rf-line: #e5e6eb;
    --rf-line-strong: #dcdee2;
    --rf-line-soft: #f2f3f5; /* 兼作外壳页签选中底（theme.css §14，跟这一档一起调） */

    /* 高对比档（系统 prefers-contrast: more，或手动挂 .rf-contrast 时启用）：
       描边加深、提示色加浓 —— 它们是「分层」的主要线索；底色与主色不动，
       免得对比拉过头反而失真。交换规则见文件下方「高对比」一节。 */
    --rf-line-hc: #a9aeb8;
    --rf-line-strong-hc: #787e8c;
    --rf-line-soft-hc: #d7d9de;
    /* 提示色：常态 gray-6 #86909c → 高对比升到 **gray-7 #6b7785**（与正文 gray-8 隔一档）。
       ⚠️ 2026-10-16：原先这档也是 #4e5969，正文统一到 gray-8 之后两者**同色**，
       高对比下提示与正文分层直接消失（用户发现并要求降一档）。
       代价：高对比下提示与次要（--rf-muted 不动、仍是 gray-7）合并成同一色 ——
       「提示=次要」比「提示=正文」轻得多，是从塌一档收成塌半档的取舍；
       要三档全分明，得连 --rf-ink / --rf-muted 一起提档（gray-9/8/7），
       但那要同带动 --tblr-body-color(-rgb)，本次没做（待议）。 */
    --rf-faint-hc: #6b7785;

    --rf-code-bg: #f5f7f9;
    --rf-code-ink: #1d2129;

    /* 语义色：每个给「主色 / 文字 / 底 / 描边」四件套 ——
       深色下这四件都要换，所以不写成派生公式，直接列出来更好核对。
       ⚠️ 描边档（-line）的消费者很少：提示条**已去边框**（见 §9），现在只剩 danger
          那三处 —— `.rf-markdown-msg` 的上边线、`.rf-addon-mark[data-state="gone"]`
          与 `.rf-addon-hero-mark[data-state="gone"]` 的框。ok / warn / info 三档暂无
          消费者，留着是为了四件套完整、深色皮两侧对称好核对。
          「描边型」按钮读的是另一套 `-line-soft`（比这档柔，见 §5 按钮）。 */
    --rf-ok: #00b42a;
    --rf-ok-ink: #009a29;
    --rf-ok-bg: #e8ffea;
    --rf-ok-line: #aff0b5;

    --rf-warn: #ff7d00;
    --rf-warn-ink: #d25f00;
    --rf-warn-bg: #fff7e8;
    --rf-warn-line: #ffcf8b;

    --rf-danger: #f53f3f;
    --rf-danger-ink: #cb272d;              /* Arco red-7（原手调 #cb2634，2026-10-16 归位） */
    --rf-danger-bg: #ffece8;
    --rf-danger-line: #fdcdc5;

    --rf-info: #722ed1;
    --rf-info-ink: #551db0;
    --rf-info-bg: #f5e8ff;
    --rf-info-line: #ddbef6;               /* Arco purple-2（原手调 #dcc1ff，2026-10-16 归位） */

    /* 阴影：**纯中性灰** + 低透明度 —— 卡片**不描边**（见 §6 卡片），分层全靠它，
       所以只做到「看得出浮起一层」，不做成描一圈、也不糊出光晕。
       色相 rgba(29, 33, 41) 是固定的中性墨（Arco gray-10 的 RGB，与标题同源）——
       影是「字色的影」，与文字、描边同一条中性轴。原先用 rgba(23, 35, 61)（iView
       蓝调），文字描边中性化后它是全站唯一还带蓝调的色相，已归位（2026-10-16）。
       2026-10-16 整体收一档：.06→.05 / 模糊 8→6（卡片）、.12→.10 / 20→16（浮层）；
       同日再收一档（用户拍板「还要小」）：卡片 .05→.04 / 模糊 6→4、小件 .04→.03。 */
    --rf-shadow-sm: 0 1px 2px rgba(29, 33, 41, .03);
    --rf-shadow: 0 2px 4px rgba(29, 33, 41, .04);
    --rf-shadow-pop: 0 6px 16px rgba(29, 33, 41, .10);

    /* 侧栏底：#ffffff —— 这是**浅色轴**侧栏（data-bs-theme="light"）的值；
       默认的深色轴侧栏不读这里，它在深色块里另有一份（#191a23）。
       ⚠️ 侧栏底**不能**直接读 --tblr-bg-surface：侧栏本身是 data-bs-theme 子树，
          那会解到「该主题的卡片色」（深色块里是 #20222e）—— 于是「浅色页面 + 深色
          侧栏」这组最常见的搭配下，侧栏显示的是深色皮的卡片色，而不是侧栏该有的墨色。 */
    --rf-sidebar-bg: #ffffff;

    /* 侧栏文字三档（.rf-menu-nav .nav-link / .rf-submenu-link / .nav-section-title /
       .rf-menu-arrow / .rf-fence-item 共用）：一级菜单 = ink，二级 / 分组标题 / 箭头 = muted，
       faint 备用。消费点见 rageframe.css 的菜单与栅栏两节。
       ⚠️ 与 --rf-sidebar-bg 同理，**必须按轴各定一份**：侧栏配色是独立轴，直接读
          --tblr-body-color / --tblr-secondary 会拿到「该主题的正文 / 次要色」，
          整栏菜单会比内容区还亮。浅色轴侧栏是白底，三档与内容区同值。 */
    --rf-sidebar-ink: #4e5969;      /* gray-8，一级菜单 */
    --rf-sidebar-muted: #6b7785;    /* gray-7，二级 / 分组标题 / 箭头 */
    --rf-sidebar-faint: #86909c;    /* gray-6，暂无消费者：留作「分组标题再压一档」的口子 */

    /* 链接悬停色：**固定取最深一档**（Arco gray-10），不跟正文色同值 ——
       文字链统一到 #4e5969 之后，若悬停也取它，链接 hover 就没有任何反馈了
       （链接常态是主色蓝，悬停沉到深墨，这一档必须比正文更深才看得出来）。 */
    --rf-link-hover: #1d2129;

    /* ── 映射到 Tabler 变量 ──────────────────────────────────
       Tabler 的组件（alert / progress / dropdown / modal / pagination /
       list-group / avatar / toast …）内部都是读这些变量拼出来的，
       在这里改一次 = 改了它们的皮，不必逐个组件写规则。
       组件规则只用来补变量表达不了的东西（圆角刻度、阴影层次、内边距）。 */
    --tblr-body-bg: var(--rf-page);
    --tblr-body-bg-rgb: 245, 247, 249;
    --tblr-body-color: var(--rf-ink);
    --tblr-body-color-rgb: 78, 89, 105;   /* = --rf-ink #4e5969（正文色，见上） */
    --tblr-body-font-family: var(--rf-font);
    --tblr-font-sans-serif: var(--rf-font);   /* 直接读 sans-serif 的组件（.font-sans-serif 等）同步 */
    --tblr-body-font-size: var(--rf-fs-body);   /* 正文 14px —— 全站档位见顶部「字号阶层」 */
    --tblr-body-line-height: 1.6;

    /* 标题档位映射：Tabler 的 h1~h6 与 .page-title 都读这几档，
       覆盖变量即一刀切（改档位改顶部的 --rf-fs-* 即可）。 */
    --tblr-font-size-h1: var(--rf-fs-h1);
    --tblr-font-size-h2: var(--rf-fs-h2);
    --tblr-font-size-h3: var(--rf-fs-h3);
    --tblr-font-size-h4: var(--rf-fs-h4);
    --tblr-font-size-h5: var(--rf-fs-h5);
    --tblr-font-size-h6: var(--rf-fs-h6);

    /* 字重：**全站上限 500**。
       关键事实：微软雅黑只有 Regular(400) / Bold(700) 两个字重，
       CSS 600 会向上匹配到 **Bold** —— 小字号下又糊又浓淡不均（观感「一个字大一个字小」）。
       所以 400 与 500 都落 Regular，600 及以上才会跳成 Bold，全站不用。
       档位：正文/控件/标题 = 400；结构性标签（th / label / 卡片标题 / strong）= 500。 */
    --tblr-font-weight-normal: 400;
    --tblr-font-weight-medium: 500;
    --tblr-font-weight-semibold: 500;   /* strong / b 走这档（承担 label 角色），**不是** 600 */
    --tblr-font-weight-bold: 500;       /* 不放到 700：雅黑 Bold 在小字号下发糊 */
    --tblr-font-weight-headings: 400;   /* 标题靠字号 + 颜色拉层级，不靠粗细 */

    /* 主色的**落点**：从 --rf-accent 派生（它只在 :root 定义、子树继承，
       所以浅色侧栏 / 栅栏拿到的也是用户选的那个色 —— 见文件顶部「三条改色纪律」）。
       本文件不定义「皮肤自己的主色」，默认值只在上面那条 :root 规则里。 */
    --tblr-primary: var(--rf-accent);
    --tblr-primary-fg: var(--rf-on-strong);

    /* Tabler 自带的「加深」派生（oklch 从主色减亮）——它是 Tabler 实心按钮
       hover / active 的默认底。改成 Arco 的 7 号（点击档）：字面语义「加深」保住了，
       而 hover 那一档由我们自己的规则显式覆盖成 5 号（Arco 口径 hover 变亮一档），
       权重更高、压得住这条。语义色同理（各自的 -active 档，两皮取值不同）。 */
    --tblr-primary-darken: var(--rf-accent-active);
    --tblr-success-darken: var(--rf-ok-active);
    --tblr-warning-darken: var(--rf-warn-active);
    --tblr-danger-darken: var(--rf-danger-active);
    --tblr-info-darken: var(--rf-info-active);

    --tblr-secondary: var(--rf-muted);
    --tblr-secondary-fg: var(--rf-ink);
    --tblr-secondary-bg: var(--rf-surface);

    --tblr-bg-surface: var(--rf-surface);
    --tblr-bg-surface-secondary: var(--rf-surface-2);
    --tblr-bg-surface-tertiary: var(--rf-surface-3);
    --tblr-bg-surface-dark: var(--rf-fence);
    --tblr-bg-forms: var(--rf-field-bg);

    --tblr-border-color: var(--rf-line);
    --tblr-border-color-translucent: rgba(229, 230, 235, .72);
    --tblr-border-light-color: var(--rf-line-soft);

    --tblr-link-color: var(--tblr-primary);
    --tblr-link-hover-color: var(--rf-link-hover);

    --tblr-border-radius: var(--rf-r-sm);
    --tblr-border-radius-sm: var(--rf-r-xs);
    --tblr-border-radius-lg: var(--rf-r);
    --tblr-border-radius-xl: var(--rf-r-lg);
    /* ⚠️ --tblr-border-radius-pill **刻意不覆盖**。
       Tabler 给它的值是 1600px，语义是「**不管多高都是正圆**」——
       .avatar / .rounded-pill / .badge-pill / .form-control-rounded 全靠它。
       早先把它映射到项目那档 --rf-r-pill(20px)，于是 .avatar-xl(80px=80px) 只拿到
       20px 圆角，正圆变成圆角方块（.avatar-lg 48px 同理）。
       项目自己的「胶囊档 20px」是 --rf-r-pill，组件**直接用**它即可
       （.badge / .progress / .rf-lightbox-* 都是这么写的），不必经这个变量转手。 */

    /* 中性控件（.btn 不带 variant 时是「白底 + 描边」那颗） */
    --tblr-btn-bg: var(--rf-surface);
    --tblr-btn-color: var(--rf-ink);
    --tblr-btn-border-color: var(--rf-line-strong);
    --tblr-btn-hover-bg: var(--rf-surface-2);
    --tblr-btn-hover-color: var(--rf-ink);
    --tblr-btn-hover-border-color: var(--rf-line-strong);
    --tblr-btn-box-shadow: var(--rf-shadow-sm);
    --tblr-btn-border-radius: var(--rf-r-sm);

    --tblr-success: var(--rf-ok);
    --tblr-warning: var(--rf-warn);
    --tblr-danger: var(--rf-danger);
    --tblr-info: var(--rf-info);
}

/* ── 深色：同一套关系的夜航版本 ───────────────────────────── */
[data-bs-theme="dark"] {
    /* 墨蓝夜航系 —— **直接用 iView Admin Plus 的实抓真身色**，不做色彩推导：
       页面底 = #191a23（adminplus 侧栏底，实测 rgb(25,26,35)）。
       教训（2026-10-16 三轮）：深色好看靠的是**蓝调色相**而非明度 ——
       纯中性灰再怎么提亮都是「死灰一片」；自己另配一套墨蓝又会「不上不下」，
       所以主色层直接取真身，只有 iView 没有的「浮起层」（卡片/内嵌）才补两档同色相值。
       （栅栏 #101117 是同一套里的更深一档，但它与明暗无关、定在文件上方的刻度区；
        侧栏底 #191a23 也是真身色，见下方 --rf-sidebar-bg。） */
    --rf-page: #191a23;
    --rf-surface: #20222e;
    --rf-surface-2: #282b39;
    --rf-surface-3: #242734;
    --rf-field-bg: #1a1c27;

    --rf-ink: #e9ebf2;
    --rf-muted: #a9adbe;
    --rf-faint: #787d92;

    --rf-line: #34374a;
    /* 与浅色皮不同值：#494d66 在深底上已经是「看得清边」的最轻一档，
       再往浅色那个方向调，输入框就糊进 --rf-field-bg（#1a1c27）里了。 */
    --rf-line-strong: #494d66;
    --rf-line-soft: #2a2d3d; /* 兼作外壳页签选中底（theme.css §14，跟这一档一起调） */

    /* 高对比档：深色下同样把描边提亮、提示色加浓（同一组交换规则） */
    --rf-line-hc: #565b78;
    --rf-line-strong-hc: #757a99;
    --rf-line-soft-hc: #3a3e55;
    --rf-faint-hc: #b4b9cd;

    --rf-code-bg: #282b39;
    --rf-code-ink: #d5d9e8;

    /* 语义色取浅色那四色的**亮一档**（同一色相、深底上才够显），
       底与描边走半透明，深浅两套皮之间只有明度差、没有色相漂移。
       ⚠️ 深皮的基色是 Arco 的 **5 号**，所以它的 hover / active 档是 4 / 6 号
          （浅皮基色 6 号 → 5 / 7 号）—— 规律一致：hover 朝亮走一档、active 朝暗走一档。
          这两档必须在这里覆盖 :root 里那份浅皮值（写死色的令牌不会自动跟随）。 */
    --rf-ok: #23c343;
    --rf-ok-ink: #4cd263;
    --rf-ok-bg: rgba(35, 195, 67, .10);
    --rf-ok-line: rgba(35, 195, 67, .28);
    --rf-ok-hover: #4cd263;    /* green-4 */
    --rf-ok-active: #00b42a;   /* green-6 */

    --rf-warn: #ff9a2e;
    --rf-warn-ink: #ffb65d;
    --rf-warn-bg: rgba(255, 154, 46, .10);
    --rf-warn-line: rgba(255, 154, 46, .28);
    --rf-warn-hover: #ffb65d;  /* orange-4 */
    --rf-warn-active: #ff7d00; /* orange-6 */

    --rf-danger: #f76560;
    --rf-danger-ink: #f98981;
    --rf-danger-bg: rgba(247, 101, 96, .10);
    --rf-danger-line: rgba(247, 101, 96, .28);
    --rf-danger-hover: #f98981;  /* red-4 */
    --rf-danger-active: #f53f3f; /* red-6 */

    --rf-info: #8d4eda;
    --rf-info-ink: #a871e3;
    --rf-info-bg: rgba(141, 78, 218, .10);
    --rf-info-line: rgba(141, 78, 218, .28);
    --rf-info-hover: #a871e3;  /* purple-4 */
    --rf-info-active: #722ed1; /* purple-6 */

    /* 阴影：深色底上**只能用纯黑**（浅色皮那套中性灰 #1d2129 压在深底上等于看不见），
       靠加大不透明度出层次。浅色皮那条「影取字色色相」的规则到深色皮不成立，是刻意的。 */
    --rf-shadow-sm: 0 1px 2px rgba(0, 0, 0, .30);
    --rf-shadow: 0 2px 8px rgba(0, 0, 0, .28);
    --rf-shadow-pop: 0 6px 20px rgba(0, 0, 0, .48);

    /* 侧栏底 = #191a23 —— iView Admin Plus 的**侧栏底**实测色（rgb(25,26,35)）。
       「浅色内容区 + 墨黑侧栏」正是它的招牌观感：侧栏配色是**独立轴**（不随页面明暗
       翻），所以深色轴侧栏在浅色页面与深色页面下都是这一个值；#101117 留给更深的栅栏。
       ⚠️ 别改成读 --tblr-bg-surface（= --rf-surface #20222e）—— 那是**深色皮的卡片色**，
          「浅色页面 + 深色侧栏」时侧栏就会跟着解成卡片色（这会静默跑偏）。 */
    --rf-sidebar-bg: #191a23;

    /* 侧栏文字三档（深色轴）—— 必须是**与底同色相的灰**，不能用纯中性灰：
       底色 #191a23 是蓝调（hue≈228°），中性灰压上去会被同时对比衬得发暖。
       ink #babdca = hsl(228,13%,76%)，明度比 --tblr-body-color 低一档（整栏才不顶眼）；
       muted / faint 复用深皮那两档（本就是蓝调灰）。
       交互态回 --tblr-body-color，见 rageframe.css 的菜单与栅栏两节。
       ⚠️ 栅栏（.rf-fence，#101117）是固定 dark 子树，读到的就是这三档。 */
    --rf-sidebar-ink: #babdca;
    --rf-sidebar-muted: #a9adbe;
    --rf-sidebar-faint: #787d92;

    --rf-link-hover: #f4f5fa;

    --tblr-body-bg: var(--rf-page);
    --tblr-body-bg-rgb: 25, 26, 35;
    --tblr-body-color: var(--rf-ink);
    --tblr-body-color-rgb: 233, 235, 242;
    --tblr-body-font-family: var(--rf-font);
    --tblr-font-sans-serif: var(--rf-font);   /* 直接读 sans-serif 的组件（.font-sans-serif 等）同步 */
    --tblr-body-font-size: var(--rf-fs-body);   /* 正文 14px（档位见顶部「字号阶层」） */
    --tblr-body-line-height: 1.6;

    /* 标题档位映射（与浅色块一致：Tabler 变量按主题块各自声明） */
    --tblr-font-size-h1: var(--rf-fs-h1);
    --tblr-font-size-h2: var(--rf-fs-h2);
    --tblr-font-size-h3: var(--rf-fs-h3);
    --tblr-font-size-h4: var(--rf-fs-h4);
    --tblr-font-size-h5: var(--rf-fs-h5);
    --tblr-font-size-h6: var(--rf-fs-h6);

    /* 字重：**全站上限 500**。
       关键事实：微软雅黑只有 Regular(400) / Bold(700) 两个字重，
       CSS 600 会向上匹配到 **Bold** —— 小字号下又糊又浓淡不均（观感「一个字大一个字小」）。
       所以 400 与 500 都落 Regular，600 及以上才会跳成 Bold，全站不用。
       档位：正文/控件/标题 = 400；结构性标签（th / label / 卡片标题 / strong）= 500。 */
    --tblr-font-weight-normal: 400;
    --tblr-font-weight-medium: 500;
    --tblr-font-weight-semibold: 500;   /* strong / b 走这档（承担 label 角色），**不是** 600 */
    --tblr-font-weight-bold: 500;       /* 不放到 700：雅黑 Bold 在小字号下发糊 */
    --tblr-font-weight-headings: 400;   /* 标题靠字号 + 颜色拉层级，不靠粗细 */

    /* 深色下同样取用户选的那个主色（不在这里另定提亮色）——
       否则「深色页面里主色被提亮、深色侧栏里主色又不变」会自相矛盾；
       将来要给深色整体提亮，只改这一行即可（如 color-mix 掺白）。 */
    --tblr-primary: var(--rf-accent);
    --tblr-primary-fg: var(--rf-on-strong);

    /* Tabler 自带的「加深」派生（oklch 从主色减亮）——它是 Tabler 实心按钮
       hover / active 的默认底。改成 Arco 的 7 号（点击档）：字面语义「加深」保住了，
       而 hover 那一档由我们自己的规则显式覆盖成 5 号（Arco 口径 hover 变亮一档），
       权重更高、压得住这条。语义色同理（各自的 -active 档，两皮取值不同）。 */
    --tblr-primary-darken: var(--rf-accent-active);
    --tblr-success-darken: var(--rf-ok-active);
    --tblr-warning-darken: var(--rf-warn-active);
    --tblr-danger-darken: var(--rf-danger-active);
    --tblr-info-darken: var(--rf-info-active);

    --tblr-secondary: var(--rf-muted);
    --tblr-secondary-fg: var(--rf-ink);
    --tblr-secondary-bg: var(--rf-surface);

    --tblr-bg-surface: var(--rf-surface);
    --tblr-bg-surface-secondary: var(--rf-surface-2);
    --tblr-bg-surface-tertiary: var(--rf-surface-3);
    --tblr-bg-surface-dark: var(--rf-fence);
    --tblr-bg-forms: var(--rf-field-bg);

    --tblr-border-color: var(--rf-line);
    --tblr-border-color-translucent: rgba(52, 55, 74, .72);
    --tblr-border-light-color: var(--rf-line-soft);

    --tblr-link-color: var(--tblr-primary);
    --tblr-link-hover-color: var(--rf-link-hover);

    --tblr-border-radius: var(--rf-r-sm);
    --tblr-border-radius-sm: var(--rf-r-xs);
    --tblr-border-radius-lg: var(--rf-r);
    --tblr-border-radius-xl: var(--rf-r-lg);
    /* --tblr-border-radius-pill 不覆盖，理由见浅色块同名注释 */

    --tblr-btn-bg: var(--rf-surface);
    --tblr-btn-color: var(--rf-ink);
    --tblr-btn-border-color: var(--rf-line-strong);
    --tblr-btn-hover-bg: var(--rf-surface-2);
    --tblr-btn-hover-color: var(--rf-ink);
    --tblr-btn-hover-border-color: var(--rf-line-strong);
    --tblr-btn-box-shadow: var(--rf-shadow-sm);
    --tblr-btn-border-radius: var(--rf-r-sm);

    --tblr-success: var(--rf-ok);
    --tblr-warning: var(--rf-warn);
    --tblr-danger: var(--rf-danger);
    --tblr-info: var(--rf-info);
}

/* ── 高对比（无障碍）────────────────────────────────────────
   两个触发条件挂**同一组值**：系统开了「对比度增强」（prefers-contrast: more），
   或手动在 <html> 上挂 .rf-contrast。换的四件（描边三档 + 提示色）各自的具体
   色值写在两套皮的 --rf-*-hc 令牌里，这里只做「换名」—— 深浅两皮因此共用同一份
   交换规则，不用把颜色再抄一遍。

   选择器必须带上 [data-bs-theme]：侧栏与栅栏是**自带明暗属性的子树**，
   它们在皮里各自声明过 --rf-line 系列，只改 :root 传不进去（直接命中压过继承）。 */
@media (prefers-contrast: more) {
    :root,
    [data-bs-theme] {
        --rf-line: var(--rf-line-hc);
        --rf-line-strong: var(--rf-line-strong-hc);
        --rf-line-soft: var(--rf-line-soft-hc);
        --rf-faint: var(--rf-faint-hc);
    }
}

html.rf-contrast,
html.rf-contrast [data-bs-theme] {
    --rf-line: var(--rf-line-hc);
    --rf-line-strong: var(--rf-line-strong-hc);
    --rf-line-soft: var(--rf-line-soft-hc);
    --rf-faint: var(--rf-faint-hc);
}

/* ============================================================
   2. 基础
   ============================================================ */

body {
    background-color: var(--rf-page);
    /* 纯色页面底（2026-10-16 拍板去掉「越往下越淡」的上下渐变）：
       纯色更干净，也不再需要 background-attachment: fixed 兜滚动。
       独立页（登录 / 错误页）的底同口径，见 rageframe.css 的 .rf-plain-body。 */
    /* 这三条**防御性**恢复系统默认（Tabler 默认开着灰度平滑）：
       -webkit-font-smoothing 是 macOS 专属属性、Windows 版 Chrome 不实现，
       对 Windows 渲染没有影响；中文小字号开灰度只会更糊。
       保留三行在 macOS 上等于恢复系统默认。 */
    -webkit-font-smoothing: auto;
    -moz-osx-font-smoothing: auto;
    text-rendering: auto;
}

/* ── 图标尺寸：全局基线 ─────────────────────────────────────────────
   一条规则定死图标字体的大小口径，其余地方不再散写：
     font-size: max(10px, 1em)
       · 1em = 跟随所在文本 —— 正文 / 按钮（默认档）/ 表格 / 菜单的文本都是
         14px，图标自然就是 14px（= --rf-icon-size，默认档）；
         小控件（btn-sm 12px …）的文本是 12px，图标就是 12px。
       · max(…, 10px) 是**全站地板**（--rf-icon-size-xs）：任何比 10px 还小的
         上下文里的图标都被托到 10px。这一档只兜底 —— 12 与 14 那些落点都是
         显式指定，不受影响。

   ⚠️ 用 :where() —— 零优先级。所以各处**刻意**的尺寸（品牌标 16 / 页头图标片
      20 / 空态大图标 24 / 加载转圈 20 …）以及显式写死的图标规则照常胜出，
      这条只兜「谁都没管」的图标。
   ⚠️ 只对图标基础类生效（`.rfi` —— classFor() 产出的串恒带它）；
      Tabler 的 SVG .icon 是宽高定尺寸、不受影响。 */
:where(.rfi) {
    font-size: max(var(--rf-icon-size-xs), 1em);
}

/* ── 主题切换的一次性过渡（挂类的是 rageframe.js，不是 CSS）────────────
   切明暗 / 换主色时整页颜色本来是硬切，观感上「闪一下」。rageframe.js 在用户
   切换的那一刻给 <html> 挂 .rf-theme-anim，改完值 ~260ms 后摘掉 —— 首屏永远
   不会带上它，所以不存在「打开页面整页渐变」这回事。

   只过渡**颜色类**属性（背景 / 文字 / 描边 / 图标填充），不动几何：高度、
   间距、圆角一变就是布局位移，那才叫真的晃。

   ⚠️ 用 !important 压过组件各自的 transition 简写（.btn 的 .15s 等）——
      否则同一页里两种时长并存，大色块先到位、按钮慢半拍。
   ⚠️ 与 rageframe.css 那两条压制的关系：首屏压制 `html.rf-booting .rf-shell *`
      特异性更高，照样压得住；「减少动态效果」不靠特异性 —— 它把 --rf-dur-slow
      压到 .01ms，这条过渡拿到的就是近乎 0 的时长，自然没了。 */
html.rf-theme-anim,
html.rf-theme-anim * {
    transition: background-color var(--rf-dur-slow) var(--rf-ease-curve),
                border-color var(--rf-dur-slow) var(--rf-ease-curve),
                color var(--rf-dur-slow) var(--rf-ease-curve),
                fill var(--rf-dur-slow) var(--rf-ease-curve),
                stroke var(--rf-dur-slow) var(--rf-ease-curve) !important;
}

::selection {
    background: var(--rf-selection-bg);
    color: var(--rf-ink);
}

/* 细滚动条：外壳的侧栏 / 标签栏 / iframe 都会滚，默认那条灰槽太抢眼 */
* {
    scrollbar-width: thin;
    scrollbar-color: var(--rf-line-strong) transparent;
}

::-webkit-scrollbar {
    width: 10px;
    height: 10px;
}

::-webkit-scrollbar-track {
    background: transparent;
}

::-webkit-scrollbar-thumb {
    /* 3px 透明边框 + content-box：视觉上是 4px 宽的圆条，不顶着内容 */
    background: var(--rf-line-strong);
    border: 3px solid transparent;
    background-clip: content-box;
    border-radius: 999px;
}

::-webkit-scrollbar-thumb:hover {
    background: var(--rf-faint);
    background-clip: content-box;
}

a {
    color: var(--tblr-primary);
    text-decoration-color: var(--rf-link-underline);
    text-underline-offset: 2px;
}

@media (hover: hover) {
    a:hover {
        color: var(--rf-link-hover);
    }
}

hr {
    border-top-color: var(--rf-line-soft);
    opacity: 1;
}

/* 行内代码：淡靛底的小方块；<pre> 里不再套一层底 */
code {
    background: var(--rf-code-bg);
    color: var(--rf-code-ink);
    padding: 2px 6px;
    border-radius: var(--rf-r-xs);
    font-size: var(--rf-fs-sm);   /* 13 —— 行内代码走次要档 */
}

pre {
    background: var(--rf-surface-2);
    border: 1px solid var(--rf-line);
    border-radius: var(--rf-r);
    padding: 16px 18px;
    color: var(--rf-ink);
}

pre code {
    background: none;
    padding: 0;
    color: inherit;
    font-size: inherit;
}

/* ============================================================
   3. 排版
   ============================================================ */

/* 标题只管颜色与字重 —— 字号由顶部「字号阶层」映射到 Tabler 的
   --tblr-font-size-h*（h1 24 / h2 19 / h3 16 / h4 16 / h5 14 / h6 12），
   不需要在这里逐个写 size（要调档位就改 --rf-fs-*）。 */
h1, h2, h3, h4, h5, h6 {
    color: var(--rf-ink);
    font-weight: 400;
}

/* 视图里的「指标数字」用的是 Tabler 的 .fs-3（卡片大数字，三处列表页）——
   它默认 16px = 16px，和区块标题同号、数字一点都不抢眼；提到指标档 26px。
   Tabler 那条是 !important，所以这里必须同样 !important 且后置才能压过。 */
.fs-3 {
    font-size: var(--rf-fs-metric) !important;
}

/* Bootstrap 的 .small 是 0.875em —— 正文 14px 下算出 12.25px（分数、跨档位，
   落不到任何一档上）。统一收到提示档 12px：全站小字只有一个值。 */
.small,
small {
    font-size: var(--rf-fs-xs);
}

/* Tabler 另一批辅助文字同样用 .875em（表单提示、校验反馈、引用署名、图注）——
   em 是相对字号，正文一变它就跟着漂、还会算出 12.25px 这种分数。
   全部钉到提示档 12px（.form-text 34 处、.invalid-feedback 15 处，都是提示语义）。 */
.form-hint,
.form-text,
.valid-feedback,
.invalid-feedback,
.blockquote-footer,
.figure-caption,
.initialism {
    font-size: var(--rf-fs-xs);
}

.page-title {
    font-size: var(--rf-fs-h1);   /* 24 —— 页面标题（排版刻度最高档） */
    font-weight: 400;           /* 标题不靠粗细（h1~h6 全 400） */
    color: var(--rf-ink);
}

/* 页头右侧的操作按钮跟标题同排，靠这条对齐基线 */
.page-header {
    margin-bottom: 16px;
}

/* Tabler 的 .fw-* 是硬编码 !important，不读变量，这里单独重映射到 500 档：
   视图里的 fw-semibold / fw-bold 都用在表头、字段名上，统一收进
   「结构性标签（th / label / 卡片标题）= 500」这一档。**不放到 600/700** ——
   雅黑只有 Regular/Bold 两个字重，600 向上匹配会跳成 Bold，小字号发糊。 */
.fw-semibold,
.fw-bold,
.fw-bolder {
    font-weight: 500 !important;
}

/* ============================================================
   4. 按钮
   ============================================================ */

/* ── 控件高度刻度（.btn / .form-control / .form-select 共用）────
   高度与内边距**成对**钉住：`min-height` 定下限、`padding-y` 定天然高度，
   两者一起才是一档（算式见顶部 --rf-ctl-h-* / --rf-ctl-py-* 的注释）。
   令牌档是 Tabler 原生高度**收紧后**的值：Tabler 现算是 28 / 40 / 48，而
   2026-10-13 已整体收成 24/30/36/44、2026-10-14 lg 再收成 42、2026-10-16 对照
   Arco 整体再压一档成 24/28/32/36（见顶部令牌注释）——
   所以这里是**有意改矮**、不再是等价替换；换「紧凑」档时两组一起走，控件才会真的变矮。
   ⚠️ 只写 min-height 是压不动默认档的：它的天然高度本来就等于令牌值。 */
.btn,
.form-control,
.form-select {
    min-height: var(--rf-ctl-h);
    padding-top: var(--rf-ctl-py);
    padding-bottom: var(--rf-ctl-py);
}

.btn-sm,
.form-control-sm,
.form-select-sm {
    min-height: var(--rf-ctl-h-sm);
    padding-top: var(--rf-ctl-py-sm);
    padding-bottom: var(--rf-ctl-py-sm);
}

.btn-lg,
.form-control-lg,
.form-select-lg {
    min-height: var(--rf-ctl-h-lg);
    padding-top: var(--rf-ctl-py-lg);
    padding-bottom: var(--rf-ctl-py-lg);
}

/* ── 输入组（.input-group / .input-group-{sm,lg}）里的控件也得吃同一组令牌 ──
   Tabler 给 `.input-group-lg > .form-control / > .btn / > .form-select /
   > .input-group-text` 自带 min-height 与 padding，**特异性 0,2,0** —— 压过上面
   那条 `.form-control`（0,1,0）。结果是输入组里的控件对四档令牌**完全免疫**：
   实测恒为 sm 28 / lg 48，而令牌（与人的预期）是 24 / 36 —— 演示页上并排一放
   就看得出来。这里把子元素拉回同一组令牌，高度与内边距照样成对给。
   ⚠️ 只补 sm / lg 是不够的：默认档一样被 Tabler 挡着（内边距 7px，
      天然高度 36 而非令牌的 32），不补就是「整排里只有输入组高 4px」。 */
.input-group > .btn,
.input-group > .form-control,
.input-group > .form-select,
.input-group > .input-group-text {
    min-height: var(--rf-ctl-h);
    padding-top: var(--rf-ctl-py);
    padding-bottom: var(--rf-ctl-py);
}

.input-group-sm > .btn,
.input-group-sm > .form-control,
.input-group-sm > .form-select,
.input-group-sm > .input-group-text {
    min-height: var(--rf-ctl-h-sm);
    padding-top: var(--rf-ctl-py-sm);
    padding-bottom: var(--rf-ctl-py-sm);
}

.input-group-lg > .btn,
.input-group-lg > .form-control,
.input-group-lg > .form-select,
.input-group-lg > .input-group-text {
    min-height: var(--rf-ctl-h-lg);
    padding-top: var(--rf-ctl-py-lg);
    padding-bottom: var(--rf-ctl-py-lg);
}

/* md 档的输入组（没有 .input-group-md 容器档，由前缀槽自己带上 .form-control-md /
   .form-select-md 撑起来）必须写在上面两条之后：组的默认档规则是 (0,2,0)、
   压得过 `.form-control-md` 的 (0,1,0)，不补这一条就会出现
   「组内冒号那截 32px、输入框那截 28px」的错位。 */
.input-group > .form-control-md,
.input-group > .form-select-md,
.input-group > .input-group-text.form-control-md,
.input-group > .btn-md {
    min-height: var(--rf-ctl-h-md);
    padding-top: var(--rf-ctl-py-md);
    padding-bottom: var(--rf-ctl-py-md);
}

/* md 档的**容器**写法：`.input-group.input-group-md > *`。
   与上面那组「前缀槽自己带 .form-control-md」并存 —— 后者适合只有一个输入框的
   场合，前者适合「输入框 + 按钮 + 附加物」整组换档：只换容器一个类，组内每一格
   跟着走，不用逐个子元素补 class（漏一个就是 32 / 28 混排）。
   选择器同权 (0,2,0) 且写在 `.input-group > .btn` 之后，靠后置胜出。 */
.input-group-md > .btn,
.input-group-md > .form-control,
.input-group-md > .form-select,
.input-group-md > .input-group-text {
    min-height: var(--rf-ctl-h-md);
    padding-top: var(--rf-ctl-py-md);
    padding-bottom: var(--rf-ctl-py-md);
    font-size: var(--rf-fs-sm);
}

/* md 档的按钮组：Tabler 只给了 sm / lg 两档容器，组内按钮的高度由容器规则
   统一下压 —— 想要 28px 的组只能自己补一档，否则组里每颗按钮还是各自的档位。 */
.btn-group-md > .btn {
    min-height: var(--rf-ctl-h-md);
    padding-top: var(--rf-ctl-py-md);
    padding-bottom: var(--rf-ctl-py-md);
    font-size: var(--rf-fs-sm);
}

/* ── 附加物里装的是**按钮**时，把那层壳摘掉 ────────────────────────────────
   `.input-group-text` 本来是给「元」「kg」「图标」这类**文字附加物**的：灰底 +
   左右内边距 + 与输入框同高。往里塞一颗 `.btn`（ActiveField 的
   `->append(Html::button(...))`）就变成「灰纸包着一颗白按钮」—— 双层边界，
   按钮四周各留一圈灰边，看着像按钮贴错了位置。

   这里只摘壳、不搬家：灰底 / 边框 / 内边距归零，按钮就成为输入组里普通的一格。
   高度不写死 —— 附加物是 flex item，按钮在它内部被 stretch，自动跟随组的档位
   （sm / lg 不用另外补）。-1px 让按钮的左边框压住输入框的右边框，两条 1px
   不至于并成 2px；左侧圆角清零，与输入框的平角对接。

   ⚠️ 只命中「附加物里直接装按钮」这一形态：
      · 图标 / 文字附加物（`<i>`、`元`）不匹配；
      · 自身就是 `.input-group-text` 的按钮（色板预览那种 `<button
        class="input-group-text">`）是 `> .btn` 的**兄弟**而非子元素，也不受影响。
   ⚠️ 必须写在上面的 `.input-group > .input-group-text`（0,2,0）之后 ——
      本条同为 (0,2,0)，靠后置胜出才能压掉那几条内边距。 */
.input-group-text:has(> .btn) {
    display: flex;
    padding: 0;
    border: 0;
    background: transparent;
}

.input-group-text:has(> .btn) > .btn {
    margin-left: -1px;
    border-top-left-radius: 0;
    border-bottom-left-radius: 0;
    /* 发码按钮的文案会变（「获取验证码」→「60 秒后重发」），不定宽的话
       每变一次输入框就跟着缩一截。按长的那句占位，点击前后宽度不动。 */
    min-width: 8em;
}

/* md 档：夹在 sm(24) 与默认(32) 之间的中号档，整组尺寸（padding / 字号 /
   圆角 / 高度）都写在这里。内边距不用 Tabler 的 --tblr-btn-padding-y，
   改走 --rf-ctl-py-md —— 密度换挡靠的就是这组令牌。 */
.btn-md {
    --tblr-btn-padding-x: 12px;
    --tblr-btn-font-size: var(--rf-fs-sm);
    --tblr-btn-border-radius: var(--rf-r-sm);
    min-height: var(--rf-ctl-h-md);
    padding-top: var(--rf-ctl-py-md);
    padding-bottom: var(--rf-ctl-py-md);
}

/* ⚠️ 这两个 md 档表单控件的内边距刻意写 0：它们**靠 min-height 定位**
   （自然高度 0+0+16+2 = 18 < 令牌），所以密度换挡时高度自动跟着令牌走，
   不需要再配一份内边距令牌 —— 与上面 .btn-md 的情况正好相反。 */
.form-control-md {
    min-height: var(--rf-ctl-h-md);
    padding: 0 12px;
    font-size: var(--rf-fs-sm);
    line-height: 16px;
    border-radius: var(--rf-r-sm);
}

/* .form-select 的 inline-end 内边距要给下拉箭头留位置，不能整条 padding 覆盖 */
.form-select-md {
    min-height: var(--rf-ctl-h-md);
    padding-top: 0;
    padding-bottom: 0;
    padding-inline-start: 12px;
    font-size: var(--rf-fs-sm);
    line-height: 16px;
    border-radius: var(--rf-r-sm);
}

/* ── 开关（.ui-switch）：与控制件同高居中 + 加大一号 ──────────────
   开关本体只有「一行文字」那么高（Tabler 的 32px × 20px），在
   「label 在上」与「label 在左」的字段行里都顶着上沿 —— 与邻居的
   36px 控件（--rf-ctl-h）垂直中心差半个头，看着像没对齐的一行。

   于是两件事一起做：
     ① 放进与控制件同高的**居中带**（min-height + align-items:center），
        垂直中心自然与同行的 input / select 对齐；
     ② 轨道**加大一号**：32px × 20px → 40px × 24px。
   ⚠️ knob 尺寸在 Tabler 里是**按 20px 硬算**的
      （--tblr-form-switch-knob-size: calc(20px * .75)），不是 em ——
      放大轨道必须把这两个 knob 令牌一起按新高度重算，否则 18px 的 knob
      会顶在 24px 的轨道里错位（轨道变大、珠子不跟着长）。

   ⚠️ 类名加在 .form-check **自身**，不要再包一层：form-validate.js 的
      anchorOf() 对 checkbox / switch 锚的就是 .form-check，提示块必须是它的
      **紧邻下一个兄弟** —— 中间插一层包裹，服务端 @error 块与脚本自建的
      提示会各出一份（重影）。
   ⚠️ 不动 padding-inline-start：Tabler 靠「容器 padding 40px + input 负外边距
      -40px」把轨道拉到最左。改成 flex 后这套仍然成立（flex 下负外边距照算），
      自己再写一遍反而把轨道右推 40px。
   ⚠️ 去掉 .form-check 自带的 margin-bottom(12px)：占位改由 min-height 那条
      居中带负责，留着它这一格会比邻居高 12px。 */
.form-check.ui-switch {
    display: flex;
    align-items: center;
    /* 轨道与文字之间要留一口气：Tabler 原本靠「容器 padding 40px + input 负
       外边距 -40px」把文字推到轨道右侧 40px 处，改成 flex 后这个间距归零，
       开关与标签贴在一起像连体。10px 与 `.btn-list` / `.form-hint` 的口径一致。 */
    gap: 10px;
    min-height: var(--rf-ctl-h);
    margin-bottom: 0;
}

.form-check.ui-switch .form-check-input {
    --tblr-form-switch-knob-size: calc(24px * .75);
    --tblr-form-switch-knob-offset: calc(24px * .125);
    flex: 0 0 auto;
    width: 40px;
    height: 24px;
}

/* ── 行内开关（Html::ajaxEditableToggle 的格子形态）────────────────────
   Tabler 的 .form-check 是「给 label 让位」的盒子：容器 padding-inline-start
   32px + input 负外边距 -32px + margin-bottom 12px。这套在「有 label 的
   表单行」里成立，进了表格格子就是一串视觉毛病：input 浮在文档流外、包装盒
   只剩一条 strut 的高度（开关本体溢出盒子），再叠上 margin-bottom —— 格子里
   的开关既不居中也偏出半格。

   行内形态直接改成 flex：盒子高度 = 开关本体高度，落在线上垂直居中；
   padding / 负外边距一并清零 —— 没有 label 可让位，那套只会把轨道拽出格子。 */
.form-check.rf-inline-toggle {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin: 0;
    padding-inline-start: 0;
    vertical-align: middle;
}

.form-check.rf-inline-toggle .form-check-input {
    margin-inline-start: 0;
}

.btn {
    /* 圆角与字重都不直接写属性，走 Tabler 的槽：
       · --tblr-btn-border-radius 在令牌区已经映射成控件档，Tabler 的 .btn 会去读它；
         直接写 border-radius 的话，会以「同权重后置」把 .btn-sm / .btn-lg 各自
         重定义的那份尺寸档圆角一起压平（三档尺寸全变成控件档）。
       · --tblr-btn-font-weight 就是 Tabler 自己在 .btn 上定义的那个槽，
         这里只是把默认的 500 换成 400。 */
    --tblr-btn-font-weight: var(--tblr-font-weight-normal);   /* 按钮 400 */
    /* 中性态的白底 / 描边 / 悬停底来自令牌区的 --tblr-btn-*。
       这里**不能**再写 --tblr-btn-bg —— .btn-primary 等 variant 是在自己的
       规则里局部重定义这些变量，同权重后置的话会被这条整片盖掉，
       所有 primary 按钮都会变白底。 */
    transition: background-color var(--rf-dur) var(--rf-ease-curve),
                border-color var(--rf-dur) var(--rf-ease-curve),
                color var(--rf-dur) var(--rf-ease-curve),
                box-shadow var(--rf-dur) var(--rf-ease-curve);
}

.btn:not(:disabled):not(.disabled):active {
    box-shadow: none;
}

/* 键盘焦点环 = 柔光环（① 形态：按钮大量出现在表格与贴边容器里，outline 环
   会被相邻单元格压住；box-shadow 不占布局、跟着圆角走）。

   ⚠️ 两条都要与 Tabler 同形才压得动，别「简化」回 .btn:focus-visible 一条：
     - outline 压 Tabler 的 .btn:focus-visible（它自带一圈 2px 浅灰实心环，
       不压就是双环）—— 同权重靠 theme.css 后置取胜；
     - box-shadow 压 Tabler 的 .btn:focus-visible:not(:active) —— :not() 里的
       参数也计入权重，少写 :not(:active) 就是 (0,2,0) 输给 (0,4,0) 的死规则
       （实测过：只写 .btn:focus-visible 时柔光环从未生效，按钮聚焦一直是
       Tabler 的浅灰 outline 环 + 按钮常规投影）。
   :active（按住）时环随 .btn:active 的 box-shadow:none 一起收掉，属预期。 */
.btn:focus-visible {
    outline: 0;
}

.btn:focus-visible:not(:active) {
    box-shadow: var(--rf-focus-ring-shadow);
}

/* 实心按钮的 hover / active 取 **Arco 色板原档**（--rf-accent-hover / -active，
   = 该色相的 5 / 7 号）。
   ⚠️ hover 是**变亮一档**，不是 Tabler 默认的「加深」—— 2026-10-16 用户拍板
      「要 Arco 味」：Arco 的实心按钮悬停变亮、按住（active）才沉到 7 号。
      两档都照抄色板（color-mix 算不出来，见令牌区注释）。
   ⚠️ hover 选择器必须带 :not(:disabled):not(.disabled) 才压得住 Tabler 的
      .btn:not(:disabled):not(.disabled):hover —— 同权重靠后置取胜，少一个 :not
      就输给 Tabler 的 oklch 派生底。
   ⚠️ active **不套 @media (hover: hover)**：触屏按下也要有反馈。 */
@media (hover: hover) {
    .btn-primary:not(:disabled):not(.disabled):hover {
        background-color: var(--rf-accent-hover);
        border-color: var(--rf-accent-hover);
    }
}

.btn-primary:not(:disabled):not(.disabled):active {
    background-color: var(--rf-accent-active);
    border-color: var(--rf-accent-active);
}

/* 实心语义按钮同理，只是换各自的 hover / active 档（浅皮 5 / 7 号、
   深皮 4 / 6 号，见令牌区）。
   ⚠️ 只改底与描边，**不动文字色** —— Tabler 给这些实心按钮的前景色按各自基色
      定的（warning 走深字），手改会把对比度做反。 */
@media (hover: hover) {
    .btn-success:not(:disabled):not(.disabled):hover {
        background-color: var(--rf-ok-hover);
        border-color: var(--rf-ok-hover);
    }

    .btn-danger:not(:disabled):not(.disabled):hover {
        background-color: var(--rf-danger-hover);
        border-color: var(--rf-danger-hover);
    }

    .btn-warning:not(:disabled):not(.disabled):hover {
        background-color: var(--rf-warn-hover);
        border-color: var(--rf-warn-hover);
    }

    .btn-info:not(:disabled):not(.disabled):hover {
        background-color: var(--rf-info-hover);
        border-color: var(--rf-info-hover);
    }
}

.btn-success:not(:disabled):not(.disabled):active {
    background-color: var(--rf-ok-active);
    border-color: var(--rf-ok-active);
}

.btn-danger:not(:disabled):not(.disabled):active {
    background-color: var(--rf-danger-active);
    border-color: var(--rf-danger-active);
}

.btn-warning:not(:disabled):not(.disabled):active {
    background-color: var(--rf-warn-active);
    border-color: var(--rf-warn-active);
}

.btn-info:not(:disabled):not(.disabled):active {
    background-color: var(--rf-info-active);
    border-color: var(--rf-info-active);
}

.btn-outline-primary {
    color: var(--tblr-primary);
    border-color: var(--rf-accent-line);
}

@media (hover: hover) {
    .btn-outline-primary:not(:disabled):not(.disabled):hover {
        background-color: var(--tblr-primary);
        border-color: var(--tblr-primary);
        color: var(--tblr-primary-fg);
    }
}

.btn-outline-secondary {
    color: var(--rf-muted);
    border-color: var(--rf-line-strong);
}

@media (hover: hover) {
    .btn-outline-secondary:not(:disabled):not(.disabled):hover {
        background-color: var(--rf-surface-2);
        border-color: var(--rf-line-strong);
        color: var(--rf-ink);
    }
}

.btn-outline-danger {
    color: var(--rf-danger);
    border-color: var(--rf-danger-line-soft);
}

@media (hover: hover) {
    .btn-outline-danger:not(:disabled):not(.disabled):hover {
        background-color: var(--rf-danger);
        border-color: var(--rf-danger);
        color: var(--rf-on-strong);
    }
}

/* 其余三个语义色走与上面**完全相同**的一套（静止 = 柔线描边 + 语义色文字，
   hover = 实心 + on-color）。此前它们没有主题规则、用的是 Tabler 默认，
   描边口径与 primary / secondary / danger 对不上。
   文字取 -ink 档而不是基色：这三个基色在浅底上偏亮，-ink 才是可读的深一档
   （danger 的基色与 -ink 恰好同值，所以上面那条直接写基色也等价）。 */
.btn-outline-success {
    color: var(--rf-ok-ink);
    border-color: var(--rf-ok-line-soft);
}

.btn-outline-warning {
    color: var(--rf-warn-ink);
    border-color: var(--rf-warn-line-soft);
}

.btn-outline-info {
    color: var(--rf-info-ink);
    border-color: var(--rf-info-line-soft);
}

@media (hover: hover) {
    .btn-outline-success:not(:disabled):not(.disabled):hover {
        background-color: var(--rf-ok);
        border-color: var(--rf-ok);
        color: var(--rf-on-strong);
    }

    .btn-outline-warning:not(:disabled):not(.disabled):hover {
        background-color: var(--rf-warn);
        border-color: var(--rf-warn);
        color: var(--rf-on-strong);
    }

    .btn-outline-info:not(:disabled):not(.disabled):hover {
        background-color: var(--rf-info);
        border-color: var(--rf-info);
        color: var(--rf-on-strong);
    }
}

/* 描边按钮的**静止底**：浅色皮给实的表面色，不吃「透明透父级」。
   Tabler 的 outline 变体把 --tblr-btn-bg 设成 transparent —— 深色皮下卡片、
   页底、内嵌底都同族，透出来几乎看不出；浅色皮下按钮挂在 #F5F7F9 的页头区
   或 #FAFAFA 的卡片头上时，按钮自己会跟着发灰，和白卡片差一档，
   于是「按钮像少了块底」。
   ⚠️ 只改这个变量，不写 background-color：hover / active 各自有底色规则，
      变量式覆盖让「静止」与「悬浮」两条路互不干扰。
   ⚠️ 限定浅色皮：深色皮下透明反而更贴合（深底上的按钮不需要多一层同色片）。 */
[data-bs-theme="light"] .btn-outline-primary,
[data-bs-theme="light"] .btn-outline-secondary,
[data-bs-theme="light"] .btn-outline-danger,
[data-bs-theme="light"] .btn-outline-success,
[data-bs-theme="light"] .btn-outline-warning,
[data-bs-theme="light"] .btn-outline-info {
    --tblr-btn-bg: var(--rf-surface);
}

/* 描边按钮的「展开中 / 按下」态（.show / :active）不能套实心档
   ─────────────────────────────────────────────────────────────
   Tabler 有一条共用规则把 `.btn.show` 与 `.btn:active` 一并指向
   `--tblr-btn-active-{bg,color,border-color,shadow}`，而描边按钮的这几档
   就是**该变体的实心色**（`--tblr-btn-variant`）外加一枚 inset 按压阴影。
   后果：描边按钮一展开下拉就变成一整块实心深灰（工具栏的「导出」「列」
   两颗最明显）——与「静止 = 描边、悬浮 = 淡底」的主题口径对不上。

   这里只改这四个**变量**，观感就是「与 hover 同一档淡底 + 深字 + 原描边」：
   鼠标本就停在按钮上，点开下拉的整段时间里颜色不跳变（Tabler 那条激活规则
   的权重是 (0,3,0) 的 `:not(.btn-check)+.btn:active`，照抄选择器还得再加权
   才压得住；变量则和其它按钮规则一样按「theme.css 后加载」生效）。
   ⚠️ `--tblr-btn-active-color` 必须覆盖：它的缺省是 on-color（浅色字），
      在淡底上会变成白字看不见。
   ⚠️ `--tblr-btn-active-shadow` 回指静止档的 `--tblr-btn-box-shadow` 而**不是
      none**：Tabler 的激活规则是直接写 `box-shadow` 属性的，给 none 会把按钮
      静止时那层 1px 投影也一起抹掉，展开的一瞬间按钮会「塌」下去一点。 */
.btn-outline-primary,
.btn-outline-secondary,
.btn-outline-danger,
.btn-outline-success,
.btn-outline-warning,
.btn-outline-info {
    --tblr-btn-active-bg: var(--rf-surface-2);
    --tblr-btn-active-color: var(--rf-ink);
    --tblr-btn-active-border-color: var(--rf-line-strong);
    --tblr-btn-active-shadow: var(--tblr-btn-box-shadow);
}

.btn-light {
    background-color: var(--rf-surface-2);
    border-color: var(--rf-line);
    color: var(--rf-ink);
}

@media (hover: hover) {
    .btn-light:not(:disabled):not(.disabled):hover {
        background-color: var(--rf-surface-3);
        border-color: var(--rf-line-strong);
        color: var(--rf-ink);
    }
}

.btn-link {
    color: var(--tblr-primary);
}

.btn:disabled,
.btn.disabled {
    opacity: var(--rf-state-disabled-opacity);
    box-shadow: none;
}

/* 禁用态的 outline 按钮：摘掉语义色，走中性灰。
   ⚠️ 不能只靠上面那条 opacity —— 语义色是**乘**在透明度上的，常态描边本来
      就只有 40%（--rf-accent-line / --rf-*-line-soft），乘 0.55 之后反而比
      常态更浓，而文字被压淡：一浓一淡叠在一起，看着像「按了没反应」而不是
      「不可用」（「立即备份」点下去就是这副样子）。这里把文字与描边一起换成
      中性令牌、透明度交回 1，禁用态才与「semantic color = 可操作」这条约定
      对得上。
   ⚠️ 颜色要比 .btn-outline-secondary（次要 = --rf-muted + --rf-line-strong）
      **再淡一档**（提示色 + 最浅描边），否则「不可用的主行动」与「可用的次要
      行动」长得一模一样。 */
.btn-outline-primary:disabled,
.btn-outline-primary.disabled,
.btn-outline-secondary:disabled,
.btn-outline-secondary.disabled,
.btn-outline-danger:disabled,
.btn-outline-danger.disabled,
.btn-outline-success:disabled,
.btn-outline-success.disabled,
.btn-outline-warning:disabled,
.btn-outline-warning.disabled,
.btn-outline-info:disabled,
.btn-outline-info.disabled {
    color: var(--rf-faint);
    border-color: var(--rf-line);
    background-color: transparent;
    opacity: 1;
}

.btn-list {
    gap: 6px;
}

/* 表单组里的单选/复选按钮（.btn-check + .btn）：选中态用主色淡底 + 主色文字，
   与下拉、页签的选中口径一致（实心块在表单里显得太重） */
.btn-check:checked + .btn,
.btn-check:active + .btn {
    background-color: var(--rf-accent-bg);
    border-color: var(--tblr-primary);
    color: var(--tblr-primary);
}

/* ⚠️ outline 一并压掉：Tabler 的 .btn-check:focus-visible+.btn 自带一圈浅灰
   实心环（与 .btn 那条同款问题），不压就是柔光环外套硬环的双环。 */
.btn-check:focus-visible + .btn {
    outline: 0;
    box-shadow: var(--rf-focus-ring-shadow);
}

/* ============================================================
   5. 表单
   ============================================================ */

/* 标签：竖向 .form-label（label 在上）与横向 .col-form-label（label 在左，
   即 x-ui.field 的 horizontal 布局）**同色同重**。
   Tabler 给 .col-form-label 的只有字重，颜色是继承来的正文色 —— 不补的话
   横向标签比竖标签深一档。 */
.form-label,
.col-form-label {
    color: var(--rf-muted);
    font-weight: 500;
}

/* 横向标签：右对齐 + 顶格。
   · 右对齐 —— 标签贴近右侧控件，一列标签的右缘与控件左缘拉成一条竖线；
     窄屏（<576px）控件落到标签下方时改回左对齐，跟着正文走。
   · 顶格 —— 去掉 Tabler 的 padding-top；它把标签文字推到与输入框内文字齐平，
     一旦右列换成开关 / 单选这类没有内边距的控件就明显偏低。顶格之后标签
     始终与行的顶部（也就是输入框的上边框）同一水平线。 */
@media (min-width: 576px) {
    .col-form-label {
        text-align: right;
        padding-top: 0;
        padding-bottom: 0;
    }
}

.form-label {
    margin-bottom: 6px;
}

.form-control,
.form-select {
    background-color: var(--rf-field-bg);
    /* 控件描边档：输入框 / 按钮 / 勾选框 / 分页**同一色**（全站统一，见令牌区）。
       1px 的宽度不重写，它来自 Tabler 的 --tblr-border-width（Tabler 的 .form-control 读的就是它）。 */
    border-color: var(--rf-line-strong);
    /* 圆角不写：Tabler 的 .form-control / .form-select 读 --tblr-border-radius
       （已在令牌区映射成控件档），而 .form-control-sm / -lg 各自读 -sm / -lg 那一档。
       直接写 border-radius 会以「同权重后置」把两档尺寸圆角一起压平；
       更糟的是它对 .input-group-sm > .form-control（Tabler 是 0-2-0）压不动，
       于是「独立的小号控件」与「输入组里的小号控件」圆角会不一致。 */
    color: var(--rf-ink);
    box-shadow: none;
    transition: border-color var(--rf-dur) var(--rf-ease-curve),
                box-shadow var(--rf-dur) var(--rf-ease-curve),
                background-color var(--rf-dur) var(--rf-ease-curve);
}

.form-control::placeholder {
    color: var(--rf-faint);
}

/* 下拉的 sm 档与输入框 sm 档同高。
   Tabler 的坑：.form-select-sm 只覆盖 padding / font-size / border-radius，
   **没覆盖 line-height** —— 于是继承了 .form-select 的 20px，
   和 .form-control-sm 的 16px 差 4px（实测 28px vs 24px）。
   并排放在表头筛选行里，下拉比输入框冒出一截。
   这里补回 line-height，高度直接挂令牌（与 .form-control-sm 同档）。 */
.form-select-sm {
    line-height: 16px;
    min-height: var(--rf-ctl-h-sm);
}

/* 同一个坑的 lg 档：.form-select-lg 也只覆盖 padding / font-size，
   漏了 line-height（继承 .form-select 的 20px）—— 实测比
   .form-control-lg 矮 4px（行高 20 vs 24）。公式同 .form-control-lg，高度挂令牌。 */
.form-select-lg {
    line-height: 24px;
    min-height: var(--rf-ctl-h-lg);
}

.form-control:focus,
.form-select:focus {
    background-color: var(--rf-field-bg);
    border-color: var(--tblr-primary);
    color: var(--rf-ink);
    /* 焦点环不是 Tabler 那种硬描边，是主色 18% 的柔光 */
    box-shadow: var(--rf-field-glow);
    /* 关掉 Tabler 自带的那圈（--tblr-focus-ring：2px，在控件上偏移 -1px 贴着内缘
       画）+ 上面的柔光。本主题的聚焦表达就是「边框转主色 + 柔光」这一套，Tabler
       的环叠上去等于同一个焦点画两遍 —— 内层实心环与柔光外缘错开，看着又重又乱。
       ⚠️ 必须写在这条 :focus 规则里：Tabler 的 `.form-control:focus` 与这里同
          权重，靠本文件后置取胜；写在上面那条不带伪类的规则里会被它压过去。 */
    outline: 0;
}

.form-control:disabled,
.form-control[readonly],
.form-select:disabled {
    background-color: var(--rf-surface-2);
    border-color: var(--rf-line);
    color: var(--rf-faint);
}

.form-text {
    color: var(--rf-faint);
}

.input-group-text {
    background-color: var(--rf-surface-2);
    /* 与输入框同一档（这条边和 .form-control 是**同一圈轮廓**：Tabler 靠负 margin
       把两段边框并成一条，两边不同色就会在中缝上看出深浅两截）。 */
    border-color: var(--rf-line-strong);
    /* 圆角同 .form-control：默认档读 --tblr-border-radius，尺寸档由
       .input-group-sm/-lg > .input-group-text（Tabler 0-2-0）自己管，这里不写。 */
    color: var(--rf-muted);
}

.form-check-input {
    border-color: var(--rf-line-strong);
}

.form-check-input:checked {
    background-color: var(--tblr-primary);
    border-color: var(--tblr-primary);
}

.form-check-input:focus {
    border-color: var(--tblr-primary);
    box-shadow: var(--rf-field-glow);
    /* 同上：Tabler 的 .form-check-input:focus-visible 会再画一圈 —— 勾选框本身
       只有 16px 大，两圈描边糊在一起就看不清形态了。Tabler 那条带 :focus-visible
       与这里同权重，靠后置取胜。 */
    outline: 0;
}

.form-check-label {
    color: var(--rf-ink);
}

/* 校验失败：描边 + 柔光都用语义红，与 .invalid-feedback 的文字同色

   ⚠️ 聚焦那条必须把 `.form-control` / `.form-select` 一起写上：Tabler 对错误态的
      聚焦写的是 `.form-control.is-invalid:focus`（三个类），只写 `.is-invalid:focus`
      （两个类）权重低一档，压不住它那圈红环，于是错误框会「红柔光 + 红实环」重着画。 */
.is-invalid,
.was-validated .form-control:invalid,
.was-validated .form-select:invalid {
    border-color: var(--rf-danger);
}

.is-invalid:focus,
.form-control.is-invalid:focus,
.form-select.is-invalid:focus,
.form-check-input.is-invalid:focus,
.was-validated .form-control:invalid:focus,
.was-validated .form-select:invalid:focus {
    border-color: var(--rf-danger);
    box-shadow: var(--rf-field-glow-danger);
    outline: 0;
}

.invalid-feedback {
    color: var(--rf-danger-ink);
}

/* 必填星：宿主视图里是 <span class="text-danger">*</span>，跟着语义红走 */
.was-validated .form-control:valid,
.was-validated .form-select:valid {
    border-color: var(--rf-line-strong);
}

/* ============================================================
   6. 卡片
   ============================================================ */

/* 卡片：**不描边**（border: 0，2026-10-16 拍板）—— 白卡外再描一圈线，配上页面底
   就是双层边界；边框还会把阴影那圈柔和边缘切掉。分层只靠「卡底 + 淡阴影」。
   圆角走 Tabler 自己的槽 —— 它有**两份**：
     --tblr-card-border-radius        外壳自己
     --tblr-card-inner-border-radius  内圆角，给 .card-header:first-child /
                                      .card-img-top / .card > .list-group:first-child
   内圆角与外壳**同值**：没有边框，不再减 --tblr-border-width。卡片档是 8px，而
   Tabler 原式是 calc(--tblr-border-radius-lg - 边框宽)、-lg 那一档映射的是「内嵌块
   6px」—— 只写 border-radius 的话外壳 8px、内圆角却按 6px 算成 5px，卡里的图片/
   列表角上会露出方角。
   ⚠️ 这两条**必须写在 .card 上**：Tabler 也定义在 .card，而自定义属性是「自身声明
      胜过祖先继承」—— 写在 :root 上压不住它，只会被静默忽略。
   ⚠️ background-color **走槽**（读 --tblr-card-bg）：Tabler 的 .card-active / .card-note
      正是靠重定义这个变量换底的，直接写成 var(--rf-surface) 会把它们的底一起压平。
      槽的缺省链路是 --tblr-card-bg → --tblr-bg-surface → --rf-surface，结果与直写一致。
   ⚠️ border 仍直写 0：Tabler 的 --tblr-card-border-color 会被上面那两个变体重定义，
      在 .card 上覆盖那个变量会连坐（同 --tblr-btn-bg 那个坑）；去边框的连带见下一条。 */
.card {
    background-color: var(--tblr-card-bg);
    border: 0;
    --tblr-card-border-radius: var(--rf-r-lg);
    --tblr-card-inner-border-radius: var(--rf-r-lg);
    --tblr-card-box-shadow: var(--rf-shadow);
}

/* ⚠️ 上面 `.card { border: 0 }` 与 Tabler 的卡片变体规则**同权（0,1,0）**，而 theme.css
   加载在后 —— 这一刀会连带砍掉四个「描边即语义」的变体：
     .card-dashed / .card-transparent  自带 border 简写（虚线框）
     .card-active / .card-note         边色由变量给、宽度却来自 .card 那条简写
   下面按各自原定义把边框还回来。**只还这四个**：默认卡片仍然不描边。
   另：.card-transparent 的「去底 / 去影」也被 .card 的 background-color 与 box-shadow
   压平了，一并还原。 */
.card-dashed,
.card-transparent {
    border: var(--tblr-border-width) dashed var(--tblr-border-color);
}

.card-active,
.card-note {
    border: var(--tblr-border-width) solid var(--tblr-card-border-color);
}

.card-transparent {
    background: 0 0;
    box-shadow: none;
}

.card-header {
    background-color: transparent;
    border-bottom: 1px solid var(--rf-line-soft);
    padding: var(--rf-card-cap-y) var(--rf-pad);
}

.card-footer {
    background-color: transparent;
    border-top: 1px solid var(--rf-line-soft);
    padding: 12px var(--rf-pad);
}

.card-body {
    padding: var(--rf-pad);
}

.card-title {
    color: var(--rf-ink);
    font-size: var(--rf-fs-h3);   /* 16 —— 卡片标题与 h3 同档 */
    font-weight: 500;           /* 卡片标题属结构性标签，500 档 */
    margin-bottom: 4px;
}

.card-header .card-title {
    margin-bottom: 0;
}

/* 卡片里的表格：去掉最后一行的下边距，让表格贴住卡片圆角 */
.card > .table-responsive > .table,
.card > .table {
    margin-bottom: 0;
}

/* 小节标题（首页那三张卡片的 .subheader）：Tabler 默认是小号大写字距，
   中文大写无意义、字距一撑反而散 —— 收成「小号 + 中等字重 + 次要色」。
   .card-subtitle 的次要色也走这里，两处一个值。 */
.subheader,
.card-subtitle {
    color: var(--rf-muted);
    font-size: var(--rf-fs-sm);   /* 13 —— 次要档 */
    font-weight: 400;           /* 次要文字 400 */
    letter-spacing: 0;
    text-transform: none;
    margin-bottom: 8px;
}

/* ------------------------------------------------------------
   首页 · 近 7 日行为趋势（纯 CSS 柱状）
   ------------------------------------------------------------
   七根柱子，成功在下、失败在上堆叠（柱高 = 当天总操作数）。
   刻意不引 ECharts：首页是首屏，为了七根柱子拉 1.1 MB 的图表库不划算 ——
   要下钻 / 缩放的统计页仍旧走 x-ui.chart。

   高度归一在服务端算好（每段 height 是百分比），浏览器只负责画。

   配色：柱是**背景信息**，上面那排数字卡才是首页主角。实心主色铺满
   148px 会抢走视线，所以两档都压成半透明着色（透出来的槽底又替它降了
   一点饱和）；失败档比成功档高一档，因为失败段通常只有几像素高，太淡
   就看不见了。
   令牌同时挂在图例上：图例是 .rf-trend 的**兄弟**节点而不是子节点，
   只写在 .rf-trend 里的话色块会拿到未定义值、回退成透明。 */
.rf-trend,
.rf-trend-legend {
    --rf-trend-ok:   color-mix(in oklab, var(--tblr-primary) 45%, transparent);
    --rf-trend-fail: color-mix(in oklab, var(--rf-danger) 60%, transparent);
}

/* 深色皮下同一个百分比会更浓：主色在深色皮本身就亮一档（浅皮 #165DFF /
   深皮亮蓝），半透明叠在近黑的卡片底上，亮度差反而比浅底更大，实测
   45% 的柱子在深色里比改之前的实心还扎眼。两档各降一档，深浅两皮才
   看起来是同一个分量；槽底则相反 —— 深色里 4% 的白几乎描不出空槽。 */
[data-bs-theme="dark"] .rf-trend,
[data-bs-theme="dark"] .rf-trend-legend {
    --rf-trend-ok:   color-mix(in oklab, var(--tblr-primary) 30%, transparent);
    --rf-trend-fail: color-mix(in oklab, var(--rf-danger) 44%, transparent);
}

[data-bs-theme="dark"] .rf-trend-bar {
    background-color: color-mix(in oklab, var(--rf-ink) 7%, transparent);
}

.rf-trend {
    align-items: stretch;
    display: flex;
    gap: var(--rf-space-2);
    height: 148px;
}

.rf-trend-col {
    display: flex;
    flex: 1 1 0;
    flex-direction: column;
    height: 100%;
    min-width: 0;
}

/* 槽底：没有行为的那天也要看得出「有这一天」，而不是整列消失。
   4% 就够描出空槽，再深会和淡下来的柱子抢边界。 */
.rf-trend-bar {
    background-color: color-mix(in oklab, var(--rf-ink) 4%, transparent);
    border-radius: var(--rf-r) var(--rf-r) 0 0;
    display: flex;
    flex: 1 1 auto;
    flex-direction: column-reverse;
    margin: 0 auto;
    max-width: 36px;
    overflow: hidden;
    width: 100%;
}

/* flex: none 是必须的 —— 百分比高度在可伸缩的子项上会被压扁成等高 */
.rf-trend-seg {
    display: block;
    flex: none;
    width: 100%;
}

.rf-trend-seg.is-success {
    background-color: var(--rf-trend-ok);
}

.rf-trend-seg.is-failed {
    background-color: var(--rf-trend-fail);
}

.rf-trend-label {
    color: var(--rf-muted);
    font-size: var(--rf-fs-xs);
    line-height: 18px;
    margin-top: 6px;
    text-align: center;
    white-space: nowrap;
}

.rf-trend-legend {
    align-items: center;
    color: var(--rf-muted);
    display: flex;
    font-size: var(--rf-fs-xs);
    gap: var(--rf-space-4);
}

.rf-trend-key {
    align-items: center;
    display: inline-flex;
    gap: 6px;
}

.rf-trend-dot {
    border-radius: 2px;
    display: inline-block;
    height: 10px;
    width: 10px;
}

.rf-trend-dot.is-success {
    background-color: var(--rf-trend-ok);
}

.rf-trend-dot.is-failed {
    background-color: var(--rf-trend-fail);
}

/* ------------------------------------------------------------
   统计卡片（x-ui.stat）
   ------------------------------------------------------------
   三个部件：头行（label + 图标片）/ 数值 / 说明。
   颜色编码只落在图标片上（text-{variant} 决定 currentColor），
   数值一律墨色 —— 大数字整字染色会和标签、图标片抢层级。
   底纹沿用全站惯例：color-mix 从 currentColor 派生淡底。 */

/* 头行：min-height 钉住头行高度 —— 同一排里有没有图标，
   数值都落在同一条基线上（无图标的卡留白而不是上浮）。 */
.rf-stat-head {
    align-items: center;
    display: flex;
    gap: 12px;
    justify-content: space-between;
    min-height: 36px;        /* 36 = 图标片边长（指标卡的固定件，不跟控件档走） */
}

.rf-stat-head .subheader {
    margin-bottom: 0;           /* 对齐交给头行的 flex，去掉 .subheader 的下边距 */
}

/* 图标片：淡色底圆角块，图标色即语义色（currentColor 同时染底与染形）。
   深色下 10% 淡底几乎看不见，提到 18%。 */
.rf-stat-chip {
    align-items: center;
    background-color: color-mix(in oklab, currentColor 10%, transparent);
    border-radius: var(--rf-r);
    display: inline-flex;
    flex: none;
    font-size: 18px;        /* 图标 18px：片子 36px 的 1/2 */
    height: 36px;
    justify-content: center;
    line-height: 1;
    width: 36px;
}

[data-bs-theme="dark"] .rf-stat-chip {
    background-color: color-mix(in oklab, currentColor 18%, transparent);
}

/* 数值：指标档 26px + 表格数字（等宽数字，同列多卡上下扫读不跳）。
   字重 500 —— 与全站「结构性强调」同档（.fw-bold 已映射到 500）。 */
.rf-stat-value {
    color: var(--rf-ink);
    font-size: var(--rf-fs-metric);
    font-variant-numeric: tabular-nums;
    font-weight: 500;
    letter-spacing: -.01em;     /* 大数字略收字距，整数位多时不发飘 */
    line-height: 1.15;
    margin-top: 10px;
}

/* 说明：12px 次要色，紧贴数值 —— 它是数值的注脚，不是第二段正文 */
.rf-stat-hint {
    color: var(--rf-muted);
    font-size: var(--rf-fs-xs);
    margin-top: 4px;
}

/* 五等分档（x-ui.stat-group 的 cols=5）：Bootstrap 12 栅格除不尽 5，
   库里没有这一档，五个一排的指标页（订单 / 各类日志 / 角色…）会排到
   第二行去。md 起生效，小屏仍由调用方的 col-6 接管两列。 */
@media (min-width: 768px) {
    .col-md-fifth {
        flex: 0 0 auto;
        width: 20%;
    }
}

/* ============================================================
   7. 表格
   ============================================================ */

.table {
    color: var(--rf-ink);
    border-color: var(--rf-line-soft);
}

/* 挂在 table 上的 .bg-body（Tabler 工具类，!important）铺的是 --tblr-body-bg
   = --rf-page 页面灰，会从筛选行 / 行缝隙透出来把整表染灰。
   只在表格身上把 body-bg 重映射成表面白：!important 的声明本身不动，
   改的是它读的变量，页面底色不受影响。深色档同走 --rf-surface。 */
.table.bg-body {
    --tblr-body-bg: var(--rf-surface);
}

/* 左右 --rf-pad：与 .card-header / .card-body 的同名横向内边距**同源** ——
   表格首列的左缘和卡片标题的左缘在同一条竖线上（密度换挡也一起走）。

   纵向由「上下内边距 + 行高」定出整行高度，两项都取 Arco 的档位真身：
   `--rf-cell-pad-y` 9 + 行高 14×1.5715(22) + 9 = **40px**（Arco large）。
   ⚠️ 行高必须在这里**显式钉住**（`--rf-cell-lh`），不能让它继承正文的 1.6 ——
   全局正文行高按 1.6 算是 22.4px 而不是 22px，凑出来就是 40.4 这种非整数：
   行与行会在半像素上抖动（125%/150% 缩放取整时看得见），也对不上 Arco 的整数档。
   只作用于单元格（th / td），页面正文的排版松弛度不受影响。 */
.table > :not(caption) > * > * {
    /* 单元格底色写死成表面色（浅色 #FFFFFF / 深色 #20222E），不再继承 Tabler 的
       `--tblr-table-bg: transparent`。透明那条链身后虽然也是卡片白，但「到底谁画的」
       一直是隐式的；写死之后每格自己就有实底，与父级无关。
       表头不受影响：`.table > thead > tr > th`（0,1,3）比这条（0,1,1）强，
       #FAFAFA 灰带照旧；筛选行 / .table-light 也各有更强的那条。
       条纹与 hover 走 --tblr-table-bg-type / -state 的 inset 阴影通道，画在底色之上。 */
    background-color: var(--rf-surface);
    border-bottom-color: var(--rf-line-soft);
    padding: var(--rf-cell-pad-y) var(--rf-pad);
    line-height: var(--rf-cell-lh);
}

.table > thead > tr > th {
    background-color: var(--rf-surface-3);
    border-bottom-color: var(--rf-line);
    color: var(--rf-muted);
    /* ⚠️ 纵向内边距与行高必须在这里**再声明一次**：Tabler 的 `.table thead th`
       自带 `padding-top / padding-bottom: 8px` 与 `line-height: 16px`
       （特异性 0,1,2），比上面那条 `.table > :not(caption) > * > *`（0,1,1）**强一档** ——
       不覆盖的话表头既不跟 `--rf-cell-pad-y` 走、也不跟密度换挡走，行高恒在 32px 上下，
       总是比数据行矮一截（「表头行高与数据行不一致」的真正来源）。
       本规则特异性 0,1,3，压得住。 */
    padding-top: var(--rf-cell-pad-y);
    padding-bottom: var(--rf-cell-pad-y);
    line-height: var(--rf-cell-lh);
    /* 14 —— 与数据行**同号**（Arco 的 th 与 td 同字号，表头行高才与数据行严格一致；
       13px 时表头天生矮 1.6px，「行高统一」就只统一了一半）。
       表头的「次要感」改由 --rf-muted 字色 + 淡底 + 500 字重承担，不再靠缩字号。 */
    font-size: var(--rf-fs-body);
    font-weight: 500;
    white-space: nowrap;
}

@media (hover: hover) {
    /* 悬停底走 Tabler 的槽：`.table-hover > tbody > tr:hover > *` 读 --tblr-table-hover-bg，
       落进单元格那条 9999px inset box-shadow 的 **--tblr-table-bg-state** 通道
       （它在取值链里排在条纹的 --tblr-table-bg-type 之前）。
       直接写 background-color 是另开一条路：shadow 画在背景之上、压不动，
       结果就是「条纹行 hover 不变色」，还得再单独把条纹关掉才自洽。 */
    .table-hover {
        --tblr-table-hover-bg: var(--rf-surface-2);
    }
}

/* 条纹同样只改 Tabler 的槽，不再自己铺 background-color。
   条纹从**第 2 行（偶数行）**起铺：表头是 --rf-surface-3 灰底，若从第 1 行起铺同一个色，
   表头与首行就灰成一片（实测过：三行渲染色完全相同）。
   Tabler 自己的选择器正是 tr:nth-child(2n)（也是偶数行），与这里要的完全同形 ——
   所以只要把颜色交给 --tblr-table-striped-bg 即可，不必自己再写一条选择器、
   更不必再反手把它关掉。悬停走 -bg-state、条纹走 -bg-type，前者优先，天然互斥。
   作用域只限本类，不碰 .table-striped-columns 与行级 text-bg-* / bg-*。 */
.table-striped {
    --tblr-table-striped-bg: var(--rf-surface-3);
}

/* 紧凑表的「紧凑」只在纵向（行高）：横向与 .table 一样走 --rf-pad，
   否则同一页两张表首列左缘对不齐。
   纵向取 Arco 的中间档：7 + 22 + 7 = **36px**（默认档 40px 收一档）；
   行高基准与默认档同（--rf-cell-lh 在 .table 那条规则里钉住，本档只换内边距）。 */
.table-sm > :not(caption) > * > * {
    padding: var(--rf-cell-pad-y-sm) var(--rf-pad);
}

/* 紧凑表的**表头**再命中一次：上面 `.table > thead > tr > th`（0,1,3）已经把表头的
   纵向内边距钉成默认档的 --rf-cell-pad-y，它比 `.table-sm > :not(caption) > * > *`
   （0,1,1）强，所以紧凑表的表头会停在默认档高度 —— 比它自己的数据行高出 4px。
   本规则与表头那条**同权（0,1,3）**，靠「后置胜出」把表头也拉进紧凑档。 */
.table-sm > thead > tr > th {
    padding-top: var(--rf-cell-pad-y-sm);
    padding-bottom: var(--rf-cell-pad-y-sm);
}

/* 动作列（x-ui.grid / x-ui.tree-table 里由 GridColumn::actions() 生成的那一格）
   **不折行**：一排按钮被挤到第二行时整行高度翻倍，按钮与行内其它内容错位，
   看着就像表格坏了。宁可让 .table-responsive 横向滚动（表格本来就允许横滚）。
   列宽由 GridColumn::actionsWidth() 按动作清单算好；宽度不够时浏览器只会把列压到
   min-content —— nowrap 之后 min-content 就是各按钮宽度之和，溢出走横滚、不折行。 */
.rf-actions {
    white-space: nowrap;
}

.table-bordered > :not(caption) > * {
    border-color: var(--rf-line-soft);
}

/* .table-light 在视图里当表头底色用（10 处）：统一成内嵌块底，
   深色下才不会是一块刺眼的白 */
.table-light,
.table > .table-light > *,
.table > thead.table-light > tr > th {
    background-color: var(--rf-surface-3);
    color: var(--rf-muted);
    border-color: var(--rf-line);
}

/* 表头筛选行（x-ui.grid 的 filterLayout="header" 输出的第二行，真实标记是
   `tr.table-light[data-grid-filters]`；第二条兜住任何「th 里直接放筛选控件」的表头行 ——
   手写表格同样生效）。
   ⚠️ 必须**连行本身**一起清底：grid 输出的 row 自带 .table-light 类，而上面那条
      `.table > .table-light > *` 只清了单元格 —— 行自己的 --rf-surface-3 会从透明单元格
      后面透出来（实测 tr 背景 rgb(250,251,254)，整行仍是一条灰带，只有 th 是透的）。
   行与 th 都清成 transparent，一排控件才真正与下方数据行连读；
   表头只剩列名那一行是淡底。选择器带属性 / :has()，比上面那条 .table-light 更强。 */
.table > thead > tr[data-grid-filters],
.table > thead > tr[data-grid-filters] > th,
.table > thead > tr:has(> th > .form-control, > th > .form-select),
.table > thead > tr:has(> th > .form-control, > th > .form-select) > th {
    background-color: transparent;
}

/* 表头筛选控件的两档文字：已填 / 已选中的值走次要文字档（--rf-muted），
   与列表头同档；空着的提示（placeholder、未选择的「未选择」占位项）
   走统一提示色（--rf-faint）—— 一眼分得清「填了」和「没填」。 */
.table > thead > tr[data-grid-filters] .form-control,
.table > thead > tr[data-grid-filters] .form-select {
    color: var(--rf-muted);
}

.table > thead > tr[data-grid-filters] .form-control::placeholder {
    color: var(--rf-faint);
}

/* 单选下拉「没选」的判定：占位 option（value=""）正被选中时整只下拉当提示色；
   选中任何真实值后回到上面的 --rf-muted。多选 / 远程搜索走 Tom Select 皮肤，不在此列。 */
.table > thead > tr[data-grid-filters] select.form-select:has(option[value=""]:checked) {
    color: var(--rf-faint);
}

/* 表头筛选控件统一 md 档（28px）：整行密排，sm（24）手感太挤（下拉箭头 /
   日期占位贴边），默认档（32）又把表格头撑高。本库组件已显式标 -md
   （见 _grid-filter.blade.php），这条是**兜底**：手写筛选行里不带档位的
   .form-control / .form-select 也落回 28，不会单独冒出一格 32。
   紧凑密度下自动跟降到 26（同一组令牌在 compact 下整批换值）。 */
.table > thead > tr[data-grid-filters] .form-control,
.table > thead > tr[data-grid-filters] .form-select,
.table > thead > tr:has(> th > .form-control, > th > .form-select) .form-control,
.table > thead > tr:has(> th > .form-control, > th > .form-select) .form-select {
    min-height: var(--rf-ctl-h-md);
    padding-top: var(--rf-ctl-py-md);
    padding-bottom: var(--rf-ctl-py-md);
}

/* ── Tom Select 的控件高度也吃四档令牌 ─────────────────────────────────
   皮肤给 .ts-control 写死了内边距（calc(6px - 1px) / calc(6px - 4px)），
   高度就这么被它自己钉住、不跟 --rf-* 走 —— 控件档一收它就与同排的
   .form-control / .btn 差一截（对齐 Arco 后默认档 32px，差得就看得出来）。
   这里把高度与内边距拉回同一组令牌：高度由 min-height 定下限、py 配成天然高度
   落在令牌上；多行（表单里的多选折行）仍按内容自然增高，不受影响。 */
.ts-wrapper .ts-control {
    min-height: var(--rf-ctl-h);
    padding-top: var(--rf-ctl-py);
    padding-bottom: var(--rf-ctl-py);
    align-items: center;
}

/* 表头筛选行里的多选控件与同排的输入框一起走 md 档（28px） */
.table > thead > tr[data-grid-filters] .ts-wrapper .ts-control,
.table > thead > tr:has(> th > .ts-wrapper) .ts-wrapper .ts-control {
    min-height: var(--rf-ctl-h-md);
    padding-top: var(--rf-ctl-py-md);
    padding-bottom: var(--rf-ctl-py-md);
}

/* ── 数值区间控件（x-ui.grid 的 numberRange 列）──────────────────────────
   一行两格：左 min（≥）/ 右 max（≤），共用一个外框 —— 视觉上是一个控件，
   高度与同排的 .form-control 齐平。早期是竖排两个 form-control，在表头筛选行里
   会把整行顶高一倍（列宽只有 110px 时尤其刺眼），所以必须收成一行。
   两格刻意是裸 <input>（不带 .form-control），因此：
     · rageframe.js 的自绘步进列（只认 input.form-control）不会注入 ——
       窄列分给两格后，再各让 22px 给 ▲▼ 就没地方放数字了；
       调值走键盘 ↑/↓ 或滚轮，原生行为没动；
     · 描边 / 底色 / 聚焦柔光在这里补齐，令牌与 .form-control 完全同源。 */
.rf-num-range {
    display: flex;
    align-items: stretch;
    min-height: var(--rf-ctl-h-md);      /* 与筛选行的 .form-control-md 同档（28） */
    background-color: var(--rf-field-bg);
    border: 1px solid var(--rf-line-strong);
    border-radius: var(--rf-r-sm);
    color: var(--rf-ink);
    overflow: hidden;                    /* 中缝跟着圆角裁切，不给圆角留毛边 */
    transition: border-color var(--rf-dur) var(--rf-ease-curve),
                box-shadow var(--rf-dur) var(--rf-ease-curve),
                background-color var(--rf-dur) var(--rf-ease-curve);
}

.rf-num-range > input {
    flex: 1 1 50%;
    width: 50%;
    min-width: 0;                        /* 少了它 flex 子项拒绝收窄，列宽白给 */
    padding: 0 6px;
    border: 0;
    background: transparent;
    color: inherit;                      /* 「已填 / 空着」两档色由外层决定 */
    font-size: var(--rf-fs-sm);
    line-height: 16px;
    text-align: center;                  /* 窄格里居中：数字与 ≥ ≤ 都不会贴边 */
    outline: 0;
    appearance: textfield;
    -moz-appearance: textfield;
}

.rf-num-range > input::placeholder {
    color: var(--rf-faint);
}

/* 中缝：两格之间只隔 1px。用 gap + 各自边框要花掉 5px，窄列里这 4px 是可见的 */
.rf-num-range > input + input {
    border-inline-start: 1px solid var(--rf-line);
}

/* 聚焦：柔光落在**外框**上（与全站其它控件同款），聚焦格的字色回到正文档；
   格子里不再画第二层边框 —— 否则两格各亮一圈，读起来像两个控件 */
.rf-num-range:focus-within {
    border-color: var(--tblr-primary);
    box-shadow: var(--rf-field-glow);
}

.rf-num-range:focus-within > input {
    color: var(--rf-ink);
}

/* 表头筛选行：与同排的 .form-control 一起抬到 md 档（30px）+ 次要文字档。
   第二条 :has() 兜住手写表头（与上面清底 / 抬高两条规则同一口径）。 */
.table > thead > tr[data-grid-filters] .rf-num-range,
.table > thead > tr:has(> th > .rf-num-range) .rf-num-range {
    min-height: var(--rf-ctl-h-md);
    color: var(--rf-muted);
}

/* ── 多选筛选控件（Tom Select multi）──────────────────────────────────
   胶囊**单行收纳**：皮肤的 .ts-control 是 flex-wrap: wrap，选中几颗胶囊就折几行
   （120px 的窄列里两颗就折行，实测控件 35px → 54px），整条筛选行跟着一跳、
   与同排 30px 的控件再也对不齐。这里收成 nowrap —— 塞不下的胶囊被皮肤已有的
   overflow: hidden 裁掉，行高从此稳定；被裁的胶囊在下拉里仍看得到勾选态
   （checkbox_options），取消勾选 / 清空（clear_button）都照常可用。
   作用域只限筛选行：表单里的 x-ui.tags / 多选下拉维持折行，那是表单语义。 */
.table > thead > tr[data-grid-filters] .ts-wrapper.multi .ts-control,
.table > thead > tr:has(> th > .ts-wrapper) .ts-wrapper.multi .ts-control {
    flex-wrap: nowrap;
}

/* ============================================================
   8. 徽章与状态标签
   ============================================================ */

/* 圆角：.badge 走 Tabler 的槽；.rf-tag 是本项目标签类、Tabler 不管它，自己写一条 */
.badge {
    --tblr-badge-border-radius: var(--rf-r-pill);
}

.badge,
.rf-tag {
    font-size: var(--rf-fs-xs);   /* 12 —— 提示档 */
    font-weight: 500;
    padding: 4px 10px;
}

.rf-tag {
    border-radius: var(--rf-r-pill);
}

/* 实心状态块（.text-bg-*，视图里 30+ 处用在表格状态列）改成
   「淡底 + 同色文字」的胶囊 —— 实心饱和块在这套低饱和配色里太吵。
   必须 !important：Bootstrap 的 .text-bg-* 自带 !important。 */
.text-bg-success { background-color: var(--rf-ok-bg) !important; color: var(--rf-ok-ink) !important; }
.text-bg-warning { background-color: var(--rf-warn-bg) !important; color: var(--rf-warn-ink) !important; }
.text-bg-danger  { background-color: var(--rf-danger-bg) !important; color: var(--rf-danger-ink) !important; }
.text-bg-info    { background-color: var(--rf-info-bg) !important; color: var(--rf-info-ink) !important; }
.text-bg-primary { background-color: var(--rf-accent-bg) !important; color: var(--tblr-primary) !important; }
.text-bg-secondary,
.text-bg-light,
.text-bg-dark    { background-color: var(--rf-surface-2) !important; color: var(--rf-muted) !important; }

/* 圆点状态（Tabler 的 .status）：环色跟随令牌。
   ⚠️ 中性默认取 --rf-muted，不取 --rf-line-strong —— 环色同时是**文字色**
      （.status 的 color 读它、10% 底也是它染的），取描边色等于把整颗状态点
      淡到看不清。
   ⚠️ 下面那组变体**必须写**：Tabler 的 `.status-{variant}` 与本条是**同权重**
      （都是单类 0-1-0），而本文件加载在 tabler.min.css 之后 —— 只写 `.status`
      会把九个变体的取值全部盖成同一个中性色（实测：success / danger / warning
      全变成灰点，只有底色深浅的差别）。这里按 --rf-* 令牌重新接一遍，
      与全站语义色同源。
   字号与盒子：Tabler 默认 14px 文本 / 24px 高的裸值，没挂在字号阶层上 ——
      状态点与徽章同族（上节「徽章与状态标签」），统一收进提示档 12px，
      盒子同步收到 22px（与 .badge 渲染高度一致），内边距与点距随档收一收。 */
.status {
    --tblr-status-color: var(--rf-muted);
    --tblr-status-height: 22px;
    --tblr-status-padding-x: 10px;
    --tblr-status-gap: 6px;
    font-size: var(--rf-fs-xs);
}

/* 状态里的图标：Tabler 按 14px 文本配的 20px，随字号收到 16px */
.status .icon {
    font-size: 16px;
}

.status-primary { --tblr-status-color: var(--tblr-primary); }
.status-success { --tblr-status-color: var(--rf-ok); }
.status-warning { --tblr-status-color: var(--rf-warn); }
.status-danger  { --tblr-status-color: var(--rf-danger); }
.status-info    { --tblr-status-color: var(--rf-info); }
.status-secondary,
.status-muted,
.status-light,
.status-dark    { --tblr-status-color: var(--rf-muted); }

/* 圆点直径：Tabler 默认 0.5rem（8px），在这套紧凑排版里偏大，收到 0.375rem（6px）。
   ⚠️ --tblr-status-size 声明在 **.status-dot 自己**身上（不是 .status 容器上），
      所以必须直接命中 .status-dot —— 写在 .status 里会被它自己的声明盖掉。 */
.status-dot {
    --tblr-status-size: 6px;
}

/* ============================================================
   9. 提示条
   ============================================================ */

.alert {
    /* 内边距与圆角走 Tabler 的槽 —— `.alert > .alert-icon` 也读 --tblr-alert-padding-y/x
       定位图标，写属性的话图标位置会与内边距脱钩。 */
    --tblr-alert-padding-y: 14px;
    --tblr-alert-padding-x: 18px;
    --tblr-alert-border-radius: var(--rf-r);

    /* 去边框（2026-10-16，与 .card / .modal-content / .rf-toolbar 同一口径）：
       提示条靠**淡底**分层，不再描一圈。
       Tabler 的 .alert 自带 `border: var(--tblr-border-width) solid
       var(--tblr-alert-border-color)`，而那个边色是 --tblr-alert-bg 的同源产物
       （同色 20% 混白），本来就只有一丝轮廓 —— 去掉后颜色语义完全落在「底 + 字」上。
       ⚠️ 写 border: 0 而不是去改 --tblr-alert-border-color：后者是 color-mix 的
          产物、又喂着 .alert-important，动它会连坐（同 --tblr-btn-bg 那个坑）。
       ⚠️ 四档的 border-color 随之删掉（原先分别读 --rf-*-line）—— 不去描边了，
          留着就是四条死声明。 */
    border: 0;
}

.alert-success { background-color: var(--rf-ok-bg);     color: var(--rf-ok-ink); }
.alert-warning { background-color: var(--rf-warn-bg);   color: var(--rf-warn-ink); }
.alert-danger  { background-color: var(--rf-danger-bg); color: var(--rf-danger-ink); }
.alert-info    { background-color: var(--rf-info-bg);   color: var(--rf-info-ink); }

/* light 档：Tabler 原生那条是给**深色画布**配的「反白块」—— 字与底同色系且都
   接近纯白（实测亮色主题下 color #FAFAFA on background #FEFEFE），落在卡片白底
   上等于隐形。本项目 light 档的语义是「中性说明块」（弹层顶部的抬头摘要、表单
   里的只读信息），一律出现在表面色上，所以按令牌重画：浅表面底 + 墨色字。
   不写 border：光靠 --rf-surface-2 与卡片白的那一点明度差就够了，与上面四档
   同为「靠淡底分层」；需要更清楚的分界由调用方叠 .border（见各弹层摘要块）。 */
.alert-light {
    background-color: var(--rf-surface-2);
    color: var(--rf-ink);
}

/* secondary 档同理：Tabler 给它一档灰底 + secondary 字，与「未启用」语义撞车，
   且不跟主题。走 muted 那对令牌，深色下自动反色。 */
.alert-secondary {
    background-color: var(--rf-surface-3);
    color: var(--rf-muted);
}

/* ⚠️ 同卡片族：.alert-minor 的**语义就是「透明底 + 只留描边」**，被上面 `.alert{border:0}`
   （同权 0,1,0、后置）一并抹平；而且四档 `.alert-{色}` 的淡底也会把它自己的
   `background: 0 0` 压回来。这里按 Tabler 原定义整条还回来 —— 其余 alert 仍不描边。 */
.alert-minor {
    background: 0 0;
    border: var(--tblr-border-width) solid var(--tblr-border-color);
}

/* ============================================================
   10. 导航 / 页签 / 分页 / 面包屑
   ============================================================ */

/* 页签栏「通栏」到卡片边：负 margin 必须**正好抵消 .card-header 的内边距**。
   Tabler 的 .card-header-tabs 用 --tblr-card-cap-padding-*（20px）算负边距，
   而本节上面把卡片头收成了 `padding: var(--rf-card-cap-y) var(--rf-pad)` —— 不减的话
   页签栏左右各溢出 4px、上边顶出 6px，看起来「灰带比内容突出一节」（2026-10-31 用户报，
   实测 ul left=12/right=1428 而卡片是 16/1424）。
   ⚠️ 两个数都**只从令牌取**，不再写死：横向 --rf-pad 跟密度换挡、纵向 --rf-card-cap-y 不跟。
      写成字面量的话，将来改卡片头内边距，这里会静默错位（负 margin 的参照物变了却不报错）。
   ⚠️ 与页签个数 / 是否换行**无关**：负 margin 抵消的是卡片头的 padding，不是页签高度。
      3 签、4 签、7 签单行，以及 520 视口挤成两行，四边照样重合（2026-10-31 实测）。
   padding 横向用同一组数 —— 第一个页签的文字左边缘正好与 <x-ui.grid> / 表单内容左边缘对齐；
   纵向 10px 是灰带上方给页签的呼吸位（Tabler 原式 y×0.5 = 20×0.5），与卡片头顶边距无关。 */
.card-header .card-header-tabs {
    margin: calc(-1 * var(--rf-card-cap-y)) calc(-1 * var(--rf-pad));
    padding: 10px var(--rf-pad) 0;
}

.nav-tabs {
    border-bottom-color: var(--rf-line);
}

.nav-tabs .nav-link {
    /* 圆角不写：Tabler 的 .nav-tabs 已经把它定成 --tblr-border-radius（控件档），
       而 .nav-link 自己只吃**上面两角**（border-top-*-radius）—— 这里写四角简写
       虽然当前值一样，却把「下两角为 0」这条语义换了个来源。 */
    color: var(--rf-muted);
}

@media (hover: hover) {
    .nav-tabs .nav-link:hover {
        border-color: transparent;
        color: var(--rf-ink);
    }
}

.nav-tabs .nav-link.active {
    background-color: var(--rf-surface);
    /* 四边都不描边（2026-10-31 用户报「页签上选中当页的灰边」）：
       标签栏自己有一层浅底（.card-header-tabs 的 #fafafa），激活页签的白底
       已经把「选了哪个」说清楚了；再套一圈 --rf-line 的灰框是 Tabler 的
       「标签纸」外观，夹在卡片头里像一条脏边。
       写 transparent 而不是 border-width: 0 —— 背景默认 background-clip:
       border-box，border 区域照样被白底铺满，视觉等价于「没有边」，但标签的
       盒宽 / 圆角 / 与卡片体的 1px 衔接一点不动（改宽度会让整排页签左右跳 2px）。 */
    border-color: transparent;
    color: var(--tblr-primary);
    font-weight: 400;           /* 导航项不加粗，激活靠色 + 底色 */
}

.nav-pills .nav-link {
    /* 圆角不写：Tabler 的 .nav-pills 定成 --tblr-nav-pills-border-radius
       （= --tblr-border-radius，控件档），值与本条原本写死的相同。 */
    color: var(--rf-muted);
}

@media (hover: hover) {
    .nav-pills .nav-link:hover {
        background-color: var(--rf-surface-2);
        color: var(--rf-ink);
    }
}

/* 药丸选中：主色淡底 + 主色文字（不是实心块），与表单里的 .btn-check 同一口径 */
.nav-pills .nav-link.active {
    background-color: var(--rf-accent-bg);
    color: var(--tblr-primary);
}

.pagination {
    /* 分离式分页：每颗按钮各自带圆角，所以间距与圆角都在 Tabler 的槽上给。
       ⚠️ --tblr-pagination-border-radius 是 .pagination / -sm / -lg / -circle 共用的槽，
          在 .pagination 上覆盖它等于让那三个变体也吃小件档（与改前一致：原先直接写在
          .pagination .page-link 上，特异性更高，同样压掉了它们）。 */
    --tblr-pagination-gap: 4px;
    --tblr-pagination-border-radius: var(--rf-r-xs);
}

.pagination .page-link {
    /* 高度钉在 md 档（28px）：页脚整行都是区块工具带（每页条数 / 列 / 跳页 /
       分页），四种控件必须同高。Tabler 原值是 py 5px + 行高 21 + 描边 = 33，
       比同排的 28 高半档；这里收掉竖向内边距、改用 min-height 定位 ——
       改 padding 而不是 min-height 才压得动（display:flex 让文字在 28 里居中）。 */
    display: flex;
    align-items: center;
    justify-content: center;
    min-width: 28px;
    min-height: var(--rf-ctl-h-md);
    padding: 0 4px;
    border-color: var(--rf-line);
    color: var(--rf-muted);
    text-align: center;
}

@media (hover: hover) {
    .pagination .page-link:hover {
        background-color: var(--rf-surface-2);
        border-color: var(--rf-line-strong);
        color: var(--rf-ink);
    }
}

.pagination .page-item.active .page-link {
    background-color: var(--tblr-primary);
    border-color: var(--tblr-primary);
    color: var(--tblr-primary-fg);
    box-shadow: var(--rf-shadow-sm);
}

.pagination .page-item.disabled .page-link {
    background-color: var(--rf-surface-2);
    border-color: var(--rf-line);
    color: var(--rf-faint);
}

/* 面包屑：整条**不管几级**都落在次要档。
   color 给在 <ol> 上是**兜底**：layouts/app.blade.php 里末级渲染成 <span>
   （.breadcrumb-item.active，下面单独给色）、其余各级渲染成 <a>（下面的
   .breadcrumb a）—— 但凡哪天多出一种形态（比如某一级有标题却没 url，
   只能渲染成纯文本），它会继承到这里的次要色，而不是掉回正文墨色。
   页头工具条上「面包屑 / 刷新 / 返回」是同一族，色阶必须只有一档。 */
.breadcrumb {
    margin-bottom: 0;
    color: var(--rf-muted);

    /* 分隔符 › 的色 —— 它由 .breadcrumb-item + .breadcrumb-item::before 的
       color 决定，读的是 Tabler 变量 --tblr-breadcrumb-divider-color，
       默认 --tblr-gray-500 = oklch(55.55% 0 0deg)。换成本框架的 --rf-faint，
       三条理由，都不是审美偏好：

       ① 那是 Tabler 的**原始灰阶**。theme.css 全文不直接引用 tblr-gray-* ——
          组件的色一律走 --rf-* 令牌重映射，这一处是漏网的。
       ② 它只在 Tabler 里**定义一次、不随 data-bs-theme 翻**，深色下仍是同一块
          中灰；--rf-faint 是分主题定义的（浅 #86909c / 深 #787d92），两边都稳。

       取 faint 而不是 muted：分隔符是**符号**不是文字 —— 框架里符号与弱装饰
       （dropdown-item-icon / dropdown-header / 日历禁用日 / 滚动条 hover）
       统一走 faint，这正是「符号弱于文字」的落点。

       ⚠️ 必须写在 .breadcrumb 上，不能并进顶部 :root 的 --tblr-* 映射块：
          Tabler 把这条变量声明在 .breadcrumb 元素**自己**身上，元素级声明
          压过 :root 的继承值，写 :root 不生效。 */
    --tblr-breadcrumb-divider-color: var(--rf-faint);
}

.breadcrumb a {
    color: var(--rf-muted);
    text-decoration: none;
}

@media (hover: hover) {
    .breadcrumb a:hover {
        color: var(--tblr-primary);
    }
}

/* 面包屑当前项：与上级链接（上面的 .breadcrumb a）同色，统一取次要色 --rf-muted
   （= --bs-secondary-color = .text-muted 那一档）。刷新 / 返回已迁出布局、改由
   page-head 组件以淡色图标呈现，不再走文字链。

   「当前页」的区分度交给**字重** —— Tabler 已给 .breadcrumb-item.active 一个
   semibold，不必再靠加深颜色去强调。通用 breadcrumb（如主题演示页）仍走这条。 */
.breadcrumb-item.active {
    color: var(--rf-muted);
}

/* 列表组：与卡片同一套底与圆角 */
.list-group-item {
    background-color: var(--rf-surface);
    border-color: var(--rf-line-soft);
    color: var(--rf-ink);
}

.list-group-item.active {
    background-color: var(--rf-accent-bg);
    border-color: var(--rf-line);
    color: var(--tblr-primary);
}

/* ============================================================
   11. 浮层：下拉 / 模态 / 卡片浮层
   ============================================================ */

.dropdown-menu {
    background-color: var(--rf-surface);
    border: 1px solid var(--rf-line);
    box-shadow: var(--rf-shadow-pop);
    padding: 6px;

    /* 浮层这一套尺寸全走 Tabler 的槽 —— 它们都定义在 .dropdown-menu 上，而自定义
       属性「自身声明胜过祖先继承」，必须写在这里才压得住：
         border  两份圆角，内圆角给贴边的第一/最后一项
         font    下拉项不自己写字号，统一吃菜单这一份
         item-*  下拉项的内边距与圆角（Tabler 读 item 圆角时带兜底 0，
                 说明这一项本来就是留给使用者的槽） */
    --tblr-dropdown-border-radius: var(--rf-r);
    --tblr-dropdown-inner-border-radius: calc(var(--rf-r) - var(--tblr-border-width));
    --tblr-dropdown-font-size: var(--rf-fs-body);   /* 14 —— 正文档 */
    --tblr-dropdown-item-padding-y: 7px;
    --tblr-dropdown-item-padding-x: 10px;
    --tblr-dropdown-item-border-radius: var(--rf-r-xs);
}

.dropdown-item {
    color: var(--rf-ink);
}

.dropdown-item:hover,
.dropdown-item:focus {
    background-color: var(--rf-surface-2);
    color: var(--rf-ink);
}

.dropdown-item.active,
.dropdown-item:active {
    background-color: var(--rf-accent-bg);
    color: var(--tblr-primary);
}

.dropdown-item-icon {
    color: var(--rf-faint);
    font-size: 12px;
    /* ⚠️ 这个类是 Tabler 的：width/height 被 !important 钉在
       --tblr-dropdown-item-icon-size（20px 见方）+ text-align:center，
       字形按行盒排在盒子**顶部** —— 这就是「图标偏上」的根因。
       盒子多大无所谓（留它当点击/对齐的占位），把字形在盒内居中即可：
       flex 居中不依赖字号与行高的换算，缩字号也不会再偏。 */
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

/* RF Icons 是 mask 雪碧图，元素的宽高必须**正好等于 1em**（= font-size）：
   mask-size / mask-position 都按 em 换算，盒子比 1em 大出多少，就会把
   相邻图标的边缘一起裁进来 —— 20px 的 Tabler 盒子配 12px 字号，一个格子里
   叠出两三个图标碎片，看着像图标「花了」。
   所以这里把尺寸拉回 1em，占位窄一点无妨（对齐由上面的 flex 承担）。 */
.dropdown-item-icon.rfi {
    width: 1em !important;
    height: 1em !important;
    /* 与顶栏那一排图标同档（--rf-icon-size-nav = 16）：下拉是从顶栏点出来的，
       图标比触发它的图标还小一档，会显得「掉了一层」。 */
    font-size: 16px;
}

/* 带图标的菜单项改成 flex：图标与文字**真·垂直居中**。inline 图标靠
   vertical-align 手调光学基线，字号缩到 12px 后仍会偏（实测偏上）——
   flex 居中一次到位，图标换成多高的字身都跟着内容走，不用再逐档调偏移。 */
.dropdown-item:has(> .dropdown-item-icon) {
    display: flex;
    align-items: center;
}

.dropdown-divider {
    border-top-color: var(--rf-line-soft);
    margin: 6px 0;
}

.dropdown-header {
    color: var(--rf-faint);
}

/* 弹层容器与卡片**同族**（白底 + 8px 圆角 + 浮起），所以同一条口径：**不描边**。
   它上面还盖着 scrim 遮罩，边界靠阴影+遮罩就够；再描一圈线，观感就从「浮层」
   退化成「带了框的卡片」。 */
.modal-content,
.offcanvas {
    background-color: var(--rf-surface);
    border: 0;
    box-shadow: var(--rf-shadow-pop);
}

/* 与卡片同病同治：Tabler 的 --tblr-modal-inner-border-radius 给 .modal-header 上两角、
   .modal-footer 下两角，原式是 calc(--tblr-border-radius-lg - 边框宽) —— -lg 那一档
   映射的是「内嵌块 6px」，外壳 8px 就会跟内圆角 5px 错位；这里无边框，内圆角直接
   与外壳同值。
   Tabler 把这两条定义在 .modal 上；这里写在 .modal-content **自身**：
   自定义属性「自身声明胜过祖先继承」，所以对**没有 .modal 外壳**的用法
   （弹层承载的整页，见本节下方 `.modal-content:not(:where(.modal) *)`）同样成立。 */
.modal-content {
    --tblr-modal-border-radius: var(--rf-r-lg);
    --tblr-modal-inner-border-radius: var(--rf-r-lg);
}

/* .offcanvas 在 Tabler 里没有圆角变量槽（.offcanvas{--tblr-offcanvas-* } 只有宽度/
   高度那几项），只能直接写。 */
.offcanvas {
    border-radius: var(--rf-r-lg);
}

.modal-header,
.modal-footer {
    border-color: var(--rf-line-soft);
}

/* ── 弹窗结构不全时：padding 不许归零 ────────────────────────────────────
   Tabler 把 21 个 --tblr-modal-* 变量**全部定义在 .modal 容器上**（不是 :root），
   .modal-header / .modal-body / .modal-footer 的尺寸全靠从它继承：
       .modal-body   { padding: var(--tblr-modal-padding) }
       .modal-footer { padding: calc(var(--tblr-modal-padding) - var(--tblr-modal-footer-gap) * .5) }
   于是把 .modal-content 单独写进页面（少了 .modal > .modal-dialog 两层）时，
   变量一个都取不到 → calc 在 computed-value 阶段**整条作废** → padding 归 0 →
   「取消 / 确认」直接贴到卡片边缘。
   ⚠️ 真实弹窗一律写完整结构：宿主四处（rageframe.js 的 data-confirm 与应用更新日志、
      panel.js 的弹层、x-ui.cropper）都是完整的，本项目**没有一处裸写 .modal-content**。
   下面三条只命中「不在 .modal 里」的 .modal-content —— 正常结构下选择器根本不匹配、
   一个字节都不生效；结构真不全时把 Tabler 的默认取值补齐（内边距 24px、
   footer 纵向 12px / 横向 24px − 12px÷2，与 Tabler 默认逐像素等价）。
   颜色类变量（--tblr-modal-footer-bg / header / footer 的描边色）同样取自祖先，
   一并按 Tabler 的默认链路写回：footer 底 = --tblr-bg-surface-tertiary = --rf-surface-3，
   描边 = --tblr-border-color = --rf-line。 */
.modal-content:not(:where(.modal) *) > .modal-header {
    padding: 24px;
    border-bottom: 1px solid var(--rf-line-soft);
}
.modal-content:not(:where(.modal) *) > .modal-body {
    padding: 24px;
}
.modal-content:not(:where(.modal) *) > .modal-footer {
    padding: 12px calc(24px - 12px * .5);
    background-color: var(--rf-surface-3);
    border-top: 1px solid var(--rf-line-soft);
}

/* ── 弹层遮罩（modal / offcanvas）─────────────────────────────────────
   蒙层用主题自己的深色（浅色下是墨蓝，深色下是墨黑），比纯黑柔和。
   ⚠️ 颜色必须挂在**基础选择器**上，不能只挂 .show —— Tabler 里
   .modal-backdrop 的底色走 --tblr-backdrop-bg、.offcanvas-backdrop 直接
   写死 gray-800/black；只在 .show 上覆盖的话，关闭时 .show 一摘，底色会
   从「45% 墨色」跳变成「实心深灰」，同一帧整屏猛地变暗再开始淡出 ——
   这就是点取消时看见的「闪」（逐帧实测：摘掉 .show 那一帧
   backgroundColor 直接从 oklab(.../0.45) 跳成 oklch(0.2686 0 0)）。
   挂在基础选择器上，加不加 .show 颜色都一致，淡出就只剩 opacity 在动。 */
.modal-backdrop,
.offcanvas-backdrop {
    background-color: var(--rf-scrim);
}

.modal-backdrop.show,
.offcanvas-backdrop.show {
    opacity: 1;
}

/* ── 弹窗遮罩：出现必须与对话框同步 ──────────────────────────────────
   Bootstrap 的弹窗时序是「先等遮罩过渡结束，**再**显示对话框」
   （Modal.show → Backdrop.show(cb)，回调里才给 .modal 设 display:block）。
   遮罩自带 150ms 淡入时，打开就成了「黑 150ms 的空屏 → 弹窗才冒出来」，
   观感即「打开闪一下」（逐帧实测：t=85ms 遮罩开始淡入、t=247ms 已全黑、
   t=264ms 弹窗才出现，中间 180ms 屏幕上什么都没有）。
   把淡入的过渡时长设为 0，Bootstrap 的等待立即返回，遮罩与对话框**同帧**
   开始；淡入的观感交给下面的 animation 补回（动画不参与 Bootstrap 的
   transitionend 等待，纯视觉）。
   ⚠️ 只动弹窗这份 —— 抽屉的遮罩与面板本来就是并行动画的，别碰。 */
.modal-backdrop.show {
    transition: opacity 0s;
    animation: rf-backdrop-in .18s ease-out;
}

@keyframes rf-backdrop-in {
    from { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
    /* 主题自己的过渡全部走 --rf-dur* 三档时长 —— 把三个令牌压到 .01ms，
       全站（含内容 iframe 里由宿主组件渲染的那些）一次性生效，既不写
       !important，也不用逐个选择器列。
       **刻意不写 `*` 通杀**：theme.css 内容页也加载，通杀会连应用视图 /
       富文本自己的动效一起关掉（与 rageframe.css 那条的边界一致）。
       animation 不动 —— 转圈这类加载指示器得「在动」才读得出状态。 */
    :root {
        --rf-dur-fast: .01ms;
        --rf-dur: .01ms;
        --rf-dur-slow: .01ms;
    }

    /* Tabler 自己的动效不吃我们的令牌，唯一会晃的是模态遮罩淡入 */
    .modal-backdrop.show {
        animation: none;
    }
}

/* ============================================================
   12. 进度 / 空态
   ============================================================ */

.progress {
    background-color: var(--rf-surface-2);
    /* 圆角走 Tabler 的槽（它定义在 .progress,.progress-stacked 上，由 .progress 读）。
       .progress-bar 不需要自己再来一条：Tabler 的 .progress 是 overflow:hidden，
       父级的圆角已经把它裁出来了。 */
    --tblr-progress-border-radius: var(--rf-r-pill);
    height: 8px;
}

.progress-bar {
    background-color: var(--tblr-primary);
}

/* ============================================================
   13. 工具类（必须 !important，Bootstrap 的工具类自带 !important）
   ============================================================ */

.text-muted,
.text-secondary { color: var(--rf-muted) !important; }
.text-primary { color: var(--tblr-primary) !important; }
.text-success { color: var(--rf-ok-ink) !important; }
.text-warning { color: var(--rf-warn-ink) !important; }
.text-danger  { color: var(--rf-danger-ink) !important; }
.text-info    { color: var(--rf-info-ink) !important; }

/* .bg-white / .bg-light 在视图里当「白底块」用（20 处）：跟着皮走，
   否则深色模式下一页白斑 */
.bg-white { background-color: var(--rf-surface) !important; }
.bg-light { background-color: var(--rf-surface-2) !important; }

/* 状态徽章只有一个口径：.text-bg-*（x-ui.badge 的产物，底 = --rf-*-bg、
   字 = --rf-*-ink）。Tabler 原生的「淡底」族 .bg-{色}-lt 不再重映射 ——
   宿主里的用法已全部换成 .text-bg-*，留着两族只会让人再写出第二种写法。

   ⚠️ 中性淡底块**别**用 .bg-light-lt：Tabler 给 .bg-{色}-lt 的定义是
   「10% 该色的底 + 该色实字」两个声明都带 !important，而 .bg-light-lt 的
   「该色」就是 --tblr-light（oklch 98.51% ≈ #fafafa）—— 浅色主题下算出来
   是白字 + 10% 白底，整块文字看不见（连 <pre> 自带的 --rf-ink 都被盖掉）。
   这类「浅色底块」一律 .bg-light（= --rf-surface-2，跟着深浅皮走）。
   回归口径：工作区探针 probe-tinyui.js 的 contrast 节按 WCAG 对比度扫全页。 */

/* ============================================================
   14. 外壳：选中不点亮（不用亮蓝实底）
   ============================================================

   导航选中项一律不点亮成主色实底 —— 那在一栏里就是一块视觉噪音。
   「现在在哪」由各元素自己的做法承担，口径写在各自主规则上：
     · 侧栏菜单 / 栅栏：选中 = 悬停同色，见 rageframe.css 的菜单一节与栅栏一节；
     · 一级导航：没有选中色，当前组由侧栏第一格的分组标题说（见 rageframe.css）；
     · 标签栏页签：见下面 .rf-tab.active —— 本节唯一的覆盖，其余都在各自主规则上。
   ------------------------------------------------------------------------- */

/* 页签选中：中性底 + 无描边 —— 外壳里唯一有选中底的元素。
   标签栏里同时挂着多个已开页面，用主色点亮当前项会在 40px 高的条上形成
   一块抢眼的亮块（这正是本节要消掉的那种视觉噪音）；但底太淡又认不出
   「哪个是当前」（--rf-page 那版落在纯白的标签栏底上几乎看不见），所以
   底取比标签栏底实一档的 --rf-line-soft。**字色不加深**，与未选中同档
   （--tblr-secondary，也就是 .rf-tab 的基础字色）——「哪个是当前」只由
   这一层底纹承担，不叠第二个信号。

   border-color 写 transparent 而不是 border: 0：.rf-tab 基础态带 1px
   透明描边占位（rageframe.css），选中态真去掉那 1px 的话标签会比未选中窄
   2px，切页签时整条横移一下。显式写 color 是为了压过 .rf-tab:hover。 */
.rf-tab.active {
    background-color: var(--rf-line-soft);
    color: var(--tblr-secondary);
    border-color: transparent;
}

/* ==========================================================================
   Tom Select（x-ui.select enhance / x-ui.tags 的增强下拉）
   --------------------------------------------------------------------------
   变量桥接见上面「Bootstrap 变量命名空间兼容层」—— 皮肤能生效之后，这里只
   补皮肤里**硬编码**的部分：它是按浅色主题写死的几个灰（深色下「新建 xxx」
   与空结果文案会糊成一片），以及面板圆角 / 阴影对齐项目刻度。
   其余（间距、字号、多选胶囊、下拉宽度）不动 —— 改多了升级会碎。
   ========================================================================== */

/* ⚠️ 前挂 `body` 是为了提权重，不是为了限定范围。弹层（panel.js）会把片段里
   @push('lib-styles') 的样式表**在打开那一刻**追加进 head —— 落在 theme.css
   之后，同权重下 Tom Select 自带的 `z-index:1000` 就赢了（实测抬了令牌还是
   1000）。这里 0,1,1 压过它的 0,1,0，与加载顺序无关。 */
body .ts-dropdown {
    border-radius: var(--rf-r-sm);
    box-shadow: var(--rf-shadow-pop);

    /* 走本项目的 z 刻度，不吃 Tom Select 自带的 1000 —— 那个值低于弹层
       （1055），下拉在弹层里展开会被底栏和弹层本体压住。刻度里 dropdown
       已经抬到弹层之上，见文件顶部「z-index 刻度」那条注释。 */
    z-index: var(--rf-z-dropdown);
}

/* 选项行内缩成圆角短条：hover / 键盘高亮不再是直角通栏灰条，更软也与全站
   选中语义一致。左右内边距等量收回，文字落点不变（仍与控件 / 搜索框同一条
   12px 竖线）。分组头保持通栏（它自带分组分隔线，内缩会把线切短）。 */
.ts-dropdown .option,
.ts-dropdown .create,
.ts-dropdown .no-results {
    margin: 0 6px;
    padding-left: calc(12px - 6px);
    padding-right: calc(12px - 6px);
    border-radius: var(--rf-r-xs);
}

/* 面板内容自带的滚动条整条收掉：那条系统槽是灰的、宽度各端还不一致，
   把通栏高亮和纯白面板切掉一条，整个面板右侧看着发灰。滚轮 / 触摸板 /
   方向键照常滚（Tom Select 自己会滚动激活项），只是不再画那根槽。 */
.ts-dropdown .ts-dropdown-content {
    scrollbar-width: none;
}

/* 「新建 "xxx"」与「无匹配结果」：皮肤写死 rgba(52,58,64,.5)，深色下几乎不可见 */
.ts-dropdown .create,
.ts-dropdown .no-results {
    color: var(--rf-muted);
}

.ts-dropdown .optgroup-header {
    color: var(--rf-faint);
}

/* 键盘/悬停高亮项：走主色淡底，与全站选中语义一致（皮肤默认是灰底） */
.ts-dropdown .active {
    background-color: var(--rf-accent-soft);
    color: var(--tblr-primary);
}

/* 输入过滤时的命中高亮：皮肤是荧光黄，换成主色淡底 */
.ts-dropdown [data-selectable] .highlight {
    background: var(--rf-accent-strong);
    border-radius: 2px;
}

/* 焦点态：皮肤写死 Bootstrap 蓝（面板内搜索框一圈 rgba(13,110,253,.25) 的
   4px 淡蓝光环 + #d0d0d0 分隔线），发灰的浅蓝与全站控件聚焦（主色柔光
   --rf-field-glow）对不上 —— 全部换到主题令牌。 */
/* 面板顶部搜索框（dropdown_input 插件）。皮肤的两条搜索框规则都以
   .plugin-dropdown_input（插件类长在 wrapper 上）打头做后代匹配 —— 面板挂
   body（data-dropdown-body）时面板不在 wrapper 里，两条全部落空，形状回到
   浏览器默认、聚焦还露出一圈 UA 黑描边。
   这里换成「面板自身打头」的选择器，面板在哪都成立；类名双写把特异性抬到
   与皮肤持平（0-3-0 / 0-4-0），theme.css 在皮肤之后加载，同权重后到者赢。 */
.ts-dropdown .dropdown-input.dropdown-input {
    background: transparent;
    border: 0;
    border-bottom: 1px solid var(--rf-line);
    border-radius: 0;
    box-shadow: none;
}

.ts-dropdown .dropdown-input.dropdown-input:focus {
    outline: none;
    border-bottom-color: var(--rf-accent-line);
    box-shadow: none;
}

.ts-wrapper.focus .ts-control {
    border-color: var(--tblr-primary);
    box-shadow: var(--rf-field-glow);
}

/* ==========================================================================
   标签输入的等待态（x-ui.tags 的防闪占位条）
   --------------------------------------------------------------------------
   原生 <select multiple> 在增强前是一坨 88px 高的灰底列表框（选项全铺开），
   Tom Select 接管后换成默认档高度（32px）的胶囊控件 —— 不处理就是「先闪一坨、再跳一跳」。
   这里在增强就绪前用一根**等高的空控件条**顶住位置：观感不丑、布局不跳。

   就绪判定（纯 CSS，不依赖 JS 介入）：Tom Select 接管后在原 select 上挂
   tomselected（同时把 select 藏成 ts-hidden-accessible、把 .ts-wrapper 插在
   select 之后），占位条靠**兄弟选择器**退场。
   ========================================================================== */

/* 等待态占位条：渲染即亮，增强完成后由下面那条兄弟选择器退场。
   高度与内边距直接取控件令牌（与上面 .ts-wrapper .ts-control 那组同源）——
   字体、行高一致，两者高度天然相等，控件档改了也一起走，不会再「跳一下」。 */
.rf-tags-skeleton {
    display: block;
    min-height: var(--rf-ctl-h);
    padding: var(--rf-ctl-py) 12px;
    border: 1px solid var(--rf-line-strong);
    border-radius: var(--rf-r-sm);
    background-color: var(--rf-field-bg);
    color: var(--rf-faint);
    font-size: 14px;
    line-height: 1.5;
}

/* 增强未完成：原生 select 收成 1px（与 Tom Select 自己的 ts-hidden-accessible
   同一套手法 —— 出流、可聚焦、读屏可见），占位条占着它的位置。
   ⚠️ 只收 data-tomselect="tags"：只有它渲染了占位条，普通 enhance 下拉
   （x-ui.select）没有，一起收就是一片空白。 */
select[data-tomselect="tags"]:not(.tomselected) {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    border: 0;
    clip-path: inset(50%);
    overflow: hidden;
    white-space: nowrap;
}

/* 增强完成（select 挂上 tomselected）：占位条退场，.ts-wrapper 顶上同位置。
   兄弟选择器而不是 :has() —— .ts-wrapper 是插在 select 后面的兄弟节点。 */
select.tomselected ~ .rf-tags-skeleton {
    display: none;
}

/* 错误态同步：占位条也描红，别「红 → 灰 → 红」闪一下 */
.rf-tags-skeleton.is-invalid {
    border-color: var(--rf-danger, #f53f3f);
}

/* ==========================================================================
   日期输入框的清空按钮（x-ui.datepicker / rageframe.js 的 addClearButton 注入）
   --------------------------------------------------------------------------
   外壳 + 按钮都由 rageframe.js 造（见那边的注释：为什么必须包一层、为什么调
   fp.clear()）。这里只管长相：
     - 包裹层 position:relative 是按钮定位的锚，display:block 保证包裹前后
       布局一致（.form-control 是 width:100% 的块级，块级父元素尺寸不变）；
     - 按钮用 inset-inline-end 而不是 right，跟 Tabler 的逻辑属性保持一致；
     - 错误态要让开 Tabler 画的那个感叹号（它在右侧 16px 处、宽 20px，
       且 padding-inline-end 带 !important），所以 × 挪到它左边、输入框的
       行尾内边距同步加大 —— 三个值是一组，改一个要一起改。
   ========================================================================== */

.rf-datepicker {
    position: relative;
    display: block;
}

/* 进 input-group 时（表格筛选行的「日期范围」），这层壳必须当**可伸缩的一格**：
   input-group 是 flex 容器，壳按自身内容站住宽度，右边那颗「快速选择」按钮就
   排不进去 —— 实测列宽 172px 时输入框占掉 138px，按钮被挤到它身上（左边界比
   输入框还靠左 1px），看起来就是「按钮溢出了」。flex:1 + min-width:0 让它吃
   剩余宽度，按钮回到组内正常排布。 */
.input-group > .rf-datepicker {
    flex: 1 1 auto;
    width: 1%;
    min-width: 0;
}

.input-group > .rf-datepicker > .form-control {
    width: 100%;
}

.rf-datepicker-clear {
    position: absolute;
    top: 50%;
    inset-inline-end: 8px;
    transform: translateY(-50%);
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    padding: 0;
    border: 0;
    border-radius: var(--rf-r-pill);
    background: transparent;
    color: var(--rf-faint);
    font-size: 14px;
    line-height: 1;
    cursor: pointer;
}

/* ⚠️ display 写在 :not([hidden]) 上，不能裸写 display:flex —— 裸写会盖过
   [hidden]（理由见 docs/UI.md 与文件头注释），按钮的显隐完全靠这个属性。 */
.rf-datepicker-clear:not([hidden]) {
    display: flex;
}

@media (hover: hover) {
    .rf-datepicker-clear:hover {
        background: var(--rf-surface-2);
        color: var(--rf-ink);
    }
}

/* 值可能很长（range 是「起 至 止」两个日期），别让文字钻到按钮底下 */
.rf-datepicker > .form-control {
    padding-inline-end: 32px;
}

.rf-datepicker > .form-control.is-invalid,
.rf-datepicker > .form-control.is-valid {
    /* Tabler 那条是 !important，这里必须跟它同权才盖得住 */
    padding-inline-end: 60px !important;
}

.rf-datepicker > .form-control.is-invalid ~ .rf-datepicker-clear,
.rf-datepicker > .form-control.is-valid ~ .rf-datepicker-clear {
    inset-inline-end: 40px;
}

/* ==========================================================================
   数字步进按钮（input[type=number] · rageframe.js 的 enhanceNumberStepper 注入）
   --------------------------------------------------------------------------
   原生 spinner 各浏览器各画各的且几乎无法自定义（Chrome 的
   ::-webkit-inner-spin-button 是固定布局，Firefox 只能整体隐藏不能改样式），
   所以统一摘掉、右侧注入自绘 ▲▼ 步进列。外壳与按钮由 rageframe.js 造，
   这里只管长相：
     - 与 .rf-datepicker（清空按钮）同款外壳约定：position:relative 锚 +
       display:block，包裹前后布局不变；form-validate 的 anchorOf 认这个壳
       （错误提示要落在壳外），两处是一对；
     - 错误/成功态**挪图标不挪按钮**：Tabler 把感叹号画在右侧 16px 处且带
       !important，这里用同权 background-position 把它推到步进列左边；
     - .input-group 里的 number 不注入（组合边框要求子元素直连，包壳会断）
       —— 原生箭头也已全局摘掉，那里的步进只剩键盘 ArrowUp / ArrowDown。
   ========================================================================== */

/* 原生箭头全局摘除（统一交给自绘步进列；含 .input-group 里的） */
input[type="number"] {
    appearance: textfield;
    -moz-appearance: textfield;
}

input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
}

.rf-number {
    position: relative;
    display: block;
}

/* 步进列：内嵌 4px 贴右缘，两格等高 + 1px 分隔线，浅灰底读作「小部件」。
   尺寸是定死的 22px：form-control（36px）与 form-control-sm（24px）都放得下，
   不随字号缩放 —— 图标只有 10px，跟随缩放反而糊。 */
.rf-number-btns {
    position: absolute;
    top: 4px;
    bottom: 4px;
    inset-inline-end: 4px;
    display: flex;
    flex-direction: column;
    width: 22px;
    border-radius: calc(var(--rf-r-sm) - 3px);
    overflow: hidden;
    background: var(--rf-surface-2);
    pointer-events: auto;
}

.rf-number-btn {
    flex: 1 1 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: 0;
    background: transparent;
    color: var(--rf-faint);
    cursor: pointer;
    transition: background-color var(--rf-dur) var(--rf-ease-curve),
                color var(--rf-dur) var(--rf-ease-curve);
    /* 触屏：长按不能被认成双击缩放，也不能从按钮上起手就锁死页面滚动；
       长按选中文字/系统高亮气泡同理，都要挡掉（连发期间手指是按住不动的） */
    touch-action: manipulation;
    -webkit-user-select: none;
    user-select: none;
    -webkit-tap-highlight-color: transparent;
}

.rf-number-btn + .rf-number-btn {
    border-top: 1px solid var(--rf-line);
}

/* 悬停/按下：主色淡底 + 主色图标（全站选中语义），不是实心色块 */
@media (hover: hover) {
    .rf-number-btn:hover {
        background: var(--rf-accent-soft);
        color: var(--rf-accent);
    }
}

.rf-number-btn:active {
    background: var(--rf-accent-strong);
    color: var(--rf-accent);
}

/* 连发中：JS 在起步时打上 is-repeating。此时鼠标/手指可能已经滑出按钮
   （:active 靠不住），靠这个类把「按下」的视觉钉住；40ms 一跳的节奏下
   底色过渡会来回闪，索性关掉过渡，读作「一直在按」而不是「在闪」。 */
.rf-number-btn.is-repeating {
    background: var(--rf-accent-strong);
    color: var(--rf-accent);
    transition: none;
}

.rf-number-btn:focus-visible {
    outline: var(--rf-focus-ring-width) solid var(--rf-focus-ring-color-soft);
    outline-offset: var(--rf-focus-ring-offset-inner);
}

/* 输入框让出步进列的宽度（列 22px + 内嵌 4px + 呼吸 6px） */
.rf-number > .form-control {
    padding-inline-end: 32px;
}

/* 错误/成功态：Tabler 的感叹号/对勾带 !important 画在右侧 16px（占
   16px~36px），与步进列（4px~26px）同高重叠 —— 同权把图标推到列左边
   （30px 起，宽 20px，到 50px），行尾内边距同步加大。 */
.rf-number > .form-control.is-invalid,
.rf-number > .form-control.is-valid {
    padding-inline-end: 56px !important;
    background-position: right 30px center !important;
}

/* ==========================================================================
   随机串生成（x-ui.generator）
   --------------------------------------------------------------------------
   输入框 + 一枚「生成新的」按钮，给「值要自己去对方平台设置一遍」的字段用
   （公众号服务器 Token、EncodingAESKey、微信支付 APIv3 / APIv2 密钥）。
   点一下把随机串填进框里，用户复制到微信后台再粘贴回来。

   与 .rf-number / .rf-datepicker 同一条壳约定：.rf-generate 既是 flex 排布
   容器，也是定位锚点 —— form-validate 的 anchorOf 认这个壳（错误提示落在
   整行下面，不会压在按钮上），两处是一对。

   ⚠️ 原生的 button 点一次会抢走焦点（用户下一步多半是「全选复制」）—— 焦点
      处理在 JS 里（生成后 focus + select），CSS 不做多余的事。

   ========================================================================== */

.rf-generate {
    position: relative;
    display: flex;
    align-items: stretch;
    gap: 8px;
}

.rf-generate > .form-control {
    /* min-width:0 让它作为 flex 子项可以被压缩 —— 43 位 EncodingAESKey 这么长的
       内容也不会把按钮顶出容器（否则窄屏上按钮会被挤出可视区） */
    flex: 1 1 auto;
    min-width: 0;
}

.rf-generate-btn {
    flex: none;
    /* 按钮里是图标 + 两个字，别被 flex 压到换行 */
    white-space: nowrap;
    display: inline-flex;
    align-items: center;
    gap: 6px;
}

.rf-generate-btn i {
    font-size: 16px;
    line-height: 1;
}

@media (max-width: 575.98px) {
    /* 窄屏：图标足够表意，文字让位给按钮宽度（框里的内容本身就长） */
    .rf-generate-btn-text {
        display: none;
    }

    .rf-generate-btn {
        padding-inline: 10px;
    }
}

/* ==========================================================================
   flatpickr（x-ui.datepicker 的弹层）—— 覆盖性重写皮肤
   --------------------------------------------------------------------------
   官方 css 是自成一体的浅色风（150px 正圆日期格、#569ff7 蓝选中、300 细字
   大标题、#e6e6e6 硬边框、四角小箭头），与项目「方角带圆角 + 令牌化」的
   设计语言完全不搭 —— 这里**整段接管**：官方给的每一条视觉属性都在下面
   用 --rf-* 令牌重写一遍，颜色全部走令牌 → 深浅色自动跟随，不需要
   单独的深色补丁块。

   覆盖成立的前提：flatpickr.min.css 由组件 @push 进 lib-styles 栈，
   **排在 theme.css 之前**（_head.blade.php 的注释），同权重选择器后者赢。

   结构性宽度（.flatpickr-days/.dayContainer 的 307.875px）不动 —— 那是
   flatpickr 内部定位和 multiMonth 排版的基准，改了会碎；视觉只在
   间距 / 圆角 / 颜色 / 字重层面重写。
   ========================================================================== */

.flatpickr-calendar {
    /* 范围选中的连色带靠 box-shadow 补缝，颜色先备成局部变量（两处引用） */
    --rf-cal-tint: var(--rf-accent-bg);

    /* 弹层级外观：卡片底 + 描边 + 浮起阴影（官方那组 #e6e6e6 硬边框阴影整组替换） */
    background: var(--rf-surface);
    border: 1px solid var(--rf-line);
    border-radius: var(--rf-r);            /* 弹层与内嵌块同刻度（6px），不是卡片那档的 8px */
    box-shadow: var(--rf-shadow-pop);
    color: var(--rf-ink);

    /* 排版对齐全站：正文 14 档 + 项目字体栈（官方 line-height 24px 保留，
       它参与 39px 日期格的垂直居中，别动） */
    font-size: var(--rf-fs-body);
    line-height: 24px;
    font-family: inherit;

    /* 官方 padding:0，日期格贴边；底部补一口气，头部与星期行的呼吸感
       由下面各自加高来承担 */
    padding: 0 0 6px;
}

/* 四角小箭头：全站弹层（dropdown / Tom Select / panel）都没有箭头，
   单独长一颗反而突兀，且深浅切换还要多维护两个颜色 —— 摘掉 */
.flatpickr-calendar::before,
.flatpickr-calendar::after {
    display: none;
}

/* ── 头部：月份标题 + 上下月 ─────────────────────────────────
   官方 300 细字 + 135%（≈19px）在雅黑下又大又飘；压到 15px/500，
   与卡片标题（h3/h4 = 16/400-500 档）衔接。头部加高到 44px：
   上下月按钮的命中区随之从 34px 扩到 44px（移动端友好）。 */
.flatpickr-months .flatpickr-month {
    height: 44px;
    color: var(--rf-ink);
}

.flatpickr-current-month {
    height: 44px;
    line-height: 44px;
    padding: 0;                            /* 官方 7.48px 顶垫是给 34px 高配平的 */
    font-size: 15px;
    font-weight: 500;
    color: var(--rf-ink);
}

.flatpickr-current-month span.cur-month {
    font-weight: 500;
    color: var(--rf-ink);
}

@media (hover: hover) {
    .flatpickr-current-month span.cur-month:hover,
    .flatpickr-current-month .flatpickr-monthDropdown-months:hover {
        background: var(--rf-surface-2);
        border-radius: var(--rf-r-xs);
    }
}

.flatpickr-current-month input.cur-year {
    font-weight: 500;
    color: var(--rf-ink);
}

.flatpickr-current-month input.cur-year[disabled],
.flatpickr-current-month input.cur-year[disabled]:hover {
    color: var(--rf-faint);
}

.flatpickr-current-month .flatpickr-monthDropdown-months {
    font-weight: 500;
    color: var(--rf-ink);
}

/* 上下月：官方是 10px 垫 + 14px 图标的「裸箭头」，悬停变 #f64747（红！）
   —— 改成 44px 命中区、居中、悬停浮起 surface-2 圆角块，色随主色 */
.flatpickr-months .flatpickr-prev-month,
.flatpickr-months .flatpickr-next-month {
    top: 0;
    width: 44px;
    height: 44px;
    padding: 0;
    display: grid;
    place-items: center;
    color: var(--rf-muted);
    border-radius: var(--rf-r-sm);
    transition: background var(--rf-dur) var(--rf-ease-curve),
                color var(--rf-dur) var(--rf-ease-curve);
}

@media (hover: hover) {
    .flatpickr-months .flatpickr-prev-month:hover,
    .flatpickr-months .flatpickr-next-month:hover {
        color: var(--rf-accent);
        background: var(--rf-surface-2);
    }
}

@media (hover: hover) {
    .flatpickr-months .flatpickr-prev-month:hover svg,
    .flatpickr-months .flatpickr-next-month:hover svg {
        fill: currentColor;                    /* 官方 hover 是 #f64747 红，必须接管 */
    }
}

.flatpickr-months .flatpickr-prev-month svg,
.flatpickr-months .flatpickr-next-month svg {
    width: 16px;
    height: 16px;
    fill: currentColor;
}

/* ── 星期行 ─────────────────────────────────────────────────
   官方 bolder(700) + 半透明黑 —— 提示档：12px / 500 / --rf-faint */
.flatpickr-weekdays {
    height: 32px;
}

span.flatpickr-weekday {
    font-size: var(--rf-fs-xs);
    font-weight: 500;
    color: var(--rf-faint);
}

/* ── 日期格 ─────────────────────────────────────────────────
   核心改造：官方 150px 正圆 → **6px 方角带圆角**（项目签名风格，
   与 logo / 图标 / 徽标同一套刻度）；文字 #393939 → --rf-ink。
   1px 透明边框保留 —— 「今天」的描边靠它。 */
.flatpickr-day {
    border-radius: var(--rf-r-xs);
    color: var(--rf-ink);
    transition: background var(--rf-dur) var(--rf-ease-curve),
                color var(--rf-dur) var(--rf-ease-curve),
                border-color var(--rf-dur) var(--rf-ease-curve);
}

/* 悬停 / 键盘焦点：官方 #e6e6e6 灰块 → 内嵌块底色 */
.flatpickr-day.inRange,
.flatpickr-day.prevMonthDay.inRange,
.flatpickr-day.nextMonthDay.inRange,
.flatpickr-day.today.inRange,
.flatpickr-day.prevMonthDay.today.inRange,
.flatpickr-day.nextMonthDay.today.inRange,
.flatpickr-day:hover,
.flatpickr-day.prevMonthDay:hover,
.flatpickr-day.nextMonthDay:hover,
.flatpickr-day:focus,
.flatpickr-day.prevMonthDay:focus,
.flatpickr-day.nextMonthDay:focus {
    background: var(--rf-surface-2);
    border-color: transparent;
}

/* 今天：主色描边（选中态会盖掉描边，官方同款行为） */
.flatpickr-day.today {
    border-color: var(--rf-accent);
}

.flatpickr-day.today:hover,
.flatpickr-day.today:focus {
    background: var(--rf-accent);
    border-color: var(--rf-accent);
    color: var(--rf-on-strong);
}

/* 选中 / 范围起止：官方 #569ff7 蓝 → 主色。选择器列表照抄官方
   （selected/startRange/endRange × inRange/focus/hover/跨月日），
   少一条就会有一处组合漏掉。 */
.flatpickr-day.selected,
.flatpickr-day.startRange,
.flatpickr-day.endRange,
.flatpickr-day.selected.inRange,
.flatpickr-day.startRange.inRange,
.flatpickr-day.endRange.inRange,
.flatpickr-day.selected:focus,
.flatpickr-day.startRange:focus,
.flatpickr-day.endRange:focus,
.flatpickr-day.selected:hover,
.flatpickr-day.startRange:hover,
.flatpickr-day.endRange:hover,
.flatpickr-day.selected.prevMonthDay,
.flatpickr-day.startRange.prevMonthDay,
.flatpickr-day.endRange.prevMonthDay,
.flatpickr-day.selected.nextMonthDay,
.flatpickr-day.startRange.nextMonthDay,
.flatpickr-day.endRange.nextMonthDay {
    background: var(--rf-accent);
    border-color: var(--rf-accent);
    color: var(--rf-on-strong);
    box-shadow: none;
}

/* 范围两端的圆角：官方 50px 半圆 → 6px 方角带圆角（与日期格一致） */
.flatpickr-day.selected.startRange,
.flatpickr-day.startRange.startRange,
.flatpickr-day.endRange.startRange {
    border-radius: var(--rf-r-xs) 0 0 var(--rf-r-xs);
}

.flatpickr-day.selected.endRange,
.flatpickr-day.startRange.endRange,
.flatpickr-day.endRange.endRange {
    border-radius: 0 var(--rf-r-xs) var(--rf-r-xs) 0;
}

.flatpickr-day.selected.startRange.endRange,
.flatpickr-day.startRange.startRange.endRange,
.flatpickr-day.endRange.startRange.endRange {
    border-radius: var(--rf-r-xs);
}

/* 范围中段：官方的连色带是「方块底 + 左右 5px 同色 box-shadow 补缝」。
   底色与补缝色都用 12% 主色淡染（--rf-cal-tint），起始日紧邻的那针
   补缝（-10px 0 0）也要跟着换色，否则接缝处露官方蓝。 */
.flatpickr-day.inRange {
    border-radius: 0;
    background: var(--rf-cal-tint);
    box-shadow: -5px 0 0 var(--rf-cal-tint), 5px 0 0 var(--rf-cal-tint);
}

.flatpickr-day.selected.startRange + .endRange:not(:nth-child(7n+1)),
.flatpickr-day.startRange.startRange + .endRange:not(:nth-child(7n+1)),
.flatpickr-day.endRange.startRange + .endRange:not(:nth-child(7n+1)) {
    box-shadow: -10px 0 0 var(--rf-accent);
}

.flatpickr-day.week.selected {
    box-shadow: -5px 0 0 var(--rf-cal-tint), 5px 0 0 var(--rf-cal-tint);
}

/* 不可选 / 跨月引导日：官方 rgba(黑,.3) → --rf-faint */
.flatpickr-day.flatpickr-disabled,
.flatpickr-day.flatpickr-disabled:hover,
.flatpickr-day.prevMonthDay,
.flatpickr-day.nextMonthDay,
.flatpickr-day.notAllowed,
.flatpickr-day.notAllowed.prevMonthDay,
.flatpickr-day.notAllowed.nextMonthDay {
    color: var(--rf-faint);
}

/* ── 年份/时间的步进小箭头（numInputWrapper）───────────────── */
.numInputWrapper span {
    border-color: var(--rf-line-strong);
}

.numInputWrapper span.arrowUp:after {
    border-bottom-color: var(--rf-faint);
}

.numInputWrapper span.arrowDown:after {
    border-top-color: var(--rf-faint);
}

@media (hover: hover) {
    .numInputWrapper span:hover {
        background: var(--rf-surface-2);
    }
}

@media (hover: hover) {
    .numInputWrapper:hover {
        background: transparent;               /* 官方 rgba(黑,.05) 会在年份上糊一块灰 */
    }
}

.flatpickr-current-month .numInputWrapper span.arrowUp:after {
    border-bottom-color: var(--rf-ink);
}

.flatpickr-current-month .numInputWrapper span.arrowDown:after {
    border-top-color: var(--rf-ink);
}

/* ── 时间行 ─────────────────────────────────────────────────
   分隔线走 --rf-line-soft（官方 #e6e6e6）；数字用 500 字重
   （全站字重上限，见令牌区注释：雅黑 600 会整档上跳到 Bold 发糊） */
.flatpickr-calendar.hasTime .flatpickr-time {
    border-top: 1px solid var(--rf-line-soft);
}

.flatpickr-time input {
    color: var(--rf-ink);
    font-weight: 500;
    border-radius: var(--rf-r-xs);
}

.flatpickr-time input.flatpickr-hour {
    font-weight: 500;                      /* 官方 bold(700) */
}

.flatpickr-time input:hover,
.flatpickr-time .flatpickr-am-pm:hover,
.flatpickr-time input:focus,
.flatpickr-time .flatpickr-am-pm:focus {
    background: var(--rf-surface-2);
}

.flatpickr-time .flatpickr-time-separator,
.flatpickr-time .flatpickr-am-pm {
    color: var(--rf-faint);
}

.flatpickr-time .numInputWrapper span.arrowUp:after {
    border-bottom-color: var(--rf-faint);
}

.flatpickr-time .numInputWrapper span.arrowDown:after {
    border-top-color: var(--rf-faint);
}

/* ==========================================================================
   wangEditor-next（x-ui.editor 的编辑器外壳）对齐主题视觉
   --------------------------------------------------------------------------
   官方 style.css 自成一体，这里只包一层壳：圆角 / 边框 / 工具栏底色 /
   深色模式 / is-invalid 红框。编辑区内部（z-index 层、菜单弹层）不动，
   改多了升级会碎。
   ========================================================================== */

/* 外壳宿主：壳（工具栏 + 编辑区）由 rageframe.js 现造、插在 textarea 之前。
   这里先把位置按**终态高度**占住 —— 不预留就是「120px（textarea）→ 340px」的
   一下撑开，下方内容整体下移（CLS）。
   ⚠️ --rf-editor-h 是编辑区高度（视图下发），40px 是工具栏单行高度；窄屏
      工具栏换行时会略高一点，那点微差不值得追。
   ⚠️ 过渡 textarea 首帧就藏起（同 x-ui.markdown）：它是提交源不是可见形态，
      留它在场上就是「原生多行框突变成富文本」。 */
.rf-editor-host {
    min-height: calc(var(--rf-editor-h, 300px) + 40px);
}

.rf-editor-host > textarea {
    display: none;
}

.rf-editor {
    border: var(--tblr-border-width, 1px) solid var(--tblr-border-color, #dadfe5);
    border-radius: var(--rf-r-sm);
    overflow: hidden; /* 圆角裁掉子元素直角 */
    background: var(--tblr-bg-forms, #fff);
}

.rf-editor .rf-editor-toolbar {
    border-bottom: var(--tblr-border-width, 1px) solid var(--tblr-border-color, #dadfe5);
}

/* 编辑器内部的工具栏/容器自带白底和边框，抹平成壳的一部分 */
.rf-editor .w-e-toolbar,
.rf-editor .w-e-text-container {
    background: transparent;
    border: none !important;
}

.rf-editor .rf-editor-body {
    overflow-y: auto;
}

[data-bs-theme="dark"] .rf-editor {
    border-color: var(--tblr-border-color, #34374a);
    background: var(--tblr-bg-forms, #1a1c27);
}

[data-bs-theme="dark"] .rf-editor .w-e-toolbar {
    background: var(--tblr-bg-surface, #20222e);
}

[data-bs-theme="dark"] .rf-editor .w-e-text-container {
    background: transparent;
    color: var(--tblr-body-color, #e9ebf2);
}

[data-bs-theme="dark"] .rf-editor .w-e-text-placeholder {
    color: var(--tblr-secondary-color, #a9adbe);
}

/* is-invalid：编辑器外壳画同款红框（textarea 已藏起，红框要落在壳上） */
.rf-editor.rf-editor-invalid {
    border-color: var(--rf-danger, #f53f3f);
}

.rf-editor:focus-within {
    border-color: var(--tblr-primary, #165DFF);
}

/* ==========================================================================
   ByteMD（x-ui.markdown 的编辑器外壳 / x-ui.markdown-view 的渲染正文）
   --------------------------------------------------------------------------
   两件事：
     ① .rf-markdown       编辑器外壳（圆角 / 描边 / 底色 / 深色 / 错误态）
     ② .rf-markdown-body  渲染出来的 Markdown 排版
   共同前提：ByteMD 自带的 bytemd.css **只给 .markdown-body 设了最大宽度与内边距**
   （它的定位就是「排版你自己带」）—— 标题 / 列表 / 引用 / 表格 / 任务列表一样都没有。
   所以 ② 不是锦上添花，是必需的；渲染出来没排版不是接错了。

   官方 bytemd.css 里写死了 GitHub 那套浅色（#fff / #e1e4e8 / #0366d6 / #f6f8fa /
   CodeMirror 的 #d7d4f0），深色皮必然坏，所以这里按 theme.css 的纪律**整段接管**：
   只引用 --rf-* 令牌、不写死色值，两套皮自动跟随。
   ========================================================================== */

/* ── ① 编辑器外壳 ───────────────────────────────────────────────────────────
   根元素按**终态高度**占位（--rf-markdown-h = ByteMD 的总高）：编辑器要等
   rageframe.js 挂上去，不预留就是「190px（textarea）→ 320px」的一下撑开。

   ⚠️ 过渡 textarea 首帧就藏起（见下）：它是**提交源**不是可见形态，留它在场上
      会先看到一个原生多行框、再被编辑器整个替换（形态突变）。
   ⚠️ 描边仍等 rageframe.js 挂上 data-ui-markdown-ready 之后才画 —— 就绪前这里是
      一块占位空位（高度已经对了），就绪后才是一副完整的编辑器。
   错误态用 :has() 认 textarea 上的 .is-invalid —— 藏起不影响选择器匹配；
   form-validate 是**运行时**才加这个类的（失焦 / 提交时），模板里只有服务端那一份，
   所以不能只在初始化时同步一次。 */
.rf-markdown {
    min-height: var(--rf-markdown-h, 320px);
}

.rf-markdown > textarea {
    display: none;
}

.rf-markdown[data-ui-markdown-ready] {
    border: var(--tblr-border-width, 1px) solid var(--rf-line);
    border-radius: var(--rf-r-sm);
    background: var(--rf-surface);
    overflow: hidden; /* 圆角裁掉子元素直角 */
}

.rf-markdown[data-ui-markdown-ready]:focus-within {
    border-color: var(--tblr-primary);
}

.rf-markdown[data-ui-markdown-ready]:has(> textarea.is-invalid) {
    border-color: var(--rf-danger);
}

/* 高度由组件参数下发到 --rf-markdown-h（ByteMD 默认 300px 是它自己的写死值） */
.rf-markdown .bytemd {
    height: var(--rf-markdown-h, 320px);
    /* 官方 .bytemd 自带描边 / 白底 / 深灰字 / 系统字体栈 —— 全部让位给外壳 */
    border: none;
    border-radius: 0;
    background: transparent;
    color: var(--rf-ink);
    font-family: var(--rf-font);
}

.rf-markdown .bytemd-toolbar {
    border-bottom: 1px solid var(--rf-line);
    background: var(--rf-surface-2);
}

.rf-markdown .bytemd-toolbar-icon {
    color: var(--rf-muted);
}

.rf-markdown .bytemd-toolbar-icon:hover {
    background: var(--rf-accent-bg);
    color: var(--tblr-primary);
}

.rf-markdown .bytemd-toolbar-icon-active,
.rf-markdown .bytemd-toolbar-tab-active {
    color: var(--tblr-primary);
}

/* 分屏时两栏之间那条分隔线（官方挂在预览区的左边框上） */
.rf-markdown .bytemd-split .bytemd-preview {
    border-left: 1px solid var(--rf-line);
}

/* 底部状态栏（行数 / 字数 / 滚动同步） */
.rf-markdown .bytemd-status {
    border-top: 1px solid var(--rf-line);
    color: var(--rf-faint);
}

.rf-markdown .bytemd-status-right span:hover {
    color: var(--tblr-primary);
}

.rf-markdown .bytemd-status-error {
    color: var(--rf-danger);
}

/* 右侧目录（全屏 / 点「目录」时出现） */
.rf-markdown .bytemd-sidebar {
    border-left: 1px solid var(--rf-line);
    background: var(--rf-surface);
    color: var(--rf-ink);
}

.rf-markdown .bytemd-sidebar ul {
    color: var(--rf-muted);
}

.rf-markdown .bytemd-toc-active {
    color: var(--tblr-primary);
    background: var(--rf-accent-soft);
}

/* 弹层（「标题」下拉 / 帮助）—— 与项目其它浮层同一套底与描边 */
.rf-markdown .bytemd-dropdown-title {
    color: var(--rf-muted);
    border-bottom: 1px solid var(--rf-line);
}

.rf-markdown .bytemd-dropdown-item:hover {
    background: var(--rf-surface-2);
}

/* CodeMirror 5 的内置主题同样是硬编码浅色，一并接管 */
.rf-markdown .CodeMirror {
    background: var(--rf-surface);
    color: var(--rf-ink);
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: var(--rf-fs-sm);
    line-height: 1.7;
}

.rf-markdown .CodeMirror-cursor {
    border-left-color: var(--rf-ink);
}

.rf-markdown .CodeMirror-selected,
.rf-markdown .CodeMirror-focused .CodeMirror-selected,
.rf-markdown .CodeMirror-line::selection,
.rf-markdown .CodeMirror-line > span::selection,
.rf-markdown .CodeMirror-line > span > span::selection {
    background: var(--rf-accent-strong);
}

.rf-markdown .CodeMirror-activeline-background {
    background: var(--rf-surface-2);
}

.rf-markdown .CodeMirror-gutters {
    border-right: 1px solid var(--rf-line);
    background: var(--rf-surface-2);
}

.rf-markdown .CodeMirror-linenumber {
    color: var(--rf-faint);
}

.rf-markdown .CodeMirror pre.CodeMirror-placeholder {
    color: var(--rf-faint);
}

/* ── ② 渲染出来的 Markdown 排版 ─────────────────────────────────────────────
   选择器同时覆盖两处，保证「编辑时预览」与「详情页展示」**同源**（这正是选
   ByteMD 而不是「编辑器一个库、展示一个库」的原因）：
     .rf-markdown .markdown-body        编辑器右栏（ByteMD 自己是 .bytemd-preview）
     .rf-markdown-body .markdown-body   展示块（Viewer 的根就是 .markdown-body，
                                        挂在组件的 .rf-markdown-body 壳里）

   ⚠️ 标题字号**刻意不复用**全站的 --rf-fs-h*：正文区块里的标题要按「文档层级」
      递进，而全站档位（h1 24 / h2 19）是「页面标题」语境 —— 一篇 Markdown 里
      一、二、三级标题挤在一起时会头重脚轻。这里单列一套文档刻度。 */
.rf-markdown .markdown-body,
.rf-markdown-body .markdown-body {
    padding: 16px 20px;
    color: var(--rf-ink);
    font-size: var(--rf-fs-body);
    line-height: 1.75;
    word-break: break-word; /* 长链接 / 长英文串别把容器撑破 */
}

.rf-markdown .markdown-body > :first-child,
.rf-markdown-body .markdown-body > :first-child {
    margin-top: 0;
}

.rf-markdown .markdown-body > :last-child,
.rf-markdown-body .markdown-body > :last-child {
    margin-bottom: 0;
}

.rf-markdown .markdown-body h1,
.rf-markdown .markdown-body h2,
.rf-markdown .markdown-body h3,
.rf-markdown .markdown-body h4,
.rf-markdown .markdown-body h5,
.rf-markdown .markdown-body h6,
.rf-markdown-body .markdown-body h1,
.rf-markdown-body .markdown-body h2,
.rf-markdown-body .markdown-body h3,
.rf-markdown-body .markdown-body h4,
.rf-markdown-body .markdown-body h5,
.rf-markdown-body .markdown-body h6 {
    color: var(--rf-ink);
    font-weight: 600;
    line-height: 1.4;
}

.rf-markdown .markdown-body h1,
.rf-markdown-body .markdown-body h1 {
    font-size: 22px;
    margin: 24px 0 12px;
    padding-bottom: 6px;
    border-bottom: 1px solid var(--rf-line-soft);
}

.rf-markdown .markdown-body h2,
.rf-markdown-body .markdown-body h2 {
    font-size: 18px;
    margin: 24px 0 10px;
    padding-bottom: 6px;
    border-bottom: 1px solid var(--rf-line-soft);
}

.rf-markdown .markdown-body h3,
.rf-markdown-body .markdown-body h3 {
    font-size: 16px;
    margin: 20px 0 8px;
}

.rf-markdown .markdown-body h4,
.rf-markdown .markdown-body h5,
.rf-markdown .markdown-body h6,
.rf-markdown-body .markdown-body h4,
.rf-markdown-body .markdown-body h5,
.rf-markdown-body .markdown-body h6 {
    font-size: var(--rf-fs-body);
    margin: 16px 0 6px;
    color: var(--rf-muted);
}

.rf-markdown .markdown-body p,
.rf-markdown-body .markdown-body p {
    margin: 0 0 14px;
}

.rf-markdown .markdown-body ul,
.rf-markdown .markdown-body ol,
.rf-markdown-body .markdown-body ul,
.rf-markdown-body .markdown-body ol {
    margin: 0 0 14px;
    padding-left: 24px;
}

.rf-markdown .markdown-body li,
.rf-markdown-body .markdown-body li {
    margin: 3px 0;
}

/* 列表里的段落别再叠一层下边距（Markdown 允许项内写多段） */
.rf-markdown .markdown-body li > p,
.rf-markdown-body .markdown-body li > p {
    margin-bottom: 6px;
}

.rf-markdown .markdown-body blockquote,
.rf-markdown-body .markdown-body blockquote {
    margin: 16px 0;
    padding: 10px 16px;
    border-left: 3px solid var(--rf-accent-line);
    border-radius: 0 var(--rf-r-xs) var(--rf-r-xs) 0;
    background: var(--rf-surface-2);
    color: var(--rf-muted);
}

.rf-markdown .markdown-body blockquote > :last-child,
.rf-markdown-body .markdown-body blockquote > :last-child {
    margin-bottom: 0;
}

.rf-markdown .markdown-body a,
.rf-markdown-body .markdown-body a {
    color: var(--tblr-primary);
}

/* 表格：宽了要能横向滚（GFM 表格列多起来必然溢出，硬撑会把整页顶宽） */
.rf-markdown .markdown-body table,
.rf-markdown-body .markdown-body table {
    display: block;
    width: max-content;
    max-width: 100%;
    overflow-x: auto;
    margin: 16px 0;
    border-collapse: collapse;
    font-size: var(--rf-fs-sm);
}

.rf-markdown .markdown-body th,
.rf-markdown .markdown-body td,
.rf-markdown-body .markdown-body th,
.rf-markdown-body .markdown-body td {
    border: 1px solid var(--rf-line);
    padding: 6px 12px;
    text-align: left;
}

.rf-markdown .markdown-body thead th,
.rf-markdown-body .markdown-body thead th {
    background: var(--rf-surface-2);
    color: var(--rf-ink);
    font-weight: 600;
}

.rf-markdown .markdown-body tbody tr:nth-child(even),
.rf-markdown-body .markdown-body tbody tr:nth-child(even) {
    background: var(--rf-surface-3);
}

.rf-markdown .markdown-body img,
.rf-markdown-body .markdown-body img {
    max-width: 100%;
    border-radius: var(--rf-r-xs);
}

.rf-markdown .markdown-body hr,
.rf-markdown-body .markdown-body hr {
    margin: 24px 0;
}

/* GFM 任务列表：remark-gfm 产出的是 <input type="checkbox" disabled>，默认会顶在
   项目符号外面。按 github-markdown-css 的口径把它拉回行内，并去掉项目符号。 */
.rf-markdown .markdown-body .task-list-item,
.rf-markdown-body .markdown-body .task-list-item {
    list-style: none;
}

.rf-markdown .markdown-body .task-list-item input,
.rf-markdown-body .markdown-body .task-list-item input {
    margin: 0 .35em .25em -1.4em;
    vertical-align: middle;
}

.rf-markdown .markdown-body .contains-task-list,
.rf-markdown-body .markdown-body .contains-task-list {
    padding-left: 26px;
}

/* ── 上传失败的提示条（组件模板里的 .rf-markdown-msg）────────────────────────
   为什么要有它：ByteMD 不接管 uploadImages 的 rejection，失败是静默的 ——
   这一条是「失败必须可见」的落点，不是可选的装饰。 */
.rf-markdown-msg {
    padding: 8px 16px;
    border-top: 1px solid var(--rf-danger-line);
    background: var(--rf-danger-bg);
    color: var(--rf-danger-ink);
    font-size: var(--rf-fs-sm);
}

/* 外壳还没增强时（textarea 在原位）提示条不该凭空出现 —— 它只服务增强态 */
.rf-markdown:not([data-ui-markdown-ready]) .rf-markdown-msg {
    display: none;
}

/* ==========================================================================
   文件上传（x-ui.upload 的自研方块网格）
   --------------------------------------------------------------------------
   与 x-ui.cropper 的显示框同款视觉语言：虚线方块 + 缩略图铺满 + hover 蒙层里的
   删除角标。零第三方样式依赖（FilePond 已移除），全部样式都在这里。
   multiple 另有左上角拖动排序手柄（SortableJS 的 handle）。

   可见性（两块都由脚本接管）：
     .rf-upload         两块都不显示 —— 网格要 rageframe.js 现造；原生框只是它
                        触发系统文件选择器的把手（不挂 name、不参与提交），
                        显示出来会是个「选了也不提交」的假控件
     .rf-upload-ready   rageframe.js 初始化成功后挂上 → 网格上场

   ⚠️ 根元素按**终态高度**占位（--rf-upload-h）：网格要等 rageframe.js 现造，
      不预留就是「0 → 128px」的一下撑开，下方内容整体下移（CLS）。
   ⚠️ 方块尺寸走 --rf-upload-w/h（服务端按 ratio 算好，同 cropper 的
      --rf-cropper-w/h）—— 高度一致、宽度随比例变，表单里几行控件对得齐。
   ⚠️ 显隐一律用类选择器，别在方块**内部子元素**上裸写 display —— 裸 display
      会盖过 [hidden] 的 UA 规则（理由见文件头注释）。
   ========================================================================== */

.rf-upload {
    min-height: var(--rf-upload-h, 128px);
}

.rf-upload [data-upload-grid],
.rf-upload [data-upload-native] {
    display: none;
}

/* rageframe.js 就绪：网格上场 */
.rf-upload.rf-upload-ready [data-upload-grid] {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

/* ── 方块：与「＋添加」格同尺寸 ──────────────────────────────────────────── */
.rf-upload-tile {
    position: relative;
    width: var(--rf-upload-w, 128px);
    height: var(--rf-upload-h, 128px);
    overflow: hidden;
    border: var(--tblr-border-width, 1px) solid var(--tblr-border-color, #dadfe5);
    border-radius: var(--rf-r-sm);
    background: var(--tblr-bg-surface-secondary, #f6f8fb);
}

/* 图片方块：铺满裁切，点击 = 放大（lightbox） */
.rf-upload-tile-media {
    display: block;
    width: 100%;
    height: 100%;
    cursor: zoom-in;
}

.rf-upload-tile-media img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* ── 预览媒体三态：加载中 / 已就绪 / 不可用 ───────────────────────────────
   谁在用：任何「地址来自数据、可能不可达」的预览媒体 —— 上传方块的缩略图、
   cropper 的常态回显、lightbox 的放大图。三处共用 rageframe.js 的
   buildMediaState() 造的这一块遮罩，样式只有这一份。

   状态怎么走（rageframe.js 的 guardMediaSrc / lightbox 各自驱动）：
     加载中   .rf-media-state              转圈；**延后 200ms 才出现**（快图不闪骨架）
     不可用   .rf-media-state.is-broken    换成「图片 / 视频 / 音频不可用」文案
     就绪     —— 遮罩直接撤掉，什么也不留（快图全程没有过渡层）

   ⚠️ 遮罩是**真实节点**而不是容器的伪元素：图片 / 视频是替换元素，伪元素在它们
      身上根本不生成，遮罩只能画在容器上；而容器各不相同（上传方块是 <a>、
      cropper 是 <button>、lightbox 是舞台 <div>），走伪元素就得写三套、还要跟
      各容器既有的 display 抢优先级（理由见文件头注释）。一个节点，三处共用。
   ⚠️ pointer-events:none —— 遮罩压在方块上，但「更换 / 移除 / 点开改图」都得点得穿。
   ⚠️ 显隐用 :not([hidden])：裸写 display 会盖过 [hidden] 的 UA 规则（同上）。 */
.rf-media-state {
    position: absolute;
    inset: 0;
    /* ⚠️ 0 而不是 1：遮罩只压「媒体本体」，不许压住各容器自己的交互浮层 ——
       cropper 的 hover 蒙层（更换 / 移除）是无层级的定位元素，z-index 给大了
       就会盖在「✓更换 / ✕移除」上面，图坏的时候按钮直接点不着。 */
    z-index: var(--rf-z-under);
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 6px;
    background: var(--rf-media-state-bg, var(--tblr-bg-surface-secondary, #f6f8fb));
    color: var(--tblr-secondary, #616776);
    font-size: 12px;
    text-align: center;
    pointer-events: none;
}

.rf-media-state:not([hidden]) {
    display: flex;
}

.rf-media-state-spin {
    font-size: 20px;
    line-height: 1;
    animation: rf-upload-spin .9s linear infinite;
}

/* 文案只在「不可用」态出场：加载中就写着「图片不可用」等于提前宣判 */
.rf-media-state-text {
    display: none;
}

.rf-media-state.is-broken .rf-media-state-spin {
    display: none;
}

.rf-media-state.is-broken .rf-media-state-text {
    display: inline;
}

/* 不可用的媒体本体退场（破图图标 / alt 文字都别露），位置留给遮罩。
   注：与「各容器自己的 img 规则」同特异性时比源码顺序，所以 cropper 段里
   为它的 <img> 另补了一条（见 .rf-cropper-thumb img.is-media-broken）。 */
img.is-media-broken {
    display: none;
}

/* 地址不可用的方块：没有能点开的东西了，光标别装作有（href 已由 JS 摘掉） */
.rf-upload-tile-media.is-media-broken {
    cursor: default;
}

/* 视频 / 音频方块：同为「点开播放」的媒体格，光标不是放大镜 */
.rf-upload-tile-media.rf-upload-tile-video,
.rf-upload-tile-media.rf-upload-tile-audio {
    cursor: pointer;
}

/* 视频：<video preload="metadata"> 用首帧当封面；指针事件让给外层 <a> */
.rf-upload-tile-media video {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    pointer-events: none;
}

/* 播放角标：居中圆底三角，一眼看出「能点开播」 */
.rf-upload-tile-play {
    position: absolute;
    top: 50%;
    left: 50%;
    z-index: var(--rf-z-base);
    display: flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    margin: -17px 0 0 -17px;
    border-radius: 50%;
    background: rgba(15, 23, 42, .62);
    color: var(--rf-on-strong);
    font-size: 18px;
    pointer-events: none;
}

/* 还没等到画面（加载中）/ 压根没有画面（不可用）时不摆播放角标：
   那会儿点开也没有东西可播，角标只会让人以为坏了 */
.rf-upload-tile-media.is-media-loading ~ .rf-upload-tile-play,
.rf-upload-tile-media.is-media-broken ~ .rf-upload-tile-play {
    display: none;
}

/* 音频：没有画面，音符图标 + 后缀名 */
.rf-upload-tile-media.rf-upload-tile-audio {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 4px;
    padding: 8px;
    color: var(--tblr-secondary, #616776);
    text-align: center;
}

.rf-upload-tile-media.rf-upload-tile-audio i {
    font-size: 30px;
    line-height: 1;
}

/* 非图片方块：后缀名大字 + 文件名小字（不支持预览） */
.rf-upload-tile-file {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 4px;
    width: 100%;
    height: 100%;
    padding: 8px;
    overflow: hidden;
    color: var(--tblr-secondary, #616776);
    text-align: center;
}

.rf-upload-tile-ext {
    font-size: 22px;
    font-weight: 600;
    line-height: 1;
    letter-spacing: .02em;
    color: var(--tblr-body-color, #182433);
}

.rf-upload-tile-name {
    max-width: 100%;
    overflow: hidden;
    font-size: 11px;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ── 上传中：半透明深底 + 进度指示（层在蒙层之下，途中仍能点 ✕ 撤销） ─────
   三态由 rageframe.js 挂 data-stage 切换：
     queued      排队等前一个传完 → 只转圈（不报数，还没开始传）
     uploading   正在传 → 「42%」+ 底部进度条按比例填
     processing  字节发完、等服务端回包 → 「处理中」+ 满格呼吸
   上传成功/失败后方块 pending 归位，整层（含进度条）随重渲染消失。
   ⚠️ 填充宽度由 JS 原地改（paintProgress），不走重渲染 —— 不重建节点。
   ⚠️ 显隐用类/属性选择器，别在子元素上裸写 display（理由同文件头注释）。 */
.rf-upload-tile-pending {
    position: absolute;
    inset: 0;
    z-index: var(--rf-z-base);
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    color: var(--rf-on-strong);
    background: rgba(15, 23, 42, .45);
    pointer-events: none;
}

.rf-upload-tile-spin {
    font-size: 22px;
    animation: rf-upload-spin .9s linear infinite;
}

.rf-upload-tile-pct {
    font-size: 13px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

/* 底部进度条：轨道铺满 + 填充（宽度 = 百分比） */
.rf-upload-tile-bar {
    position: absolute;
    right: 0;
    bottom: 0;
    left: 0;
    height: 4px;
    background: rgba(255, 255, 255, .28);
}

.rf-upload-tile-bar-fill {
    display: block;
    width: 0;
    height: 100%;
    background: #fff;
    transition: width var(--rf-dur, .15s) linear;
}

/* 排队中：只转圈不报数；真在传 / 处理中才显示百分比 */
.rf-upload-tile-pending[data-stage="queued"] .rf-upload-tile-pct {
    display: none;
}

.rf-upload-tile-pending:not([data-stage="queued"]) .rf-upload-tile-spin {
    display: none;
}

/* 处理中：进度已满格但回包没到，呼吸一下表示「还没完」 */
.rf-upload-tile-pending[data-stage="processing"] .rf-upload-tile-bar-fill {
    animation: rf-upload-bar-breathe 1s ease-in-out infinite;
}

@keyframes rf-upload-bar-breathe {
    50% { opacity: .45; }
}

@keyframes rf-upload-spin {
    to { transform: rotate(360deg); }
}

/* ── hover 蒙层：中央「更换」（仅单文件）+ 右上角 ✕ 删除 ──────────────────
   ⚠️ 蒙层整体 pointer-events:none —— 只有里面的按钮单独恢复 auto，这样蒙层
      不会挡住方块本体的「点击放大」。
   ⚠️ hover 判定挂 .rf-upload-tile：鼠标移到 ✕ 上时方块本体已不是 :hover，
      挂本体上蒙层会闪掉、按钮点不着（cropper 同款处理）。
   ⚠️ 键盘可达用 :has(:focus-visible) 而不是 :focus-within —— 后者鼠标点一下
      就常驻（cropper 试过）。单独成条规则：浏览器不认 :has() 时丢的只是这条。 */
.rf-upload-tile-mask {
    position: absolute;
    inset: 0;
    z-index: var(--rf-z-raised);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 6px;
    font-size: 12px;
    color: var(--rf-on-strong);
    background: rgba(15, 23, 42, .45);
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--rf-dur, .15s) var(--rf-ease-curve-out, ease-in-out);
}

.rf-upload-tile:hover .rf-upload-tile-mask,
.rf-upload-tile:has(:focus-visible) .rf-upload-tile-mask {
    opacity: 1;
}

/* 中央「更换」（单文件专属）：点它 = 重新选一张 */
.rf-upload-tile-change {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 4px 8px;
    border: 0;
    border-radius: 999px;
    background: rgba(15, 23, 42, .55);
    color: var(--rf-on-strong);
    font-size: 12px;
    line-height: 1;
    cursor: pointer;
    pointer-events: auto;
    transition: background var(--rf-dur, .15s) var(--rf-ease-curve-out, ease-in-out);
}

.rf-upload-tile-change:hover,
.rf-upload-tile-change:focus-visible {
    background: rgba(15, 23, 42, .8);
    outline: none;
}

/* 右上角 ✕：24px 圆形热区（图标本身很小，热区保证够点）。默认给一层比蒙层
   更深的实底 —— 蒙层 rgba(15,23,42,.45) 叠在浅色缩略图上会被冲淡，纯透明
   ✕ 看不出是按钮；hover / 键盘聚焦再换危险色。 */
.rf-upload-tile-remove {
    position: absolute;
    top: 4px;
    right: 4px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: rgba(15, 23, 42, .78);
    color: var(--rf-on-strong);
    font-size: var(--rf-icon-size-sm);   /* 小控件档：24px 圆钮里的 ✕ */
    line-height: 1;
    cursor: pointer;
    pointer-events: auto;
    transition: background var(--rf-dur, .15s) var(--rf-ease-curve-out, ease-in-out),
                color var(--rf-dur, .15s) var(--rf-ease-curve-out, ease-in-out);
}

.rf-upload-tile-remove:hover,
.rf-upload-tile-remove:focus-visible {
    background: var(--tblr-danger, #d63939);
    color: var(--rf-on-strong);
    outline: none;
}

/* ── 左上角拖动排序手柄（multiple 且方块 ≥2 时 rageframe.js 才渲染） ───────────
   与右上角 ✕ 镜像对称：同尺寸、同圆底。
   ⚠️ 常驻可见（不是藏在蒙层里）—— 藏起来就等于「没人知道能拖」，而且
      蒙层一出现还得再挪一次鼠标去够它，很难点。所以给一层**半透明常驻底**，
      hover 到方块上再加深。
   ⚠️ z-index 3 在蒙层（2）之上，否则 hover 时被蒙层盖住、鼠标按不到它。
   ⚠️ 拖拽只能从它开始（Sortable 的 handle 选择器）—— 方块本体留给「点击放大」。
   ⚠️ touch-action:none 挡掉触屏上的页面滚动，否则手指一拖滚的是页面。 */
.rf-upload-tile-drag {
    position: absolute;
    top: 4px;
    left: 4px;
    z-index: var(--rf-z-front);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    border-radius: 50%;
    background: rgba(15, 23, 42, .42);
    color: var(--rf-on-strong);
    font-size: var(--rf-icon-size-sm);   /* 小控件档：与右上角 ✕ 同尺寸 */
    line-height: 1;
    cursor: grab;
    touch-action: none;
    transition: background var(--rf-dur, .15s) var(--rf-ease-curve-out, ease-in-out);
}

.rf-upload-tile:hover .rf-upload-tile-drag,
.rf-upload-tile-drag:hover {
    background: rgba(15, 23, 42, .78);
}

.rf-upload-tile-drag:active {
    cursor: grabbing;
}

/* 图标不参与命中测试：12px 的小图标没盖满 24px 圆，点偏一点事件就落到下面的
   <img> 上 —— 而 Sortable 默认 ignore: 'a, img'，落在图片上拖拽直接不响应。
   图标让位后，整个圆内都是手柄本体的命中区。 */
.rf-upload-tile-drag .rfi {
    pointer-events: none;
}

/* ── Sortable 拖动态 ──────────────────────────────────────────────────────
   ghost   = 原位占位（拖走的那格留下的虚位）
   chosen  = 被抓住的方块；dragging = 跟随指针的漂浮副本（触屏走 fallback 才有） */
.rf-upload-tile-ghost {
    border-style: dashed;
    background: var(--tblr-bg-surface-secondary, #f6f8fb);
    opacity: .5;
}

.rf-upload-tile-ghost > * {
    visibility: hidden;
}

.rf-upload-tile-chosen {
    cursor: grabbing;
}

.rf-upload-tile-dragging {
    opacity: .95;
    /* 拖动中的格子：需要「被拎起来很高」的重影，与 --rf-shadow-pop 同色相（中性灰）
       但更大更深。原先是 rgba(15, 23, 42)（slate 蓝调），已随阴影色相一起归位。 */
    box-shadow: 0 12px 24px rgba(29, 33, 41, .28);
}

/* ── 「＋添加」格：与方块同尺寸的虚线格（满额时 rageframe.js 根本不渲染它） ── */
.rf-upload-add {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 4px;
    width: var(--rf-upload-w, 128px);
    height: var(--rf-upload-h, 128px);
    padding: 0;
    border: var(--tblr-border-width, 1px) dashed var(--tblr-border-color, #dadfe5);
    border-radius: var(--rf-r-sm);
    background: var(--tblr-bg-surface-secondary, #f6f8fb);
    color: var(--tblr-secondary, #616776);
    font-size: 12px;
    cursor: pointer;
    transition: border-color var(--rf-dur, .15s) var(--rf-ease-curve-out, ease-in-out),
                color var(--rf-dur, .15s) var(--rf-ease-curve-out, ease-in-out);
}

.rf-upload-add:hover,
.rf-upload-add:focus-visible {
    border-color: var(--tblr-primary, #165DFF);
    color: var(--tblr-primary, #165DFF);
}

.rf-upload-add .rfi {
    font-size: 22px;
    line-height: 1;
}

/* ── 错误态：is-invalid 挂在原生 input 上，ready 后它已隐藏，红框落到网格 ── */
.rf-upload.rf-upload-invalid .rf-upload-tile,
.rf-upload.rf-upload-invalid .rf-upload-add {
    border-color: var(--rf-danger, #f53f3f);
}

/* ── 深色模式 ───────────────────────────────────────────────────────────── */
[data-bs-theme="dark"] .rf-upload-tile,
[data-bs-theme="dark"] .rf-upload-add {
    background: var(--tblr-bg-surface-secondary, #282b39);
    border-color: var(--tblr-border-color, #34374a);
}

[data-bs-theme="dark"] .rf-upload-tile-ext {
    color: var(--tblr-body-color, #e9ebf2);
}

/* ── 尾格的 hover 蒙层：两条来路（选择文件 / 立即上传）────────────────────────
   尾格平时就是「＋添加图片」那一格。鼠标放上去（或键盘聚焦 / 触屏点一下钉住）
   才把两条路摊开 —— 上「选择文件」（从文件库挑已传过的），下「立即上传」（选本地
   文件传上去）。合并成一个格子的理由见 rageframe.js 的 buildAdd。

   ⚠️ 外层 .rf-upload-add 在「有文件库入口」时是 <div>（要能装下两个真按钮 ——
      <button> 里嵌 <button> 非法），退化路径下仍是 <button>，样式共用上面那节。
   ⚠️ 蒙层整体 pointer-events:none，只有里面的按钮恢复 auto —— 否则蒙层把静态面
      挡死，「点一下把蒙层钉住」就成了「点不动」（与 .rf-upload-tile-mask 同理）。
   ⚠️ hover 判定挂**容器**，不挂蒙层自己：鼠标移到里面按钮上时蒙层不再是 :hover，
      挂蒙层会闪掉（与 .rf-upload-tile-mask 同理）。 */
.rf-upload-add-split {
    position: relative;
    overflow: hidden; /* 蒙层方角裁成格的圆角（与 .rf-upload-tile 一致） */
}

.rf-upload-add-face {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
}

.rf-upload-add-mask {
    position: absolute;
    inset: 0;
    z-index: var(--rf-z-raised);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 6px;
    background: rgba(15, 23, 42, .45);
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--rf-dur, .15s) var(--rf-ease-curve-out, ease-in-out);
}

.rf-upload-add:hover .rf-upload-add-mask,
.rf-upload-add.is-open .rf-upload-add-mask {
    opacity: 1;
}

/* 键盘可达单独成条：浏览器不认 :has() 时丢的只是这条，hover / 点开照常。
   （用 :has(:focus-visible) 而不是 :focus-within —— 后者鼠标点一下就常驻） */
.rf-upload-add:has(:focus-visible) .rf-upload-add-mask {
    opacity: 1;
}

/* 蒙层里给「图/文件从哪来」的那几条路：胶囊按钮，形态与方块蒙层的「更换」
   一致（同一处视觉语言）。x-ui.upload 尾格（选择文件 / 立即上传）与
   x-ui.cropper 显示框蒙层（选择图片 / 上传图片）共用这一条 —— 刻意写成并列
   选择器，改一处两边一起变，不会各自漂移。 */
.rf-upload-act,
.rf-cropper-mask-act {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 4px 8px;
    border: 0;
    border-radius: 999px;
    background: rgba(15, 23, 42, .55);
    color: var(--rf-on-strong);
    font-size: 12px;
    line-height: 1;
    white-space: nowrap;
    cursor: pointer;
    pointer-events: auto;
    transition: background var(--rf-dur, .15s) var(--rf-ease-curve-out, ease-in-out);
}

.rf-upload-act:hover,
.rf-upload-act:focus-visible,
.rf-cropper-mask-act:hover,
.rf-cropper-mask-act:focus-visible {
    background: rgba(15, 23, 42, .8);
    outline: none;
}

/* ⚠️ 必须盖掉 .rf-upload-add 里那颗图标的 22px（那是给静态面那个大加号定的）——
   不盖的话胶囊里的图标顶出格子。写在 .rf-upload-add 那节之后靠顺序取胜。 */
.rf-upload-act .rfi,
.rf-cropper-mask-act .rfi {
    font-size: var(--rf-icon-size-sm);   /* 小控件档：胶囊里与 12px 文字同排 */
}

/* ── 文件库选择器弹层 ─────────────────────────────────────────────────────
   由 rageframe.js 动态创建（页面上没有静态标记），所以样式只能挂类名 —— 类名是
   契约，改这里要同时改 rageframe.js 里的 ensurePicker()。 */

.rf-pick-filter {
    display: flex;
    gap: 8px;
    margin-bottom: 12px;
}

.rf-pick-filter .rf-pick-cate { max-width: 220px; }

.rf-pick-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(112px, 1fr));
    gap: 8px;
    min-height: 160px;
}

.rf-pick-tile {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 6px;
    border: 1px solid var(--tblr-border-color, #dadfe5);
    border-radius: var(--rf-r-sm);
    background: var(--tblr-bg-surface, #fff);
    text-align: start;
    cursor: pointer;
    transition: border-color var(--rf-dur-fast, .12s) var(--rf-ease-curve-out, ease-in-out),
                box-shadow var(--rf-dur-fast, .12s) var(--rf-ease-curve-out, ease-in-out);
}

@media (hover: hover) {
    .rf-pick-tile:hover {
        border-color: var(--tblr-primary, #165DFF);
    }
}

.rf-pick-tile.is-picked {
    border-color: var(--tblr-primary, #165DFF);
    box-shadow: 0 0 0 2px color-mix(in oklab, var(--tblr-primary, #165DFF) 25%, transparent);
}

/* 已经在当前控件列表里的项：置灰 + 打勾，且 disabled（不可重复选） */
.rf-pick-tile.is-added {
    opacity: .55;
    cursor: default;
}

.rf-pick-media {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    height: 72px;
    border-radius: calc(var(--rf-r-sm) - 2px);
    background: var(--tblr-bg-surface-secondary, #f6f8fb);
    color: var(--tblr-secondary, #616776);
    font-size: 24px;
    overflow: hidden;
}

.rf-pick-media img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.rf-pick-flag {
    position: absolute;
    right: 4px;
    top: 4px;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    background: var(--tblr-primary, #165DFF);
    color: var(--rf-on-strong);
    font-size: 12px;
}

.rf-pick-name {
    font-size: 12px;
    line-height: 1.3;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.rf-pick-meta {
    font-size: 11px;
    color: var(--tblr-secondary, #616776);
}

.rf-pick-foot {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding-top: 8px;
}

.rf-pick-loading {
    grid-column: 1 / -1;
    padding: 32px 0;
    text-align: center;
    color: var(--tblr-secondary, #616776);
    font-size: 13px;
}

[data-bs-theme="dark"] .rf-pick-tile {
    background: var(--tblr-bg-surface, #20222e);
}

[data-bs-theme="dark"] .rf-pick-media {
    background: var(--tblr-bg-surface-secondary, #282b39);
}

/* ==========================================================================
   Cropper.js（x-ui.cropper）—— 常态显示框 + 弹窗裁剪
   --------------------------------------------------------------------------
   官方 css 只管裁剪框本身，这里补：常态显示框 / 可见性切换 / 弹窗画布 /
   深色模式，外加把裁剪框主色从官方蓝换成主题主色。裁剪框内部几何
   （遮罩、手柄位置）一律不动，改多了升级会碎。
   ========================================================================== */

/* 可见性切换：两块都默认不显示 —— 显示框的形态整个由脚本渲染，原生 file input
   只是脚本触发系统文件选择器的把手（不挂 name、不参与提交）。
   rageframe.js 初始化成功后给根挂 .rf-cropper-ready，显示框上场。
   ⚠️ 不要在显示框内部子元素上裸写 display —— 它们靠 [hidden] 切换

   ⚠️ 根元素按**终态高度**占位（--rf-cropper-h，即显示框高度）：显示框要等
      rageframe.js 渲染，不预留就是「0 → 128px」的一下撑开，下方内容整体下移。
      只保底主要高度 —— 下方那行比例提示（约 20px）的微差不值得追。 */
.rf-cropper {
    min-height: var(--rf-cropper-h, 128px);
}

.rf-cropper [data-cropper-box],
.rf-cropper [data-cropper-native] {
    display: none;
}

.rf-cropper.rf-cropper-ready [data-cropper-box] {
    display: block;
}

/* 显示框舞台：定尺寸 + 承载 hover 蒙层
   （蒙层里要放「移除」按钮，而 <button> 不能嵌套 <button>，所以蒙层在显示框外面） */
.rf-cropper-stage {
    position: relative;
    width: var(--rf-cropper-w, 128px);
    height: var(--rf-cropper-h, 128px);
}

/* 常态显示框：空态虚线占位 / 有图回显，点击 = 选图 */
.rf-cropper-thumb {
    position: relative;
    display: block;
    width: 100%;
    height: 100%;
    padding: 0;
    overflow: hidden;
    border: var(--tblr-border-width, 1px) dashed var(--tblr-border-color, #dadfe5);
    border-radius: var(--rf-r-sm);
    background: var(--tblr-bg-surface-secondary, #f6f8fb);
    cursor: pointer;
    transition: border-color var(--rf-dur, .15s) var(--rf-ease-curve-out, ease-in-out);
}

.rf-cropper-thumb:hover,
.rf-cropper-thumb:focus-visible {
    border-color: var(--tblr-primary, #165DFF);
}

.rf-cropper-thumb img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* ⚠️ 上面的 display:block 会盖过 [hidden] 的 UA 规则，必须补回 */
.rf-cropper-thumb img[hidden] {
    display: none;
}

/* 同款补回：地址不可用（rageframe.js 摘掉了 src）时，本条要压过上面的 display:block
   —— 通用那条 img.is-media-broken 排在文件更前面，同特异性会被它盖掉 */
.rf-cropper-thumb img.is-media-broken {
    display: none;
}

/* 空态占位 */
.rf-cropper-thumb-empty:not([hidden]) {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 4px;
    font-size: 12px;
    color: var(--tblr-secondary, #616776);
    transition: opacity var(--rf-dur-fast, .12s) var(--rf-ease-curve-out, ease-in-out);
}

.rf-cropper-thumb-empty .rfi {
    font-size: 22px;
    line-height: 1;
}

/* hover 蒙层：「更换」在上、「移除」在下（整块在 <button> 外面，见视图注释）
   ⚠️ 蒙层整体 pointer-events:none —— 「更换」那块点击才穿得回显示框；
      只有「移除」按钮单独恢复 auto。
   ⚠️ hover 判定挂 .rf-cropper-stage 而不是显示框：鼠标移到「移除」上时显示框
      已经不是 :hover 了，挂在它身上蒙层会闪掉、按钮点不着。 */
.rf-cropper-thumb-mask {
    position: absolute;
    inset: 0;
    /* 压过显示框里的 .rf-media-state（加载中 / 不可用遮罩，--rf-z-under）：
       图坏掉时「更换 / 移除」照样得能点 */
    z-index: var(--rf-z-base);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 6px;
    font-size: 12px;
    color: var(--rf-on-strong);
    background: rgba(15, 23, 42, .45);
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--rf-dur, .15s) var(--rf-ease-curve-out, ease-in-out);
}

.rf-cropper-stage:hover .rf-cropper-thumb-mask {
    opacity: 1;
}

/* 键盘可达：Tab 到显示框 / 「移除」时也要能看见（:focus-visible 只认键盘，
   鼠标点击不触发 —— 用 :focus-within 的话点一下蒙层就常驻了，试过）。
   单独成条规则：万一浏览器不认 :has()，丢的只是这一条，hover 不受影响。 */
.rf-cropper-stage:has(:focus-visible) .rf-cropper-thumb-mask {
    opacity: 1;
}

.rf-cropper-mask-hint {
    display: inline-flex;
    align-items: center;
    gap: 4px;
}

/* 「图从哪来」的两条路（配了 server 才有）：**纵向**堆叠 —— 显示框最窄可能只有
   72px（9:16），并排两个胶囊会挤爆；纵向与 x-ui.upload 尾格的分叉蒙层也是同一个
   样子。胶囊本身用 .rf-upload-act 那一条规则（同一处视觉语言）。 */
.rf-cropper-mask-acts {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 5px;
    max-width: 100%;
}

/* 触屏没有 hover，蒙层里那两条路够不着：点一下显示框把它**钉住**
   （类由 rageframe.js 挂，同 x-ui.upload 尾格 .is-open 的手感） */
.rf-cropper-thumb-mask.is-open {
    opacity: 1;
}

/* 空态（虚线框 + 「上传图片」）被 hover 时把提示淡出 —— 不淡的话蒙里那两个胶囊
   正好压在「上传图片」那行字上，两层文字糊成一片。
   ⚠️ 只在**有胶囊**（= 有文件库入口）时淡出：没入口时蒙层只有「更换」一行，
      把空态提示淡掉就只剩一层黑底，反而像坏了。 */
.rf-cropper-stage:has(.rf-cropper-mask-acts):hover .rf-cropper-thumb-empty:not([hidden]) {
    opacity: 0;
}

/* 蒙层里的「移除」：钉在图片**右上角**的纯图标角标（只有 ✕，无文字）。
   位置刻意远离中央的「更换」——两者上下堆叠时间距只有几 px，点「更换」
   极易点偏到「移除」误删图片。24px 见方的圆形热区保证够点，图标本身很小。
   默认给一层**比蒙层更深**的实底（蒙层 rgba(15,23,42,.45) 叠在图上会被浅色
   图片冲淡，纯透明 ✕ 看不出是按钮），hover / 键盘聚焦再换成危险色。 */
.rf-cropper-mask-remove {
    position: absolute;
    top: 4px;
    right: 4px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: rgba(15, 23, 42, .78);
    color: var(--rf-on-strong);
    font-size: var(--rf-icon-size-sm);   /* 小控件档：24px 圆钮里的 ✕ */
    line-height: 1;
    cursor: pointer;
    pointer-events: auto;
    transition: background var(--rf-dur, .15s) var(--rf-ease-curve-out, ease-in-out),
                color var(--rf-dur, .15s) var(--rf-ease-curve-out, ease-in-out);
}

.rf-cropper-mask-remove:hover,
.rf-cropper-mask-remove:focus-visible {
    background: var(--tblr-danger, #d63939);
    color: var(--rf-on-strong);
    outline: none;
}

/* ⚠️ 上面的 display:inline-flex 会盖过 [hidden] 的 UA 规则，必须补回 */
.rf-cropper-mask-remove[hidden] {
    display: none;
}

/* 裁剪弹窗里的画布（随视口伸缩，小屏不至于顶出弹窗） */
.modal .rf-cropper-viewport {
    height: min(56vh, 420px);
}

.rf-cropper-viewport {
    background: var(--tblr-bg-surface-secondary, #f6f8fb);
    border-radius: var(--rf-r-sm);
    overflow: hidden;
}

.rf-cropper-viewport img {
    display: block;
    max-width: 100%;
}

/* 裁剪框主色对齐主题（官方是 #39f，与 Tabler 主色不同） */
.rf-cropper-viewport .cropper-view-box {
    outline-color: var(--tblr-primary, #165DFF);
}

.rf-cropper-viewport .cropper-line,
.rf-cropper-viewport .cropper-point {
    background-color: var(--tblr-primary, #165DFF);
}

/* 无 Modal 构造器时的降级：弹窗就地展开成普通块 */
.rf-cropper-modal.rf-cropper-modal-open {
    display: block;
    position: static;
    background: none;
}

/* is-invalid：ready 后原生 input 已隐藏，红框落在显示框上 */
.rf-cropper.rf-cropper-invalid .rf-cropper-thumb {
    border-color: var(--rf-danger, #f53f3f);
}

[data-bs-theme="dark"] .rf-cropper-thumb {
    background: var(--tblr-bg-surface-secondary, #282b39);
    border-color: var(--tblr-border-color, #34374a);
}

[data-bs-theme="dark"] .modal .rf-cropper-viewport {
    background: var(--tblr-bg-surface-secondary, #282b39);
}

/* ==========================================================================
   lightbox（x-ui.image 的图片预览放大）
   --------------------------------------------------------------------------
   缩略图本体 + 点击后的放大浮层。浮层**恒为深底**（浅色主题下也是深底）：
   看图时深底不干扰对画面本身的判断，也省掉一整套深色适配 ——
   所以这一段里的白字 / 半透明白底是**故意的硬编码**，不跟主题走。

   缩略图则完全走主题令牌（--rf-line / --rf-field-bg…），两套皮自动成立。

   ⚠️ 显隐一律写 :not([hidden])：裸写 display:flex 会盖过 [hidden] 的
      display:none（理由见文件头注释）。浮层只有 [hidden] 与 .is-open 两个状态位。
   ========================================================================== */

/* ---- 缩略图 ---------------------------------------------------------- */

/* 缩略图 = **两层背景**：上层是图片地址（`--rf-image-bg`，组件内联给），
   下层是底图图标（下面这个 data URI）。图一到位就盖住图标，加载中 / 失败则
   露出图标 —— 天然三态，零 JS。图片为什么不用 `<img src>` 见
   App\View\Components\Ui\Image::boxStyle()。 */
.rf-image {
    --rf-image-fallback: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%239aa4b2'%3E%3Cpath d='M6.002 5.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0'/%3E%3Cpath d='M2.002 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V3a2 2 0 0 0-2-2zm12 1a1 1 0 0 1 1 1v6.5l-3.777-1.947a.5.5 0 0 0-.577.093l-3.71 3.71-2.66-1.772a.5.5 0 0 0-.63.062L1.002 12V3a1 1 0 0 1 1-1z'/%3E%3C/svg%3E");

    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--rf-image-w, 40px);
    max-width: 100%;
    overflow: hidden;
    border-radius: var(--rf-r-xs, 4px);
    background-color: var(--rf-field-bg, #fff);
    background-image: var(--rf-image-bg, none), var(--rf-image-fallback);
    background-repeat: no-repeat, no-repeat;
    background-position: center, center;
    background-size: cover, 38%;
    /* 非锁定（没给 height / ratio）时按 1:1 显示 —— 背景图撑不起容器，
       而宽度是已知的，所以高度只能由比例定死；宽高都给定时 aspect-ratio 自动失效 */
    aspect-ratio: var(--rf-image-ratio, 1);
}

/* 锁定宽高（给了 height 或 ratio）：非锁定时 --rf-image-h 不存在。
   注意这与上面的 aspect-ratio 不冲突：宽高都确定时 aspect-ratio 不参与计算 */
.rf-image-boxed {
    height: var(--rf-image-h, 40px);
}

.rf-image-round {
    border-radius: 999px;
}

/* 可点态（有图 + preview）：描边 + 放大光标。
   描边只在 <a> 上 —— 占位框与 preview=false 的纯展示图都不该看着像能点。
   聚焦环用 box-shadow 而不是 outline：表格里 outline 会被相邻单元格压住。 */
a.rf-image {
    border: 1px solid var(--rf-line, #e5e6eb);
    cursor: zoom-in;
    transition: border-color var(--rf-dur, .15s) var(--rf-ease-curve, ease),
                box-shadow var(--rf-dur, .15s) var(--rf-ease-curve, ease);
}

a.rf-image:hover,
a.rf-image:focus-visible {
    outline: none;
    border-color: var(--rf-accent, #165DFF);
    box-shadow: var(--rf-focus-ring-shadow, 0 0 0 3px color-mix(in oklab, var(--tblr-primary) 22%, transparent));
}

/* 占位（src 为空）：虚框 + 图标，不参与点击 */
.rf-image-empty {
    border: 1px dashed var(--rf-line-strong, #dcdee2);
    background: var(--rf-line-soft, #f2f3f5);
    color: var(--rf-faint, #86909c);
    font-size: 16px;
}

/* ---- 浮层 ------------------------------------------------------------ */

body.rf-lightbox-open {
    overflow: hidden;
}

.rf-lightbox {
    position: fixed;
    inset: 0;
    z-index: var(--rf-z-lightbox); /* 刻度最高档：压过 Tabler / Bootstrap 的 modal（1055） */
    display: none;
    flex-direction: column;
    background: rgba(10, 14, 24, .92);
    opacity: 0;
    transition: opacity var(--rf-dur, .15s) var(--rf-ease-curve, ease);
}

.rf-lightbox:not([hidden]) {
    display: flex;
}

.rf-lightbox.is-open {
    opacity: 1;
}

/* 浮层自己可聚焦（tabindex="-1"，打开时收焦点）——焦点环不该画在这一层上 */
.rf-lightbox:focus,
.rf-lightbox:focus-visible {
    outline: none;
}

/* 顶部信息条：计数 + 说明 */
.rf-lightbox-bar {
    display: flex;
    flex: 0 0 auto;
    align-items: center;
    gap: 8px;
    padding: 12px 56px;
    color: rgba(255, 255, 255, .92);
    font-size: var(--rf-fs-sm, 13px);
}

.rf-lightbox-count {
    flex: 0 0 auto;
    padding: 0 8px;
    border-radius: var(--rf-r-pill, 20px);
    background: rgba(255, 255, 255, .14);
    font-variant-numeric: tabular-nums;
}

.rf-lightbox-count[hidden],
.rf-lightbox-caption[hidden],
.rf-lightbox-nav[hidden] {
    display: none;
}

.rf-lightbox-caption {
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

/* 舞台：图片居中，吃满剩余高度 */
.rf-lightbox-stage {
    position: relative;
    z-index: var(--rf-z-base); /* 显式层级：它是定位元素，不写就会按文档序压住左右箭头（见下） */
    display: flex;
    flex: 1 1 auto;
    align-items: center;
    justify-content: center;
    min-height: 0; /* 没有它，flex 子项会被内容撑破、把工具条挤出视口 */
    padding: 0 56px;
    overflow: hidden;
}

.rf-lightbox-image {
    max-width: 100%;
    max-height: 100%;
    cursor: zoom-in;
    user-select: none;
    -webkit-user-select: none;
    transform-origin: center center;
    transition: transform var(--rf-dur-fast, .12s) var(--rf-ease-curve, ease);
}

.rf-lightbox.is-zoomed .rf-lightbox-image {
    cursor: grab;
}

/* 拖拽中关掉过渡与指针事件：否则 transform 有 120ms 延迟，手感跟不上 */
.rf-lightbox-image.is-dragging {
    cursor: grabbing;
    transition: none;
}

/* 视频 / 音频播放器：与图片共用同一个舞台（同一时刻只显示一个，靠 [hidden] 切）
   ⚠️ 这里**故意不写 display** —— 作者样式里的 display 会盖掉 UA 的
   [hidden]{display:none}（同特异性下作者样式优先），写了隐藏就失效。
   video 默认 inline-block，在 flex 舞台里会被 blockify，不用手动设。 */
.rf-lightbox-video {
    max-width: 100%;
    max-height: 100%;
    background: #000;
}

.rf-lightbox-audio {
    width: min(560px, 100%);
}

/* 保险丝：上面那两个 + 缩放按钮组 + 图片，隐藏态一律钉死 */
.rf-lightbox-image[hidden],
.rf-lightbox-video[hidden],
.rf-lightbox-audio[hidden],
.rf-lightbox-zoomers[hidden] {
    display: none;
}

/* 缩放 / 复位按钮组：只服务图片（音视频时整组 hidden） */
.rf-lightbox-zoomers:not([hidden]) {
    display: inline-flex;
    align-items: center;
    gap: 4px;
}

/* 加载中 / 不可用遮罩：与上传方块、cropper 共用 .rf-media-state（见文件前部），
   这里只改配色 —— 浮层**恒为深底**（浅色主题下也是），所以白字 + 深色纱罩是
   故意的硬编码，跟本段其余规则同一套，不跟主题走。 */
.rf-lightbox-stage > .rf-media-state {
    --rf-media-state-bg: rgba(10, 14, 24, .55);
    color: rgba(255, 255, 255, .92);
    gap: 8px;
    font-size: 14px;
}

/* 舞台大、观众远，转圈比方块里的粗一号 */
.rf-lightbox-stage > .rf-media-state .rf-media-state-spin {
    font-size: 28px;
}

/* 左右导航：贴边圆形按钮
   ⚠️ z-index 不能省 —— 它与 .rf-lightbox-stage 同为定位元素，而 stage 在 DOM 里
   排在后面；不给层级就会被 stage 整块盖住，表现为「点击箭头不切换，
   反而把浮层关掉」（点到的其实是作为背景的 stage）。 */
.rf-lightbox-nav {
    position: absolute;
    z-index: var(--rf-z-raised);
    top: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    padding: 0;
    border: 0;
    border-radius: 999px;
    background: rgba(255, 255, 255, .12);
    color: var(--rf-on-strong);
    font-size: 20px;
    line-height: 1;
    cursor: pointer;
    transform: translateY(-50%);
    transition: background var(--rf-dur, .15s) var(--rf-ease-curve, ease);
}

@media (hover: hover) {
    .rf-lightbox-nav:hover {
        background: rgba(255, 255, 255, .26);
    }
}

.rf-lightbox-prev {
    left: 16px;
}

.rf-lightbox-next {
    right: 16px;
}

/* 底部工具条：缩小 / 放大 / 原始尺寸 / 新窗口打开 / 关闭 */
.rf-lightbox-tools {
    display: flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 12px 16px 20px;
}

.rf-lightbox-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    border: 0;
    border-radius: var(--rf-r-sm, 6px);
    background: rgba(255, 255, 255, .12);
    color: var(--rf-on-strong);
    font-size: 18px;
    line-height: 1;
    text-decoration: none;
    cursor: pointer;
    transition: background var(--rf-dur, .15s) var(--rf-ease-curve, ease);
}

.rf-lightbox-btn:hover,
.rf-lightbox-btn:focus-visible {
    background: rgba(255, 255, 255, .26);
    color: var(--rf-on-strong);
    outline: none;
}

/* 手机端：两侧留白与按钮都收一档，图片才剩得下地方 */
@media (max-width: 575.98px) {
    .rf-lightbox-bar {
        padding: 8px 44px;
    }

    .rf-lightbox-stage {
        padding: 0 44px;
    }

    .rf-lightbox-nav {
        width: 36px;
        height: 36px;
        font-size: 16px;
    }

    .rf-lightbox-prev {
        left: 8px;
    }

    .rf-lightbox-next {
        right: 8px;
    }

    .rf-lightbox-btn {
        width: 36px;
        height: 36px;
        font-size: 16px;
    }
}

/* ==========================================================================
   地图选点 / 区域范围（x-ui.map）
   --------------------------------------------------------------------------
   只做「壳与浮层」：地图本体的样式全部由三家 SDK 自己注入，这里一个字都不碰
   （碰了升级必碎）。三段：

     1. 可见性切换 —— .rf-map / .rf-map-ready 两态（同 cropper 的套路）
     2. 我们自己的浮层（搜索 / 工具栏 / 提示 / 行政区输入）
     3. 表单字段区（状态条 / 区域 JSON 折叠块）

   ⚠️ 层级的账要算清：SDK 会把内容塞进 [data-map-canvas]，而我们在 canvas 上
      给了 --rf-z-base —— 于是**它内部再大的 z-index 也被关在这个层叠上下文
      里**（SDK 自己常用 1000+ 的浮标 / 信息窗）。我们的浮层只要在 canvas 之后、
      层级高于它，就一定盖得住，不用跟 SDK 比数字。

   ⚠️ 浮层里的显示切换一律 :not([hidden])：裸写 display:flex/block 会盖过
      [hidden] 的 UA 规则（理由见文件头注释）。

   ⚠️ 底图上的第三方 logo 与版权（右下/左下角）**不动** —— 那是使用条款的
      一部分，不是审美问题。我们的浮层因此全部避开下方两角：提示条放顶部
      居中，状态条挂在画布外面的独立一行。
   ========================================================================== */

/* ---- 1. 可见性切换 ---------------------------------------------------- */

/* 默认（PHP 判定没配 Key，或 JS 没跑起来）：只显示提示块，画布不占位 */
.rf-map [data-map-canvas] {
    display: none;
}

.rf-map.rf-map-ready [data-map-canvas] {
    display: block;
}

.rf-map.rf-map-ready [data-map-notice] {
    display: none;
}

.rf-map-shell {
    position: relative;
    overflow: hidden;
    border: 1px solid var(--rf-line, #e5e6eb);
    border-radius: var(--rf-r, 6px);
    background: var(--rf-line-soft, #f2f3f5);
}

.rf-map-canvas {
    position: relative;
    z-index: var(--rf-z-base); /* 见文件头：给 SDK 内容划一个层叠上下文，别让它的 1000+ 压住我们 */
    width: 100%;
    height: var(--rf-map-h, 360px);
}

.rf-map-canvas-inner {
    width: 100%;
    height: 100%;
}

/* 加载遮罩（首次加载 / 换底图期间）
   ⚠️ 它是有反馈的那一半。换底图的两件重活（拆旧地图、联网拉 SDK）都压在主线程
      上，浏览器在那段时间不重绘；画布又刚被清空 —— 不遮就是一块空白，观感正好
      是"点右上角按钮没反应"。所以 map.js 在**点击的同一个任务里**就把
      .rf-map-busy 挂上，这一帧连同遮罩一起上屏，重活推到下一帧才开始。
   ⚠️ --rf-z-raised = 盖住画布（--rf-z-base），但让工具栏与搜索框
      （--rf-z-overlay）浮在上面 —— 用户得看着自己点的那家按钮转圈，
      遮罩不该把按钮糊住。 */
.rf-map-loading {
    position: absolute;
    inset: 0;
    z-index: var(--rf-z-raised);
    display: none;
    align-items: center;
    justify-content: center;
    background: var(--rf-line-soft, #f2f3f5);
}

.rf-map-busy .rf-map-loading {
    display: flex;
}

.rf-map-loading-spin {
    width: 28px;
    height: 28px;
    border: 2px solid var(--rf-line-strong, #dcdee2);
    border-top-color: var(--rf-accent, #165DFF);
    border-radius: 50%;
    animation: rf-map-spin .7s linear infinite;
}

@keyframes rf-map-spin {
    to { transform: rotate(360deg); }
}

/* 降级提示块（顶在画布位置） */
.rf-map-notice {
    display: flex;
    align-items: center;
    gap: 12px;
    height: var(--rf-map-h, 360px);
    padding: 20px;
    color: var(--rf-muted, #6b7785);
    background: var(--rf-line-soft, #f2f3f5);
}

.rf-map-notice > i {
    flex: none;
    font-size: 28px;
    color: var(--rf-faint, #86909c);
}

.rf-map-notice strong {
    display: block;
    color: var(--rf-ink, #4e5969);
}

/* ---- 2. 浮层 ---------------------------------------------------------- */

.rf-map-search,
.rf-map-toolbar,
.rf-map-adcode,
.rf-map-hint {
    position: absolute;
    z-index: var(--rf-z-overlay);
}

/* 搜索：左上。宽度用 clamp 而不是百分比 —— 窄屏下也得留下工具栏的位置 */
.rf-map-search {
    top: 10px;
    left: 10px;
    width: clamp(180px, 42%, 320px);
}

.rf-map-search .input-group-text {
    background: var(--rf-field-bg, #fff);
    border-color: var(--rf-line-strong, #dcdee2);
}

/* 结果下拉。⚠️ 必须 :not([hidden])，否则和 [hidden] 打架 */
.rf-map-suggest {
    display: none;
    max-height: 260px;
    margin-top: 4px;
    overflow-y: auto;
    padding: 4px;
    border: 1px solid var(--rf-line, #e5e6eb);
    border-radius: var(--rf-r-sm, 6px);
    background: var(--rf-surface, #fff);
    box-shadow: var(--rf-shadow-pop, 0 6px 16px rgba(29, 33, 41, .10));
}

.rf-map-suggest:not([hidden]) {
    display: block;
}

.rf-map-suggest-item {
    display: block;
    width: 100%;
    padding: 6px 8px;
    border: 0;
    border-radius: var(--rf-r-xs, 4px);
    background: transparent;
    color: var(--rf-ink, #4e5969);
    text-align: left;
    line-height: 1.35;
}

.rf-map-suggest-item:hover,
.rf-map-suggest-item:focus-visible {
    background: var(--rf-line-soft, #f2f3f5);
    outline: none;
}

.rf-map-suggest-name {
    display: block;
    font-size: var(--rf-fs-sm, 13px);
}

.rf-map-suggest-addr {
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* 工具栏：右上。默认半透明白底 —— 底图颜色不可控，纯白会显得很突兀 */
.rf-map-toolbar {
    top: 10px;
    right: 10px;
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: 4px;
    max-width: calc(100% - 20px);
}

.rf-map-toolbar .btn {
    background: color-mix(in srgb, var(--rf-surface, #fff) 88%, transparent);
    border-color: var(--rf-line-strong, #dcdee2);
    color: var(--rf-ink, #4e5969);
    backdrop-filter: blur(4px);
}

@media (hover: hover) {
    .rf-map-toolbar .btn:hover {
        background: var(--rf-surface, #fff);
    }
}

.rf-map-toolbar .btn.active {
    background: var(--rf-accent, #165DFF);
    border-color: var(--rf-accent, #165DFF);
    color: var(--rf-on-strong);
}

/* 正在加载的那家：按钮尾部挂个小圈（和遮罩同一个动画）。
   ⚠️ 用 currentColor 描边 —— active 态是白字、普通态是深字，两种都读得清。
   ⚠️ 换底图期间按钮会 disabled（防连点堆请求），Bootstrap 会把它压到 .65 透明，
      这里把 loading 那个提回 1：它承载的是"正在干活"这条信息，不是不可用。 */
.rf-map-toolbar .btn.is-loading {
    opacity: 1;
}

.rf-map-toolbar .btn.is-loading::after {
    content: '';
    display: inline-block;
    width: .6em;
    height: .6em;
    margin-left: .35em;
    vertical-align: -.05em;
    border: 1.5px solid currentColor;
    border-top-color: transparent;
    border-radius: 50%;
    animation: rf-map-spin .7s linear infinite;
}

/* 行政区输入：底部居中（避开两角的第三方 logo，见文件头） */
.rf-map-adcode {
    right: 10px;
    bottom: 10px;
    left: 10px;
    max-width: 420px;
    margin: 0 auto;
}

.rf-map-adcode:not([hidden]) {
    display: block;
}

/* 操作提示：顶部居中。pointer-events:none —— 它是纯提示，不能挡住地图拖动 */
.rf-map-hint {
    top: 10px;
    left: 50%;
    max-width: 60%;
    padding: 4px 10px;
    transform: translateX(-50%);
    border-radius: var(--rf-r-pill, 20px);
    background: color-mix(in srgb, var(--rf-fence, #191a23) 82%, transparent);
    color: var(--rf-on-strong);
    font-size: var(--rf-fs-xs, 12px);
    line-height: 1.5;
    text-align: center;
    pointer-events: none;
}

.rf-map-hint:not([hidden]) {
    display: block;
}

/* ---- 3. 表单字段区 ---------------------------------------------------- */

.rf-map-status {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-top: 8px;
}

.rf-map-chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 8px;
    border: 1px solid var(--rf-line, #e5e6eb);
    border-radius: var(--rf-r-pill, 20px);
    background: var(--rf-line-soft, #f2f3f5);
    color: var(--rf-muted, #6b7785);
    font-size: var(--rf-fs-xs, 12px);
}

.rf-map-chip i {
    color: var(--rf-accent, #165DFF);
}

/* 区域原始数据折叠块：增强态收起（用户不必看 JSON），降级态展开（唯一入口） */
.rf-map-raw > summary {
    color: var(--rf-muted, #6b7785);
    font-size: var(--rf-fs-sm, 13px);
    cursor: pointer;
}

@media (hover: hover) {
    .rf-map-raw > summary:hover {
        color: var(--rf-ink, #4e5969);
    }
}

.rf-map-raw textarea {
    background: var(--rf-code-bg, #f5f7f9);
    color: var(--rf-code-ink, #1d2129);
    font-size: var(--rf-fs-xs, 12px);
}

/* 强制校验错误的字段在 .is-invalid 上由 Tabler 处理，这里只补 textarea 的
   （Bootstrap 默认只管 input/select，textarea 的 is-invalid 是同一套规则，
   但我们的 textarea 在 details 里 —— 折叠时看不见，加个左边框提示） */
.rf-map-raw textarea.is-invalid {
    border-color: var(--rf-danger, #f53f3f);
}

/* ---- 窄屏：工具栏会跟搜索框抢顶部 ------------------------------------- */

@media (max-width: 575.98px) {
    .rf-map-search {
        width: calc(100% - 20px);
    }

    /* 搜索占满一行，工具栏挪到它下面 */
    .rf-map-toolbar {
        top: 52px;
        left: 10px;
        right: 10px;
    }

    .rf-map-hint {
        top: auto;
        bottom: 10px;
        max-width: calc(100% - 20px);
    }
}


/* ==========================================================================
   行编辑（x-ui.multiple-input）—— 可增删的重复行 / 键值对
   --------------------------------------------------------------------------
   结构（视图 resources/views/components/ui/multiple-input.blade.php）：

     .rf-rows               根 —— 内联 --rf-rows-tracks 一条轨道变量
       .rf-rows-head        列标题行（窄屏收起，标题下沉到每一格的 data-title）
       .rf-rows-list        行容器
         .rf-rows-item      一行 = grid（列轨道由变量给）
           .rf-rows-grip    拖动把手（sortable 才有）
           .rf-rows-cell    一格：控件 + 该格的服务端报错
           .rf-rows-actions 删除键那条窄轨道
       .rf-rows-empty       空态
       .rf-rows-foot        「添加一行」 + 计数
       template             克隆源（惰性内容，不参与布局）

   一条响应式门控：
     · max-width:767.98px —— 窄屏：一行一卡、每格自带标题、删除键浮右上角。
   ========================================================================== */

.rf-rows-head,
.rf-rows-item {
    display: grid;
    grid-template-columns: var(--rf-rows-tracks);
    gap: 8px;
    align-items: start;
}

/* ⚠️ 左右 padding 必须为 0：表头与数据行共用同一份轨道变量，但轨道里的
   minmax(0,1fr) 是**按各自的 content-box 宽度**解析的 —— 表头只要左右各缩
   4px，1fr 就会解出更小的像素值，表头文字与下方列盒逐列错开。
   对齐的落点是 grid 本身：要缩进就缩进整个 grid，别只缩表头。 */
.rf-rows-head {
    padding: 0 0 6px;
    font-size: var(--rf-fs-xs);
    color: var(--rf-faint);
}

.rf-rows-list {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

/* 行是「绝对定位的删除键」的定位基准（窄屏用） */
.rf-rows-item {
    position: relative;
}

.rf-rows-grip,
.rf-rows-actions {
    display: flex;
    align-items: center;
    height: var(--rf-ctl-h);
}

.rf-rows-grip {
    justify-content: center;
    color: var(--rf-faint);
    cursor: grab;
}

/* ⚠️ 表头里那个 .rf-rows-grip 只是「占掉第一轨」的对齐占位（没有图标、也不
   参与拖动），**不能跟行里的把手一样被撑到控件高** —— 它一旦撑高，有排序的
   表头就比没排序的高出一大截、文字浮在列盒上方（表现是「标题头脱离文本框」）。
   两种形态的表头必须长得一样，所以这里显式把高度交还给内容。 */
.rf-rows-head .rf-rows-grip {
    height: auto;
}

.rf-rows-grip:active {
    cursor: grabbing;
}

.rf-rows-actions {
    justify-content: flex-end;
}

/* 删除键：与 .rf-copy / .rf-search-clear 同一档的图标键 ——
   透明底、淡灰图标、hover 才转成危险态（一行一个删除键，常亮红色会
   把整张表单喊成一片警报）。尺寸写死，行高不随图标变。 */
.rf-rows-remove {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    padding: 0;
    border: 0;
    border-radius: var(--rf-r-xs);
    background: transparent;
    color: var(--rf-faint);
    font-size: var(--rf-icon-size-sm);   /* 小控件档：24px 方钮里的 ✕ */
    line-height: 1;
    cursor: pointer;
    transition: color var(--rf-ease), background-color var(--rf-ease);
}

.rf-rows-remove:hover:not(:disabled) {
    color: var(--rf-danger);
    background: var(--rf-danger-bg);
}

.rf-rows-remove:disabled {
    opacity: var(--rf-state-disabled-opacity);
    cursor: not-allowed;
}

.rf-rows-remove:focus-visible {
    outline: 2px solid var(--tblr-primary);
    outline-offset: 1px;
}

/* 空态：虚线框（不是实线卡边 —— 它是「没有内容」的占位，不该看起来像个控件） */
.rf-rows-empty {
    padding: 12px 16px;
    border: 1px dashed var(--rf-line);
    border-radius: var(--rf-r);
    color: var(--rf-faint);
    font-size: var(--rf-fs-sm);
    text-align: center;
}

/* ⚠️ 上面给了 display，作者样式会赢过 UA 的 [hidden] —— 不写这条，空态在
   有行以后照样显示（同 .rf-back-float[hidden] 踩过的坑）。 */
.rf-rows-empty[hidden] {
    display: none;
}

.rf-rows-foot {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-top: 12px;
}

.rf-rows-count {
    margin-left: auto;
    font-size: var(--rf-fs-xs);
    color: var(--rf-faint);
    font-variant-numeric: tabular-nums;
}

/* 窄屏：一行一卡，列标题从表头下沉到每一格（.rf-rows-cell 的 data-title） */
@media (max-width: 767.98px) {
    .rf-rows-head {
        display: none;
    }

    .rf-rows-item {
        grid-template-columns: 1fr;
        gap: 10px;
        padding: 12px;
        border: 1px solid var(--rf-line);
        border-radius: var(--rf-r);
        background: var(--rf-surface-2);
    }

    /* 触屏拖拽的把手不好按，窄屏不给 */
    .rf-rows-grip {
        display: none;
    }

    .rf-rows-cell[data-title]:not([data-title=""])::before {
        content: attr(data-title);
        display: block;
        margin-bottom: 4px;
        font-size: var(--rf-fs-xs);
        color: var(--rf-faint);
    }

    .rf-rows-actions {
        position: absolute;
        top: 8px;
        right: 8px;
        height: auto;
    }

    /* 给右上角的删除键让位，长文本不会钻到它下面 */
    .rf-rows-cell {
        padding-right: 28px;
    }
}

/* ==========================================================================
   二维码（x-ui.qr-code）
   --------------------------------------------------------------------------
   图在**服务端**就算完了（组件给的是 data-URI），所以这里没有「组件皮肤」，
   只修一处：<img> 是行内元素，文字基线底下会给它留约 4px 的空隙 —— 贴着
   卡片边放、或者并排两个二维码时，那点空隙看得出来。

   想并排 / 居中就用 Bootstrap 工具类盖它（.d-inline-block / .mx-auto 带
   !important，优先级压得过这条）。

   带说明文字（label）时会多包一层 .rf-qrcode-figure：那层的宽度收缩到码那么宽，
   说明文字才是相对**码**居中，而不是相对整列居中（码是固定宽度、默认靠左，
   两者不在一根轴上就会错位）。没有 label 的调用方不多这层，DOM 与从前一致。
   ========================================================================== */
.rf-qrcode {
    display: block;
}

.rf-qrcode-figure {
    display: inline-block;
    vertical-align: top;
}

/* 字号与颜色借 .form-text（主题里已有），这里只补「与码之间」的基准间距；
   左右与上下内边距由组件的行内样式按参数给。 */
.rf-qrcode-label {
    margin-top: 8px;
}
