/* ===== Talkpod Hub 后台主题变量（单一来源） =====
 * 后台亮白 UI / 品牌色：拓朋红 #E2231A
 * 修改本文件即可全局换肤后台，无需重新编译 Vite（CSS 变量运行时生效）
 * 加载顺序：必须在 @vite 编译产物之后（layouts/admin.blade.php 已配置）
 *
 * 🔴 2026-10-08 第 25 轮：**只有一套皮肤**（国际化 SaaS / 原探索台 skin=c）。
 *    发哥决定"去掉 3 个皮肤，只要国际 SaaS" ⇒ A/B 两套已删，
 *    原来的 `html[data-skin="c"]` 覆盖块取值已合并进下面这个 `:root`。
 *    "换风格"的机制仍在（改这一组变量即可），只是不再提供多皮肤切换器。
 */

/* ---- 本地字体（2026-10-08 第 25 轮补齐） ----
   🔴 为什么必须补：`--admin-font-sans` 第一位就是 Inter，但**后台根本没有加载过 Inter**
      （@font-face 只写在 theme.css，而那文件只被前台 app/doc 布局引用）
      ⇒ 后台的英文实际是「微软雅黑里的西文字形」/「Consolas」在顶上，
        同一页出现两种西文字体（发哥第 5 项："英文字体没有统一"）。
      字体文件在 public/fonts/，与前台共用同一份文件，不新增体积。 */
@font-face { font-family: 'Inter'; font-style: normal; font-weight: 400; font-display: swap; src: url('/fonts/inter-normal-400.woff2') format('woff2'); }
@font-face { font-family: 'Inter'; font-style: normal; font-weight: 500; font-display: swap; src: url('/fonts/inter-normal-500.woff2') format('woff2'); }
@font-face { font-family: 'Inter'; font-style: normal; font-weight: 600; font-display: swap; src: url('/fonts/inter-normal-600.woff2') format('woff2'); }
@font-face { font-family: 'Inter'; font-style: normal; font-weight: 700; font-display: swap; src: url('/fonts/inter-normal-700.woff2') format('woff2'); }

:root {
    --admin-font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Microsoft YaHei', 'Segoe UI', sans-serif;
    /* 🔴 单一皮肤取值（原 skin=c 国际化 SaaS）——发哥 2026-10-08 第 25 轮 */
    --admin-bg: #F8FAFC;            /* 页面底色 */
    --admin-surface: #FFFFFF;       /* 卡片/侧栏白 */
    --admin-border: #E6EAF0;        /* 边框（柔和中性） */
    --admin-text: #0F172A;          /* 主文字 */
    --admin-body-text: #3F4A5A;     /* 导航/次级文字 */
    --admin-muted: #66707F;         /* 弱化文字（第 157 轮：3.64 → 5.01，AA 达标） */
    --admin-subtle: #8A9099;        /* 更弱文字/占位（3.22，仅占位与禁用态） */
    --admin-hover: #F5F8FC;         /* hover 底色 */
    --admin-accent: #E2231A;        /* 品牌色（拓朋红） */
    --admin-accent-soft: rgba(226, 35, 26, 0.07);     /* 选中态底色 */
    --admin-accent-soft-40: rgba(226, 35, 26, 0.4);  /* 强调描边 */
    --admin-accent-soft-50: rgba(226, 35, 26, 0.5);  /* 强调描边（版本徽章） */
    --admin-danger: #DC2626;                         /* 危险操作（删除/清空，不可逆） */
    --admin-danger-soft: rgba(220, 38, 38, 0.1);     /* 危险操作浅底 */
    --admin-ok: #047857;                             /* 正向（授权/发布/启用）—— 第 157 轮 3.77 → 5.48；暗色另由 theme.css 覆盖为 #059669 */
    --admin-warn: #B45309;                           /* 回退/撤销（可逆但需留意） */

    /* ===== 骨架高度（第 154 轮 · 唯一来源） =====
       🔴 为什么要有这两个变量：`.tp-topbar` 曾在文件里声明两次（56 / 60），
          末尾那条 60 生效，而下游的 `top:56px`（标签栏）、`top:94px`（页面内固定表头）
          没跟着改 ⇒ **标签栏上沿被顶栏盖住 4px**（激活标签的顶部红线被切一半）。
          现在高度只在这里定义，下游一律 `calc()`，改一处全线跟走。
          闸门：`scripts/check-layout-metrics.py`（谁再写死 56/94 就红）。 */
    --tp-topbar-h: 60px;                             /* 顶栏 */
    --tp-tagsbar-h: 38px;                            /* 多页签标签栏 */

    /* ===== 间距阶梯（第 156 轮 · 唯一来源） =====
       4 / 8 / 12 / 16 / 24 / 32，六档，4 的倍数。
       🔴 以前模板里内联 padding/margin 写过 2/3/4/5/6/7/8/10/12/14/16/18/20/24/32/96 共 16 种值，
          同一页里 6px 和 8px 混着用，页面就"看着有点不齐但说不出哪里"。
          现在内联一律 `var(--sp-N)`，新写组件也用这六档；闸门 `check-spacing-tokens.py` 守内联。
       🔴 不要在这里加 6px / 10px / 14px：差 2px 的档位不构成层级，只会多出一个"差不多大"的选择。 */
    --sp-1: 4px;                                     /* 紧邻：图标与文字、标签与值 */
    --sp-2: 8px;                                     /* 小间隔：列表项、按钮组 */
    --sp-3: 12px;                                    /* 中：卡片内距、表单字段间距 */
    --sp-4: 16px;                                    /* 大：区块内距 */
    --sp-5: 24px;                                    /* 段落之间 */
    --sp-6: 32px;                                    /* 大区块之间 */

    /* 微圆角（第 158 轮 · 发哥：「刷新按钮加 2px 圆角」）——
       只有 22~32px 的小控件用它（2px 在 26px 高的按钮上刚好"不那么硬"，再多就成胶囊）。 */
    --admin-r-xs: 5px;   /* 第 159 轮：发哥「2px 不明显，改 5px 试试」 */
}

/* ===== 通用按钮（本文件不参与 Vite 构建，新增样式写这里即时生效） =====
 * 🔴 为什么要有这一段：Tailwind 工具类是 **Vite 构建产物** ——
 *    项目里**从未用过**的类（例如 bg-red-600）不会出现在线上 CSS 里，
 *    表现就是「按钮白底白字、完全看不见」（2026-09-23 设备删除弹窗踩过）。
 *    所以：新增的一次性样式一律用这里的自定义类，不要赌 Tailwind 类是否已编译。
 */
.btn-danger {
    background: var(--admin-danger);
    color: #FFFFFF;
}
.btn-danger:hover { filter: brightness(0.92); }
.btn-danger[disabled] { opacity: 0.6; cursor: not-allowed; }


/* ============================================================
   业务申请 · 盖章申请（2026-09-24 新增）
   说明：本文件不参与 Vite 构建，改完即生效；新增一次性样式一律写这里。
   ============================================================ */
/* 🔴 gap 必须为 0：格子要与底图**逐格严格对应**。
   原为 `gap: 3px` → 10 列 × 3px × 12 个间隔 ≈ 底部累计偏移 30px+，
   底图对得再准也没用（2026-10-07 低清底图那轮改掉的根因之一）。 */
.tp-grid { display: grid; gap: 0; }
.tp-grid-cell {
    /* ⚠️ 本段只放**布局**属性。
       边框 / 底色是「每页位置」的视觉规则 → 统一写在文件后部那一节（单一来源），
       避免两处矛盾（历史上正是"原始定义"与"后部覆盖"打架，才出现"线看着粗"）。 */
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 11px;
    line-height: 1;
    color: var(--admin-muted);
    cursor: pointer;
}
.tp-grid-cell:hover { background: var(--admin-accent-soft); }
.tp-grid-cell.is-pos { background: var(--admin-accent); color: #fff; font-weight: 700; }
.tp-grid-cell.is-sign { background: #1565C0; color: #fff; font-weight: 700; }
.tp-grid-cell.is-off { opacity: .35; }


.tp-picks { display: flex; gap: 10px; flex-wrap: wrap; }
.tp-pick {
    border: 2px solid var(--admin-border);
    background: var(--admin-surface, #fff);
    padding: 8px 10px;
    cursor: pointer;
    text-align: center;
    min-width: 116px;
}
.tp-pick:hover { background: var(--admin-hover); }
.tp-pick.is-active { border-color: var(--admin-accent); background: var(--admin-accent-soft); }
.tp-pick img { height: 46px; margin: 0 auto 6px; display: block; }
.tp-pick .tp-pick-name { font-size: 12px; font-weight: 600; }

/* 列表页筛选条（第 102 轮 · 发哥第 5 条）
   ------------------------------------------------------------
   「固件市场的『全部/升级包/扩展应用/静态资源』『最新/最多下载/最多浏览』和技术交流的
     『全部/A37/固件市场』『最新回复/最热/最多浏览』样式完全不一样，先统一成固件市场的样式」
   根因：固件市场用 `.mk-bar`+`.tp-seg`（有外壳的分段按钮），论坛/下载页是各自手搓的
        `border px-3 py-1.5` 链接 + 裸文字按钮 —— **同一类东西 3 处实现**。
   ➡️ 现在只有一套：`.tp-bar`（一行放几组）+ `.tp-seg`（分段按钮）+ `.tp-input`（搜索/下拉）。
      前台**禁止**再手搓 `border border-[var(--border)] px-3 py-1.5` 这种筛选按钮。
   ============================================================ */
.tp-bar {
    margin-top: 20px;
    display: flex;
    align-items: center;
    gap: 12px 16px;
    flex-wrap: wrap;
}
.tp-bar-tools {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
}
/* 筛选条里的输入框 / 下拉与分段按钮**同高**（35px）—— 一行里三条线才对得齐。
   🔴 只在 `.tp-bar` 里放大：后台表格内的 `.tp-seg`（上/中/下）保持原尺寸，
      否则一次统一把整个后台的分段按钮都变胖了（第 102 轮自查）。 */
.tp-bar .tp-input,
.tp-bar-tools .tp-input { height: 35px; }
.tp-bar .tp-seg > button,
.tp-bar .tp-seg > a {
    min-height: 35px;
    padding: 0 13px;
    font-size: 14px;
}
@media (max-width: 767px) {
    /* 窄屏：工具组换到下一行并占满（否则搜索框被挤成一条缝） */
    .tp-bar-tools { margin-left: 0; width: 100%; }
    .tp-bar-tools > .tp-input { width: 100%; }
}

.tp-seg { display: inline-flex; border: 1px solid var(--admin-border); position: relative; }
.tp-seg > button,
.tp-seg > a,
.tp-seg > label {
    display: inline-flex; align-items: center; gap: 5px;
    padding: 5px 12px;
    font-size: 12px;
    background: var(--admin-surface, #fff);
    border-right: 1px solid var(--admin-border);
    color: var(--admin-body-text);
    text-decoration: none;
    cursor: pointer;
}
/* 🆕 第 166 轮：筛选条改用 radio + wire:model.live（不走 $set 魔术动作），
   radio 视觉隐藏、由 label 承担 chip 外观。 */
.tp-seg-radio {
    position: absolute;
    width: 1px; height: 1px;
    opacity: 0;
    pointer-events: none;
}
.tp-seg > button:last-child,
.tp-seg > a:last-child,
.tp-seg > label:last-child { border-right: 0; }
.tp-seg > button.is-active,
.tp-seg > a.is-active,
.tp-seg > label.is-active { background: var(--admin-accent); color: #fff; font-weight: 600; }
.tp-seg > button.is-active:hover,
.tp-seg > a.is-active:hover,
.tp-seg > label.is-active:hover { color: #fff; }
/* 段内计数（前台固件市场的 tab：名称 + 条数）
   🔴 不单独写字号：跟父级同一阶梯（13.5px），只用透明度做层次 ——
      写 11px 会被 audit-font-ladder 判"低于阶梯"。 */
.tp-seg > a .n,
.tp-seg > button .n { font-size: inherit; opacity: .7; }
.tp-seg > a.is-active .n,
.tp-seg > button.is-active .n { opacity: .85; }

.tp-note { font-size: 12px; color: var(--admin-muted); }
.tp-badge { display: inline-block; padding: 1px 8px; font-size: 12px; border: 1px solid var(--admin-border); }
.tp-badge.ok { border-color: #1a7a2e; color: #1a7a2e; }
.tp-badge.warn { border-color: #b45309; color: #b45309; }
.tp-badge.err { border-color: var(--admin-accent); color: var(--admin-accent); }
.tp-badge.run { border-color: #1565C0; color: #1565C0; }


/* ============================================================
   盖章申请 · 体验优化（2026-09-25）
   目的：① 网格按 A4 真实比例（格子不再横宽）
        ② 印章选择去掉缩略图，只留中文名，更紧凑
        ③ 图例圆点
   说明：本节用「后定义覆盖」的方式调整既有规则，不改动上方原始定义。
   ============================================================ */

/* ① 格子高度由 grid-template-rows 决定（容器按 A4 比例约束），去掉固定高度 */
.tp-grid-cell {
    height: auto;
    min-height: 0;
    font-size: 11px;
    user-select: none;          /* 右键操作时避免误选中文字 */
}
.tp-grid-cell.is-off { opacity: .3; }

/* ② 印章选择：只留中文名 */
.tp-pick {
    padding: 11px 20px;
    min-width: 104px;
    font-size: 14px;
    font-weight: 600;
    color: var(--admin-body-text);
}
.tp-pick img { display: none; }        /* 兼容：即便模板里还有图也不下载显示 */
.tp-pick .tp-pick-name { font-size: 14px; margin: 0; }
.tp-pick .tp-note { display: none; }   /* 隐藏 code 等辅助文字 */

/* ③ 图例圆点 */
.tp-legend-dot {
    display: inline-block;
    width: 9px;
    height: 9px;
    margin-right: 5px;
    vertical-align: middle;
}


/* ============================================================
   盖章申请 · 上传改双拖拽框 / 多页平铺（2026-09-26）
   🔴 全部用自定义类，不用 Tailwind 工具类 —— 本文件不参与 Vite 构建，改完即生效
   ============================================================ */

/* ① 两个上传框：竖版框大、横版框小（拖进哪个框 = 选哪种版式） */
.tp-drops { display: flex; gap: 20px; flex-wrap: wrap; align-items: flex-start; margin-top: 14px; }
.tp-drop {
    border: 2px dashed var(--admin-border);
    background: var(--admin-surface, #fff);
    padding: 18px 16px;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 7px;
    text-align: center;
    transition: border-color .15s, background .15s;
}
.tp-drop:hover { border-color: var(--admin-accent); background: var(--admin-accent-soft); }
.tp-drop.is-portrait  { width: 300px; }   /* 竖版：面积大一点 */
.tp-drop.is-landscape { width: 236px; }   /* 横版：面积小一点 */
.tp-drop.is-busy { opacity: .45; pointer-events: none; }

/* 框内的"纸张形状"：直接提示该框要什么方向的文件 */
.tp-drop-shape { border: 2px solid var(--admin-subtle); background: var(--admin-surface, #fff); }
.tp-drop-shape.is-portrait  { width: 62px; height: 88px; }   /* ≈ 210:297 */
.tp-drop-shape.is-landscape { width: 88px; height: 62px; }   /* ≈ 297:210 */

.tp-drop-title { font-size: 16px; font-weight: 700; color: var(--admin-text); }

.tp-drop-btn {
    position: relative;
    display: inline-block;
    border: 1px solid var(--admin-accent);
    color: var(--admin-accent);
    padding: 5px 16px;
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
    overflow: hidden;
    background: var(--admin-surface, #fff);
}
.tp-drop-btn:hover { background: var(--admin-accent); color: #fff; }
/* 透明 input 铺满按钮 → 点按钮等于点原生选择器（同时保留拖拽） */
.tp-drop-input {
    position: absolute;
    left: 0; top: 0;
    width: 100%; height: 100%;
    opacity: 0;
    cursor: pointer;
}

/* ② 多页平铺：每页一张卡片 */
.tp-page-card {
    border: 1px solid var(--admin-border);
    background: var(--admin-surface, #fff);
    padding: 12px;
    margin-top: 14px;
}
.tp-page-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 14px;
    margin-bottom: 10px;
}
.tp-page-no { font-size: 14px; font-weight: 700; color: var(--admin-text); }

/* ③ 签名：单选"勾选框"
   🔴 2026-10-25 第 90 轮（发哥第 8 条）：`.tp-checks` / `.tp-check` / `.tp-tick` 的**基础样式**
      已搬到 `public/css/controls.css`（前后台共用一份，全站只有这一套多选外观）。
      本文件只保留页面级的尺寸微调（见下方"勾选…小一号"）。 */

/* ④ 图例 */
.tp-legend { display: flex; gap: 18px; flex-wrap: wrap; }

/* ⑤ 单框上传（2026-09-26：版式由文件自动识别，不再需要两个框）
     发哥要求 8：宽度自适应铺满浏览器、高度 ×3，方便拖拽 */
.tp-drops { display: block; }
.tp-drop.is-single {
    width: 100%;              /* 自适应：铺满内容区宽度 */
    min-height: 570px;        /* 2026-09-26 发哥要求 2：再 +50%（380 → 570），拖拽更好命中 */
    justify-content: center;
    padding: 32px 24px;
}
.tp-drop-shape.is-single { width: 130px; height: 130px; border-style: dashed; border-width: 3px; }

/* ⑥ 文字型行内按钮（🔴 不用 Tailwind 工具类：本文件不参与 Vite 构建，写这里最稳） */
.tp-link-btn {
    font-size: 12px;
    color: var(--admin-accent);
    background: none;
    border: 0;
    padding: 0;
    cursor: pointer;
    text-decoration: none;
}
.tp-link-btn:hover { text-decoration: underline; }

/* ⑦ 顶部状态横幅（提交后唯一一条提示：处理中 → 已完成/失败） */
.tp-banner {
    margin-top: 16px;
    border: 1px solid var(--admin-border);
    padding: 12px 16px;
}
.tp-banner.is-ok  { border-color: #1a7a2e; background: rgba(26, 122, 46, 0.08); }
.tp-banner.is-err { border-color: var(--admin-accent); background: var(--admin-accent-soft); }
.tp-banner.is-run { border-color: #1565C0; background: rgba(21, 101, 192, 0.08); }

/* 🔴 v0.36（发哥 2026-10-06 第 4 项）：行内操作提示用**小号**。
   `.tp-banner` 本身不设 font-size（继承父级），放在正文里会比周围标准文案大一号，
   看着像"没用标准样式"。`.tp-banner-sm` 与 `.tp-note` 对齐（12px），
   **只在行内提示用**；页面级的大提示仍用原 `.tp-banner`。 */
.tp-banner-sm { font-size: 12px; line-height: 1.7; padding: 8px 12px; margin-top: 10px; }

/* ⑧ 「本页盖普通章 / 本页加签名」的方框勾
   🔴 样式在 controls.css（全站唯一来源）；此处不再重复定义，避免两处各改一半。 */

/* ⑨ 每页位置：一行 2 页平铺（发哥要求 11）
     3 页 = 2 行 / 4 页 = 2 行 / 5 页 = 3 行（grid 自动换行） */
.tp-page-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 16px;
    margin-top: 14px;
}
@media (max-width: 767px) {
    .tp-page-grid { grid-template-columns: 1fr; }
}
.tp-page-card { margin-top: 0; }
.tp-grid { width: 100%; }

/* ⑩ 提交行：与上方卡片留出明显空隙（发哥要求 4） */
.tp-submit-row {
    margin-top: 30px;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 14px;
}

/* ⑪ 大号下载按钮（发哥要求 2：把"下载"两个字做成大按钮） */
.tp-btn {
    display: inline-block;
    padding: 6px 18px;
    font-size: 14px;
    font-weight: 600;
    color: #fff;
    background: var(--admin-accent);
    border: 1px solid var(--admin-accent);
    cursor: pointer;
    text-decoration: none;
}
.tp-btn:hover { filter: brightness(0.92); }
/* 灰态（第 143 轮 · 发哥）：「只要不满足所有条件，提交审核的按钮应该是灰色不可按的」
   —— 只改观感，不动尺寸；缺什么由旁边的说明文字给（灰按钮点不动，不给理由就等于"点了没反应"）。 */
.tp-btn:disabled,
.tp-btn.is-off {
    background: var(--admin-border);
    border-color: var(--admin-border);
    color: var(--admin-muted, #9ca3af);
    cursor: not-allowed;
    filter: none;
}


/* ============================================================
   ⑧ 后台顶栏 + 我的工作台（2026-09-26）
   ------------------------------------------------------------
   🔴 一律写在本文件（不进 Vite 构建）：
      本项目 public/css/admin.css 是后台样式的唯一来源，
      Tailwind 工具类是构建产物 —— **从没用过的类线上根本不存在**。
   ============================================================ */

/* 顶栏：账号信息 + 退出登录（发哥要求 6：退出登录从侧栏挪到右上角） */
.tp-topbar {
    position: sticky;
    top: 0;
    z-index: 30;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 16px;
    height: var(--tp-topbar-h);
    flex-shrink: 0;
    padding: 0 24px;
    background: var(--admin-surface, #fff);
    border-bottom: 1px solid var(--admin-border);
}
.tp-topbar-user {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 14px;
}
.tp-topbar-name {
    font-weight: 600;
    color: var(--admin-text);
}
.tp-topbar-sub {
    font-size: 12px;
    color: var(--admin-muted);
}
/* .tp-topbar-mail 已于 2026-10-07 删除（发哥第 6 项：顶栏去掉邮箱与「我的工作台」）
   —— 按 UI 规范「删功能顺手删样式」，避免留下死规则。 */
.tp-logout-btn {
    border: 1px solid var(--admin-border);
    background: var(--admin-surface, #fff);
    padding: 5px 14px;
    font-size: 12px;
    font-weight: 600;
    color: var(--admin-body-text);
    cursor: pointer;
}
.tp-logout-btn:hover {
    border-color: var(--admin-accent);
    color: var(--admin-accent);
    background: var(--admin-accent-soft);
}

/* 工作台：卡片 / 键值 / 标签 / 分组标题 */
.tp-card {
    border: 1px solid var(--admin-border);
    background: var(--admin-surface, #fff);
    padding: 18px 20px;
}
.tp-group-title {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 14px;
    font-weight: 700;
    color: var(--admin-text);
}
.tp-subgroup-title {
    font-size: 12px;
    font-weight: 600;
    color: var(--admin-muted);
}
.tp-kv-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: 14px;
}

/* 「字段 / 数据」两列键值表（第 101 轮 · 发哥第 4 条）
   ------------------------------------------------------------
   发哥：「资源审核，详情，里面的『资源类型、属性』等等信息，排版是乱的，
          应该分清楚字段和数据」
   根因：`<div class="tp-kv"><div class="k">字段</div><div class="v">数据</div></div>`
   —— **`.tp-kv` 从来就没有样式**，于是 40 多个字段全挤成一列纯文本，看不出哪个是标签。
   ➡️ 需要"左标签 / 右数据"的地方用 `.tp-kv-2col`（`.tp-kv` 保持原样：标签在上、值在下）。
   🔴 标签列固定 96px 且**不换行**，数据列吃满剩余宽度，长文本按词换行。 */
.tp-kv-2col {
    display: grid;
    grid-template-columns: 96px minmax(0, 1fr);
    column-gap: 14px;
    row-gap: 7px;
    align-items: baseline;
}
.tp-kv-2col > .k {
    font-size: 14px;          /* 字号阶梯：admin.css 里不允许 <13px（audit-font-ladder 闸门） */
    line-height: 1.7;
    color: var(--admin-muted);
    white-space: nowrap;
}
.tp-kv-2col > .v {
    font-size: 14px;
    line-height: 1.7;
    color: var(--admin-text);
    word-break: break-word;
}
@media (max-width: 767px) {
    /* 窄屏：标签独占一行，数据另起一行（两列会挤成"字段值"糊在一起） */
    .tp-kv-2col { grid-template-columns: minmax(0, 1fr); row-gap: 2px; }
    .tp-kv-2col > .k { margin-top: 6px; }
}
.tp-kv-k { font-size: 12px; color: var(--admin-muted); }
.tp-kv-v { margin-top: 3px; font-size: 14px; font-weight: 600; color: var(--admin-text); word-break: break-all; }
.tp-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.tp-chip {
    display: inline-block;
    border: 1px solid var(--admin-border);
    background: var(--admin-hover);
    padding: 4px 10px;
    font-size: 12px;
    color: var(--admin-body-text);
}
.tp-chip-link {
    color: var(--admin-accent);
    border-color: var(--admin-accent);
    background: var(--admin-surface, #fff);
    text-decoration: none;
    font-weight: 600;
}
.tp-chip-link:hover { background: var(--admin-accent-soft); }


/* ============================================================
   ⑨ 表单瘦身 + 权限面板（2026-09-26 傍晚 · 发哥要求 1/8/9）
   ------------------------------------------------------------
   背景：发哥反馈"文件类型 / 盖章方式 / 本页盖章这些按钮都太明显了，没必要"，
        而且为了设置「每页位置」还得把页面滚到下半部分。
   做法：① 选择类控件降级（细边框 + 小内距 + 常规字重），选中态才加重；
        ② 步骤卡片内距 20px → 12px，标题字号收敛；
        ③ 每页卡片与格子间距收紧、格子区最大宽度收紧；
        ④ 让「每页位置」尽量出现在首屏内。
   🔴 全部写在本文件（Tailwind 工具类是 Vite 构建产物，没用过的类线上不存在）。
   ============================================================ */

/* ① 步骤卡片（替代视图里的 p-5 / mt-4 / mt-5 组合） */
.tp-step {
    margin-top: 12px;
    border: 1px solid var(--admin-border);
    background: var(--admin-surface, #fff);
    padding: 12px 16px;
}
.tp-step-title {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 14px;
    font-weight: 600;
    color: var(--admin-text);
}

/* ② 可选卡片（文件类型 / 印章）：细边框 + 小内距 + 常规字重，选中才加重 */
.tp-picks { gap: 6px; }
.tp-pick {
    border-width: 1px;
    border-color: var(--admin-border);
    padding: 5px 12px;
    min-width: 0;
    font-size: 14px;
    font-weight: 400;
}
.tp-pick.is-active {
    border-color: var(--admin-accent);
    background: var(--admin-accent-soft);
    color: var(--admin-accent);
    font-weight: 600;
}

/* ③ 勾选（本页盖章 / 本页加签名 / 骑缝章）
   🔴 2026-10-26 第 91 轮（发哥第 1 条）：尺寸已并入 `controls.css` 作为**全站唯一标准**。
      原来这里有一条**全局**覆盖，导致「后台小号、前台大号」两套观感 —— 删掉。 */

/* ④ 每页卡片与格子区更紧凑 */
.tp-page-card { padding: 8px 10px; }
.tp-page-head { gap: 8px; margin-bottom: 6px; }
.tp-page-no { font-size: 12px; }
.tp-page-grid { gap: 10px; margin-top: 10px; }

/* ⑤ 员工权限面板：分组标题（中文，与菜单同名）+ 紧凑勾选项
   🔴 第 99 轮（发哥第 9 条）：「去掉这个框左边的红色条，太 AI 味了」——
      全站**删除"强调色竖条 + 卡片"这种 AI 味套式**：分组标题只靠字重区分。 */
.tp-perm-group {
    font-size: 12px;
    font-weight: 600;
    color: var(--admin-body-text);
}
.tp-perm-item {
    display: flex;
    align-items: center;
    gap: 6px;
    border: 1px solid var(--admin-border);
    padding: 4px 10px;
    font-size: 14px;
}


/* ============================================================
   ⑨ 权限面板：菜单权限 + 数据权限（2026-09-26 两层架构）
   ------------------------------------------------------------
   🔴 一律写在本文件（不进 Vite 构建）：
      本项目 public/css/admin.css 是后台样式唯一来源，
      Tailwind 工具类是构建产物 —— 从没用过的类线上根本不存在。
   ============================================================ */

/* 层说明条 */
.tp-perm-legend {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 26px;
    margin-top: 14px;
    padding: 10px 12px;
    border: 1px solid var(--admin-border);
    background: var(--admin-hover);
    font-size: 12px;
    color: var(--admin-body-text);
}
.tp-perm-legend-item { display: flex; align-items: center; gap: 6px; }
.tp-legend-tag { display: inline-block; padding: 1px 7px; font-size: 12px; font-weight: 600; }
.tp-legend-menu { border: 1px solid var(--admin-accent); background: var(--admin-accent-soft); color: var(--admin-accent); }
.tp-legend-data { border: 1px solid #1565C0; background: #1565C0; color: #fff; }

/* 数据权限块：缩进 + 浅底 —— 与菜单权限一眼可分（竖条已按第 99 轮去掉） */
.tp-data-block {
    margin-top: 10px;
    margin-left: 16px;
    border: 1px solid var(--admin-border);
    background: rgba(21, 101, 192, 0.045);
    padding: 10px 12px;
}
/* 父菜单未勾 → 半透明 + 不可点（服务端还会再剥离一次，界面只是提示） */
.tp-data-block.is-locked { opacity: 0.42; pointer-events: none; }
.tp-data-head { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.tp-data-tag { padding: 1px 7px; font-size: 12px; font-weight: 600; background: #1565C0; color: #fff; }
.tp-data-title { font-size: 14px; font-weight: 600; color: var(--admin-text); }
.tp-data-dep { font-size: 12px; color: var(--admin-muted); }
.tp-data-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(158px, 1fr));
    gap: 6px 10px;
    margin-top: 8px;
}
.tp-data-grid .tp-perm-item { background: var(--admin-surface, #fff); }


/* ============================================================
   ⑨ 产品知识库 · 属性字典（2026-09-27 P1a）
   ------------------------------------------------------------
   🔴 一律写在本文件（不进 Vite 构建）：
      Tailwind 工具类是 Vite 构建产物 —— 从没用过的类线上根本不存在。
   ============================================================ */

/* 通用输入控件（知识库各页复用） */
.tp-input {
    border: 1px solid var(--admin-border);
    background: var(--admin-surface, #fff);
    padding: 6px 10px;
    font-size: 14px;
    color: var(--admin-text);
}
.tp-input:focus { outline: none; border-color: var(--admin-accent); }

/* 统计条 */
.tp-kb-stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 12px;
    border: 1px solid var(--admin-border);
    background: var(--admin-surface, #fff);
    padding: 14px 16px;
}

/* 左侧分组列表（第一层） */
.tp-kb-groups {
    /* 🔴 2026-09-29 发哥第 4 项：220px 太窄 → 分组名被挤成两行。加宽到 300px。 */
    width: 300px;
    border: 1px solid var(--admin-border);
    background: var(--admin-surface, #fff);
    padding: 12px;
}
.tp-kb-grow { display: flex; align-items: center; gap: 4px; min-width: 0; }
/* 名称不许折行（宽度不够时省略号，而不是断成两行） */
.tp-kb-gitem > span:first-child {
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.tp-kb-grow.is-off { opacity: 0.5; }
.tp-kb-gitem {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    width: 100%;
    padding: 6px 10px;
    font-size: 14px;
    color: var(--admin-body-text);
    text-align: left;
    background: none;
    border: 0;
    cursor: pointer;
}
.tp-kb-gitem:hover { background: var(--admin-hover); }
.tp-kb-gitem.is-active { background: var(--admin-accent-soft); font-weight: 600; color: var(--admin-accent); }
.tp-kb-gcount { font-size: 12px; color: var(--admin-muted); }
.tp-kb-gtools { display: flex; gap: 2px; }

/* 右侧筛选条 */
.tp-kb-filter {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 16px;
    border: 1px solid var(--admin-border);
    background: var(--admin-surface, #fff);
    padding: 12px 14px;
}

/* ============================================================
   ⑩ 产品规格（产品 × 版本 × 属性）· 2026-09-27
   ------------------------------------------------------------
   发哥第 3 项要的"大表格 / 层次分明的管理页面"。
   🔴 布局全部写在本文件：Tailwind 是 Vite 构建产物，
      没在别处用过的类线上根本不存在（如 grid-cols-3 之类的组合）。
   ============================================================ */

.tp-spec-head {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: space-between;
    gap: 12px;
}
.tp-spec-headtools { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 10px; }
.tp-field { display: flex; flex-direction: column; gap: 3px; }
.tp-field > span { font-size: 12px; color: var(--admin-muted); }

/* 版本卡片条（一眼看清这个产品有哪些版本） */
.tp-spec-chips { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.tp-spec-chip {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    border: 1px solid var(--admin-border);
    background: var(--admin-surface, #fff);
    padding: 5px 10px;
    font-size: 14px;
    color: var(--admin-body-text);
}
.tp-spec-chip.is-on { border-color: var(--admin-accent); background: var(--admin-accent-soft); font-weight: 600; }
.tp-spec-chip.is-off { opacity: 0.5; }
.tp-spec-chip .acts { display: inline-flex; gap: 6px; }

/* 主体：左树 + 右表单 */
.tp-spec-body { display: flex; gap: 16px; margin-top: 16px; align-items: flex-start; }
.tp-spec-side {
    width: 236px;
    flex-shrink: 0;
    border: 1px solid var(--admin-border);
    background: var(--admin-surface, #fff);
    padding: 10px 8px;
    max-height: 74vh;
    overflow: auto;
}
.tp-spec-main { flex: 1; min-width: 0; }

.tp-spec-cat {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 6px;
    width: 100%;
    padding: 7px 6px;
    font-size: 14px;
    font-weight: 700;
    color: var(--admin-text);
    background: none;
    border: 0;
    border-top: 1px solid var(--admin-border);
    cursor: pointer;
    text-align: left;
}
.tp-spec-side > .tp-spec-cat:first-child { border-top: 0; }
.tp-spec-cat:hover { color: var(--admin-accent); }
.tp-spec-cat.is-on { color: var(--admin-accent); }
.tp-spec-g {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    width: 100%;
    padding: 5px 8px 5px 16px;
    font-size: 14px;
    color: var(--admin-body-text);
    background: none;
    border: 0;
    text-align: left;
    cursor: pointer;
}
.tp-spec-g:hover { background: var(--admin-hover); }
.tp-spec-g.is-on { background: var(--admin-accent-soft); color: var(--admin-accent); font-weight: 600; }
.tp-cnt { font-size: 12px; color: var(--admin-muted); white-space: nowrap; }

/* 分组块 */
.tp-spec-block { border: 1px solid var(--admin-border); background: var(--admin-surface, #fff); margin-bottom: 14px; }
.tp-spec-block-h {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 9px 14px;
    border-bottom: 1px solid var(--admin-border);
    background: var(--admin-hover);
    font-size: 14px;
    font-weight: 600;
    color: var(--admin-text);
}
.tp-spec-row {
    display: grid;
    grid-template-columns: 250px minmax(0, 1fr) 190px;
    gap: 12px;
    padding: 9px 14px;
    border-bottom: 1px dashed var(--admin-border);
    align-items: start;
}
.tp-spec-row:last-child { border-bottom: 0; }
.tp-spec-row.is-na { background: #fafafa; }
.tp-spec-label { font-size: 14px; font-weight: 600; color: var(--admin-text); }
.tp-spec-meta { margin-top: 3px; font-size: 12px; color: var(--admin-muted); line-height: 1.5; }
.tp-spec-val { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.tp-spec-mini { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--admin-muted); }
.tp-spec-unit { font-size: 12px; color: var(--admin-muted); }

/* 第 127 轮：已审核的行 —— **只锁填写控件，不锁按钮**.
   「修改」按钮就在这一格里，锁住就再也解不开了（上一版全格 pointer-events:none 的坑）。 */
.tp-spec-val.is-locked > *:not(button) { pointer-events: none; opacity: .72; }

/* 小标签 */
.tp-tag {
    display: inline-block;
    padding: 1px 6px;
    font-size: 12px;
    line-height: 1.6;
    border: 1px solid var(--admin-border);
    color: var(--admin-muted);
    white-space: nowrap;
}
.tp-tag.any { border-color: #1565C0; color: #1565C0; }
.tp-tag.var { border-color: var(--admin-accent); color: var(--admin-accent); }
.tp-tag.req { border-color: #b45309; color: #b45309; }
.tp-tag.na  { border-color: #9ca3af; color: #9ca3af; }
.tp-tag.ok  { border-color: #1a7a2e; color: #1a7a2e; }

/* 总览矩阵（大表格） */
.tp-matrix-wrap { border: 1px solid var(--admin-border); background: var(--admin-surface, #fff); overflow: auto; max-height: 74vh; }
.tp-matrix { border-collapse: collapse; font-size: 14px; width: 100%; }
.tp-matrix th, .tp-matrix td {
    border: 1px solid var(--admin-border);
    padding: 6px 9px;
    text-align: left;
    vertical-align: top;
    white-space: nowrap;
}
.tp-matrix thead th { position: sticky; top: 0; background: var(--admin-hover); font-weight: 600; z-index: 2; }
.tp-matrix .rowhead { min-width: 230px; white-space: normal; font-weight: 600; color: var(--admin-text); }
/* 🔴 2026-10-21 第 66 轮（发哥第 5 条）：**总览矩阵的属性列也要横向钉住** ——
   横向滚动时左侧"属性名"不能跟着走（性质同批量矩阵的首列）。
   背景必须给实色（sticky 元素透明会透出下面的格子）。 */
.tp-matrix th.rowhead,
.tp-matrix td.rowhead {
    position: sticky;
    left: 0;
    z-index: 1;
    background: var(--admin-surface, #fff);
}
.tp-matrix thead th.rowhead { z-index: 3; background: var(--admin-hover); }
.tp-matrix .cellEmpty { color: #c4c4c4; }
.tp-matrix .tp-sc-parent {
    background: var(--admin-hover);
    font-weight: 700;
    color: var(--admin-text);
    font-size: 12px;
}

/* 面板 / 预览 */
.tp-panel { border: 1px solid var(--admin-border); background: var(--admin-surface, #fff); padding: 16px; margin-top: 14px; }
.tp-pre {
    white-space: pre-wrap;
    word-break: break-word;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 12px;
    line-height: 1.65;
    background: var(--admin-hover);
    border: 1px solid var(--admin-border);
    padding: 12px;
    max-height: 460px;
    overflow: auto;
    color: var(--admin-text);
}
.tp-input-xs { width: 96px; }
.tp-input-sm { width: 160px; }

@media (max-width: 767px) {
    .tp-spec-body { flex-direction: column; }
    .tp-spec-side { width: 100%; max-height: none; }
    .tp-spec-row { grid-template-columns: minmax(0, 1fr); }
}

/* 左栏两级：分类（顶层）/ 分组（缩进）—— 2026-09-27 四层结构 */
.tp-kb-cat { margin-top: 6px; }
.tp-kb-cat .tp-kb-gitem { font-weight: 600; }
.tp-kb-sub { padding-left: 14px; }
.tp-kb-sub .tp-kb-gitem { font-size: 12px; color: var(--admin-muted); }

/* ============================================================
   侧栏（2026-09-27 数据化 + 可折叠）
   —— 渲染结构来自 App\Support\AdminPermissions::SIDEBAR，
      本文件只负责长相；不写死任何菜单。
   ============================================================ */
.tp-sidebar {
    position: sticky;
    top: 0;
    display: flex;
    height: 100vh;
    width: 224px;                 /* 与原来 w-56 一致 */
    flex-shrink: 0;
    flex-direction: column;
    border-right: 1px solid var(--admin-border);
    background: var(--admin-surface, #fff);
}
.tp-sidebar-brand {
    display: flex;
    /* 2026-09-29（发哥第 3 项）：logo 放大到 128px 宽（约 47px 高）后，
       品牌行由 56px 提到 64px，否则上下只剩 4px、看着压抑。 */
    height: 64px;
    flex-shrink: 0;
    align-items: center;
    gap: 8px;
    padding: 0 16px;
    border-bottom: 1px solid var(--admin-border);
}
/* 品牌图（2026-09-29 起侧栏改用 Talkpod logo，不再是「TP」方块 + 文字）
   旧规则 .tp-sidebar-logo / .tp-sidebar-title 已废弃并删除（视图不再产出这两个元素）。

   🔴 尺寸（2026-09-29 发哥第 3 项）：**宽 128px**（原 96px），等比 → 约 47px 高。
      logo 原图 300×111（`public/images/talkpod/talkpod-logo.png`），
      放大到 128px 后边缘略糊 —— 发哥会补 SVG，到时直接换文件、这里不用改。 */
.tp-sidebar-brand-img {
    display: block;
    width: 128px;
    height: auto;
    max-width: 100%;
}

.tp-sidebar-nav {
    flex: 1;
    overflow-y: auto;
    padding: 8px;
    font-size: 14px;
}

/* 一级链接（我的工作台 / 分组内菜单项） */
.tp-side-link {
    display: block;
    padding: 7px 12px;
    color: var(--admin-body-text);
    text-decoration: none;
}
.tp-side-link:hover { background: var(--admin-hover); }
.tp-side-link.is-active {
    background: var(--admin-accent-soft);
    color: var(--admin-accent);
    font-weight: 600;
}

/* 单条同名分组（概览 / 下载管理）→ 标题即为链接 */
.tp-side-solo {
    display: block;
    margin-top: 10px;
    padding: 7px 12px;
    border-top: 1px solid var(--admin-border);
    color: var(--admin-body-text);
    font-weight: 600;
    text-decoration: none;
}
.tp-side-solo:hover { background: var(--admin-hover); }
.tp-side-solo.is-active { color: var(--admin-accent); }

/* 可折叠分组 */
.tp-side-group { margin-top: 10px; border-top: 1px solid var(--admin-border); }
.tp-side-group-head {
    display: flex;
    width: 100%;
    align-items: center;
    justify-content: space-between;
    gap: 6px;
    padding: 6px 12px;
    border: none;
    background: none;
    color: var(--admin-muted);
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
    text-align: left;
}
.tp-side-group-head:hover { color: var(--admin-text); }
.tp-side-caret {
    font-size: 11px;
    line-height: 1;
}
/* 🔴 2026-10-08 第 25 轮：二级改成 hover 飞出后，`.tp-side-caret.is-open`（旋转箭头）
   与 `.tp-side-group-body`（内联展开容器）都已废弃 —— 视图不再产出这两个东西。
   按「死代码一律删」的规矩删掉（留着会误导后来人以为侧栏还能展开/收起）。 */

/* ============================================================
   产品目录（2026-09-27 合并「产品分类」+「产品与版本」）
   —— 分类树 + 产品大列表 + 行内添加 + 批量添加 + 分类 CRUD
   ============================================================ */

/* 页头 */
.tp-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
}
.tp-h1 { font-size: 24px; font-weight: 600; color: var(--admin-text); margin: 0; }
/* 🔴 2026-10-20（发哥第 5 条）：**页头标题唯一口径**。
   Tailwind preflight 会把裸 <h1> 重置成"继承字号"（≈14px），
   于是「产品介绍」看着比其他页的 24px 小一号。
   这里兜住：进 `.tp-page-head` 的 h1 一律 24px/600，不再依赖每个页面记得加 class。 */
.tp-page-head > h1,
.tp-page-head h1 {
    font-size: 24px;
    font-weight: 600;
    line-height: 1.3;
    color: var(--admin-text);
    margin: 0;
}
.tp-head-ops { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

/* 次级按钮（描边）—— `.tp-btn` 是主按钮（拓朋红实心），不要再改它 */
.tp-btn-2 {
    display: inline-block;
    padding: 6px 14px;
    font-size: 14px;
    font-weight: 600;
    color: var(--admin-body-text);
    background: var(--admin-surface, #fff);
    border: 1px solid var(--admin-border);
    cursor: pointer;
    text-decoration: none;
}
.tp-btn-2:hover { border-color: var(--admin-accent); color: var(--admin-accent); }
.tp-btn-2.is-danger { color: var(--admin-danger); border-color: rgba(220, 38, 38, .45); }
.tp-btn-2.is-danger:hover { background: var(--admin-danger-soft); border-color: var(--admin-danger); }
.tp-btn-sm { padding: 3px 9px; font-size: 12px; font-weight: 500; }

/* 输入尺寸 */
.tp-input-md { width: 240px; }
.tp-input-full { width: 100%; box-sizing: border-box; }

/* 筛选条 */
.tp-cat-toolbar {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    margin-top: 12px;
    padding: 10px 12px;
    border: 1px solid var(--admin-border);
    background: var(--admin-surface, #fff);
}

/* 分类分组（树的第二层） */
.tp-cat-group { margin-top: 14px; border: 1px solid var(--admin-border); background: var(--admin-surface, #fff); }
.tp-cat-head {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 9px 12px;
    background: var(--admin-hover);
    border-bottom: 1px solid var(--admin-border);
}
/* .tp-cat-caret 的样式统一在 v0.10 段（+ / − 小方块按钮）—— 旧写法已删，避免两份定义打架 */
.tp-cat-name { font-size: 14px; font-weight: 700; color: var(--admin-text); }
.tp-cat-name.is-off { opacity: .5; text-decoration: line-through; }
.tp-cat-ops { margin-left: auto; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }

/* 行内添加 */
.tp-inline-add {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    padding: 10px 12px;
    border-bottom: 1px dashed var(--admin-border);
    background: var(--admin-accent-soft);
}
.tp-empty {
    padding: 22px 12px;
    text-align: center;
    font-size: 14px;
    color: var(--admin-muted);
}

/* 大列表 */
.tp-tbl-wrap { overflow-x: auto; }
.tp-tbl { width: 100%; border-collapse: collapse; font-size: 14px; }
.tp-tbl thead th {
    padding: 8px 10px;
    text-align: left;
    font-size: 12px;
    font-weight: 600;
    color: var(--admin-muted);
    border-bottom: 1px solid var(--admin-border);
    white-space: nowrap;
}
.tp-tbl tbody td {
    padding: 8px 10px;
    border-bottom: 1px solid #F3F4F6;
    color: var(--admin-text);
    vertical-align: middle;
}
.tp-tbl tbody tr:hover { background: var(--admin-hover); }
.tp-tbl tr.is-off td { opacity: .55; }
.tp-col-order { width: 46px; white-space: nowrap; }
.tp-col-num { width: 52px; text-align: center; }
.tp-col-ops { white-space: nowrap; }
.tp-mini {
    border: 1px solid var(--admin-border);
    background: var(--admin-surface, #fff);
    color: var(--admin-muted);
    font-size: 12px;
    line-height: 1;
    padding: 2px 4px;
    cursor: pointer;
}
.tp-mini:hover { border-color: var(--admin-accent); color: var(--admin-accent); }
.tp-mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--admin-accent); }

/* 徽章 / 链接式操作 */
.tp-chip.is-default { border-color: var(--admin-accent); color: var(--admin-accent); font-weight: 600; background: var(--admin-surface, #fff); }
.tp-chip.is-off { opacity: .45; text-decoration: line-through; }
/* ------------------------------------------------------------------
 * 行内操作按钮（.tp-op）· 语义色 **唯一来源**
 * ------------------------------------------------------------------
 * 第 122 轮（发哥第 8 条）：原来默认色是品牌红，导致一整排按钮全是红的
 *   ——「查看授权码 / 取消授权 / 已签发 / 拒绝签发」四件事看起来一模一样，
 *   既分不出轻重，也分不出"看不看"和"删不删"。
 *
 * 规则（只有五档，别再自己写 text-red-500 之类的颜色）：
 *   默认     中性深灰   → 查看 / 详情 / 编辑 / 设置   （不改数据或改了也不破坏）
 *   is-primary 绿      → 正向推进：授权、发布、启用、通过、批准
 *   is-danger  红      → 破坏性：删除、拒绝、驳回、停止授权、清空
 *   is-warn    琥珀    → 可逆回退：撤销、撤回、取消授权、撤销拒绝
 *   is-muted   浅灰    → 弱化 / 当前状态占位（配 disabled 用）
 *   任何 disabled → 一律浅灰，不参与语义（"已签发/已拒绝"就是这种）
 * ---------------------------------------------------------------- */
.tp-op {
    display: inline-block;
    margin-right: 8px;
    font-size: 12px;
    color: var(--admin-body-text);
    background: none;
    border: none;
    padding: 0;
    cursor: pointer;
    text-decoration: none;
}
.tp-op:hover { color: var(--admin-accent); text-decoration: underline; }
.tp-op.is-primary { color: var(--admin-ok); font-weight: 600; }
.tp-op.is-danger { color: var(--admin-danger); }
.tp-op.is-warn { color: var(--admin-warn); }
.tp-op.is-muted { color: var(--admin-subtle); }
.tp-op.is-primary:hover { color: var(--admin-ok); }
.tp-op.is-danger:hover { color: var(--admin-danger); }
.tp-op.is-warn:hover { color: var(--admin-warn); }
.tp-op.is-muted:hover { color: var(--admin-muted); }
.tp-op[disabled],
.tp-op[disabled]:hover {
    color: var(--admin-subtle);
    font-weight: 400;
    text-decoration: none;
    cursor: default;
}

/* 状态文字 */
.tp-ok-text { color: var(--admin-ok); }
.tp-err-text { color: var(--admin-danger); }
.tp-warn-text { color: var(--admin-warn); font-weight: 600; }

/* 弹窗 */
.tp-modal-mask {
    position: fixed;
    inset: 0;
    z-index: 60;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(15, 23, 42, .45);
}
.tp-modal {
    width: 560px;
    max-width: calc(100vw - 32px);
    max-height: calc(100vh - 64px);
    overflow: auto;
    background: var(--admin-surface, #fff);
    border: 1px solid var(--admin-border);
    box-shadow: 0 12px 40px rgba(0, 0, 0, .18);
}
.tp-modal-wide { width: 720px; }
.tp-modal-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 13px 18px;
    border-bottom: 1px solid var(--admin-border);
    font-size: 16px;
    font-weight: 600;
    color: var(--admin-text);
}
.tp-modal-x { border: none; background: none; font-size: 16px; line-height: 1; color: var(--admin-muted); cursor: pointer; }
.tp-modal-body { padding: 18px; }
/* 🔴 2026-10-28 第 113 轮（发哥第 2 条）：「同名文件不会覆盖…」和「开始上传」都太靠边
   —— `.tp-modal-foot` 原来只有 padding-top，左右是 0，内容直接贴弹窗边框。
   现在左右补 18px（= `.tp-modal-body` 的 padding），两种写法都对得上：
     · foot 与 body **平级**（新写法）  → 自带 18px
     · foot 写在 body **里面**（历史写法）→ 用负 margin 抵消 body 的 18px，再补自己的 18px
   这样分隔线还能横贯整个弹窗宽度，看起来是一条"底部动作条"。 */
.tp-modal-foot {
    display: flex;
    align-items: center;
    gap: 16px;
    flex-wrap: wrap;
    margin-top: 16px;
    padding: 14px 18px 18px;
    border-top: 1px solid var(--admin-border);
}
.tp-modal-body > .tp-modal-foot {
    margin: 16px -18px -18px;
    padding: 14px 18px 18px;
}
.tp-modal-foot-ops { margin-left: auto; display: flex; gap: 10px; }

/* 表单零件 */
/* v0.13：改成**自适应列数** —— 宽屏自动排更多列、窄屏自动并成一列。
   原来是写死的 `1fr 1fr`：在 1400px 的屏上两个字段被拉到很宽，看着很空。 */
.tp-form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 12px; }
/* 🆕 第 80 轮（发哥第 4 条）：**同一属性的中/英两列，必须永远并排**
   auto-fit 在宽屏会排到 5~6 列，中文名会被推到第 1 行末尾、English 掉到第 2 行。
   这一格固定占满整行 + 内部固定两列 ⇒ 无论屏多宽都并排，且不会把别的字段挤走。 */
.tp-form-pair { grid-column: 1 / -1; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
@media (max-width: 767px) { .tp-form-pair { grid-template-columns: 1fr; } }
.tp-label { display: block; font-size: 12px; color: var(--admin-muted); margin-bottom: 4px; }
.tp-req { color: var(--admin-accent); }
.tp-err { display: block; margin-top: 4px; font-size: 12px; color: var(--admin-danger); }
.tp-field-inline { display: flex; align-items: center; gap: 6px; }
.tp-field-inline .tp-label { margin-bottom: 0; }
.tp-check-inline { display: flex; align-items: center; gap: 6px; font-size: 14px; color: var(--admin-muted); cursor: pointer; }
.tp-check-inline input[type="checkbox"] { accent-color: var(--admin-accent); }
/* 一行多选的复选框组（2026-09-29 属性字典弹窗 / 批量栏）：自动换行、组内统一间距 */
.tp-check-row { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 18px; margin-top: 6px; }

/* ============================================================
   产品目录 v0.7（2026-09-27 晚）
   —— 顶部操作栏（发哥第 3 项：操作统一到上方）/ 双型号 / 内部编号
   ============================================================ */
.tp-actbar {
    margin-top: 12px;
    border: 1px solid var(--admin-border);
    background: var(--admin-surface, #fff);
    padding: 8px 12px;
}
.tp-actbar-row {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    padding: 4px 0;
}
.tp-actbar-row + .tp-actbar-row { border-top: 1px dashed var(--admin-border); }
.tp-actbar-count { font-size: 14px; color: var(--admin-text); }
.tp-actbar-count b { color: var(--admin-accent); font-size: 16px; }
.tp-actbar-sep {
    display: inline-block;
    width: 1px;
    height: 18px;
    background: var(--admin-border);
    margin: 0 2px;
}
.tp-col-cb { width: 34px; text-align: center; }
.tp-col-cb input[type="checkbox"] { accent-color: var(--admin-accent); cursor: pointer; }
.tp-col-hw { white-space: nowrap; font-weight: 700; letter-spacing: .3px; }
/* v0.12（发哥第 4 项）：国内型号 / 海外型号**字体必须一致** ——
   原来这里 `font-weight: 700` 让国内型号比海外型号粗，看着像"只有国内型号是重点"。
   两者都是型号，权重相同；链接的"可点"靠 hover 下划线表达就够了。 */
.tp-model-link { font-weight: 400; text-decoration: none; }
.tp-model-link:hover { text-decoration: underline; }
.tp-alert-danger {
    border: 1px solid rgba(220, 38, 38, .45);
    background: var(--admin-danger-soft);
    color: #7F1D1D;
    padding: 10px 12px;
    font-size: 14px;
    line-height: 1.7;
}

/* ============================================================
   产品域 v0.8（2026-09-27 深夜）：输入宽度补充
   —— 🔴 只加 `.tp-*` 类；**不要新增 Tailwind 工具类**（如 grid-cols-3），
      那是 Vite 构建产物，没用过的类线上根本不存在（v0.7 踩过：grid-cols-3 不生效）。
      新视图里的多列布局一律用 inline style。
   ============================================================ */
.tp-input-lg { width: 300px; }
/* 内部版本号的三段下拉：术语改成「模块/芯片」「频段/平台」后 96px 放不下 → 加宽 */
.tp-input-hw { width: 150px; }

/* ============================================================
   产品域 v0.9（2026-09-27 深夜）
   —— ① 页面内 sticky 生效的基础（主内容区不能是滚动容器）
      ② 产品目录"筛选+操作"固定两行（发哥第 1 项）
      ③ 内部版本号 1:N 的展开明细（发哥第 3/5 项）
   ============================================================ */

/* ① 🔴 关键：`.tp-main-body` 一旦是滚动容器，页面内 `position: sticky` 全部失效。
      布局里已去掉 `overflow-x-auto`，这里再兜一层（overflow-x 用 visible/clip
      才不会被规范"传染"成 overflow-y:auto）。 */
.tp-main-body { padding: 24px; overflow: visible; }

/* ② 产品目录：筛选 + 操作两行固定在顶部。
      top = 顶栏 + 标签栏 = calc(var(--tp-topbar-h) + var(--tp-tagsbar-h))（默认 60 + 38 = 98）。
      🔴 两个高度都只在 `:root` 定义，改那里就行，**不要在这里写数字**
      （第 154 轮之前这里写死 94 = 56 + 38，而顶栏实际 60 → 固定表头被上移 4px、盖住标签栏下沿）。 */
.tp-sticky-head {
    position: sticky;
    /* 第 154 轮：不再写死 94 —— = 顶栏 + 标签栏，两个高度都走变量，
       以后改任一个都不会再出现"表头被盖住/悬空"（闸门 check-layout-metrics.py 守这条）。 */
    top: calc(var(--tp-topbar-h) + var(--tp-tagsbar-h));
    z-index: 20;
    /* 必须有不透明底色，否则下方内容会从两行之间透出来 */
    background: var(--admin-bg);
    padding-bottom: 4px;
}
.tp-sticky-head .tp-cat-toolbar { margin-top: 10px; }
.tp-sticky-head .tp-actbar { margin-top: 6px; }

/* 在售状态下拉（窄、每行一个） */
.tp-input-sale {
    width: 96px;
    padding: 3px 6px;
    font-size: 12px;
    line-height: 1.4;
}

/* ③ 产品行里的内部版本号单元格 —— 具体样式在 v0.10 段（.tp-hw-nos / .tp-hw-chip），
      这里不再重复定义，避免出现两份"同名但值不同"的规则（谁的优先级赢靠猜）。
      ⚠️ v0.9 曾在此写过一条「后代 + 旧类名」的选择器（11px 小字号），v0.10 换类名后
         它永远匹配不到任何元素 —— 已删除（死 CSS 会让人以为样式还在生效）。 */

/* 子版本号明细行（占满整行；单元格不要 padding，内层自己控制） */
.tp-tbl tbody tr.tp-subrow > td {
    padding: 0;
    background: var(--admin-hover);
}
.tp-sub-wrap { padding: 10px 14px 12px; }
.tp-sub-head { font-size: 14px; color: var(--admin-text); margin-bottom: 8px; }
.tp-tbl-sub { background: var(--admin-surface, #fff); border: 1px solid var(--admin-border); }
.tp-tbl-sub thead th { background: var(--admin-surface, #fff); }

/* ============================================================
   产品域 v0.10（2026-09-27 · 发哥 11 项）
   —— ① 内部版本号 chip「字体正常、不用红色」+ 展开按钮改 + / −
      ② 分类展开按钮也统一成 + / −
      ③ 在售状态读徽章（在售状态行内不再给下拉）
   ============================================================ */

/* ① 分类的展开/折叠按钮：由 ▸/▾ 改成 +/−，做成小方块按钮更好点 */
.tp-cat-caret {
    width: 20px;
    height: 20px;
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--admin-border);
    background: var(--admin-surface, #fff);
    color: var(--admin-body-text);
    font-size: 14px;
    font-weight: 700;
    line-height: 1;
    cursor: pointer;
}
.tp-cat-caret:hover { border-color: var(--admin-accent); color: var(--admin-accent); }

/* 产品行里的「展开内部版本号」按钮（发哥第 3 项：▸ → + / −） */
.tp-vexp {
    width: 18px;
    height: 18px;
    margin-right: 6px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    vertical-align: middle;
    border: 1px solid var(--admin-border);
    background: var(--admin-surface, #fff);
    color: var(--admin-body-text);
    font-size: 12px;
    font-weight: 700;
    line-height: 1;
    cursor: pointer;
}
.tp-vexp:hover { border-color: var(--admin-accent); color: var(--admin-accent); }

/* 内部版本号 chip：**正常字号、深色**（原来 11px + 等宽红字，发哥说太小又太红） */
.tp-hw-nos { display: inline-flex; flex-wrap: wrap; gap: 4px; }
.tp-hw-chip {
    display: inline-block;
    border: 1px solid var(--admin-border);
    background: var(--admin-surface, #fff);
    color: var(--admin-text);
    padding: 2px 8px;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 14px;
    font-weight: 600;
    letter-spacing: .3px;
    line-height: 1.5;
    white-space: nowrap;
}
.tp-hw-chip.is-off { color: var(--admin-muted); text-decoration: line-through; }

/* v0.12（发哥第 7 项）：**量产状态是一个普通字段** ——
   原来这里有 `.tp-sale` 徽章（绿底绿框 = 在售、红底红框 = 停产），发哥要求"不要绿色、不要框起来"。
   视图已改成纯文本，所以这条徽章规则**整体删除**（留着就是死样式：没人用，却让人以为样式还在生效）。 */

/* ============================================================
   ★ 标准组件层（Design System · 2026-09-27 发哥第 3 项「视觉规范整治」）
   ------------------------------------------------------------
   🔴 这一层是**后台样式的唯一规范来源**。规则：
     1. **视图只用这里列出的类名** —— 不写 inline style 颜色、不新增"一次性"类；
     2. **颜色一律走 `:root` 变量**（`--admin-accent` = 拓朋红 #E2231A），
        要在视图里硬编码颜色 = 以后换主题必漏（已被 lint 纪律禁止）；
     3. **不要用 Tailwind 工具类做后台布局** —— 那是 Vite 构建产物，
        「没用过的类线上根本不存在」（踩过：`grid-cols-3` 不生效）；
        多列布局用 inline `style="display:grid; grid-template-columns:…"`；
     4. 新增组件 → **加在这一层**（文件末尾，天然覆盖历史写法），
        并在 `docs/ops/ui-design-system.md` 登记。
   ------------------------------------------------------------
   10 个标准组件（视图里就这 10 类，别再发明第 11 类）：
     ① 页头      .tp-head / .tp-h1 / .tp-head-ops
     ② 筛选条    .tp-cat-toolbar（要钉在顶部 → 外层套 .tp-sticky-head）
     ③ 操作栏    .tp-actbar / .tp-actbar-row / .tp-actbar-count / .tp-actbar-sep
     ④ 分组卡片  .tp-cat-group / .tp-cat-head / .tp-cat-name / .tp-cat-ops / .tp-section-title
     ⑤ 表格      .tp-tbl / .tp-tbl-wrap / .tp-tbl-sub / .tp-col-{cb,order,num,ops,hw}
     ⑥ 按钮      .tp-btn(主) / .tp-btn-2(次) / .tp-btn-sm / .tp-op / .tp-mini / .tp-vexp / .tp-cat-caret
     ⑦ 表单      .tp-input / .tp-input-{sm,md,lg,xs,full,hw,sale} / .tp-label / .tp-field-inline
                 .tp-check-inline / .tp-err / .tp-fieldset
     ⑧ 徽章      .tp-badge / .tp-chip / .tp-hw-chip
                 ⚠️ `.tp-sale`（在售徽章）已在 v0.12 删除 —— 量产状态是**普通文本**，不用徽章
     ⑨ 弹窗      .tp-modal-mask / .tp-modal(-wide) / .tp-modal-{head,body,foot,x} / .tp-form-grid
     ⑩ 提示      .tp-note / .tp-empty / .tp-alert-danger / .tp-readonly-bar
                 .tp-warn-text / .tp-ok-text / .tp-err-text
   ============================================================ */

/* ⑩-补 只读提示条（v0.11：权限分「编辑/只读」后的统一提示形态） */
.tp-readonly-bar {
    margin: 12px 0;
    padding: 9px 14px;
    border: 1px solid var(--admin-border);
    background: var(--admin-hover);
    color: var(--admin-body-text);
    font-size: 14px;
    line-height: 1.7;
}

/* ④-补 分区标题（内部版本号的「专网 / 公网」两族） */
.tp-section-title {
    display: flex;
    align-items: baseline;
    gap: 10px;
    margin: 22px 0 8px;
    padding-bottom: 6px;
    border-bottom: 2px solid var(--admin-border);
    font-size: 16px;
    font-weight: 700;
    color: var(--admin-text);
}

/* ⑦-补 表单容器：只读时用 `fieldset[disabled]` 一次关掉全部输入（不用逐个 @disabled） */
.tp-fieldset { border: 0; margin: 0; padding: 0; min-width: 0; }
.tp-fieldset:disabled { opacity: .72; }

/* ⑩-补 权限面板图例：新增「操作权限」（能不能改） */
.tp-legend-action { background: var(--admin-accent-soft); color: var(--admin-accent); }

/* 操作权限块：与数据权限同一形态，只把色条与角标换成主色，一眼区分"能不能改" */
.tp-action-block { border-left-color: var(--admin-accent); }
.tp-action-tag { background: var(--admin-accent-soft); color: var(--admin-accent); }

/* ============================================================
   产品域 v0.13（2026-09-27 深夜 · 发哥第 1/2 项）
   —— ① 列宽统一：`table-layout: fixed`（配合视图里的 <colgroup>）
      ② 表单页统一 + 宽屏自适应（编辑产品页原来套了 max-w-xl，右边一大片空白）
   ============================================================ */

/* ① 固定表格布局 —— 必须显式写，否则 colgroup 的宽度会被"内容自动撑开"顶掉，
     各系列的表看起来还是歪的。 */
.tp-tbl-fixed { table-layout: fixed; }
.tp-tbl-fixed td { overflow-wrap: break-word; }

/* 🆕 第 131 轮（发哥）：「表格的自适应做得不好，有些内容还是会挤压」
   ------------------------------------------------------------
   `table-layout: fixed` 只保证**列宽**听话，不保证**内容**不折行 ——
   邮箱、长名称这类串落进窄列就被压成两三行，行高忽高忽低，两张表并排就"对不齐"。

   治法：普通单元格 **一行 + 省略号**（超宽截断，悬停看全）。
     · 行高恒定 → 相邻页/相邻列的行高天然一致；
     · 列不够宽时让内容自己省略，而不是把字挤成竖排。

   ⚠️ 两种 td 必须排除（否则会切掉真内容）：
     · `td[colspan]` —— 展开详情行，里面是整块 panel，截断等于丢内容；
     · `td.tp-ops`   —— 操作列是 flex 换行按钮组，overflow:hidden 会把按钮切掉。
   确实需要多行的单元格，给 td 加 `.is-wrap` 就地恢复（逃生口）。 */
.tp-tbl-fixed > tbody > tr > td:not([colspan]):not(.tp-ops) {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.tp-tbl-fixed > tbody > tr > td.is-wrap {
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
}

/* ② 表单页外壳：**占满可用宽度**（宽屏自适应），不再有 max-width 限制 */
.tp-form { width: 100%; }
.tp-form-body { margin-top: 14px; }

/* 表单分区卡片（一节一张卡，宽屏时靠 grid 自适应列数） */
.tp-form-section {
    border: 1px solid var(--admin-border);
    background: var(--admin-surface, #fff);
    padding: 14px 16px;
}
.tp-form-section + .tp-form-section { margin-top: 12px; }
.tp-form-title {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    margin: 0 0 10px;
    /* 🔴 第 151 轮：这是**区块标题**（资源资料 / 上传资源文件 / 技术参数…）→ 走 H2 档 16px。
       纯数值映射会把它从 15 降到 13，那是"标题比正文还小"，必须按语义单独定。 */
    font-size: 16px;
    font-weight: 600;
    color: var(--admin-text);
}
/* 说明文字（表单里的次级提示，颜色统一走变量） */
.tp-form-note {
    display: block;
    margin-top: 4px;
    font-size: 12px;
    line-height: 1.7;
    color: var(--admin-muted);
}
/* 跨整行（多列 grid 里需要独占一行时用） */
.tp-form-span { grid-column: 1 / -1; }
.tp-form-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    align-items: center;
    margin-top: 14px;
}

/* ============================================================
   ★ 页面骨架间距（2026-09-28 · 根治「元素挨在一起」）

   🔴 为什么必须用这些类、而不是 Tailwind 的 mt-4 / mt-5：
      那些是 **Vite 构建产物** —— 只有"构建那一刻被扫描到"的类才会被生成。
      实测：线上 CSS 里 `.mt-5` 出现 **0 次** → 「盖章记录」筛选框的 mt-5 完全失效，
      于是它和标题**挨在一起**；系统设置的保存按钮同理。
      这类错误 **php -l / view:cache / lint-php.py 全都发现不了**，只能在浏览器里肉眼发现。

      本文件是 `<link>` **直接引入**（还带 ?v=filemtime 自动破缓存）
      → **改完立即生效、永不受构建影响**。

   ➡️ 规矩：页面级/区块级间距一律用下面四个类；写 blade 时**不要**再用 Tailwind 的间距类。
   ============================================================ */
.tp-page-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    flex-wrap: wrap;
    margin-bottom: 18px;      /* 标题行与下方第一个区块留白 */
}
.tp-block-gap { margin-top: 16px; }
.tp-toolbar {
    border: 1px solid var(--admin-border);
    background: var(--admin-surface, #fff);
    padding: 16px;
    margin-top: 16px;         /* 筛选条与上方内容留白 */
}
.tp-actions-row {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 12px;
    margin-top: 22px;         /* 底部操作行（保存/提交）与上方内容留白 */
}


/* ============================================================
   产品规格 · 批量矩阵（2026-09-29 发哥第 7 项）
   —— 行 = 属性、列 = 同系列各型号；首列与表头 sticky，格子直接编辑
   ============================================================ */
.tp-batch-wrap {
    margin-top: 12px;
    border: 1px solid var(--admin-border);
    background: var(--admin-surface, #fff);
    overflow: auto;
    /* 🔴 2026-10-20（发哥第 1 条）：高度按**视口**算，不是 72vh。
       72vh 会让"页头 + 筛选条 + 表格"超过一屏 → 页面本身也要滚，
       一滚，型号那行（thead）就跟着页面移出视野。
       🔴 2026-10-21 第 66 轮：批量模式下统计条已隐藏、筛选条只剩一条，
          所以扣减从 286 收到 **320**（多留一点：宁可表体矮一点，也不要页面跟着滚）。
          ↑ 数值越小表体越高；320 = 顶栏 56 + 页头 60 + 页签 52 + 受众 40 + 筛选条 80 + 余量 32。 */
    max-height: calc(100vh - 320px);
    min-height: 320px;
    overscroll-behavior: contain;   /* 滚到边界时不要把滚动传给页面 */
}
.tp-batch {
    border-collapse: separate;
    border-spacing: 0;
    font-size: 12px;
}
.tp-batch th,
.tp-batch td {
    border-bottom: 1px solid var(--admin-border);
    border-right: 1px solid var(--admin-border);
    padding: 6px 8px;
    vertical-align: middle;
}
.tp-batch thead th {
    position: sticky;
    top: 0;
    z-index: 2;
    background: var(--admin-hover);
    text-align: left;
    white-space: nowrap;
}
/* 首列（属性名）横向钉住 */
.tp-batch th.rowhead,
.tp-batch td.rowhead {
    position: sticky;
    left: 0;
    z-index: 1;
    background: var(--admin-surface, #fff);
    min-width: 200px;
    max-width: 260px;
}
.tp-batch thead th.rowhead { z-index: 3; }
/* 空格子（还没填）给一层淡黄底 → 一眼看出缺哪些 */
.tp-batch td.cellEmpty { background: #FFFDF0; }
.tp-batch-sub {
    margin-top: 2px;
    font-size: 12px;
    font-weight: 400;
    color: var(--admin-muted);
}
/* 矩阵内的输入控件（不依赖 .tp-input，避免被它的尺寸规则带偏） */
.tp-batch-in {
    width: 100%;
    min-width: 86px;
    box-sizing: border-box;
    padding: 4px 6px;
    font-size: 12px;
    color: var(--admin-text);
    background: var(--admin-surface, #fff);
    border: 1px solid var(--admin-border);
}
.tp-batch-in:focus { outline: none; border-color: var(--admin-accent); }
.tp-batch-two { display: flex; gap: 4px; align-items: center; }


/* ============================================================
   频段与运营商矩阵（v0.22 · 2026-09-30 发哥第 3 问）
   —— 三层表头（代际 → 制式/运营商 → 叶子）+ 首列/表头 sticky + 勾选格
   ============================================================ */
.tp-cap-wrap {
    margin-top: 12px;
    border: 1px solid var(--admin-border);
    background: var(--admin-surface, #fff);
    overflow: auto;
    max-height: 72vh;               /* 型号多时纵向滚动，表头钉住 */
}
.tp-cap {
    border-collapse: separate;
    border-spacing: 0;
    font-size: 12px;
}
.tp-cap th,
.tp-cap td {
    border-bottom: 1px solid var(--admin-border);
    border-right: 1px solid var(--admin-border);
    padding: 4px 6px;
    text-align: center;
    vertical-align: middle;
}
/* 整个 thead 一起钉住（三层表头一起吸顶，比逐行算 top 稳） */
.tp-cap thead {
    position: sticky;
    top: 0;
    z-index: 2;
}
.tp-cap thead th {
    background: var(--admin-hover);
    white-space: nowrap;
}
/* 首列（型号 × 内部版本号）横向钉住 */
.tp-cap th.rowhead,
.tp-cap td.rowhead {
    position: sticky;
    left: 0;
    z-index: 1;
    background: var(--admin-surface, #fff);
    min-width: 160px;
    text-align: left;
}
.tp-cap thead th.rowhead { z-index: 3; }
.tp-cap td.is-on { background: var(--admin-accent-soft); }
.tp-cap td input[type="checkbox"] { accent-color: var(--admin-accent); }
.tp-cap-model { font-weight: 600; }
.tp-cap-sub {
    margin-top: 2px;
    font-size: 12px;
    font-weight: 400;
    color: var(--admin-muted);
}
/* 导入弹窗里的「待你决定」条目 */
.tp-cap-decide {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 10px;
    margin-top: 8px;
    padding: 8px 10px;
    border: 1px solid var(--admin-border);
    background: var(--admin-surface, #fff);
}


/* 公网频段 · 产品规格里的只读展示（v0.23 · 2026-09-30） */
.tp-band-ro {
    margin-top: 10px;
    padding: 8px 12px;
    border: 1px dashed var(--admin-border);
    background: var(--admin-hover);
}
.tp-band-ro-h {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    font-size: 14px;
    font-weight: 600;
    color: var(--admin-body-text);
}
.tp-band-ro-row {
    display: flex;
    align-items: baseline;
    gap: 8px;
    margin-top: 4px;
    font-size: 12px;
    color: var(--admin-body-text);
}
.tp-band-ro-row code { color: var(--admin-muted); }

/* ============================================================
   ★ Tailwind 缺失类补齐（2026-09-28）

   Vite 构建产物里**没有**这些类（构建时间早于它们被使用的时间），
   而 blade 里已经在用 → 线上等于没写。补齐后全站立刻正常（本文件直引，无需构建）。

   🔴 以后再发现"某个类线上没生效"：先跑 `python scripts/check-ui-classes.py` 看清单，
      再在这里补一条即可。`deploy.sh` 已把该检查设为**闸门** —— 改动 blade 若引入
      产物里没有的间距/尺寸类，会直接挡住部署。
   ============================================================ */
.gap-5  { gap: 1.25rem; }
.mb-1   { margin-bottom: .25rem; }
.mb-2   { margin-bottom: .5rem; }
.ml-1   { margin-left: .25rem; }
.mr-2   { margin-right: .5rem; }
.mt-5   { margin-top: 1.25rem; }
.pb-1   { padding-bottom: .25rem; }
.pb-2   { padding-bottom: .5rem; }
.pl-0   { padding-left: 0; }
.pr-2   { padding-right: .5rem; }
.pr-3   { padding-right: .75rem; }
.pt-4   { padding-top: 1rem; }
.py-10  { padding-top: 2.5rem; padding-bottom: 2.5rem; }
.w-32   { width: 8rem; }
.w-40   { width: 10rem; }
.w-48   { width: 12rem; }
.w-52   { width: 13rem; }
.w-96   { width: 24rem; }
.gap-1\.5 { gap: .375rem; }
.pb-1\.5  { padding-bottom: .375rem; }
.py-2\.5  { padding-top: .625rem; padding-bottom: .625rem; }
/* space-y-1.5：子元素之间自动留白（Tailwind 的语义） */
.space-y-1\.5 > :not([hidden]) ~ :not([hidden]) { margin-top: .375rem; }

/* ============================================================
   ★ 列表「行操作」标准件（2026-09-29 发哥第 9 项）

   起因：内部版本号的 ↑↓ 与产品目录的 ↑↓ **样式不一样、位置也不一样**。
   发哥：「这应该是个标准的小组件，而不是哪个页面需要用的时候临时开发」。
   → 所有列表的"操作列"统一用下面这几个类（配合 <x-admin.row-sort />）。
   ============================================================ */
/* 操作列容器：按钮之间统一间距，不换行 */
.tp-row-ops {
    display: flex;
    align-items: center;
    gap: 6px;
    white-space: nowrap;
}
/* 排序按钮组：把 ↑ ↓ 视觉上归成一组（与后面的"编辑/删除"拉开一点） */
.tp-sort-group {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    margin-right: 6px;
}
/* 图标按钮（排序、小操作）：方形、无边、hover 才出底色 —— 各页统一 */
.tp-icon-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    border: 1px solid transparent;
    border-radius: 0;                /* 第 155 轮：3px 属越档，统一并入"直角"档（22px 小方块用 6px 会显得太圆） */
    background: transparent;
    color: var(--admin-body-text);
    font-size: 14px;
    line-height: 1;
    cursor: pointer;
}
.tp-icon-btn:hover {
    border-color: var(--admin-border);
    background: var(--admin-hover);
}
/* 边界行（首行 ↑ / 末行 ↓）：禁用 —— 2026-09-29 加了 first/last 支持后必须有这条，
   否则"点了没反应"，跟"按钮坏了"分不清（各页原来用 Tailwind 的 disabled:opacity-30，
   那是构建产物、线上不存在 —— 参见 L0 铁律「Tailwind 类不可靠」）。 */
.tp-icon-btn:disabled {
    opacity: .3;
    cursor: default;
}
.tp-icon-btn:disabled:hover {
    border-color: transparent;
    background: transparent;
}

/* 状态徽章（三态：量产 / 开发中 / 停产）—— 列表里用，不是勾选框 */
.tp-st {
    font-size: 12px;
    /* 🔴 显式不加粗（2026-09-29 发哥第 4 项）：产品目录与内部版本号的「量产/停产/开发中」
       都是**普通字段**，不该长得像重点。显式声明可覆盖任何继承来的字重。 */
    font-weight: 400;
}
/* 🔴 2026-10-12 第 41 轮（发哥第 4 项）：**量产状态一律不上色**
   发哥原话：「量产状态不要绿色的字体，开发中也不需要有颜色」。
   原来 `.is-ok` 是绿（#059669）、`.is-dev` 是橙（#B45309）—— 那是"重点徽章"的写法，
   而量产状态只是**另一个普通字段**（与 2026-09-29 去掉字重是同一条理由）。
   现值：`量产`/`开发中` 都随正文色（不上色）；`停产` 保持 muted（**变淡 = 失效**，
   是全站既有语义，如 `tr.is-off` 的 opacity —— 不是"上色"）。
   🔴 三个 class 继续保留在模板里（语义锚点），只把颜色去掉 —— 别再往这儿加颜色。 */
.tp-st.is-ok  { color: inherit; }
.tp-st.is-dev { color: inherit; }
.tp-st.is-off { color: var(--admin-muted); }

/* ============================================================
   ★ Tailwind 缺失类补齐 · 颜色 / 字号（2026-09-29）

   🔴 2026-09-29 把 `check-ui-classes.py` 的检查范围从「间距/尺寸」**扩到颜色/字重/字号**后，
     一次就查出 **22 个类线上根本不存在**（合计用了 ~190 处）——最典型的是**提示条**：

         class="border border-emerald-500/40 bg-emerald-500/10 px-4 py-3 text-sm text-emerald-700"

     `text-emerald-700` 在产物里有（字是绿的），`bg-emerald-500/10` 和 `border-emerald-500/40`
     **都不在** → 线上看到的是**一片没有底色、没有边框的绿字**。amber（琥珀）整族更是**一个都没有**。

   ⚠️ 这套类本来就不该手写（见 L0 铁律：Tailwind 工具类是构建产物，不可靠）。
      **新代码请用标准类**：`.tp-alert-ok / .tp-alert-warn / .tp-alert-err`（下表下方），
      下面的补齐只是让**存量 190 处**立刻恢复正确外观，不再逐页去改。
   ============================================================ */
/* —— 文字色 —— */
.text-amber-600   { color: #d97706; }
.text-amber-700   { color: #b45309; }
.text-orange-600  { color: #ea580c; }
.text-sky-600     { color: #0284c7; }
.text-emerald-800 { color: #065f46; }

/* —— 底色（实色）—— */
.bg-amber-500     { background-color: #f59e0b; }
.bg-red-500       { background-color: #ef4444; }
.bg-red-50        { background-color: #fef2f2; }
.bg-emerald-50    { background-color: #ecfdf5; }

/* —— 底色（带透明度：Tailwind 的 `/10` 写法）—— */
.bg-amber-500\/10   { background-color: rgba(245, 158, 11, .10); }
.bg-emerald-500\/10 { background-color: rgba(16, 185, 129, .10); }

/* —— 边框色（实色）—— */
.border-red-300      { border-color: #fca5a5; }
.border-red-400      { border-color: #f87171; }
.border-emerald-300  { border-color: #6ee7b7; }
.border-emerald-400  { border-color: #34d399; }

/* —— 边框色（带透明度）—— */
.border-amber-500\/30 { border-color: rgba(245, 158, 11, .30); }
.border-amber-500\/40 { border-color: rgba(245, 158, 11, .40); }
.border-amber-500\/50 { border-color: rgba(245, 158, 11, .50); }
.border-amber-500\/60 { border-color: rgba(245, 158, 11, .60); }
.border-emerald-500\/40 { border-color: rgba(16, 185, 129, .40); }

/* —— 字重 / 字号 —— */
.font-normal { font-weight: 400; }
/* 🔴 第 153 轮：`.text-base` 原来在这里还有一份 `1rem / 1.5rem`（= 16px），
   与文件末尾那组五档覆盖（14px）形成"两套真相"（后者胜、这条是死代码）。
   已删 —— 只保留末尾五档那一处定义。 */

/* —— 变体类（hover / 断点）：原来闸门跳过含 `:` 的 token → 从没被检查过，
      2026-09-29 单独盘了一遍，缺的就这几条 —— */
.hover\:bg-amber-500\/10:hover { background-color: rgba(245, 158, 11, .10); }
@media (min-width: 768px) {
    .md\:w-48 { width: 12rem; }
    .md\:w-52 { width: 13rem; }
}
/* ⚠️ 仍有 5 处「任意值变体」线上不存在（`hover:bg-[var(--admin-accent-soft)]` /
   `hover:border-[var(--admin-accent)]` / `hover:text-[var(--accent)]`）——
   只影响**鼠标悬停**的反馈色，不影响可用性；下次改到那几个页面时顺手换成标准类。 */

/* ============================================================
   ★ 标准「提示条」（新代码用这个，别再手写一串 Tailwind 颜色类）

   三种语气，一个类搞定（原来的写法是 6~7 个类拼起来，还依赖构建产物）：
       <div class="tp-alert tp-alert-ok">保存成功</div>
       <div class="tp-alert tp-alert-warn">注意……</div>
       <div class="tp-alert tp-alert-err">出错了……</div>
   ============================================================ */
.tp-alert {
    margin-top: 14px;
    padding: 10px 14px;
    border: 1px solid transparent;
    border-radius: var(--admin-r-sm);
    font-size: 14px;
    line-height: 1.6;
}
.tp-alert-ok   { border-color: rgba(16, 185, 129, .40); background: rgba(16, 185, 129, .10); color: #047857; }
.tp-alert-warn { border-color: rgba(245, 158, 11, .40); background: rgba(245, 158, 11, .10); color: #b45309; }
.tp-alert-err  { border-color: rgba(239, 68, 68, .40);  background: rgba(239, 68, 68, .10);  color: #b91c1c; }

/* ============================================================
   ⑪ 每页位置：**低清底图 + 网格叠加**（2026-10-07 发哥要求）
   ------------------------------------------------------------
   目的：点格子时能看出「章会落在文件的哪个位置」。
   🔴 三条硬规则：
     ① **gap 必须为 0** —— 格子要与底图**逐格严格对应**。
        原来 `gap: 3px`：10 列 × 3px × 12 个间隔 ≈ 底部累计偏移 30px+，
        底图对得再准也没用（这正是"看不出位置"的根因之一）。
     ② 底图**只做界面显示**：分辨率/质量与盖章件输出完全无关（不碰"逐像素等价"验收）；
        且是**临时文件**，提交后即删。
     ③ 悬停/选中一律用**半透明** —— 实色会把底图整个盖住，等于白做。
   ============================================================ */
.tp-page-canvas {
    position: relative;
    width: 100%;
    overflow: hidden;
    border: 1px solid var(--admin-border);
    background: var(--admin-surface, #fff);
}
.tp-page-preview {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: fill;          /* 容器比例 = 页面真实比例 → 不会变形 */
    pointer-events: none;      /* 别抢格子的左键 / 右键事件 */
    user-select: none;
}
.tp-grid {
    position: absolute;
    inset: 0;
    gap: 0;                    /* 硬规则 ① */
    width: auto;
}
.tp-grid-cell {
    border: 0;
    /* 🔴 2026-10-07 第十三轮（发哥第 3 项："线还是有点粗，看上去不像只有 1px"）
       真因：旧写法是每格画**一圈** outline + `outline-offset:-1px`，
             相邻两格各画一条 → 相距 2px → 视觉就是"粗线"（且 offset -1px 使之更明显）。
       现改为**单线**：每格只画「右 + 下」一条 1px 线，内部每条缝只由左/上侧格子提供，
             永远只有 1px；最外圈由 `.tp-page-canvas` 的 1px border 兜
             （= 与容器框**同粗细、同颜色**，正是发哥要的"跟这个框统一"）。
       末列 / 末行不画（避免与容器框叠成 2px）→ `.is-last-col` / `.is-last-row`。 */
    border-right: 1px solid var(--admin-border);
    border-bottom: 1px solid var(--admin-border);
    outline: none;
    background: transparent;
    color: rgba(0, 0, 0, .26);
}
.tp-grid-cell.is-last-col { border-right: 0; }
.tp-grid-cell.is-last-row { border-bottom: 0; }
.tp-grid-cell:hover { background: rgba(226, 35, 26, .22); }
.tp-grid-cell.is-pos  { background: rgba(226, 35, 26, .62);  color: #fff; font-weight: 700; }
.tp-grid-cell.is-sign { background: rgba(21, 101, 192, .62); color: #fff; font-weight: 700; }
/* 渲染失败 / 未生成底图时（`.is-plain`）回到原来的白底样式，格子仍清晰可点 */
.tp-grid.is-plain .tp-grid-cell { background: var(--admin-surface, #fff); }
.tp-grid.is-plain .tp-grid-cell:hover { background: var(--admin-accent-soft); }
.tp-grid.is-plain .tp-grid-cell.is-pos  { background: var(--admin-accent); }
.tp-grid.is-plain .tp-grid-cell.is-sign { background: #1565C0; }

/* ⑪·补 盖章设置页：脚本路径输入框
   🔴 不用 Tailwind 的 `min-w-64`（Tailwind v3 的 min-width 标度里**没有** 64 →
      类不生效；admin.css 是单一来源，不参与 Vite 构建，写这里必然生效） */
.tp-set-path { flex: 1 1 0%; min-width: 16rem; }

/* ============================================================
   ★ 统一内容框（2026-10-07 发哥第 2/3 项「全站 UI 统一」）
   ------------------------------------------------------------
   问题：同一页里「章/签名」「文件类型」「盖章申请设置」三种板块各写各的容器 ——
        有的把表格裸在外面（没有白框）、有的手写 `border bg-white p-5`、有的用 `.tp-step`。
        视觉不统一，而且**以后统一改 UI 时还得逐个单独优化**。

   规矩（写 blade 时照做）：
     ① **所有板块级容器一律用 `.tp-card`**（白底 + 1px 边框 + 统一内距）
     ② 板块标题用 `.tp-card-title`（等宽 + 半粗）
     ③ 板块之间的间距用 `.tp-card-gap`（16px）
     ④ 🔴 **不要在视图里手写** `border bg-white p-5` / `mt-5 border …` 这类容器组合
        —— 它们无法被统一样式一次改掉（这正是发哥担心的"单独优化"）

   > 与「★ 标准组件层」的关系：那边是**全站** 10 个标准组件（`.tp-cat-group` 等），
   > 这边是**业务申请域 + 工作台**共用的"内容区块"容器；两者都只在本文件里定义。
   ============================================================ */
.tp-card {
    border: 1px solid var(--admin-border);
    background: var(--admin-surface, #fff);
    padding: 16px 18px;          /* 统一内距（覆盖上方工作台段的 18/20） */
}
.tp-card-gap { margin-top: 16px; }
.tp-card-title {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 14px;
    font-weight: 600;
    color: var(--admin-text);
}
/* 步骤卡片（提交盖章页）与统一内容框保持同一内距，视觉才是"一种卡片" */
.tp-step { padding: 16px 18px; }

/* 侧栏品牌行：**logo 居中**（2026-10-07 发哥第 5 项） */
.tp-sidebar-brand { justify-content: center; }

/* 顶栏：邮箱与「我的工作台」入口已移除（发哥第 6 项：左侧导航人人都有「我的工作台」，
   右上角没必要重复）→ `.tp-topbar-mail` 成为死样式，按规范顺手删掉。 */

/* 每页位置：格子线已于 2026-10-07 第十三轮改为**单线**（见上文 `.tp-grid-cell` 段）。
   此处原有的 `outline: 1px solid var(--admin-border)` 覆盖规则**已删除** ——
   它只改了颜色，没去掉 `outline-offset:-1px`（相邻两线相距 2px = 看着粗的真因）。 */

/* ============================================================
   ⑬ 表格列宽（2026-10-07 发哥第 4 / 6 项）
   ------------------------------------------------------------
   ④ 产品型号 / 别名两列**收紧并挨在一起**：
      原来列宽（150/130px）远超内容，加上 `.tp-tbl { width:100% }` 在
      `table-layout:fixed` 下会把固定列**等比撑大** → 列内右侧一大片空白，
      看上去两列"离得很远"。现按最长型号的实际像素（≈86px）+ 内距定宽。
   ⑥ 全站表格**列宽可拖动**（人人可用，不属于数据编辑权限）：
      热区贴在表头右缘；宽度按**账号**存浏览器 localStorage，
      重新登录仍然记得（逻辑见 `public/js/admin.js`）。
   ============================================================ */

/* 紧凑列：内容短、需要与相邻列挨紧；超长省略，用拖动手柄可自行放宽 */
.tp-col-tight { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* 🔴 2026-10-12 第 41 轮（发哥第 5 项）：**需要看全的列**（产品中文名称）
   ------------------------------------------------------------
   发哥原话：「产品中文名称，不管怎么拉动列的宽度，都不会显示完整的数据」。
   真因：这一列用了 `.tp-col-tight`（nowrap + ellipsis）——
        中文名最长 21 字（≈275px），而列宽只有 158px，**只能靠省略号**；
        列宽改大也只是把"截断点"往后挪，名字一长又截。
   治法：这一类列**允许换行**（`white-space:normal` + 断词）——
        无论列多宽，文字都会折行显示完整，不再依赖"拉多宽"。
   🔴 只给"必须看全"的列用；短字段（型号/别名/颜色/配置）继续 `.tp-col-tight`，
      否则整行高度会被拉高、表格变得难扫。 */
.tp-col-wrap { white-space: normal; overflow: visible; overflow-wrap: anywhere; }

/* 列宽拖动热区（不占布局、不挡排序按钮与链接点击） */
.tp-tbl thead th { position: relative; }
/* 🔴 2026-10-21 第 66 轮（发哥第 5 条）真因：这里原先把 `.tp-batch thead th` 与
      `.tp-matrix thead th` 也写成了 `position: relative` ——
      它与上面「钉住表头」的 `position: sticky` **同选择器、后定义胜** ⇒
      sticky 被覆盖 ⇒ 矩阵滚动到下方时**表头与型号行跟着页面跑掉**（发哥说"没有固定"）。
      治法：只给 `.tp-tbl`（不需钉住的那种）加 relative；
      而 `sticky` 本身**就是定位元素**，`.tp-colresize` 的绝对定位照样有包含块，
      拖动热区功能一点没丢。 */

.tp-colresize {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    width: 7px;
    cursor: col-resize;
    user-select: none;
    background: transparent;
    z-index: 3;
}
.tp-colresize:hover,
.tp-colresize.is-active { background: var(--admin-accent); opacity: .45; }
body.tp-colresizing { cursor: col-resize !important; }
body.tp-colresizing * { user-select: none !important; }

/* 场景库「用户画像」列（2026-10-07 第十四轮 · 发哥第 2 项）
   `.tp-matrix td` 默认 `white-space: nowrap` → 画像多了会把表格撑爆，
   所以这一列要**允许换行**并限宽 */
.tp-cell-personas { white-space: normal !important; max-width: 300px; }
.tp-personas { display: inline-flex; flex-wrap: wrap; gap: 4px; }
.tp-persona { background: var(--admin-hover); }

/* ============================================================
   ⑭ POC批量激活 · 仪表盘（2026-10-07 第十六轮 · 发哥要求 5）
   ------------------------------------------------------------
   放在 admin.css（单一来源 · <link> 直引 · filemtime 自动破缓存）：
   **不要用 Tailwind 类** —— 那是 Vite 构建产物，后加的类线上不存在
   （见 `scripts/check-ui-classes.py` 头部记录的真实事故）。
   ============================================================ */
/* ============================================================
   ★ 统计卡（仪表盘数字）—— 全站**唯一**一套（2026-10-08 第 26 轮 · 发哥第 2 项）
   ------------------------------------------------------------
   问题：概览页用 Tailwind 的 `font-mono text-3xl font-bold text-[var(--admin-accent)]`
        （30px / 700 / 强调红），而 POC批量激活用 `.tp-stat-value`
        （30px / 600 / 正文黑）—— **同一类东西两套风格**。
   改法：以 `.tp-stat-*` 为唯一来源，**模板一律用它**，不再手写 Tailwind 字号；
        并让两边的取值完全一致（30px / 700 / 拓朋红）。
   🔴 语义色例外（仍然保留，因为它表达"好/坏"而不是"风格"）：
        `.is-ok` 绿 · `.is-danger` 红。
   ============================================================ */
.tp-stat-grid {
    display: grid;
    gap: 12px;
    /* 🔴 auto-fill + 190~240px：2 张卡不撑满半屏、7 张卡也不会挤成一条 */
    grid-template-columns: repeat(auto-fill, minmax(190px, 240px));
}
.tp-stat-card {
    border: 1px solid var(--admin-border);
    background: var(--admin-surface, #fff);
    padding: 14px 18px;
}
.tp-stat-label { font-size: 14px; color: var(--admin-muted); }
.tp-stat-value {
    margin-top: 6px;
    font-family: var(--admin-font-sans);   /* 与全站同一字体（第 25 轮统一），不写等宽 */
    font-variant-numeric: tabular-nums;    /* 数字等宽对齐 */
    font-size: 30px;
    font-weight: 700;                      /* = 概览原值 */
    line-height: 36px;                     /* 第 153 轮：整数行高（原 1.1 = 33px 不在 2px 网格上） */
    color: var(--admin-accent);            /* = 概览原值（拓朋红） */
}
.tp-stat-value.is-ok     { color: #1a7a2e; }
.tp-stat-value.is-danger { color: var(--admin-danger); }
.tp-stat-note { margin-top: 6px; font-size: 14px; color: var(--admin-muted); }

/* ============================================================
   ⑮ ⓘ「本页说明」折叠弹窗（2026-10-07 第十七轮 · 发哥第 5 项）
   ------------------------------------------------------------
   全站**唯一**的说明样式：以后要改说明的观感，只动这一段，
   不用再去每个页面找散落的说明文字（这正是发哥要的效果）。
   结构见 `resources/views/components/admin/help.blade.php`；
   交互见 `public/js/admin.js` ②（纯前端开关，不走 Livewire）。
   ============================================================ */
.tp-help { display: inline-flex; align-items: center; vertical-align: middle; }

.tp-help-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 18px;
    height: 18px;
    margin-left: 6px;
    padding: 0;
    font-size: 12px;
    line-height: 1;
    color: var(--admin-muted);
    background: transparent;
    border: 1px solid var(--admin-border);
    border-radius: 50%;
    cursor: pointer;
}
.tp-help-btn:hover { color: var(--admin-accent); border-color: var(--admin-accent); }

/* 遮罩：fixed + 高层级（不受页面 sticky / overflow 影响） */
.tp-help-mask {
    position: fixed;
    inset: 0;
    z-index: 60;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
    background: rgba(0, 0, 0, .42);
}
.tp-help-mask[hidden] { display: none; }   /* 🔴 必须显式写：flex 会盖掉 hidden 的默认 display:none */

.tp-help-dialog {
    display: flex;
    flex-direction: column;
    width: 100%;
    max-width: 780px;
    max-height: 84vh;
    background: var(--admin-surface, #fff);
    border: 1px solid var(--admin-border);
}
.tp-help-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 12px 18px;
    border-bottom: 1px solid var(--admin-border);
}
.tp-help-title { font-size: 16px; font-weight: 600; color: var(--admin-text); }   /* H2 档 */
.tp-help-x {
    padding: 2px 6px;
    font-size: 14px;
    color: var(--admin-muted);
    background: transparent;
    border: 0;
    cursor: pointer;
}
.tp-help-x:hover { color: var(--admin-accent); }

.tp-help-body { flex: 1 1 auto; overflow-y: auto; padding: 14px 18px; }
.tp-help-sec + .tp-help-sec {
    margin-top: 14px;
    padding-top: 14px;
    border-top: 1px dashed var(--admin-border);
}
.tp-help-sec-title {
    margin: 0 0 6px 0;
    font-size: 14px;
    font-weight: 600;
    color: var(--admin-text);
}
.tp-help-sec-body { font-size: 14px; line-height: 1.85; color: var(--admin-body-text); }
.tp-help-sec-body code {
    padding: 1px 4px;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    background: var(--admin-hover);
}
.tp-help-foot {
    padding: 10px 18px;
    font-size: 12px;
    color: var(--admin-muted);
    border-top: 1px solid var(--admin-border);
}

/* 弹窗打开时锁住页面滚动 */
body.tp-help-open { overflow: hidden; }

@media (max-width: 767px) {
    .tp-help-mask { padding: 12px; }
    .tp-help-dialog { max-height: 90vh; }
}

/* ============================================================
   ⑯ 场景库列表：**默认列宽**（2026-10-07 第十七轮 · 发哥第 2 项）
   ------------------------------------------------------------
   原来 `.tp-matrix` 是 `table-layout: auto` + `.rowhead { min-width: 230px }`：
   自动布局下第一列（勾选）会分到一大块空白，把「场景」列挤出视窗，
   默认打开必须横向拖动才看得到场景名。
   → 本表改用 `table-layout: fixed` + 模板里的 `<colgroup>` 指定默认比例：
       勾选 4% · 场景 30% · 层级 8% · 用户画像 22% · 关联型号 22% · 素材 6% · 操作 8%
   🔴 与「列宽可拖动」（admin.js ①）不冲突：拖动会把列宽写成显式像素，覆盖 colgroup。
   ============================================================ */
.tp-scen-tbl { table-layout: fixed; width: 100%; }
/* 固定布局下 `nowrap` 会让长内容溢出列外 → 本表允许换行 */
.tp-scen-tbl th,
.tp-scen-tbl td { white-space: normal; word-break: break-word; }
/* 🔴 必须清掉全局的 min-width:230px：固定布局下它会把 colgroup 的比例撑破 */
.tp-scen-tbl .rowhead { min-width: 0; }

/* ============================================================
   ⑰ 推送设置（2026-10-07 第十八轮 · 发哥第 3 项）
   「分类管理」「上传规则」合并页：板块标题的间距
   ============================================================ */
.tp-ota-sec { margin: 18px 0 10px; }

/* 🔴 第 136 轮（发哥）：「字体也不一样了，需要和其他内容统一字体」
   `<x-ota.version>` **不再自带任何字体/字号/颜色** ——
   原来这里挂了 `font-family: ui-monospace`，结果那一列的字形与同表其他列不一致
   （POC 升级看板本来是正文，被强行变成等宽）。
   现在组件只输出文本，字体字号由所在的 `<td>` 决定（该列本来是什么就是什么）。 */

/* ============================================================
   ⑱ 侧栏：一级 = 图标(左) + 文字(右)；二级 = hover 飞出
   （2026-10-08 第 21 轮起 · 第 25 轮改成「飞出」，对齐探索台 rail）
   ------------------------------------------------------------
   发哥 2026-10-08 第 25 轮第 1 项：「要探索台 rail 是 hover 飞出的」
   ⇒ 二级菜单从「点击内联展开」改成 **鼠标悬停 → 右侧飞出**（与
     docs/preview/ui-lab-v2.html 的 rail 行为逐项一致）。
      · 一级行仍是「图标在左 + 文字在右」（第 21 轮定稿，未变）
      · 二级不再占侧栏高度 ⇒ 侧栏永远只有 13 行（不再"又长又乱"）
      · 当前页所在分组高亮（否则飞出后看不到"我在哪"）

   🔴 全部走「后定义覆盖」——上方原始定义**一行不改**（本文件既有约定）。
   ============================================================ */

/* ---------- 高度统一（发哥第 3 项：「没有一致的高度」）----------
   原来 品牌区 64px / 顶栏 56px → 两者底线不齐，视觉上"错位一截"。
   现在统一为 **60px**：品牌区底线 = 顶栏底线，形成一条贯通的横向分界线。 */
.tp-sidebar { width: 212px; overflow: visible; z-index: 40; }   /* 212px = 探索台 rail.rail-wide 的宽度 */
/* 🔴 `z-index: 40` 不是装饰，是**飞出层能看见的前提**（第 25 轮实测踩到）：
   `position: sticky` **本身就会创建层叠上下文** ⇒ 侧栏里任何元素的 `z-index`
   （包括飞出层的 60）都**逃不出侧栏这一层**；而侧栏在 DOM 里排在 `<main>` **之前**，
   于是主区里 `z-index: 20` 的 `.tp-sticky-head` 把飞出层**整块盖住** ——
   现象是 `display:block`、`getBoundingClientRect` 也正常，但**肉眼什么都看不到**，
   `document.elementFromPoint(卡片中心)` 返回的是主区的 `<select>`。
   40 = 高于 `.tp-sticky-head`(20)，也高于 `.tp-topbar`(30，两者不重叠)；
   `.tp-modal-mask` / `.tp-help-mask`(60) 仍在最上层。 */
/* 🔴 第 29 轮（发哥第 2 项）：logo 再下移 20px ⟹ 品牌区 60 → **80px**。
   这里与文件末尾「㉔」段两处都声明 —— **故意保持两处同值**（本文件约定「后定义覆盖」，
   但留一个过期的 60px 会让后来人以为品牌区还是 60px）。
   ⚠️ 顶栏仍是 60px：品牌区与顶栏**不再同高**（差 20px = 下移量）。 */
.tp-sidebar-brand { height: 80px; }
/* 🔴 第 154 轮：`.tp-topbar` 的 height **不再在这里重复声明** —— 统一由 `:root` 的
   `--tp-topbar-h`（60px）决定。这里留一条重复声明就是"两套真相"：
   末尾那条 60 生效、而下游 `top:56px` / `top:94px` 没跟着改，就出现了 4px 错位。 */

/* ---------- 侧栏导航容器 ---------- */
/* 🔴 默认 `overflow: visible` —— 因为 `overflow-y:auto` 会让 `overflow-x` 按规范也算成 `auto`，
   把 :hover 的绝对定位飞出层裁掉（第 25 轮实测踩到：矮视口下飞出"点了没反应"）。
   🔴 只有**菜单真的放不下**时才允许滚动：由 `admin.js` 探测高度后加 `.is-scroll`。
      此时飞出层由 JS 改成 `position: fixed` 定位 → **不再受任何祖先裁切影响**（双保险）。 */
.tp-sidebar-nav { --tp-side-gutter: 8px; padding: var(--tp-side-gutter); overflow: visible; }
.tp-sidebar-nav.is-scroll { overflow-y: auto; }

/* —— 一级行（我的工作台 / 单条分组 / 可折叠分组标题）统一为「图标 + 文字」 —— */
.tp-side-home,
.tp-side-solo,
.tp-side-group-head {
    display: flex;
    align-items: center;
    justify-content: flex-start;   /* 🔴 必须显式写：原规则是 space-between，会把文字推离图标 */
    gap: 10px;
    width: 100%;
    /* 🔴 2026-10-08 第 26 轮（发哥第 7 项）：
       ① 「字体不要太粗」→ 字重 600 → **500**；
       ② 「每行的行高增加 50%」→ 38px × 1.5 = **57px**（内距 8/12 → 12/14 配合，
          文字仍靠 flex 垂直居中，不会偏上）。 */
    /* 🔴 第 158 轮（发哥）：「一级菜单字号改小一档，行高也减少 20%」
       16 → 14（五档阶梯的下一档，12 会太小）、57 → 46（×0.8）、
       上下内距 12 → 10 配合，文字仍靠 flex 垂直居中。 */
    min-height: 46px;
    padding: 10px 14px;
    border: none;
    border-radius: var(--admin-r-sm);
    background: none;
    text-align: left;
    font-size: 14px;
    font-weight: 500;
    line-height: 1.3;
    color: var(--admin-text);
    text-decoration: none;
    cursor: pointer;
}
.tp-side-home .tp-ic,
.tp-side-solo .tp-ic,
.tp-side-group-head .tp-ic { color: var(--admin-subtle); flex: 0 0 auto; }

.tp-side-home:hover,
.tp-side-solo:hover,
.tp-side-group-head:hover { background: var(--admin-hover); }
.tp-side-home:hover .tp-ic,
.tp-side-solo:hover .tp-ic,
.tp-side-group-head:hover .tp-ic { color: var(--admin-accent); }

/* 分组之间不再用「粗上边框 + 大间距」分隔（那更像表格），改小间距 + 悬停底色 */
.tp-side-group { margin-top: 2px; border-top: 0; position: relative; }
.tp-side-solo  { margin-top: 2px; border-top: 0; }
.tp-side-home  { margin-top: 2px; }

.tp-side-home.is-active,
.tp-side-solo.is-active { color: var(--admin-accent); background: var(--admin-accent-soft); }
.tp-side-home.is-active .tp-ic,
.tp-side-solo.is-active .tp-ic { color: var(--admin-accent); }

/* 🔴 展开指示箭头：飞出模式永远是 ▸（不再随展开旋转）
   —— 它现在是「右边有子菜单」的记号，不是「展开/收起」的开关。 */
.tp-side-group-head .tp-side-caret { margin-left: auto; font-size: 11px; color: var(--admin-subtle); }
.tp-side-group-head:hover .tp-side-caret { color: var(--admin-accent); }

/* 🔴 当前页所在分组高亮（飞出模式下必需：否则用户不知道自己在哪一组） */
.tp-side-group.is-active > .tp-side-group-head { color: var(--admin-accent); background: var(--admin-accent-soft); }
.tp-side-group.is-active > .tp-side-group-head .tp-ic { color: var(--admin-accent); }

/* ---------- 二级：hover 飞出（右侧浮层）----------
   🔴 为什么 `position: absolute` 就够、不需要 JS：
      .tp-sidebar-nav / .tp-sidebar 都已 `overflow: visible` ⇒ 不会被裁。
      菜单总量只有 13 行，不需要侧栏内滚动 ⇒ 不必用 fixed + JS 定位（少一份运行时故障面）。

   🔴 `left: 100%` 是**相对分组行**（不是相对侧栏）：
      分组行的右边 = 212(侧栏) − 8(导航内边距) − 1(侧栏右边框) = 203。
   🔴 `padding-left: 16px` 是**透明桥**（8 内边距 + 1 边框 + 7 视觉间隙）：
      ① 鼠标必须能"穿过"这段而不离开分组 ⇒ 否则触发 mouseleave，菜单闪一下又没了；
      ② 同时也让**卡片**从 219px 开始（= 侧栏右边 212 + 7px 间隙），
         不会像第一版那样压住侧栏的右边框（left:100% + 6px 时卡片落在 209，压线）。 */
.tp-side-flyout {
    display: none;
    position: absolute;
    left: 100%;
    top: -6px;
    z-index: 60;
    min-width: 208px;
    padding-left: 16px;                /* 透明桥，别删（见上） */
}
.tp-side-group:hover > .tp-side-flyout,
.tp-side-group:focus-within > .tp-side-flyout { display: block; }

/* 靠近侧栏底部的分组：飞出层向上对齐，避免超出视口 */
.tp-side-group:nth-last-child(-n+3) > .tp-side-flyout { top: auto; bottom: -6px; }

.tp-side-flyout-inner {
    padding: 6px;
    background: var(--admin-surface);
    border: 1px solid var(--admin-border);
    border-radius: var(--admin-r-md) !important;
    box-shadow: 0 12px 32px rgba(16, 24, 40, .14);
    max-height: min(72vh, 560px);
    overflow-y: auto;
}
/* 🔴 2026-10-08 第 26 轮（发哥第 7 项③）：**二级也要有图标**（模板每项传 `icon`）。
   图标列固定 18px + 间距 10px ⇒ 同级项左对齐整齐；无图标的项由组件回落 `box`，不会空白。 */
.tp-side-link {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 9px 12px;
    border-radius: var(--admin-r-sm);
    font-size: 14px;
    font-weight: 400;
    color: var(--admin-body-text);
    text-decoration: none;
    white-space: nowrap;
}
.tp-side-link .tp-ic { color: var(--admin-subtle); flex: 0 0 auto; }
.tp-side-link:hover { background: var(--admin-hover); }
.tp-side-link:hover .tp-ic { color: var(--admin-accent); }
.tp-side-link.is-active { background: var(--admin-accent-soft); color: var(--admin-accent); font-weight: 600; }
.tp-side-link.is-active .tp-ic { color: var(--admin-accent); }
.tp-side-flyout-title {
    padding: 5px 9px 7px;
    margin-bottom: 4px;
    border-bottom: 1px solid var(--admin-border);
    font-size: 14px;
    font-weight: 700;
    letter-spacing: .04em;
    color: var(--admin-muted);
}

/* 二级菜单项：样式统一在上面 `.tp-side-flyout-inner` 之后（第 26 轮改成 flex + 图标），
   这里不再重复定义（两处定义同一个类 = 历史上"线看着粗"那类事故的根源）。 */


/* ============================================================
   ⑲ 字号阶梯：整体上抬一档
   （2026-10-08 第 21 轮 · 发哥第 3 项「字体都太小了」）
   ------------------------------------------------------------
   🔴 为什么用「覆盖 Tailwind 工具类」而不是逐个改视图：
      实测全站字号的实际来源就是视图里的 `text-xs`(667 处 = 12px)
      与 `text-sm`(455 处 = 14px)。逐个文件改既慢又必然漏改；
      而 admin.css 是在 `@vite` **之后**用 <link> 加载的 ——
      **同优先级、后定义胜** ⇒ 这里覆盖一处，全站生效。

   🔴 阶梯（与皮肤解耦：可读性是全局决策，不随换肤变化）
      正文 / 菜单 / 表格 / 表单  ← text-sm   14  → 15px
      次要 / 说明               ← text-xs   12  → 13.5px
      极小 / 徽章 / 刻度         ← [10px]/[11px] → 12 / 13px
      h1(24px) 与 2xl(24px) **保持不动**（发哥已锁定 24px）
   ⚠️ 盖章页的 `.tp-grid-cell`(11px) / `.tp-tick`(9px) 是**按 A4 底图对齐的刻度**，
      **故意做小**，本段不碰它们。
   ============================================================ */
/* 🔴 这一版覆盖已**作废**（被文末第 ⑤ 段取代，同一组选择器后定义胜）。
   第 150 轮统一字号时删掉，避免"两处定义、改一处不生效"的老坑。 */

/* 🔴 行高必须跟在字号之后重定义：Tailwind 的 text-* 自带固定 rem 行高，
   字号变大后不改行高会「挤在一起」。这两条是实测全站唯一在用的行高类。 */
.leading-relaxed { line-height: 1.625; }
.leading-none    { line-height: 1; }

/* —— admin.css 自身的高频通用小字类同步上抬（保持与视图文字同一阶梯）—— */
.tp-note           { font-size: 14px; }
.tp-badge          { font-size: 14px; }
.tp-btn            { font-size: 14px; }
.tp-btn-2          { font-size: 14px; }
.tp-btn-sm         { font-size: 14px; }
.tp-topbar-user    { font-size: 14px; }
.tp-topbar-sub     { font-size: 14px; }
.tp-logout-btn     { font-size: 14px; }
.tp-group-title    { font-size: 16px; }
.tp-subgroup-title { font-size: 14px; }
.tp-kv-k           { font-size: 14px; }
.tp-chip           { font-size: 14px; }
.tp-check          { font-size: 14px; }
.tp-page-no        { font-size: 14px; }
.tp-perm-group     { font-size: 14px; }
.tp-perm-item      { font-size: 14px; }
.tp-step-title     { font-size: 14px; }
.tp-drop-btn       { font-size: 14px; }
.tp-banner-sm      { font-size: 14px; }
.tp-link-btn       { font-size: 14px; }
.tp-seg > button,
.tp-seg > a        { font-size: 14px; }

/* ============================================================
   ⑳ 皮肤 C 落地（2026-10-08 第 21 轮补 · 发哥「天差地别」的根因修复）
   ------------------------------------------------------------
   发哥在探索台选的是 skin=c + density=loose，但线上观感差很远。
   逐项核查：上一轮我**只落了 3 条功能改动（图标 / logo / 字号的一半），皮肤本体没落地**：
     ① 圆角 —— app.css（前后台共用）有一条前台品牌规则：
        `* { border-radius: 0 !important }` ⇒ 后台**所有圆角被强制归零**，
        C 皮肤的大圆角根本不可能生效（上一轮报告里提过、当时未处理）。
     ② 配色 —— admin.css 的 :root 仍是 A 皮肤（瑞士极简）取值，没换成 C。
     ③ 密度 —— density=loose 没落到表格 / 控件上。
     ④ 字号 —— 只改了 Tailwind 工具类那一半；而**产品目录页一个 Tailwind 字号类都不用**
        （736 行模板里 text-xs / text-sm = 0 次），它的字号**全部**来自本文件的 .tp-* 类
        ⇒ 那一页字号**一点没变**。「字还是小」的真因就在这里。
   ============================================================ */

/* ---------- ① 圆角体系：基线 + 例外（2026-10-08 第 24 轮重做） ---------- */
/* 🔴 为什么重做：第 22/23 轮我用【白名单】给 `.tp-*` 类加圆角 ——
   但页面上还有大量**不带 `.tp-*` 类**的盒子（Tailwind 写的容器 / 统计块 / 列表框…），
   它们仍然是直角 ⇒ 同一页里"有的圆有的不圆"，**比全直角更难看**（发哥 2026-10-08 指出）。

   ⇒ 改成【基线 + 例外】三层：
        第 1 层 例外：**不该圆的先显式归零**（html/body、表格结构、盖章 A4 网格）
        第 2 层 基线：`*` 给**所有盒子**一个统一小圆角（= 控件档）
        第 3 层 分档：大件 10px、胶囊 999px
     🔴 三层都带 `!important`；同 `!important` 下**比特异性** ⇒ 具体类永远赢过 `*`，
        所以书写顺序不影响结果（放一起只为可读）。

   🔴 为什么必须 `!important`：app.css（前后台共用）有一条前台品牌规则
      `* { border-radius: 0 !important }`；**带 !important 的声明 > 不带 !important 的声明，
      与特异性无关** ⇒ 后台任何**不带** !important 的 border-radius 都会被它压死。
      （第 22 轮真踩过：线上 computed 全是 `0px`。）
      **以后在 admin.css 里写 border-radius，一律带 !important。** */

/* 第 1 层 · 例外：这些**不该**圆 */
html,
body { border-radius: 0 !important; }                        /* 页面四角 */
table, thead, tbody, tfoot, tr, th, td,
colgroup, col { border-radius: 0 !important; }                /* 表格结构（表头有底色，圆了会变成一排小药丸） */
.tp-grid,
.tp-grid-cell,
.tp-tick { border-radius: 0 !important; }                     /* 盖章页 A4 底图刻度，圆了会错位 */

/* 🔴 第 152 轮（发哥截图）：「文本裁剪容器」一律直角。
   根因不是字体 —— 是下面第 2 层基线 `* { border-radius: 6px !important }` 给这些**高度≈一行字**
   的容器（`overflow: hidden`）也上了圆角；`overflow: hidden` 的**裁剪路径跟着圆角走**，
   于是字形的四个角被切掉（发哥原话：「每个字的四边，都是被圆角给覆盖的，显示都不完整」）。
   ⇒ 口径：**有底色/边框的盒子才许圆；会裁掉一行的文字容器一律 0**。
   闸门：`scripts/audit-radius-clip.py`（新增裁字容器不显式归零 = FAIL）。 */
.tp-tagpage-label,
.tp-col-tight,
.tp-kb-gitem > span:first-child,
.tp-swatch-cell .tp-note { border-radius: 0 !important; }

/* 第 2 层 · 基线：所有盒子统一小圆角（控件档） */
* { border-radius: var(--admin-r-sm) !important; }

/* 第 3 层 · 大件 / 胶囊 */
.tp-card,
.tp-page-card,
.tp-tbl-wrap,
.tp-actbar,
.tp-banner,
.tp-empty,
.tp-modal-box,
.tp-help-dialog,
.tp-help-panel { border-radius: var(--admin-r-md) !important; }

.tp-badge,
.tp-chip,
.tp-hw-chip,
.tp-st,
.tp-tag { border-radius: var(--admin-r-pill) !important; }

/* 圆角令牌（🔴 随皮肤变，见文件末尾「㉑ 皮肤 a / b / c」） */
:root {
    --admin-r-sm: 6px;       /* 控件档：按钮 / 输入 / 菜单项 —— 也是**所有盒子的基线** */
    --admin-r-md: 10px;      /* 大件：卡片 / 面板 / 弹窗 */
    --admin-r-pill: 999px;   /* 胶囊：徽章 / 标签 */
}

/* ---------- ② 配色：国际化 SaaS（柔和中性色） ---------- */
/* 🔴 2026-10-08 第 25 轮：单一皮肤 ⇒ 取值已**合并进文件开头的 `:root`**，
   这里不再重复声明（避免"两处定义同一变量"，那正是历史上踩过的坑）。
   想换风格：改开头 `:root` 的那 9 个变量即可。 */

/* ---------- ③ 密度：std（标准密度）----------
   🔴 发哥 2026-10-08 第 25 轮第 6 项：「density=loose 要全部改为 density=std」。
   探索台的 loose 是 11×14（表格）/ 8×12（输入）—— 放到真实列表页太松散
   （一屏看不到几行）。std 取值为：表格 9×12、输入 6×10、操作条 10×14。
   原始值（表格 8×10）不动，这里只做**最终覆盖**。 */
.tp-tbl thead th { padding: 9px 12px; }
.tp-tbl tbody td { padding: 9px 12px; }
.tp-input { padding: 6px 10px; }
.tp-actbar { margin-top: 14px; padding: 10px 14px; }
.tp-actbar-row { padding: 5px 0; }

/* ---------- ④ 字号阶梯：按「字段重要性」分档，不再一刀切 ---------- */
/* 🔴 第 128 轮（发哥：「很多筛选框的字体比较大，显得页面有点乱，按字段的重要性统一规范」）。
   原来的做法是**两档、而且偏大**（正文 15 / 次要 13.5）—— 输入框和筛选按钮跟正文一个尺寸，
   层级看不出差别，一页十几个筛选框就显得"糊成一片"。

   现在四档，差值收窄到 1px 一档（大屏也不刺眼）：

     ① 正文 / 值        13.5px —— 表格单元格、矩阵、空态
     ② 控件            12.5px —— 筛选按钮(seg) / 按钮 / 输入框 / 下拉 / 勾选项
     ③ 标签·表头·说明   12.5px —— label / note / 表头 / 徽章 / 计数 / 角标
     ④ 极次要          沿用 ③（不再另开档，避免"9 个碎档"回潮）

   🔴 判据：**要动手点的 → 12.5；要读的正文 → 13.5；解释性文字 → 12.5**。
   ⚠️ 盖章页 .tp-grid-cell / .tp-tick 是 A4 底图刻度，**故意做小，不在本段**。 */

/* 正文档 15px */
.tp-tbl,
.tp-matrix,
.tp-input,
.tp-input-sm,
.tp-input-md,
.tp-input-full,
.tp-input-hw,
.tp-input-sale,
.tp-empty,
.tp-check-inline { font-size: 14px; }

/* 次要档 → D4 辅助 12px（第 153 轮：原来是 13.5 → 13 → 13，一直跟正文档挤在一起）
   🔴 判据只看**用途**，不看原来的数值：表头 / 字段标签 / 时间 / 角标 / 计数 / 操作链接 /
      矩阵说明 —— 这些都是"读一眼就够"的次要信息，统一 12px + muted 色。
      它们在页面上永远和 14px 的正文相邻，2px + 颜色，两层一眼能分开；
      13 与 14 只差 1px，反而是"看着有点不齐但又说不出哪里"。 */
.tp-tbl thead th,
.tp-label,
.tp-err,
.tp-form-note,
.tp-note,
.tp-page-no,
.tp-mini,
.tp-vexp,
.tp-pre,
.tp-badge,
.tp-chip,
.tp-hw-chip,
.tp-st,
.tp-tag,
.tp-cnt,
.tp-cap,
.tp-cap-sub,
.tp-batch,
.tp-batch-in,
.tp-batch-sub,
.tp-legend-tag,
.tp-data-tag,
.tp-data-dep,
.tp-kb-gcount,
.tp-kb-sub .tp-kb-gitem,
.tp-band-ro-row,
.tp-field > span,
.tp-matrix .tp-sc-parent,
.tp-sc-parent,
.tp-stat-label,
.tp-stat-note,
.tp-help-btn,
.tp-help-foot,
.tp-perm-legend,
.tp-spec-unit,
.tp-spec-meta,
.tp-spec-mini,
.tp-spec-chip,
.tp-spec-g { font-size: 12px; }

/* Tailwind 任意值那一档（[10px] / [11px]）→ 统一到 12px（最小文字档）。
   🔴 用属性选择器而不是转义类名（`.text-\[10px\]`）—— 少一处反斜杠就少一处转义踩坑。 */
[class*="text-[10px]"],
[class*="text-[11px]"] { font-size: 12px; line-height: 18px; }

/* ---------- ⑤ Tailwind 字号类 → 五档（第 153 轮定稿） ----------
   🔴 为什么必须映射：视图里 `text-xs` 500+ 处、`text-sm` 300+ 处。
     不映射的话，同一页会同时出现 12px / 14px / 16px（Tailwind 原生值与 .tp-* 打架）。
   🔴 顺序：admin.css 在 app.css 之后加载，同名单类选择器**后者胜**，不用 !important。
   🔴 行高一律写**整数 px**（2px 网格），不写倍率 —— 倍率在 12/14 上会算出 18.6 这种亚像素，
      表格行高、sticky 偏移这类整数布局就对不上了。

       档位      字号  行高   干什么
       D4 辅助   12    18    表头 / 时间 / 提示 / 角标 / 表格内文字
       D3 正文   14    20    正文 / 按钮 / 输入 / 菜单 / 卡片说明
       D2 标题   16    24    卡片 / 弹窗 / 表单区标题
       D1 页面   24    32    每页唯一 h1
       D5 数据   30    36    统计卡主数字（唯一允许大于 D1 的文字） */
.text-xs   { font-size: 12px; line-height: 18px; }   /* D4 辅助 */
.text-sm   { font-size: 14px; line-height: 20px; }   /* D3 正文 */
.text-base { font-size: 14px; line-height: 20px; }   /* D3 正文（= text-sm，别再分两档） */
.text-lg   { font-size: 16px; line-height: 24px; }   /* D2 标题 */
.text-xl   { font-size: 24px; line-height: 32px; }   /* D1 页面标题：原本映射 16，导致「优惠券管理 / 电商链接」两个 h1 比别的页小一号 */

/* ============================================================
   ㉑ 字体单一化：后台**只有一种西文字体** = Inter
   （2026-10-08 第 25 轮 · 发哥第 5 项「英文字体没有统一，要统一」）
   ------------------------------------------------------------
   现象（发哥截图）：同一页里
     · 中文 / 表头  → 走 `--admin-font-sans`（但 Inter 从未加载 ⇒ 落到雅黑西文）
     · 板号 / 芯片 / 统计数字 / 卡片标题 → 走 `ui-monospace, …`（Windows 落到 Consolas）
   ⇒ 一页两种西文字体，看着"不统一"。

   修法两步：
     ① 在文件开头补 Inter 的 @font-face（真正把字体带上）；
     ② 把全站的**等宽类一并归到 Inter** —— 编号/数字用 `tabular-nums` 做等宽数字，
        这样"对齐"效果仍在，但字体只有一种。

   🔴 为什么带 `!important`：等宽声明散落在本文件 8 处（`.tp-mono` / `.tp-pre` /
      `.tp-group-title` / `.tp-card-title` / `.tp-stat-value` / `.tp-hw-chip` /
      `.tp-step-title` / `.tp-help-sec-body code`），另外 app.css 还有
      `.font-mono, code, pre`。这是**一次性的全局字体决策**，逐个改特异性不如一刀切干净。
   🔴 想要"编号保留等宽质感"？把下面这段的 `font-family` 换成
      `'JetBrains Mono', Consolas, monospace` 并在开头补 JetBrains 的 @font-face 即可
      （字体文件已在 public/fonts/），一句话的事。
   ============================================================ */
.tp-mono,
.tp-pre,
.tp-group-title,
.tp-card-title,
.tp-step-title,
.tp-hw-chip,
.tp-stat-value,
.tp-help-sec-body code,
.font-mono,
code,
pre,
kbd,
samp,
tt {
    font-family: var(--admin-font-sans) !important;
}

/* 数字等宽：同一字体下让数字列垂直对齐（不换字体，unify 与对齐两者兼得） */
.tp-mono,
.tp-stat-value,
.tp-hw-chip { font-variant-numeric: tabular-nums; }


/* ============================================================
   ㉒ 待机锁定遮罩（2026-10-08 第 27 轮 · 发哥第 2 项）
   ------------------------------------------------------------
   显示开关由 `<html class="tp-idle-on">` 驱动：
     · 主壳隐藏 / 禁止滚动 → 写在 layouts/admin.blade.php 的 <head> 内联 <style>
       （必须早于 <body> 生效，防"先看到内容再被盖住"）
     · 遮罩本体样式 → 本段
   组件：app/Livewire/Admin/IdleLock.php（只渲染 DOM + 校验密码）
   计时：public/js/admin.js 的 ⑤ 段（localStorage 记最后活动时间）
   ============================================================ */
.tp-idle-mask { display: none; }   /* 默认不显示（也保证未锁定时绝不挡路） */

html.tp-idle-on .tp-idle-mask {
    display: flex;
    align-items: center;
    justify-content: center;
    position: fixed;
    inset: 0;
    z-index: 9000;                 /* 高于侧栏(40)/顶栏(30)/表格粘性头(20) */
    padding: 24px;
    /* 半透明 + 模糊：即使主壳没被隐藏，也看不清页面内容（双保险） */
    background: rgba(248, 250, 252, .97);
    -webkit-backdrop-filter: blur(10px);
    backdrop-filter: blur(10px);
}

.tp-idle-card {
    width: min(92vw, 380px);
    padding: 30px 26px 24px;
    text-align: center;
    background: var(--admin-surface);
    border: 1px solid var(--admin-border);
    border-radius: var(--admin-r-md) !important;
    box-shadow: 0 18px 48px rgba(16, 24, 40, .16);
}
/* 🔴 第 28 轮：logo 与底部提示已按发哥要求从视图去掉 → 这两条样式删除（不留死样式） */
.tp-idle-title { margin-top: 18px; font-size: 16px; font-weight: 600; color: var(--admin-text); }
.tp-idle-sub { margin-top: 6px; font-size: 14px; color: var(--admin-muted); }

.tp-idle-input {
    margin-top: 18px;
    width: 100%;
    padding: 12px 14px;
    text-align: center;
    font-size: 16px;
    letter-spacing: 10px;
    text-indent: 10px;              /* 抵消 letter-spacing 在末位多出的间距（否则视觉偏左） */
    color: var(--admin-text);
    background: var(--admin-bg);
    border: 1px solid var(--admin-border);
    border-radius: var(--admin-r-sm) !important;
    outline: none;
}
.tp-idle-input:focus {
    border-color: var(--admin-accent);
    box-shadow: 0 0 0 3px var(--admin-accent-soft);
}
.tp-idle-err { margin-top: 12px; font-size: 14px; color: var(--admin-accent); }

/* ============================================================
   ㉓ 仪表盘「按模块分组」+ 待办卡片（2026-10-08 第 27 轮 · 发哥第 1/3 项）
   ------------------------------------------------------------
   背景：原来 7 个统计卡**平铺**在一起，模块混在一起看不出来谁是谁；
        发哥要求「尽量按模块分类，不然一堆数据看了会觉得乱」。
   改法：每个模块一个小标题 + 一组 `.tp-stat-*` 卡片；
        「需要我处理」额外上强调色（可操作的信息优先）。
   🔴 统计卡数字的样式**继续只用 `.tp-stat-*`**（第 26 轮统一，全站唯一写法），
      本段只加"分组外壳"与一个强调色变体。
   ============================================================ */

.tp-dash-module { margin-top: 22px; }
.tp-dash-module-head {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 10px;
    font-size: 14px;
    font-weight: 600;
    color: var(--admin-text);
}
.tp-dash-module-head .tp-ic { color: var(--admin-muted); flex: 0 0 auto; }
.tp-dash-module.is-todo .tp-dash-module-head { color: var(--admin-accent); }
.tp-dash-module.is-todo .tp-dash-module-head .tp-ic { color: var(--admin-accent); }

/* 统计卡可点（整卡是链接；没有目标页时是 `.is-plain`，外观一模一样但不可点） */
.tp-stat-link { display: block; text-decoration: none; color: inherit; }
.tp-stat-link.is-plain { cursor: default; }
.tp-stat-link:not(.is-plain):hover .tp-stat-label { color: var(--admin-accent); }

/* 强调色数字（待办数：盖章待审 / 待授权 / 待审问答） */
.tp-stat-value.is-accent { color: var(--admin-accent); }

/* 「我的工作台」的待办卡片（发哥第 1 项）
   🔴 第 99 轮：原先左侧那条强调色竖条已删除（AI 味）；现在靠数字本身的强调色
   （`.tp-stat-value.is-accent`）标识，不再额外加装饰线。
   → 规则整体删掉，类名保留在视图里也不影响（无样式 = 继承卡片自身）。 */

/* ============================================================
   ㉔ 第 28 轮微调（发哥 4 项）
   ------------------------------------------------------------
   ① 侧栏品牌区：去掉下面的细线、去掉 logo 图片的圆角、logo 下移并居中
   ② 待机遮罩：密码只显示 `*`（真值透明 + 星号层，同步逻辑在 admin.js ⑤ 段）
   ============================================================ */

/* ---------- ① 侧栏品牌区 ---------- */
.tp-sidebar-brand {
    /* 🔴 第 29 轮（发哥第 2 项）：logo 再下移 20px ⟹ 品牌区 60px → **80px**。
       为什么不是"在 60px 里换个对齐"就行：logo 图 47px 高，60px 盒子扣掉 8px 底距后
       只剩 4.6px 的可动空间（它本来已经靠底了），要再下移 20px **必然**需要那 20px 空间。
       ⇒ 直接加高盒子 20px（logo 仍靠底对齐）＝ logo 与下方菜单**整体下移 20px**，间距不变，
         不会出现"logo 压住『我的工作台』"。
       ⚠️ 代价：品牌区不再等于顶栏高度（顶栏仍是 60px）。V25 那条"两者同高"的断言已随需求更新。 */
    height: 80px;
    align-items: flex-end;      /* logo 靠底 → 与下方菜单保持固定间距 */
    justify-content: center;    /* 水平居中 */
    padding: 0 12px 8px;        /* 底部留 8px：紧贴「我的工作台」上方，不挤 */
    border-bottom: 0;           /* 🔴 去掉品牌区下面那条细线 */
}
/* 🔴 logo 图本身是**直角红块**，被第 24 轮的基线规则 `* { border-radius: … !important }`
   裁成了圆角 —— 这里显式还原直角（发哥：去掉 logo 周围的圆角）。
   特异性 (0,1,0) > `*` (0,0,0)，且同为 !important ⇒ 本行胜。 */
.tp-sidebar-brand-img { border-radius: 0 !important; }

/* ---------- ② 待机遮罩：密码以 * 显示 ---------- */
.tp-idle-inputwrap { position: relative; margin-top: 18px; }
.tp-idle-inputwrap .tp-idle-input {
    margin-top: 0;
    color: transparent !important;      /* 真值不可见（可见的是下面的星号层） */
    caret-color: var(--admin-accent);   /* 但光标必须看得见，否则像卡死 */
}
.tp-idle-stars {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 16px;
    letter-spacing: 6px;
    text-indent: 6px;
    color: var(--admin-text);
    pointer-events: none;               /* 别把点击/聚焦挡在输入框之外 */
}
.tp-idle-err { min-height: 19px; }      /* 占位：出错时卡片不跳高 */

/* ============================================================
   ㉕ 产品目录：商品编号 + 行内图标（2026-10-09 第 32 轮 · 发哥第 1 项）
   ------------------------------------------------------------
   背景：产品目录改成「一行 = 一个商品」（型号 × 内部版本号）平铺，
        并给每行加**商品编号**列与「编辑 / 复制」图标。
   🔴 纪律：颜色一律走变量；具体圆角一律带 `!important`（见 CONVENTIONS §3）；
           字号 ≥ 13.5px（audit-font-ladder.py 是硬闸门）。
   ============================================================ */
.tp-col-sku { white-space: nowrap; }
/* 🔴 2026-10-10：商品编号列右侧与「内部版本号」列左侧挨太近（截图里 `C09-B36E31P15✎↓` 粘连感）
      → 两列各让出一点内距。 */
.tp-cat-tbl .tp-col-sku { padding-right: 16px; }
.tp-cat-tbl .tp-col-hw { padding-left: 14px; }

/* 🔴 2026-10-09 第 32 轮实测修正：`排序 ↑↓ + 版本号 + 删版本 ×` 挤在一格里会**溢出到下一列**
      （截图里出现 `B29E22P8C10-B29E22P8` 这种粘连）→ 给这两列里的图标按钮统一留左边距。
      列宽也同步加宽（186px，见视图 `$cols`）。 */
.tp-col-hw { white-space: nowrap; }
.tp-col-hw .tp-ico-btn,
.tp-col-sku .tp-ico-btn { margin-left: 6px; }
.tp-col-hw .tp-vexp,
.tp-col-sku .tp-vexp { margin-right: 2px; }

.tp-sku {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 14px;
    font-weight: 600;
    color: var(--admin-text);
    letter-spacing: .2px;
}
/* 人工改过的编号（≠ 自动拼的值）——🔴 2026-10-12 第 41 轮（发哥第 4 项）：
   **不再标红**（发哥原话「产品编号不要有红色的字体」）。
   原来这里用 `var(--admin-accent)`（拓朋红）标"人工编号"，但整列红字太抢眼，
   而"是不是人工编号"已经由同格下方的小字说明（自动值 = …）表达 ⇒ 颜色多余。
   🔴 class 保留（语义锚点 + 将来要换表达方式只改这一处），但**不设颜色**。 */
.tp-sku.is-custom { color: inherit; }
/* 非研发视角：一个产品多个编号并排 */
.tp-sku + .tp-sku { margin-left: 8px; }

.tp-sku-edit { display: flex; align-items: center; gap: 4px; min-width: 0; }
/* 🔴 2026-10-10 实测修正：原来写 `min-width:128px` → 输入框 + 保存/取消把 172px 的列**撑破**，
     溢出压到「明细」列上（截图里按钮点不到）。改成 **flex 自适应列宽**（`min-width:0` 是关键）。 */
.tp-sku-edit .tp-input {
    flex: 1 1 auto;
    min-width: 0;
    width: 100%;
    padding: 3px 6px;
    font-size: 14px;
}
.tp-sku-edit .tp-ico-btn { flex: 0 0 auto; }

/* 行内图标按钮（编辑 / 复制 / 改编号）—— 与文字按钮 `.tp-op` 区分开 */
.tp-ico-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    padding: 0;
    border: 1px solid var(--admin-border);
    border-radius: var(--admin-r-sm) !important;
    background: var(--admin-surface);
    color: var(--admin-body-text);
    cursor: pointer;
    text-decoration: none;
    line-height: 1;
    transition: border-color .12s, color .12s, background .12s;
}
.tp-ico-btn:hover {
    border-color: var(--admin-accent-soft-40);
    color: var(--admin-accent);
    background: var(--admin-accent-soft);
}
.tp-ico-btn .tp-ic { display: block; }
/* 危险动作（删版本）—— 平时低调，hover 才红 */
.tp-ico-btn.is-danger { width: 22px; height: 22px; font-size: 14px; line-height: 1; color: var(--admin-muted); }
.tp-ico-btn.is-danger:hover { border-color: var(--admin-danger); color: var(--admin-danger); background: var(--admin-danger-soft); }
/* 确认态（保存编号）—— 品牌色 */
.tp-ico-btn.is-ok { border-color: var(--admin-accent-soft-40); color: var(--admin-accent); background: var(--admin-accent-soft); }

/* ============================================================
   产品目录表：**「操作」列固定在右侧**（2026-10-10）
   ------------------------------------------------------------
   背景：发哥 2026-10-10 把列分两级后字段变多（一级 7 列 + 二级 8 列，总宽 ≈1800px），
        横向滚动时最右的「修改 / 复制」会滚出视野 → 必须常驻可见。
   🔴 `position: sticky` 的祖先不能是 `overflow:hidden`（本项目 `.tp-tbl-wrap` 是 `overflow-x:auto` ✅）
   🔴 必须给背景色，否则下层内容会透出来。
   ⚠️ 只作用于产品目录表（`.tp-cat-tbl`）—— 别的表格仍保持原样。
   ============================================================ */
/* 🔴 2026-10-11 第 35 轮：**去掉操作列的 sticky 右固定** ——
   第 33 轮把它钉在右侧（横滚时「修改 / 复制」常驻可见）；但 `position:sticky; right:0`
   会**浮在内容之上**，把紧邻它左边的整整一列盖住。列变多后，被盖住的正是
   发哥点名要看的「量产状态」（截图上完全看不见）。
   取舍：**内容完整 > 操作便利** ⇒ 恢复普通列（横滚到最右即可见操作）。
   ⚠️ 若将来要恢复固定列，必须同时给表格右侧**预留等宽空白**，不能只靠 sticky。 */

/* ============================================================
   产品目录 · **冻结列**（2026-10-12 第 36 轮 · 发哥第 6 条）
   ------------------------------------------------------------
   冻结「序 · 操作 · 产品型号 · 产品别名」（含勾选框）—— 横向滚动时这几列不动。
   🔴 三条必须：
     ① `left` 由**列注册表**算出（`ProductColumns::frozenForView()` → 内联 style），别在这里写死；
     ② 必须给**背景色**，否则下层单元格会透出来（第 33 轮 sticky 列踩过）；
     ③ 数据行 z-index 必须**低于**表头，否则横滚时表头被行盖住。
   ============================================================ */
.tp-cat-tbl td.tp-frozen { background: var(--admin-surface); z-index: 2; }
.tp-cat-tbl thead th.tp-frozen { background: var(--admin-hover); z-index: 3; }

/* ============================================================
   ㉖ 产品目录：**单一大表**（2026-10-15 第 53 轮 · 发哥第 1 条）
   ------------------------------------------------------------
   发哥原话：「你看公开状态和显示路径，在默认情况下，就是不一致的……
              把所有系列合并成一个大表格，然后所有列是统一设置宽的，
              包括下拉导航条后，每列的标题行，都要固定在最上面，
              下面固定有个左右的导航条」。

   旧做法：每个系列一张独立 `<table>` ⇒ 各自的表头、各自的横滚条，
          纵向看过去列位置对不上（截图取证）。
   新做法（本段）：
     ① `.tp-cat-scroll`  = **定高滚动容器**：横向 + 纵向都在它内部滚，
        ⇒ 横向滚动条**固定在该容器底部**（永远可见），纵向滚动时表头不跑；
     ② `.tp-cat-tbl thead th` = `position:sticky; top:0` ⇒ **表头吸顶**；
     ③ `.tp-cat-row` = 系列标题行（表内一行，`colspan` 跨全列）。

   🔴 `border-collapse: separate` 是**必须的**：`collapse` 下 sticky 表头的
      `border-bottom` 会跟着内容滚走（Chrome 已知行为）⇒ 改为 separate + 每格自带底边。
   ============================================================ */
.tp-cat-scroll {
    margin-top: 14px;
    border: 1px solid var(--admin-border);
    background: var(--admin-surface);
    /* 🔴 定高 = 视口 − 顶栏(56) − 页头/筛选条/操作栏(约 200) − 余量(20)。
       这样：纵向滚动发生在本容器内 ⇒ 表头吸顶；横向滚动条钉在本容器底部 ⇒ 永远可见。 */
    max-height: calc(100vh - 276px);
    min-height: 320px;
    overflow: auto;          /* 横滚条固定在本容器底部；纵向滚动时表头吸顶 */
    -webkit-overflow-scrolling: touch;
}
.tp-cat-scroll .tp-cat-tbl { border-collapse: separate; border-spacing: 0; }

/* ① 表头吸顶（z-index 必须高于：
      body 冻结列 2 → 表头普通格 5 → 表头冻结格 6） */
.tp-cat-tbl thead th {
    position: sticky;
    top: 0;
    z-index: 5;
    background: var(--admin-hover);
}
.tp-cat-tbl thead th.tp-frozen { z-index: 6; }

/* ③ 系列标题行：整行一条，操作按钮照旧靠右 */
.tp-cat-row > td {
    padding: 0;
    background: var(--admin-hover);
    border-top: 1px solid var(--admin-border);
    border-bottom: 1px solid var(--admin-border);
}
.tp-cat-scroll tbody:first-of-type .tp-cat-row > td { border-top: 0; }
.tp-cat-bar {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 9px 12px;
}

/* 「＋ 加版本」输入槽：平铺后**独立占一行**（原来挂在明细子表里） */
.tp-addrow > td { background: var(--admin-hover); padding-top: 10px; padding-bottom: 12px; }

/* 极小按钮（编号编辑里的「存」/「×」） */
.tp-btn-xs {
    padding: 3px 8px;
    font-size: 14px;
    border-radius: var(--admin-r-sm) !important;
}

/* ============================================================
   配件目录 · 「配件 × 产品」适配矩阵（2026-10-17 第 55 轮 · 发哥第 6 条）
   ------------------------------------------------------------
   · 壳复用公网频段的 `.tp-cap` / `.tp-cap-wrap`（吸顶 thead + 横向滚动 + 首列钉住）
   · 本表多一个**第二冻结列**（配件编号）：第一列「类别」固定 104px，
     所以第二列 `sticky left` 定在 104px —— 🔴 两个宽度**必须一起改**，改了记得同步
   · 点位：● 标准配件 / ○ 可选配件 / - 不可适配（`Product::EMPTY_MARK`）
   ============================================================ */
.tp-cap.tp-mx td.rowhead,
.tp-cap.tp-mx th.rowhead { width: 92px; min-width: 92px; }
.tp-cap.tp-mx td.rowhead2,
.tp-cap.tp-mx th.rowhead2 {
    position: sticky;
    left: 92px;
    z-index: 1;
    background: var(--admin-surface, #fff);
    width: 116px;
    min-width: 116px;
    text-align: left;
}
.tp-cap.tp-mx td.rowhead3,
.tp-cap.tp-mx th.rowhead3 {
    position: sticky;
    left: 208px;
    z-index: 1;
    background: var(--admin-surface, #fff);
    width: 190px;
    min-width: 190px;
    text-align: left;
}
.tp-cap.tp-mx thead th.rowhead2,
.tp-cap.tp-mx thead th.rowhead3 { z-index: 3; }
/* 表头第一行的「系列」跨列格（第 56 轮：第一行 8 个系列、第二行各系列下的型号）
   🔴 第 57 轮（发哥第 5 条）：**必须是不透明色**。
      原来用 `--admin-accent-soft`（rgba 0.07）—— 这行是 sticky 吸顶的，
      底下的产品行会**透上来**，看起来"系列行和下面的内容重叠"。
      `#FDF0EF` = 同一个红 7% 叠在白底上的**实色**（视觉一致、能挡住内容）。 */
.tp-cap.tp-mx th.tp-mx-series {
    background: #FDF0EF;
    color: var(--admin-accent);
    font-weight: 600;
    /* 吸顶时上下都要盖住：给一条底边，避免和下一行表头糊在一起 */
    box-shadow: inset 0 -1px 0 var(--admin-border);
}

/* 三个点位（表里 + 图例通用 —— 名字带 `tp-mx-` 前缀，不会撞别处） */
.tp-mx-std { color: var(--admin-accent); font-weight: 600; }
.tp-mx-opt { color: var(--admin-accent); }
.tp-mx-none { color: var(--admin-muted); }

/* 停用的配件：只弱化左侧两列（点位颜色保留 —— 它确实还在被引用） */
.tp-cap.tp-mx tr.is-off td.rowhead,
.tp-cap.tp-mx tr.is-off td.rowhead2 { color: var(--admin-muted); }

/* ============================================================
   产品标准 · 部件色块取色器（2026-10-17 第 56 轮 · 发哥第 7 条）
   ------------------------------------------------------------
   部件列由 `pim_options.set_code='color_part'` 字典**动态**渲染，
   所以这里只负责"一格色块长什么样"，加部件不用改 CSS。
   ============================================================ */
.tp-swatch-cell {
    display: flex;
    align-items: center;
    gap: 4px;
    min-width: 0;
    max-width: 100%;
    overflow: hidden;                /* 🔴 第 60 轮：不允许色块/色值溢到下一列 */
}
.tp-swatch-in {
    width: 34px;
    height: 24px;
    flex: 0 0 34px;                  /* 不压缩、不撑破列宽 */
    padding: 0;
    border: 1px solid var(--admin-border);
    border-radius: 0 !important;     /* 全局圆角会把它变成药丸，取色器必须方 */
    background: none;
    cursor: pointer;
}
.tp-swatch-cell .tp-note {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 14px;
    line-height: 1.1;
}


/* ============================================================
   第 60 轮 · 字段说明 ⓘ（发哥第 9 条）
   ============================================================ */
.tp-field-hint { position: relative; display: inline-flex; align-items: center; margin-left: 4px; vertical-align: middle; }
.tp-field-hint-btn {
    border: 0; background: none; padding: 0 2px; cursor: pointer;
    color: var(--admin-muted); font-size: 14px; line-height: 1;
}
.tp-field-hint-btn:hover { color: var(--admin-accent); }
.tp-field-hint-pop {
    position: absolute; top: calc(100% + 6px); left: -8px; z-index: 70;
    width: max-content; max-width: 360px; padding: 8px 10px;
    background: var(--admin-surface); color: var(--admin-body-text);
    border: 1px solid var(--admin-border); border-radius: 6px !important;
    box-shadow: 0 8px 24px rgba(15, 23, 42, .16);
    font-size: 14px; line-height: 1.55; text-align: left; white-space: normal;
}
.tp-field-hint-pop[hidden] { display: none; }

/* 第 60 轮 · 多选统一为「标签块」（复选框 → 可点方块） */
.tp-check-inline {
    display: inline-flex; align-items: center; gap: 4px;
    padding: 2px 8px; border: 1px solid var(--admin-border); border-radius: var(--admin-r-sm) !important;
    cursor: pointer; user-select: none; font-size: 14px;
}
.tp-check-inline:has(input:checked) {
    border-color: var(--admin-accent);
    background: var(--admin-accent-soft, #FDF0EF);
    color: var(--admin-accent);
}
.tp-check-inline input[type="checkbox"] { accent-color: var(--admin-accent); }

/* 🔴 第 61 轮（发哥第 2 条）：开关**不带外框**（截图第 2 种）——
   同一个 `.tp-check-inline` 里如果装的是拨动开关，就不要「标签块」的边框/底色，
   否则会出现两种开关样式（带框 / 不带框）。 */
.tp-check-inline:has(> input.tp-switch),
.tp-check-inline:has(> input.tp-switch):has(input:checked) {
    border: 0; background: none; padding: 0; color: var(--admin-body-text);
}

/* 复选框组更紧凑（第 61 轮：适配系列 8 项在 380px 内排得下、不压到下一列） */
.tp-check-row-tight { gap: 6px 8px; }

/* 表格里的小号输入（配件状态下拉等） */
.tp-input-xs { padding: 2px 6px; font-size: 14px; line-height: 1.4; height: 28px; }

/* 第 60 轮 · 布尔开关（单选/启用）统一为「拨动开关」，不再用方形复选框 */
input.tp-switch {
    appearance: none; -webkit-appearance: none;
    width: 36px; height: 20px; flex: 0 0 36px;
    border-radius: 10px !important; border: 0; background: #cbd5e1;
    position: relative; cursor: pointer; transition: background .18s ease;
    vertical-align: middle;
}
input.tp-switch::after {
    content: ''; position: absolute; top: 2px; left: 2px;
    width: 16px; height: 16px; border-radius: 50%; background: var(--admin-surface, #fff);
    transition: transform .18s ease;
}
input.tp-switch:checked { background: var(--admin-accent); }
input.tp-switch:checked::after { transform: translateX(16px); }
input.tp-switch:disabled { opacity: .45; cursor: not-allowed; }


/* ============================================================
   ★ 统一临时提示（浮窗 toast）—— 2026-10-21 第 66 轮 · 发哥第 6 条
   ------------------------------------------------------------
   问题：保存后的「已保存 N 项」是**贴在设计流里的横幅**，位置在页头下方。
        编辑到页面下半部分时，保存成功也看不见（旧样式：`.tp-banner` 贴在设计流里），
        会被当成"没保存成功"而重复点保存。
   治法：统一成 **固定浮在视口顶部居中**的浮窗（`<x-admin.toast>`，视图里一处写法）：
     · `position: fixed` → 不占文档流，滚到哪都看得见；
     · 自动淡出（ok 4.5s / err 8s），纯 CSS 动画，无 JS 状态；
     · `pointer-events: none` → 不挡下面的按钮（`is-busy` 例外，它本来就没有交互）。
   🔴 全站只此一套：以后新页面**不要再写** `tp-banner` / `tp-alert` 当保存提示。
   ============================================================ */
.tp-toast {
    position: fixed;
    top: 68px;                       /* 顶栏 56px + 12 留白 */
    left: 50%;
    transform: translateX(-50%);
    z-index: 9500;                   /* 高于内容与吸顶表头，低于模态遮罩 */
    display: flex;
    align-items: center;
    gap: 8px;
    max-width: min(720px, calc(100vw - 40px));
    padding: 10px 16px;
    border: 1px solid var(--admin-border);
    border-radius: 6px !important;
    background: var(--admin-surface, #fff);
    box-shadow: 0 8px 24px rgba(15, 23, 42, .14);
    font-size: 14px;
    line-height: 1.55;
    color: var(--admin-text);
    pointer-events: none;
    animation: tp-toast-in .18s ease-out both, tp-toast-out .4s ease-in 4.1s forwards;
}
.tp-toast-dot {
    flex: 0 0 8px;
    width: 8px; height: 8px;
    border-radius: 50% !important;
    background: #16a34a;
}
.tp-toast-msg { min-width: 0; overflow-wrap: anywhere; }

.tp-toast-ok   { border-color: rgba(22, 163, 74, .45); }
.tp-toast-ok   .tp-toast-dot { background: #16a34a; }
.tp-toast-err  { border-color: rgba(226, 35, 26, .45); animation-delay: 0s, 7.6s; }
.tp-toast-err  .tp-toast-dot { background: var(--admin-accent); }
.tp-toast-warn { border-color: rgba(217, 119, 6, .45); }
.tp-toast-warn .tp-toast-dot { background: #d97706; }

/* 「保存中…」（wire:loading 控制显隐）：**不自动消失**，消失由 Livewire 决定 */
.tp-toast-busy {
    animation: tp-toast-in .18s ease-out both;
    border-color: var(--admin-accent-soft-40);
}
.tp-toast-busy .tp-toast-dot {
    background: var(--admin-accent);
    animation: tp-toast-pulse 1s ease-in-out infinite;
}

@keyframes tp-toast-in {
    from { opacity: 0; transform: translateX(-50%) translateY(-8px); }
    to   { opacity: 1; transform: translateX(-50%) translateY(0); }
}
@keyframes tp-toast-out {
    to { opacity: 0; transform: translateX(-50%) translateY(-8px); visibility: hidden; }
}
@keyframes tp-toast-pulse {
    0%, 100% { opacity: .35; }
    50%      { opacity: 1; }
}


/* ============================================================
   ★ 我的工作台 · 「需要我处理」待办清单 —— 2026-10-21 第 66 轮 · 发哥第 2 条
   ------------------------------------------------------------
   从仪表盘搬过来的待办块：一行一项、数字在左、点击整行进对应页面。
   🔴 只有**真有待办**才渲染（0 条连块都没有），所以这里不需要 0 态样式。
   ============================================================ */
.tp-todo-list { margin-top: 10px; border-top: 1px solid var(--admin-border); }
.tp-todo-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 10px 4px;
    border-bottom: 1px solid var(--admin-border);
    color: var(--admin-text);
    text-decoration: none;
}
.tp-todo-row:hover { background: var(--admin-hover); }
.tp-todo-n {
    flex: 0 0 44px;
    font-size: 24px;
    font-weight: 600;
    line-height: 1;
    text-align: center;
    color: var(--admin-accent);
}
.tp-todo-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.tp-todo-main b { font-size: 14px; font-weight: 600; }
.tp-todo-go {
    margin-left: auto;
    flex: 0 0 auto;
    font-size: 14px;              /* 🔴 字号阶梯：全站正文下限 13px（闸门 audit-font-ladder 会拦） */
    color: var(--admin-accent);
    white-space: nowrap;
}

/* 产品规格 · 选择条（2026-10-21 第 66 轮 · 发哥第 7 条）
   —— 系列 / 产品从页头右上角搬进「页面中部的筛选条」，与关键词/显示/操作同一行。
   🔴 产品下拉加宽到 380px：原来 160px 只能看到一半产品名（发哥原话）。
      选择器用两级（`.tp-spec-filter .tp-...`）压过后面的 `.tp-input-lg`（300px），
      否则会被它按"后定义胜"覆盖回 300px。 */
.tp-spec-filter { margin-top: 12px; }
.tp-spec-filter .tp-spec-pick-product { width: 380px; max-width: 100%; }
.tp-spec-filter .tp-field { min-width: 0; }


/* ============================================================
   ★ 时间列不折行 —— 2026-10-22 第 72 轮 · 发哥第 7/8 条
   ------------------------------------------------------------
   全站时间统一成 `2026/10/13 17:14:27`（19 个字符）。列宽不变时会被压成两行，
   于是"时间"看起来像两条不同格式。约定：
     · 时间单元格加 `.tp-time`（配合 `.tp-mono` 更好读）；
     · colgroup 宽度给 **≥172px**（19 字符 × ~7.2px + 左右内边距）。
   🔴 只加 `white-space: nowrap`，不动字号 —— 换行是列宽问题，不是字体问题。
   ============================================================ */
.tp-time { white-space: nowrap; }


/* ============================================================
   ★ 下载按钮统一 —— 2026-10-22 第 72 轮 · 发哥第 5 条
   ------------------------------------------------------------
   「提交盖章」里的下载是**红色主按钮**（`.tp-btn`）。全站下载统一走同一套：
     · 单条/结果区：`class="tp-btn"`（红色实心）
     · 列表行内：`class="tp-btn tp-btn-sm"`（红色，小一号，不撑高行）
   🔴 不要再写 `tp-op` / 纯文字链接当下载 —— 那是"查看详情"的样式，两者语义不同。
   ============================================================ */



/* ============================================================
   ★ 内部授权「生产工具」页排版 —— 2026-10-22 第 74 轮 · 发哥第 7 条
   ------------------------------------------------------------
   现象：状态徽章被挤成两行（成了个"圆圈"）、操作列四个入口用 `·` 串成一行挤成一团。
   原因：`.tp-tbl-fixed` + `fillToBox()` 会把表按容器宽等比压缩 —— 列一多就把内容
        挤到 `min-content` 以下，文字只能折行。
   治法：
     ① 徽章**永不折行**（它是"一个词"，折了就不可读）；
     ② 操作入口改成 flex（自动换行 + 固定间距），不再靠 `·` 连接；
     ③ 这类"列多、内容长"的表加 `.tp-tbl-wide`：给一个 **min-width**，
        并让 `admin.js` 的 `canFit()` 跳过它 —— 宁可横向滚动，也不把字挤扁。
   ============================================================ */
.tp-badge { white-space: nowrap; }

.tp-ops { display: flex; flex-wrap: wrap; gap: 4px 14px; align-items: center; }
.tp-ops > .tp-op { margin-right: 0; white-space: nowrap; font-size: 14px; }

/* 支持型号清单：方块（`<x-tp.check>`）+ 外侧删除按钮（第 131 轮 · 发哥）
   删除键**不能**放进 `<label>` 里 —— label 内的 button 会被"点方块"连带触发，
   手一抖就把型号删了（该动作还会写审计）。 */
.tp-model-chip { display: inline-flex; align-items: center; gap: 2px; }
.tp-model-x {
    width: 20px; height: 20px; padding: 0;
    display: inline-flex; align-items: center; justify-content: center;
    font-size: 14px; line-height: 1;
}

/* 「最近访问 = 时间 + 共 N 次」必须**同一行**（第 131 轮 · 发哥）
   ------------------------------------------------------------
   原来堆了两个 `<div>`（时间一行、次数一行），把行高顶到两行 ——
   同一个页面的「LMR批量激活DID」tab 只有一行，于是两张表行高对不齐。
   `inline-flex + nowrap`：一行放不下才由单元格省略号收尾，行高恒为一行。 */
.tp-lastseen {
    display: inline-flex;
    align-items: baseline;
    gap: 6px;
    white-space: nowrap;
}

/* 🔴 第 131 轮（发哥）：「现在的表格都不支持自适应了吗，现在需要拉到导航条，
   才能看到右边的内容，体验更不好」
   ------------------------------------------------------------
   第 74 轮给"列多、内容长"的表挂了最小宽度 1120px + 不参与收缩，
   靠**横向滚动**换"不挤压"。发哥否掉了这个取舍 —— 每次看表都要拖横向滚动条。

   现在"挤压"已经在**内容侧**解决（见 `.tp-tbl-fixed > tbody > tr > td` 那条：
   一行 + 省略号），所以这里不再给 min-width：表格按容器宽收缩，
   列宽按 `<colgroup>` 的比例分配，窄了只会出现省略号，不会把字挤变形。 */
.tp-tbl-wide { min-width: 0; }

/* ============================================================
   ⑳ 站内确认气泡（2026-10-23 第 82 轮 · 发哥第 5 条）
   ------------------------------------------------------------
   替代浏览器自带 confirm：气泡出现在**被点按钮旁边**，两个按钮「删除/取消」。
   由 public/js/admin.js 末尾的拦截器渲染（元素属性 `data-confirm`）。
   🔴 z-index 要压过后台所有弹层（`.tp-modal-mask` 是 9000 级）。
   ============================================================ */
.tp-confirm-pop {
    position: fixed;
    z-index: 99999;
    box-sizing: border-box;   /* 没设的话 320 + padding + border = 350px（实测） */
    width: 320px;
    max-width: calc(100vw - 16px);
    padding: 12px 14px;
    background: var(--admin-surface);
    border: 1px solid var(--admin-border);
    border-radius: 6px;
    box-shadow: 0 10px 30px rgba(15, 23, 42, .18);
}
.tp-confirm-msg {
    font-size: 14px;
    line-height: 1.65;
    color: var(--admin-text);
    word-break: break-word;
}
.tp-confirm-ops {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    margin-top: 12px;
}
.tp-confirm-ops > .tp-btn-2 { padding: 4px 12px; font-size: 14px; }   /* 字号阶梯下限 13px（audit-font-ladder 有闸门） */

/* ============================================================
   ⑳ 页签按钮（2026-10-24 第 84 轮 · Q&A 审核的「待审核 / 已发布」）
   ------------------------------------------------------------
   只是 `.tp-btn-2` 的选中态，不新增结构；用 `.is-on` 保持与
   `.tp-spec-chip.is-on` / `.tp-cap td.is-on` 同一套命名。
   ============================================================ */
.tp-btn-2.is-on {
    border-color: var(--admin-accent);
    color: var(--admin-accent);
    background: var(--admin-accent-soft);
    font-weight: 600;
}

/* ============================================================
   ㉑ 类型筛选按钮组（2026-10-24 第 86 轮 · 发哥第 1 条）
   ------------------------------------------------------------
   发哥原话：「把资源类型的筛选方式由下拉框改为平铺 6 个按钮，可以快速筛选
             全部和其他 5 个类型，默认显示全部」。
   所以不再用 `<select>`（看不见有哪几类，还得点开才知道），改成一排：
   `[全部 8] [固件 2] [字库 1] …` —— 一眼看到分类 + 各类条数。
   🔴 按钮数量跟着**字典**走（加一个资源类型就自动多一个按钮），不是写死 5 个。
   ============================================================ */
.tp-type-tabs {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
}
.tp-type-tab {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 5px 12px;
    border: 1px solid var(--admin-border);
    background: var(--admin-surface, #fff);
    font-size: 14px;
    line-height: 1.4;
    color: var(--admin-body-text);
    cursor: pointer;
    transition: border-color .15s ease, color .15s ease, background .15s ease;
}
.tp-type-tab:hover {
    border-color: var(--admin-accent);
    color: var(--admin-accent);
}
.tp-type-tab.is-on {
    border-color: var(--admin-accent);
    background: var(--admin-accent-soft);
    color: var(--admin-accent);
    font-weight: 600;
}
.tp-type-tab .n {
    font-size: 14px;
    color: var(--admin-muted);
    font-variant-numeric: tabular-nums;
}
.tp-type-tab.is-on .n { color: var(--admin-accent); }

/* ============================================================
   ㉒ 列表「行标识」列（2026-10-24 第 87 轮 · 发哥第 5 条）
   ------------------------------------------------------------
   全站列表首列统一：表头 = `App\Support\AdminColumns::ROW_ID`（当前 `ID`），
   值 = 纯数字（不加 `#`），等宽字体 + 固定窄列，方便竖着对齐扫一眼。
   渲染走 `<x-admin.row-id>`，这里是它唯一的样式来源。
   ============================================================ */
.tp-row-id {
    width: 84px;
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
    color: var(--admin-muted);
}

/* 勾选列 + 已选行（2026-10-24 第 87 轮 · 发哥第 1 条，资源广场批量删除） */
.tp-row-check {
    width: 15px;
    height: 15px;
    accent-color: var(--admin-accent);
    cursor: pointer;
    vertical-align: middle;
}
.tp-tbl tbody tr.is-selected { background: var(--admin-accent-soft); }

/* ============================================================
   统一日期选择器（2026-10-28 第 104 轮 · 发哥第 3 / 4 条）
   ------------------------------------------------------------
   发哥原话：「这个日期选择框，是用组件吗，为什么 UI 和全局不一样，
              框没有圆角，颜色不是主题红」
   根因：那是**浏览器原生 `<input type="date">`** —— 弹出的日历面板由 Chrome
        自己绘制，选中日是系统蓝，**CSS 完全无法覆盖**（不是样式写漏了）。
   ⇒ 改为自绘面板（`public/js/tp-date.js`）：
      · 外框 = `.tp-input`（与全站输入框同一套边框/字号/聚焦红边）
      · 选中 = `--admin-accent`（主题红，不再出现系统蓝）
      · 年份范围 2012–2039
   ============================================================ */
.tp-date {
    position: relative;
    display: inline-block;
}
.tp-date.is-block { display: block; }
.tp-date > .tp-date-input {
    width: 138px;
    padding-right: 28px;          /* 给日历图标留位 */
    cursor: pointer;
    background: var(--admin-surface, #fff);
}
.tp-date.is-block > .tp-date-input { width: 100%; }
.tp-date-ico {
    position: absolute;
    right: 9px;
    top: 50%;
    margin-top: -8px;
    display: inline-flex;
    color: var(--admin-muted);
    pointer-events: none;
}

/* ---------------- 弹出面板 ---------------- */
.tp-date-pop {
    position: fixed;              /* 🔴 表格/卡片 overflow:hidden 也裁不掉（L0 #18） */
    z-index: 3000;
    width: 272px;
    padding: 8px;
    border: 1px solid var(--admin-border);
    background: var(--admin-surface, #fff);
    box-shadow: 0 10px 28px rgba(0, 0, 0, .14);
    font-size: 14px;
    color: var(--admin-text);
    -webkit-user-select: none;
    user-select: none;
}
.tp-date-head {
    display: flex;
    align-items: center;
    gap: 4px;
    margin-bottom: 6px;
}
.tp-date-nav {
    width: 26px;
    height: 26px;
    flex: 0 0 auto;
    border: 1px solid transparent;
    background: transparent;
    color: var(--admin-muted);
    font-size: 16px;
    line-height: 1;
    cursor: pointer;
}
.tp-date-nav:hover:not(:disabled) { border-color: var(--admin-accent); color: var(--admin-accent); }
.tp-date-nav:disabled { opacity: .28; cursor: not-allowed; }
.tp-date-title {
    flex: 1;
    height: 26px;
    border: 1px solid transparent;
    background: transparent;
    color: var(--admin-text);
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
}
.tp-date-title:hover { border-color: var(--admin-border); }

.tp-date-week,
.tp-date-grid {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    gap: 2px;
}
.tp-date-week { margin-bottom: 2px; }
.tp-date-week > span {
    height: 22px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--admin-muted);
    font-size: 14px;
}
.tp-date-cell {
    height: 30px;
    border: 1px solid transparent;
    background: transparent;
    color: var(--admin-text);
    font-size: 14px;
    cursor: pointer;
}
.tp-date-cell:hover:not(:disabled):not(.is-sel) { border-color: var(--admin-accent); color: var(--admin-accent); }
.tp-date-cell.is-out { color: var(--admin-muted); opacity: .55; }
.tp-date-cell.is-today:not(.is-sel) { border-color: var(--admin-accent); color: var(--admin-accent); }
/* 选中：主题红实底白字（原来这里是浏览器画的系统蓝方块）
   🔴 第 105 轮修 bug：`hover` 那条选择器特异性 (0,3,0) 高于 `.is-sel` (0,2,0)，
      鼠标移到「已选中/今天」的格子上会把 color 改回红色 → **红底红字看不见数字**。
      现在 hover 明确排除 `.is-sel`，并单独给选中态一条 hover（只微调亮度）。 */
.tp-date-cell.is-sel,
.tp-date-cell.is-sel:hover:not(:disabled) {
    background: var(--admin-accent);
    border-color: var(--admin-accent);
    color: #fff;
    font-weight: 600;
}
.tp-date-cell.is-sel:hover:not(:disabled) { filter: brightness(1.08); }
.tp-date-cell.is-off,
.tp-date-cell:disabled { opacity: .2; cursor: not-allowed; }

.tp-date-foot {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-top: 6px;
    padding-top: 6px;
    border-top: 1px solid var(--admin-border);
}
.tp-date-link {
    border: 0;
    background: transparent;
    padding: 2px 0;
    color: var(--admin-accent);
    font-size: 14px;
    cursor: pointer;
}
.tp-date-link:hover { text-decoration: underline; }

/* 年 / 月 选择层 */
.tp-date-picker .tp-date-grid { grid-template-columns: repeat(4, 1fr); }
.tp-date-picker .tp-date-grid-y { margin-bottom: 6px; }
.tp-date-split { height: 1px; background: var(--admin-border); margin: 4px 0 6px; }

/* ============================================================
   ★ 推广素材 · 产品介绍 —— 「产品图片」列（2026-10-28 第 110 轮）
   ------------------------------------------------------------
   一行 8~15 张：56px 缩略图（点开看大图）+ 左上角勾选框；
   勾选后「复制已选 (n)」把 URL 用英文逗号合并，「删除已选 (n)」进回收站。
   🔴 图下**没有**单张复制按钮（发哥第 3 条：只保留上面的总按钮）。
   🔴 不写 font-size（继承 .tp-tbl 的 13px）—— 字号阶梯闸门只认阶梯值。
   ============================================================ */
.tp-promo { min-width: 0; }
.tp-promo-bar {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
    margin-bottom: 6px;
}
.tp-promo-grid {
    display: flex;
    flex-wrap: wrap;
    gap: 7px;
}
.tp-promo-item {
    position: relative;
    width: 56px;
    flex: 0 0 56px;
}
.tp-promo-item img {
    display: block;
    width: 56px;
    height: 56px;
    object-fit: cover;
    border: 1px solid var(--admin-border);
    background: #FFFFFF;
    cursor: zoom-in;          /* 点开看大图（第 110 轮 · 发哥第 2 条） */
}
.tp-promo-item img:hover { border-color: var(--admin-accent); }
/* 勾选后：边框变品牌红（一眼看出选中了哪几张） */
.tp-promo-item.is-picked img {
    border-color: var(--admin-accent);
    box-shadow: 0 0 0 1px var(--admin-accent);
}
.tp-promo-cb {
    position: absolute;
    top: 2px;
    left: 2px;
    margin: 0;
    z-index: 2;
    background: #FFFFFF;
}

/* ---- 看图弹窗：铺满可用宽度，**高度固定**（第 166 轮·发哥） ----
   🔴 发哥：「点击放大了图片，弹窗会先显示一个相对小的，过一会加载完又变成大的，
      只需要固定好小的弹窗就行」。
   真因：容器原来只有 `min-height: 200px`，高度由图片撑开 —— 先渲染缩略图（小），
        原图 onload 替换 src 后高度变大 → 弹窗跳一下。
   修法：容器**定高**，图片 `object-fit: contain` 永远装在里面，缩略图/原图切换高度不变。 */
.tp-asset-view {
    display: flex;
    align-items: center;
    justify-content: center;
    height: min(62vh, 520px);
    background: #F7F7F8;
    border: 1px solid var(--admin-border);
}
.tp-asset-view img {
    display: block;
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
}

/* ---- 上传弹窗 ---- */
.tp-asset-field { display: block; }
.tp-asset-up-btns {
    display: flex;
    gap: 10px;
    flex-wrap: wrap;
    margin-top: 12px;
}
.tp-asset-up-list {
    margin-top: 10px;
    min-height: 0;
}

/* ---- 上传进度（第 113 轮 · 发哥第 1 条）----
   网速慢时"点了没反应"的根源是**页面没有任何变化**：
   进度条 + 百分比 + 速度 + 剩余时间，传输完再显示「服务器处理中」。 */
.tp-asset-up-bar {
    position: relative;
    height: 6px;
    margin-top: 12px;
    background: rgba(0, 0, 0, .08);
    overflow: hidden;
}
.tp-asset-up-fill {
    height: 100%;
    width: 0;
    background: var(--admin-accent);
    transition: width .18s ease-out;
}
.tp-asset-up-status {
    margin-top: 8px;
    min-height: 18px;
    font-variant-numeric: tabular-nums;
}

/* ============================================================
   ㊾ 列表行高 / 单元格度量 —— 全站单源（第 129 轮 · 发哥第 3 条）
   ------------------------------------------------------------
   发哥原话：「POC激活 用户版、POC激活 批量、LMR批量激活DID、
     LMR维护套件 工厂版、产品列表、配件目录等等页面，每个列表的高度都不一样，
     显得整个网站很乱，应该统一下所有列表的行高」

   为什么之前不一样（三个来源，谁写谁说了算）：
     ① `.tp-tbl` 8px 10px / 13px   —— 大部分列表
     ② `.tp-matrix`（场景库）6px 9px / 12.5px —— 更矮
     ③ 视图里散落的 `style="padding:8px"`、`py-1`、`text-xs` —— 点缀
   现在全部收敛到下面四个变量 + 两条规则。

   🔴 边界（**故意不进来**）：
     · `.tp-batch`（产品规格批量矩阵）/ `.tp-cap`（能力对照矩阵）—— 它们是
       几十列的**数据网格**，行内是数字/勾，按列表行高会撑到没法用；
     · 盖章页 `.tp-grid-cell` —— A4 底图刻度，尺寸有物理含义。
   ============================================================ */
:root {
    --tp-row-py: 8px;      /* 单元格上下内边距 */
    --tp-row-px: 12px;     /* 左右内边距（比原来的 10px 松一点，密排数字更好读） */
    --tp-row-h: 40px;      /* 单行最小高度：一行文字的列表行高必然相等 */
}

/* ① 标准列表 */
.tp-tbl thead th,
.tp-tbl tbody td {
    padding: var(--tp-row-py) var(--tp-row-px);
    line-height: 1.5;
}
.tp-tbl tbody td { height: var(--tp-row-h); }
/* 展开子行（`tr.tp-subrow`）里是整块面板，别被最小行高撑开 */
.tp-tbl tbody tr.tp-subrow > td { height: auto; }

/* 数字列：等宽数字 + 右对齐（第 155 轮 · 唯一出口）
   ------------------------------------------------------------
   以前 `font-variant-numeric: tabular-nums` 在 admin 6 处、marketplace 3 处各写一遍，
   视图里也手写；新代码**一律加 `.tp-num`**，不要再自己拼（闸门会数散落处数）。
   `font-variant-numeric` 只影响等宽数字，不改字重/字号，与五档字号不冲突。 */
.tp-num {
    font-variant-numeric: tabular-nums;
    text-align: right;
}

/* ② 场景库（`.tp-matrix` 家族里唯一按"列表"用的表）—— 对齐同一度量 */
.tp-scen-tbl th,
.tp-scen-tbl td {
    padding: var(--tp-row-py) var(--tp-row-px);
    line-height: 1.5;
}
.tp-scen-tbl tbody tr { height: var(--tp-row-h); }
/* 列多时不压字：给个下限，窄屏横向滚动（与 `.tp-tbl-wide` 同一思路） */
.tp-scen-tbl { min-width: 1180px; }

/* ③ 操作列：按钮组横排、不靠空格串、不逐字换行 */
.tp-ops {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
}
.tp-ops > .tp-op { margin-right: 0; }

/* 支持型号：一级 / 二级（第 148 轮）
   —— 每个一级一块（浅色底 + 左边框），二级用现成的 .tp-model-chip（方块 + ×）。
      不引入新控件，只补"块"这一层容器。 */
.tp-model-group {
    margin-top: 12px;
    padding: 12px 14px;
    border: 1px solid var(--admin-border);
    background: var(--admin-surface, var(--admin-bg));
}
.tp-model-group-head {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
}
.tp-model-group-head .tp-model-x {
    margin-left: auto;
}

/* ============================================================
   多页签标签栏（TagsView）  —— 第 149 轮；第 150 轮改视觉
   ------------------------------------------------------------
   位置：`.tp-topbar` 下面、`.tp-main-body` 上面。高度 = `--tp-tagsbar-h`（默认 38px）：
   `.tp-sticky-head` 的 top 用 `calc(var(--tp-topbar-h) + var(--tp-tagsbar-h))` 自动跟，
   **不要再手算 94**（第 154 轮之前就是手算导致 4px 错位）。

   🔴 第 150 轮（发哥）视觉口径：
      · **白底容器 + 透明标签**：未激活标签与标签栏同色（白），靠竖线分隔；
      · **激活标签 = 页面底色**（`--admin-bg`）+ 顶部一条品牌红线 ——
        等于"当前标签融进下面的内容区"，与参考图一致；
      · 全部**直角、细边框**，不加圆角/阴影/渐变（延续整站 UI）；
      · 标签左右留白加大（`0 18px`），避免"产品列表"四个字贴着边框；
      · 刷新按钮**紧跟最后一个标签**（不是推到最右端）。
   ============================================================ */
.tp-tagsbar {
    position: sticky;
    top: var(--tp-topbar-h);      /* 第 154 轮：跟着 `--tp-topbar-h` 走（原来是写死 56，顶栏实际 60 → 盖住 4px） */
    z-index: 29;                  /* 低于顶栏 30，高于页面内 sticky 20 */
    display: flex;
    align-items: stretch;
    flex-shrink: 0;
    height: var(--tp-tagsbar-h);
    padding: 0;
    background: var(--admin-surface, #fff);
    border-bottom: 1px solid var(--admin-border);
}
/* 标签容器：**不占满**整行 —— 这样刷新按钮会紧跟在最后一个标签右边 */
.tp-tags-scroll {
    display: flex;
    align-items: stretch;
    flex: 0 1 auto;
    max-width: 100%;
    min-width: 0;
    height: 100%;
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: thin;
}
.tp-tags-scroll::-webkit-scrollbar { height: 3px; }
.tp-tags-scroll::-webkit-scrollbar-thumb { background: var(--admin-border); }

/* 单个标签：通高、直角、右侧竖线分隔
   🔴🔴 类名是 `.tp-tagpage`，**不是 `.tp-tag`**：
       `.tp-tag` 在 admin.css 第 821 行早已被「产品规格矩阵」的类型标签占用（any/var/req/na/ok），
       而且它被全局圆角规则 `border-radius: var(--admin-r-pill) !important` 命中 ——
       第 149 轮误用同名类，标签栏直接被渲染成**胶囊圆角**（发哥第 150 轮指出的"圆角"就是这个）。
       ⇒ 标签栏一律用 `.tp-tagpage*` 前缀，别再用 `tp-tag`。 */
.tp-tagpage {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    flex: 0 0 auto;
    max-width: 260px;
    height: 100%;
    padding: 0 18px;
    border-right: 1px solid var(--admin-border);
    background: transparent;               /* 与标签栏同色（白） */
    color: var(--admin-body-text);
    font-size: 14px;
    line-height: 1;
    text-decoration: none;
    white-space: nowrap;
    cursor: pointer;
    transition: background .12s, color .12s;
}
.tp-tagpage:first-child { border-left: 1px solid var(--admin-border); }
.tp-tagpage:hover {
    background: var(--admin-hover);
    color: var(--admin-text);
}
/* 当前标签：页面底色 + 顶部品牌红线（一眼看出在哪一页） */
.tp-tagpage.is-on {
    background: var(--admin-bg);
    color: var(--admin-accent);
    font-weight: 600;
    box-shadow: inset 0 2px 0 var(--admin-accent);
}
.tp-tagpage-label {
    overflow: hidden;
    text-overflow: ellipsis;
}
/* 关闭（用 span 不是 button：button 不能嵌在 a 里）；直角、无圆角 */
.tp-tagpage-x {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 16px;
    margin-right: -4px;
    color: inherit;
    font-size: 14px;
    line-height: 1;
    opacity: .45;
}
.tp-tagpage-x:hover,
.tp-tagpage-x:focus-visible {
    opacity: 1;
    color: var(--admin-danger);
    background: var(--admin-danger-soft);
    outline: none;
}

/* 刷新当前标签（紧跟最后一个标签，不推到最右） */
.tp-tags-refresh {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    align-self: center;
    flex: 0 0 auto;
    width: 32px;
    height: 26px;
    margin: 0 0 0 8px;                 /* 右侧间距交给 .tp-tags-closeall */
    border: 1px solid var(--admin-border);
    background: var(--admin-surface, #fff);
    color: var(--admin-muted);
    cursor: pointer;
}
.tp-tags-refresh:hover {
    border-color: var(--admin-accent);
    color: var(--admin-accent);
    background: var(--admin-accent-soft);
}

/* 🆕 第 161 轮：关闭全部标签（发哥：「刷新图标后面再加一个大的图标」）
   · 与刷新按钮同一行、同高，图标 18px（刷新 15px）→ 视觉上明显更大
   · 右边距 12px 收在刷新按钮上，这里只留左侧 6px 间距 */
.tp-tags-closeall {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    align-self: center;
    flex: 0 0 auto;
    width: 36px;
    height: 26px;
    margin: 0 12px 0 6px;
    border: 1px solid var(--admin-border);
    background: var(--admin-surface, #fff);
    color: var(--admin-muted);
    cursor: pointer;
}
.tp-tags-closeall:hover {
    border-color: var(--admin-accent);
    color: var(--admin-accent);
    background: var(--admin-accent-soft);
}

/* 🔴 第 152 轮（发哥）：「这个标签视图，可以改为没有圆角，改为直角」——
   整棵子树强制归零（标签本体 / 文字 / 关闭 × / 刷新按钮 / 图标）。
   必须带 `!important`：第 2 层基线 `* { border-radius: var(--admin-r-sm) !important }` 也带
   `!important`，同 `!important` 下比**特异性**，`*`（0,0,0）输给 `.tp-tagsbar *`（0,1,0）。 */
.tp-tagsbar,
.tp-tagsbar * { border-radius: 0 !important; }

/* 🔴 第 158 轮（发哥）：「最后面的刷新按钮，加 2px 的圆角」——
   整棵标签栏是直角（第 152 轮），只有这一个按钮单独微圆。
   必须写在上面那条 `!.tp-tagsbar *` 归零规则**之后**（同特异性 `.tp-tagsbar *`(0,1,0)
   vs `.tp-tags-refresh`(0,1,0)，后定义胜）。 */
.tp-tags-refresh,
.tp-tags-closeall { border-radius: var(--admin-r-xs) !important; }

/* ============================================================
   工作台 · 功能入口卡片（第 149 轮 · 发哥）
   ------------------------------------------------------------
   每个一级分组一张卡；卡内是**该组下的菜单**（已按权限过滤）。
   卡片只做导航，不放任何写操作按钮。
   ============================================================ */
.tp-menu-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
    gap: 12px;
    margin-top: 14px;
}
.tp-menu-card {
    padding: 12px 14px;
    border: 1px solid var(--admin-border);
    background: var(--admin-bg);
}
.tp-menu-head {
    display: flex;
    align-items: center;
    gap: 8px;
    padding-bottom: 8px;
    border-bottom: 1px solid var(--admin-border);
    color: var(--admin-text);
    font-size: 14px;
    font-weight: 600;
}
.tp-menu-head svg { flex: 0 0 auto; color: var(--admin-muted); }
.tp-menu-n {
    margin-left: auto;
    color: var(--admin-muted);
    font-size: 12px;
    font-weight: 400;
}
.tp-menu-links {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-top: 10px;
}
.tp-menu-link {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 4px 9px;
    border: 1px solid var(--admin-border);
    background: var(--admin-surface, #fff);
    color: var(--admin-body-text);
    font-size: 14px;
    line-height: 1.4;
    text-decoration: none;
    white-space: nowrap;
}
.tp-menu-link svg { flex: 0 0 auto; color: var(--admin-muted); }
.tp-menu-link:hover,
.tp-menu-link.is-active {
    border-color: var(--admin-accent);
    background: var(--admin-accent-soft);
    color: var(--admin-accent);
}
.tp-menu-link:hover svg,
.tp-menu-link.is-active svg { color: var(--admin-accent); }

/* ============================================================
   键盘焦点 · 动效降级（第 154 轮 · a11y 基线）
   ------------------------------------------------------------
   外部审查实测：全站 `:focus-visible` 只有 3 条零散规则（admin / marketplace / search 各 1），
   纯键盘用户看不出焦点在哪。这里给"原生可聚焦元素"一条统一基线。
   🔴 用 `:where()` 把特异性压到 **0**：组件自己的 focus 视觉（如标签栏 × 的底色、
      输入框的描边）仍然优先，不会被这条基线盖掉。
   🔴 不要写成 `*:focus-visible`：会命中 svg/div 等不可聚焦元素，
      而且特异性一旦升高就会和组件规则打架。
   ============================================================ */
:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
    outline: 2px solid var(--admin-accent);
    outline-offset: 2px;
}

/* 尊重系统"减少动态效果"：只掐时长，不改终态（布局不会跳、元素不会消失） */
@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: .01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .01ms !important;
        scroll-behavior: auto !important;
    }
}

/* ============================================================
   手机端导航（第 156 轮 · 发哥：断点只考虑电脑和手机）
   ------------------------------------------------------------
   问题：侧栏固定 212px，手机上（375~430）主区只剩 160px，后台等于不可用。
   做法：≤767 时侧栏变**抽屉**（fixed 移出屏幕，点汉堡滑出），电脑上完全不变。
   🔴 层级：抽屉 60 > 遮罩 59 > 顶栏 30 > 标签栏 29。
      遮罩不能给 60 以上：`.tp-modal-mask` / `.tp-help-mask` 也是 60，
      抬到 61 会让"打开抽屉时弹窗被遮罩盖住"这类层级错乱重现。
   🔴 `.tp-nav-toggle` 默认 `display:none`，只在手机出现 —— 电脑上顶栏仍是
      「右对齐 + 退出登录」，不是「汉堡 + 右对齐」。
   ============================================================ */
.tp-nav-toggle { display: none; }
.tp-nav-mask { display: none; }

/* 抽屉打开时锁滚动（只锁 html，不锁 body：body 上还有待机锁在用 overflow） */
html.tp-nav-on { overflow: hidden; }

@media (max-width: 767px) {
    .tp-nav-toggle {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        width: 36px;
        height: 36px;
        margin-right: auto;              /* 把右侧那组用户信息推到最右 */
        padding: 0;
        border: 1px solid var(--admin-border);
        background: var(--admin-surface);
        color: var(--admin-text);
        cursor: pointer;
    }
    .tp-nav-toggle:hover { background: var(--admin-hover); }

    .tp-sidebar {
        position: fixed;
        top: 0;
        left: 0;
        bottom: 0;
        height: 100vh;
        width: 264px;                    /* 比桌面 212 宽一点：手指点击区更好按 */
        z-index: 60;
        transform: translateX(-100%);
        transition: transform .18s ease;
        will-change: transform;
    }
    .tp-sidebar.is-open {
        transform: none;
        box-shadow: 8px 0 28px rgba(15, 23, 42, .18);
    }

    .tp-nav-mask {
        display: block;
        position: fixed;
        inset: 0;
        z-index: 59;
        background: rgba(15, 23, 42, .45);
    }
    .tp-nav-mask[hidden] { display: none; }

    .tp-topbar { padding: 0 14px; gap: 10px; }
    /* 手机上表格普遍超宽：让内容区横向滚动，而不是把列压成一字一行。
       🔴 代价：这一层会创建滚动上下文 → 页面内 sticky 表头在手机上失效（可接受）。 */
    /* 🔴 手机上也不能给主容器设 overflow（见上面 1335 行的说明）：
       要横向滚就让表格自己的容器滚，主容器一旦是滚动容器，sticky 全废。 */
    .tp-main-body { padding: 14px 12px; }
    .tp-main-body .tp-card,
    .tp-main-body .tp-tbl-wrap { overflow-x: auto; }
    .tp-bar { flex-wrap: wrap; gap: var(--sp-2); }
}

/* ============================================================
   列表页滚动：收起页头（第 158 轮 · 发哥第 3 条）
   ------------------------------------------------------------
   发哥：「筛选框固定是个很好的设计，但最好是滚动页面的时候，
           隐藏掉标题，只剩筛选框，这样可以省下一部分高度」

   做法：`public/js/admin.js` 在滚动 >40px 时给 `<html>` 挂 `is-scrolled`，
        这里把**列表页页头的标题与说明**收掉；工具按钮（新建 / 展开全部）保留 ——
        收掉标题是为了省高度，不是为了让人点不到按钮。
   🔴 只对**有固定筛选条**的页面生效（`:has(.tp-sticky-head)`）：
      表单页 / 详情页的标题必须一直在，否则用户不知道自己填到哪一步了。
   🔴 `:has()` 不支持时（老浏览器）只是"标题不收起"，功能零影响，属优雅降级。
   ============================================================ */
html.is-scrolled body:has(.tp-sticky-head) .tp-page-head h1,
html.is-scrolled body:has(.tp-sticky-head) .tp-head h1,
html.is-scrolled body:has(.tp-sticky-head) .tp-page-head .tp-page-sub,
html.is-scrolled body:has(.tp-sticky-head) .tp-head .tp-page-sub {
    display: none;
}

/* 标题没了，页头下沿的间距也跟着收，否则会留一条空白 */
html.is-scrolled body:has(.tp-sticky-head) .tp-page-head,
html.is-scrolled body:has(.tp-sticky-head) .tp-head {
    margin-bottom: 0;
    min-height: 0;
}


/* ============================================================
   列表页筛选条：统一吸顶（第 159 轮 · 发哥第 1 条）
   ------------------------------------------------------------
   发哥：「领朋平台、设备管理、业务申请、盖章记录、推广素材、产品介绍等等页面，
           导航栏往下后，并没有固定住筛选框，所以你还是没有全局都统一」

   上一轮只给 6 个页面手工套了 `.tp-sticky-head`，其余 40+ 个列表页没有 ——
   **逐个改模板不现实**（每个页面的筛选区结构都不同：有的是 `.tp-cat-toolbar`、
   有的是 `.tp-kb-filter`、有的是 `.tp-toolbar`），所以改成**在 CSS 层统一**：

       `body:has(.tp-tbl)` 限定"这一页是列表页"，
       再把三种白底筛选条容器统一变成 sticky。

   🔴 为什么用 `:has(.tp-tbl)` 而不是直接给类加 sticky：
      这三个类在**表单页**也用来装按钮行，直接 sticky 会让表单页的按钮跟着吸顶。
   🔴 已被 `.tp-sticky-head` 包着的 6 个页面不受影响：外层已经吸顶，内层 sticky
      相对同一个 top，视觉完全一样（不会出现"两层错位"）。
   🔴 白色背景必须写：吸顶后下面内容会从它底下滚过去，透明就糊成一片。
   ============================================================ */
/* 🔴 第 159 轮修正（发哥：「改成一条规则覆盖全部列表页，并没有实现」）：
      上一版判定条件写的是 `body:has(.tp-tbl)` —— 但**很多列表页的表格压根没有 `.tp-tbl` 类**
      （配件矩阵是 `.tp-mx-*`、产品规格是 `.tp-spec-*`、场景库是 `.tp-panel`…），
      条件为假 ⇒ 那些页面全都没吸顶。改成 `body:has(table)`：只要这页有表格就算列表页。 */
body:has(table) .tp-cat-toolbar,
body:has(table) .tp-toolbar,
body:has(table) .tp-kb-filter,
/* `.tp-listbar` 由 `public/js/admin-listbar.js` 在运行时打上（见该文件说明）：
   给"没有统一容器的页面"里的筛选块自动加类，改 39 个模板不如一处识别。 */
.tp-listbar {
    position: sticky;
    top: calc(var(--tp-topbar-h) + var(--tp-tagsbar-h));
    z-index: 19;                       /* 低于标签栏 29、顶栏 30；高于表格内容 */
    background: var(--admin-surface, #fff);
}

/* `.tp-listbar` 是"被识别出来"的筛选块，通常没有内距 —— 补一点，免得控件贴着边缘 */
.tp-listbar { padding: 8px 12px; }
.tp-listbar:empty { display: none; }

/* 🔴🔴 第 166 轮（发哥第 5 条：「产品目录点了『渠道 / 线上』没有反应」）：
   根因是**双层 sticky** —— `.tp-sticky-head` 本身已经是 `position: sticky`（z-index 20），
   而它内部的筛选块又被 CSS 当成 `.tp-listbar` 设成 sticky（z-index 19、同一个 `top`）。
   内层 sticky 会相对外层再算一次偏移，**渲染位置和点击命中区域错位**：
   按钮看着还在原处，鼠标点下去命中的却是别的东西 → 「点了没反应」。
   外层已经负责吸顶，内层一律退回 static（这条对所有列表页生效，不是只修产品目录）。 */
.tp-sticky-head .tp-listbar {
    position: static;
    top: auto;
    z-index: auto;
}

/* 🔴🔴 这里**故意没有** `overflow-x: auto`（第 159 轮踩过，别再手贱加回来）：
      给 `.tp-main-body` 设任何 overflow，它就成了**滚动容器**，里面所有
      `position: sticky` 都会相对它计算；而它的高度等于内容高度 ⇒ sticky 永远不吸顶。
      现象正是发哥说的「有的有有的没有」——其实是一条规则把所有页面的 sticky 全废了。
      表格宽了要横向滚动，让 `.tp-card` / `.tp-tbl-wrap` 自己滚，别动页面主容器。 */

/* ============================================================
   侧边栏可收缩（第 159 轮 · 发哥第 3 条）
   ------------------------------------------------------------
   发哥：「增加侧边栏收缩的按钮（功能）收缩后只剩一个一级菜单的图标，
           并与 logo 居中，同时 logo 缩小 80%，收缩按钮在下方」

   状态挂在 `<html class="tp-side-mini">`（不是 aside 上）：
   因为首屏同步脚本在 <head> 里执行，那时 `<body>` 还不存在，只能写到 html。
   持久化 key = `tp_side_mini_v1:<adminId>`（按账号，换人登录不串）。

   🔴 折叠后一级菜单只剩图标、水平居中；二级不丢 —— 靠现有的 **hover 飞出**
      （`.tp-side-flyout{left:100%}` 自动跟着变窄的侧栏走，不用改一行）。
   🔴 宽度 76px：能放下 18px 图标 + 20px 呼吸，同时 logo 还能留 48px 认得出。
   ============================================================ */
.tp-sidebar { transition: width .16s ease; }

/* 底部按钮（不随导航滚动） */
.tp-side-foot {
    flex-shrink: 0;
    border-top: 1px solid var(--admin-border);
    padding: 8px;
    /* 🔴 第 166 轮：底部从"一个按钮"变成"收起 + 待机锁屏"两个并排 */
    display: flex;
    align-items: center;
    gap: 6px;
}
.tp-side-toggle {
    /* 第 161 轮：**只留图标**（发哥：「去掉收起侧边栏这文字」）→ 不再需要 gap/字号 */
    display: flex;
    align-items: center;
    justify-content: center;
    flex: 1 1 auto;
    height: 34px;
    padding: 0;
    border: 1px solid transparent;
    background: none;
    color: var(--admin-muted);
    cursor: pointer;
}

/* 🆕 第 166 轮（发哥第 4 条）：收缩按钮右边的「待机锁屏」 */
.tp-side-lock {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 34px;
    width: 34px;
    height: 34px;
    padding: 0;
    border: 1px solid var(--admin-border);
    border-radius: var(--admin-r-sm, 6px);
    background: none;
    color: var(--admin-muted);
    cursor: pointer;
    transition: color .15s ease, border-color .15s ease;
}
.tp-side-lock:hover {
    color: var(--admin-accent);
    border-color: var(--admin-accent);
}

/* 侧栏收起（76px）后两个按钮平分可用宽度 */
html.tp-side-mini .tp-side-foot { gap: 4px; }
html.tp-side-mini .tp-side-toggle,
html.tp-side-mini .tp-side-lock { flex: 1 1 0; width: auto; }

/* 🆕 第 166 轮：顶栏「修改密码」钥匙入口（退出登录左边） */
.tp-topbar-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    margin-right: 6px;
    border: 1px solid var(--admin-border);
    border-radius: var(--admin-r-sm, 6px);
    color: var(--admin-muted);
    text-decoration: none;
    transition: color .15s ease, border-color .15s ease;
}
.tp-topbar-icon:hover {
    color: var(--admin-accent);
    border-color: var(--admin-accent);
}
.tp-side-toggle:hover {
    background: var(--admin-hover);
    color: var(--admin-text);
}
.tp-side-toggle-ico {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    line-height: 1;
    transition: transform .16s ease;
}

/* ---------- 折叠态 ---------- */
html.tp-side-mini .tp-sidebar { width: 76px; }

html.tp-side-mini .tp-sidebar-brand {
    padding: 0;
    justify-content: center;
}
/* logo：128px → 48px（约 -63%）。
   🔴 发哥说的「缩小 80%」如果真按 102px 缩，76px 的侧栏根本放不下（会溢出压住内容），
      所以这里取"能放下且红标还认得出"的 48px；要再大/再小只改这一个数。 */
html.tp-side-mini .tp-sidebar-brand-img {
    width: 48px;
    max-width: 48px;
}

html.tp-side-mini .tp-sidebar-nav { padding: 8px 6px; }

html.tp-side-mini .tp-side-home,
html.tp-side-mini .tp-side-solo,
html.tp-side-mini .tp-side-group-head {
    justify-content: center;
    gap: 0;
    padding: 10px 0;
}
/* 一级菜单的文字与 ▸ 全部收掉，只剩图标（图标 18px，居中） */
html.tp-side-mini .tp-side-home > span,
html.tp-side-mini .tp-side-solo > span,
html.tp-side-mini .tp-side-group-head > span { display: none; }

/* 底部按钮：收起后图标**水平翻转**（面板 + 箭头朝右 = 点它展开） */
html.tp-side-mini .tp-side-toggle-ico { transform: scaleX(-1); }

/* ============================================================
   列表页统一筛选条（第 161 轮 · 发哥）
   ------------------------------------------------------------
   结构见 components/admin/list-filter.blade.php：
     第一行 = 销售路径 chips + 产品系列 chips
     第二行 = 搜索框（宽度**全站统一**）+ 本页附加筛选
   两行在同一个 `.tp-listbar` 里 → 滚动时一起吸顶（第 159 轮那套 sticky 规则）。
   ============================================================ */
.tp-lf {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 10px 12px;
}

.tp-lf-row {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
    min-width: 0;
}

/* 两个 chips 组并排；太宽时整组换行，不横向挤压 */
.tp-lf-seg {
    flex: 0 1 auto;
    max-width: 100%;
}

/* 🔴 搜索框宽度统一 320px：发哥要求"搜索框的宽度要一样"。
   不要用 %：不同页面的容器宽度不同，百分比会让它们长得不一样。 */
.tp-lf-search {
    flex: 0 0 320px;
    width: 320px;
    min-width: 0;
}

@media (max-width: 767px) {
    /* 手机上 320px 固定宽会顶出屏幕 → 改成吃满一行 */
    .tp-lf-search {
        flex: 1 1 100%;
        width: 100%;
    }
}
