/* token 权威源：/assets/tokens.css。本文件只消费 var；局部 var(--x, fallback) 仅作极薄兼容，不形成第二套 token 表。 */
[v-cloak] {
    display: none;
}

/* 全站 focus-visible 兜底（nav.css 为主；未引 nav 的页也能用） */
:where(
    a,
    button,
    input:not([class^="el-"]):not([class*=" el-"]),
    select:not([class^="el-"]):not([class*=" el-"]),
    textarea:not([class^="el-"]):not([class*=" el-"]),
    summary,
    [tabindex]:not([tabindex="-1"])
):focus-visible {
    outline: var(--console-focus-outline);
    outline-offset: 2px;
}

/* 卡片/面板表面统一（完整定义，禁止仅覆盖 padding 与 video-ui 冲突） */
.card,
.panel,
.app-card,
.ui-panel {
    border: 1px solid var(--vui-border, var(--vui-border));
    border-radius: var(--vui-radius-md, 10px);
    background: var(--vui-surface, var(--vui-surface));
    box-shadow: var(--vui-shadow-sm);
    box-sizing: border-box;
}

/* 无 card-head/card-body 结构时给简单卡片一点内边距；有结构时由子节点管 */
.card:not(:has(.card-head)):not(:has(.card-body)) {
    padding: 12px 14px;
}

.card h2 {
    margin: 0 0 4px;
    font-size: var(--vui-font-md, 14px);
    font-weight: 800;
}

.card .hint {
    margin: 0 0 8px;
    color: var(--vui-text-3);
    font-size: var(--vui-font-xs, 12px);
}

/* 与 tokens 控件高度对齐(base 也可单独引) */
/* 按钮的完整定义（基础 + hover/disabled + primary/light/soft/danger）在 video-ui.css。
   这里原本还抄了一份基础属性，逐条和那边相同，只是多带了 var() 兜底值——
   两份全站基线并存，谁赢取决于加载顺序，改一处永远漏另一处。
   兜底值本来就该由 tokens.css 提供，不该散在使用处。 */

.btn.sm,
.btn-small,
.btn.compact {
    min-height: var(--vui-ctrl-h-sm);
    height: var(--vui-ctrl-h-sm);
    padding: 0 var(--vui-ctrl-px-sm);
    font-size: var(--vui-font-xs);
}

.badge {
    display: inline-flex;
    align-items: center;
    min-height: 20px;
    height: 20px;
    padding: 0 6px;
    border-radius: var(--vui-radius-sm, 8px);
    background: var(--vui-border-muted);
    color: var(--vui-text-2);
    font-size: var(--vui-font-xs, 12px);
    font-weight: 800;
    white-space: nowrap;
}

.badge.work,
.badge.s0,
.badge.wait {
    background: #fff7ed;
    color: var(--vui-warn);
}

.badge.info,
.badge.s1 {
    background: var(--vui-primary-soft);
    color: var(--vui-primary-active);
}

.badge.done,
.badge.s2 {
    background: var(--vui-success-bg);
    color: var(--vui-success);
}

.badge.fail,
.badge.s3 {
    background: var(--vui-danger-bg);
    color: var(--vui-danger);
}

.badge.empty {
    background: var(--vui-border-muted);
    color: #667085;
}

.tag,
.cat {
    display: inline-flex;
    align-items: center;
    min-height: 20px;
    padding: 0 8px;
    border-radius: 999px;
    background: var(--vui-border-muted);
    color: #475569;
    font-size: var(--vui-font-xs, 12px);
    font-weight: 800;
    line-height: 1.1;
}

.pill.on {
    border-color: transparent;
    background: var(--vui-success-bg, var(--vui-success-bg));
    color: var(--vui-success, var(--vui-success));
}

.pill.off {
    border-color: transparent;
    background: #f3f4f6;
    color: var(--vui-text-3, var(--vui-text-3));
}

/* empty / empty-state / app-empty 统一空态 */
/* 空状态：一个组件两种密度。
   .ui-empty            块状，带虚线框，用在整块区域没内容时（列表页首屏、看板分区）
   .ui-empty--compact   同上但矮一档，用在卡片里
   .ui-empty--inline    只有一行灰字、不画框，用在表格/网格里占一行

   下面那串历史类名是同一个东西的旧写法，等价于 --inline。它们散在十几个页面里，
   markup 逐步换成 .ui-empty--inline，换完这条别名就能删。别再新增。 */
.ui-empty--inline,
.empty,
.empty-state,
.app-empty,
.monitor-empty {
    grid-column: 1 / -1;
    padding: 20px 14px;
    border: 0;
    background: transparent;
    color: var(--vui-text-3);
    font-size: var(--vui-font-sm, 13px);
    text-align: center;
}

.empty-state {
    min-height: 96px;
    display: grid;
    place-items: center;
    padding: 16px 12px;
    border: 1px dashed var(--vui-border, var(--vui-border));
    border-radius: var(--vui-radius-sm, 8px);
    background: var(--vui-surface-muted, var(--vui-surface-muted));
}

.app-empty {
    padding: 42px 18px;
}

.actions {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
}

.field {
    display: grid;
    gap: 4px;
    min-width: 0;
}

.field label,
.form-row label {
    color: var(--vui-text-2);
    font-size: 11px;
    font-weight: 700;
}

.msg {
    font-size: 12px;
}

.msg.err {
    color: #c0392b;
}

.msg.ok {
    color: #1c7d3f;
}

/* 文件选择器组件(ui_file_picker):虚线框 + 统一按钮,替代原始 input[type=file] 样式 */
.ui-file {
    width: 100%;
    box-sizing: border-box;
    min-height: var(--vui-ctrl-h, 32px);
    padding: 6px 8px;
    font-size: var(--vui-font-sm, 13px);
    color: var(--vui-text-3);
    background: var(--vui-surface);
    border: 1px dashed var(--vui-border);
    border-radius: var(--vui-radius-sm, 8px);
    cursor: pointer;
}

.ui-file:hover {
    border-color: var(--vui-primary-active);
    background: var(--vui-primary-soft);
}

.ui-file::file-selector-button,
.ui-file::-webkit-file-upload-button {
    margin: 0 8px 0 0;
    height: var(--vui-ctrl-h-sm, 28px);
    padding: 0 10px;
    font-size: var(--vui-font-xs, 12px);
    font-weight: 700;
    color: var(--vui-text-2);
    background: var(--vui-surface);
    border: 1px solid var(--vui-border);
    border-radius: var(--vui-radius-sm, 8px);
    cursor: pointer;
}

.ui-file:hover::file-selector-button {
    color: var(--vui-primary-active);
    border-color: var(--vui-primary-active);
    background: var(--vui-surface);
}

/* 面包屑:详情页统一返回导航(← 上级 / 当前页),全局单源 */
.crumb {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 12px;
    color: var(--vui-text-3);
}

.crumb a {
    color: var(--vui-primary-active);
    font-weight: 700;
}

/* 标签导航(ui_tabnav):详情页下划线式标签,当前项高亮,走服务端渲染 */
.tabbar {
    display: flex;
    gap: 0;
    padding: 0 4px;
    border-bottom: 1px solid var(--vui-border-muted);
    flex-wrap: wrap;
}

.tabbar a {
    position: relative;
    display: inline-flex;
    align-items: center;
    height: 30px;
    padding: 0 10px;
    color: var(--vui-text-2);
    font-size: 12px;
    font-weight: 700;
    text-decoration: none;
}

.tabbar a:hover {
    color: #111827;
}

.tabbar a.active {
    color: var(--vui-primary-active);
}

.tabbar a.active::after {
    content: '';
    position: absolute;
    left: 8px;
    right: 8px;
    bottom: -1px;
    height: 2px;
    background: var(--vui-primary-active);
    border-radius: 2px 2px 0 0;
}

input:not([type]):not([class^="el-"]):not([class*=" el-"]),
input[type="date"]:not([class^="el-"]):not([class*=" el-"]),
input[type="search"]:not([class^="el-"]):not([class*=" el-"]),
input[type="password"]:not([class^="el-"]):not([class*=" el-"]),
input[type="url"]:not([class^="el-"]):not([class*=" el-"]),
input[type="email"]:not([class^="el-"]):not([class*=" el-"]),
input[type="datetime-local"]:not([class^="el-"]):not([class*=" el-"]) {
    width: 100%;
    box-sizing: border-box;
    min-height: var(--vui-ctrl-h, 32px);
    height: var(--vui-ctrl-h, 32px);
    padding: 0 10px;
    border: 1px solid var(--vui-border);
    border-radius: var(--vui-radius-sm, 8px);
    background: var(--vui-surface);
    color: var(--vui-text);
    font-size: var(--vui-font-sm, 13px);
    outline: 0;
}

.modal-backdrop {
    position: fixed;
    inset: 0;
    z-index: 40;
    display: grid;
    place-items: center;
    padding: 12px;
    background: var(--console-mask, rgba(15, 23, 42, .42));
}

.modal-backdrop[hidden] {
    display: none;
}

/* 弹窗：全站一份。.share-* 是视频/图文那套的旧类名，等价物，markup 逐步换掉。 */
.modal,
.share-modal {
    position: fixed;
    inset: 0;
    z-index: 40;
    display: none;
    align-items: center;
    justify-content: center;
    padding: 12px;
    background: var(--console-mask, rgba(15, 23, 42, .42));
}

.modal.show,
.share-modal.show {
    display: flex;
}

.modal-dialog,
.share-dialog {
    display: flex;
    flex-direction: column;
    width: min(520px, 100%);
    max-height: min(88vh, 720px);
    /* 自己不滚，滚的是 .modal-body：整块滚会把标题和底部按钮一起卷走。 */
    overflow: hidden;
    border-radius: var(--vui-radius-md, 10px);
    background: var(--vui-surface);
    box-shadow: var(--vui-shadow-lg);
}

/* 被 ui-dialog-upgrade.js 升级过的弹窗：原节点留在原地（类名不摘，
   免得打断别处 `.modal xxx` 的选择器），但不再自己画壳——遮罩、居中、
   圆角全交给里面的 el-dialog。display:contents 让它不生成盒子，
   同时保证点击照常冒泡到它身上：好几个页面把关闭按钮的委托挂在这个节点上。

   .show 那条要显式压住：base.css 里 `.modal.show{display:flex}` 和
   `.modal.is-ui-dialog` 特异性相同，只靠位置压不稳。 */
.modal.is-ui-dialog,
.modal.is-ui-dialog.show,
.share-modal.is-ui-dialog,
.share-modal.is-ui-dialog.show {
    display: contents;
}

/* 正文和页脚在被 el-dialog 收养之前先留在原节点里藏着——不能离开文档，
   页面 JS 初始化时就要按 id 抓这些元素。EP 渲染出插槽之后它们会被搬进去，
   那时选择器命中下面这条，重新显示。 */
.ui-dialog-legacy-body,
.ui-dialog-legacy-footer {
    display: none;
}

.el-dialog .ui-dialog-legacy-body,
.el-dialog .ui-dialog-legacy-footer {
    display: block;
}

/* 老壳整个退休：遮罩交给 EP，标题栏 EP 自己画，
   而 .modal-dialog 在内容被搬进 el-dialog 之后只剩一个空架子——
   不藏掉的话它会带着原来的标题栏杵在页面左上角。

   注意是 display:none 而不是把节点删掉：正文和页脚在被 el-dialog 收养
   之前就挂在这个空架子里，节点必须留在文档中，页面 JS 才按 id 抓得到。
   display:none 的子树照样能 getElementById，取值也正常。 */
.modal.is-ui-dialog > .modal-mask,
.share-modal.is-ui-dialog > .share-mask,
.modal.is-ui-dialog > .modal-dialog,
.share-modal.is-ui-dialog > .share-dialog {
    display: none;
}

/* 搬进 el-dialog 之后，正文和页脚的外边距交给 .ui-dialog 那套，
   老壳自己的内边距要清掉，否则会叠两层。 */
.ui-dialog--legacy .ui-dialog-legacy-body > .modal-body,
.ui-dialog--legacy .ui-dialog-legacy-body > .share-body {
    padding: 0;
    overflow: visible;
}

.ui-dialog--legacy .ui-dialog-legacy-footer > .modal-actions,
.ui-dialog--legacy .ui-dialog-legacy-footer > .publish-actions {
    padding-top: 0;
    justify-content: flex-end;
}

.modal-head,
.share-head {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 8px 10px;
    border-bottom: 1px solid var(--vui-border-muted);
    background: var(--vui-surface-muted);
}

.modal-head h3,
.share-head h3 {
    margin: 0;
    font-size: 14px;
    font-weight: 800;
    line-height: 1.3;
}

.modal-body,
.share-body {
    min-height: 0;
    display: grid;
    gap: 8px;
    padding: 10px;
    overflow: auto;
}

.modal-actions {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 6px;
    flex-wrap: wrap;
    padding-top: 2px;
}

/* element-plus 的对话框走同一套行为：高度封顶、标题和底部按钮钉住、只有正文滚。
   EP 默认是整个对话框跟着遮罩一起滚，长表单会被推出屏幕——批量创作实测对话框
   1050px 高、视口 720px，确认按钮落在折线下 418px 的地方。
   task-market-admin.css 的 .tma-dialog 和 tenant-admin.css 的 .tenant-*-dialog
   早就各自私下修过一遍，这里收回公共层，它们排在后面，自己的边距仍然生效。
   选择器重复一次：base.css 排在 element-plus 的 index.css 前面，同特异性后来者赢，
   (0,2,0) 才压得住 EP 自己那条 (0,1,0)。 */
.el-dialog.el-dialog {
    display: flex;
    flex-direction: column;
    /* EP 的外边距是 var(--el-dialog-margin-top, 15vh) auto 50px，照着扣。 */
    max-height: calc(100% - var(--el-dialog-margin-top, 15vh) - 50px);
    overflow: hidden;
}

.el-dialog.el-dialog.is-fullscreen {
    max-height: 100%;
}

.el-dialog .el-dialog__header,
.el-dialog .el-dialog__footer {
    flex: 0 0 auto;
}

.el-dialog .el-dialog__body {
    min-height: 0;
    overflow: auto;
    overscroll-behavior: contain;
}

/* ── 对话框外壳：element-plus 的 el-dialog 统一长这样 ──────────────────────
   用法：<el-dialog class="ui-dialog" modal-class="ui-dialog-overlay">。
   在这之前 task-market-admin.css 的 .tma-dialog、tenant-admin.css 的
   .tenant-*-dialog、video-templates.css 的 .vt-editor-dialog 各抄了一份外壳：
   三套内边距（24 / 22 / EP 默认）、三种标题字号（18 / 20 / 默认）、两个圆角
   （22 / lg）、各自的分隔线颜色和阴影。同一个东西写三遍，改一处永远漏两处。
   页面自己的类名保留，但只负责页面独有的内容，外壳一律由这里出。
   选择器一律把类名写两遍：base.css 排在 element-plus 的 index.css 前面，同特异性
   后来者赢，(0,3,0) 才压得住 EP 自己的 .el-dialog__header.show-close 那条 (0,2,0)。 */
.ui-dialog-overlay {
    background: var(--console-mask, rgba(15, 23, 42, .42));
}

.ui-dialog-overlay .el-overlay-dialog {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--vui-space-4);
}

.ui-dialog.ui-dialog {
    /* 遮罩已经用 padding 留出四周边距，这里吃满剩下的高度；超高时滚的是正文。 */
    max-height: 100%;
    margin: 0 !important;
    /* 内边距交给头/身/脚，分隔线才能贯通整个宽度。 */
    --el-dialog-padding-primary: 0px;
    box-shadow: var(--vui-shadow-lg);
}

.ui-dialog.ui-dialog .el-dialog__header {
    margin: 0;
    padding: var(--vui-space-4) 52px var(--vui-space-3) var(--vui-space-6);
    border-bottom: 1px solid var(--vui-border);
    /* EP 给标题栏挂了 cursor: move，但这里的弹窗都不能拖。 */
    cursor: default;
}

.ui-dialog.ui-dialog .el-dialog__title {
    color: var(--vui-text);
    font-size: var(--vui-font-xl);
    font-weight: 800;
}

.ui-dialog.ui-dialog .el-dialog__headerbtn {
    top: var(--vui-space-2);
    right: var(--vui-space-3);
    width: 32px;
    height: 32px;
    border-radius: var(--vui-radius-sm);
}

.ui-dialog.ui-dialog .el-dialog__headerbtn:hover {
    background: var(--vui-surface-muted);
}

.ui-dialog.ui-dialog .el-dialog__close {
    color: var(--vui-text-3);
}

.ui-dialog.ui-dialog .el-dialog__body {
    padding: var(--vui-space-4) var(--vui-space-6);
    scrollbar-gutter: stable;
}

.ui-dialog.ui-dialog .el-dialog__footer {
    padding: var(--vui-space-3) var(--vui-space-6) var(--vui-space-4);
    border-top: 1px solid var(--vui-border);
    background: var(--vui-surface);
}

/* 窄屏贴底：手机上居中的弹窗上下都够不着，收成抽屉。 */
@media (max-width: 640px) {
    .ui-dialog-overlay .el-overlay-dialog {
        align-items: flex-end;
        padding: var(--vui-space-2);
    }

    .ui-dialog.ui-dialog {
        width: 100% !important;
    }

    .ui-dialog.ui-dialog .el-dialog__header {
        padding: var(--vui-space-3) 48px var(--vui-space-2) var(--vui-space-4);
    }

    .ui-dialog.ui-dialog .el-dialog__body {
        padding: var(--vui-space-3) var(--vui-space-4);
    }

    .ui-dialog.ui-dialog .el-dialog__footer {
        padding: var(--vui-space-3) var(--vui-space-4) calc(var(--vui-space-3) + env(safe-area-inset-bottom));
    }
}

.modal-close {
    width: 28px;
    height: 28px;
    padding: 0;
    border: 1px solid var(--vui-control-border, var(--vui-control-border));
    border-radius: var(--vui-radius-sm, 8px);
    background: var(--vui-surface);
    color: var(--vui-text-2);
    font-size: 16px;
    line-height: 1;
    cursor: pointer;
}

.field .req {
    color: var(--vui-danger, #c0392b);
    font-weight: 850;
}

.field .hint,
.hint {
    margin: 0;
    color: var(--vui-text-3);
    font-size: 11px;
    line-height: 1.45;
}

.ui-input {
    width: 100%;
    box-sizing: border-box;
    min-height: var(--vui-ctrl-h, 32px);
    height: var(--vui-ctrl-h, 32px);
    padding: 0 10px;
    border: 1px solid var(--vui-border);
    border-radius: var(--vui-radius-sm, 8px);
    background: var(--vui-surface);
    color: var(--vui-text);
    font-size: var(--vui-font-sm, 13px);
    outline: 0;
}

select.ui-input {
    appearance: auto;
}

/* 分段单选 ui_field_seg */
.ui-seg {
    display: inline-flex;
    flex-wrap: wrap;
    gap: 2px;
    padding: 2px;
    border: 1px solid var(--vui-border);
    border-radius: var(--vui-radius-sm, 8px);
    background: var(--vui-surface-muted, var(--vui-surface-muted));
}

.ui-seg-item {
    position: relative;
    display: inline-flex;
    cursor: pointer;
    margin: 0;
}

.ui-seg-item input {
    position: absolute;
    opacity: 0;
    pointer-events: none;
}

.ui-seg-item span {
    display: inline-flex;
    align-items: center;
    min-height: var(--vui-ctrl-h-sm, 28px);
    height: var(--vui-ctrl-h-sm, 28px);
    padding: 0 10px;
    border-radius: 6px;
    color: var(--vui-text-2);
    font-size: var(--vui-font-xs, 12px);
    font-weight: 800;
    white-space: nowrap;
}

.ui-seg-item input:checked + span {
    background: var(--vui-surface, var(--vui-surface));
    color: var(--vui-primary-active);
    box-shadow: 0 1px 3px rgba(15, 23, 42, .08);
}

.ui-seg-item input:disabled + span {
    opacity: .45;
    cursor: not-allowed;
}

/* 面包屑 ui_crumbs */
.ui-crumbs {
    display: flex;
    align-items: center;
    gap: 4px;
    flex-wrap: wrap;
    min-width: 0;
    font-size: 12px;
}

.ui-crumbs a {
    color: var(--vui-primary-active, var(--vui-primary-active));
    font-weight: 700;
    text-decoration: none;
}

.ui-crumbs a:hover {
    text-decoration: underline;
}

.ui-crumbs-sep {
    color: var(--vui-text-3);
}

.ui-crumbs-cur {
    color: var(--vui-text, var(--vui-text));
    font-weight: 800;
}

/* 页头统一：h1 22px/800，辅助 13px（layout + layui 老页共用） */
.ui-page-head,
.app-page-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 12px;
    flex-wrap: wrap;
}

.ui-page-head h1,
.app-page-head h1 {
    margin: 0;
    font-size: 22px;
    font-weight: 800;
    line-height: 1.3;
    color: var(--vui-text, var(--vui-text));
}

.ui-page-head-sub,
.app-page-subtitle {
    margin-top: 4px;
    color: var(--vui-text-3);
    font-size: 13px;
    line-height: 1.5;
}

/* 网盘式宫格(素材库等) */
.ui-drive {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
    gap: 8px;
    padding: 10px;
}

.ui-drive-item {
    display: grid;
    gap: 5px;
    justify-items: center;
    text-align: center;
    min-width: 0;
    padding: 10px 6px;
    border: 1px solid transparent;
    border-radius: var(--vui-radius-sm, 8px);
    color: inherit;
    text-decoration: none;
    background: transparent;
}

a.ui-drive-item:hover,
.ui-drive-item:hover {
    background: var(--vui-surface-muted, var(--vui-surface-muted));
    border-color: var(--vui-border-muted, var(--vui-border-muted));
}

.ui-drive-thumb {
    width: 64px;
    height: 64px;
    border-radius: 8px;
    overflow: hidden;
    background: var(--vui-border-muted);
    border: 1px solid var(--vui-border-muted, var(--vui-border-muted));
    display: grid;
    place-items: center;
    font-size: 26px;
    line-height: 1;
}

.ui-drive-thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

.ui-drive-name {
    width: 100%;
    font-size: 12px;
    font-weight: 750;
    color: var(--vui-text, var(--vui-text));
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ui-drive-meta {
    font-size: 11px;
    color: var(--vui-text-3);
}

.ui-drive-item.is-rejected .ui-drive-name {
    color: var(--vui-text-4);
}

.ui-toolbar,
.filter-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    flex-wrap: wrap;
    padding: 10px 12px;
    border: 1px solid var(--vui-border);
    border-radius: var(--vui-radius-md, 10px);
    background: var(--vui-surface);
    box-shadow: var(--vui-shadow-sm);
    box-sizing: border-box;
}

.filter-bar {
    justify-content: flex-start;
}

.ui-panel {
    min-height: 240px;
}

.ui-page {
    width: min(1100px, calc(100% - 20px));
    margin: 12px auto 24px;
    display: grid;
    gap: 10px;
}

/* 宽页画布：运营中台列表/仪表盘用，比 .ui-page 更宽 */
.ui-page-wide {
    width: min(1440px, calc(100% - 32px));
    margin: 16px auto 28px;
    display: grid;
    gap: 14px;
    min-width: 0;
    box-sizing: border-box;
}

@media (max-width: 640px) {
    /* 公共顶栏由 nav.css 单源管理，禁止在此强制 topbar/top-left 竖排
       （历史 column 规则会把手机顶栏拆成多行，污染全站壳） */
    .modal-backdrop,
    .modal {
        padding: 10px;
        place-items: stretch;
    }
}

/* ===== 通用表格(ui_table + layui app-data-table)===== */
.table-wrap {
    width: 100%;
    max-width: 100%;
    min-width: 0;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}
.dtable,
.app-data-table {
    width: 100%;
    border-collapse: collapse;
    background: var(--vui-surface, var(--vui-surface));
    color: var(--vui-text, var(--vui-text));
}
.dtable th,
.dtable td,
.app-data-table th,
.app-data-table td {
    padding: 10px 12px;
    border-bottom: 1px solid var(--vui-border-muted, var(--vui-border-muted));
    text-align: left;
    vertical-align: middle;
    font-size: var(--vui-font-sm, 13px);
    white-space: nowrap;
}

/* 紧凑工具栏 + 「更多」折叠（页面工具条共用，不碰公共顶栏） */
.toolbar-actions {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
    min-width: 0;
}
.more-tools {
    position: relative;
    margin: 0;
    border: 0;
    padding: 0;
    min-width: 0;
}
.more-tools > summary {
    list-style: none;
    cursor: pointer;
    user-select: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 4px;
    box-sizing: border-box;
    min-height: var(--vui-ctrl-h-sm, 28px);
    height: var(--vui-ctrl-h-sm, 28px);
    padding: 0 var(--vui-ctrl-px-sm, 8px);
    border: 1px solid var(--vui-control-border, var(--vui-control-border));
    border-radius: var(--vui-radius-sm, 8px);
    background: var(--vui-surface, var(--vui-surface));
    color: var(--vui-text, var(--vui-text));
    font-size: var(--vui-font-xs, 12px);
    font-weight: 700;
    line-height: 1.15;
    white-space: nowrap;
}
.more-tools > summary::-webkit-details-marker { display: none; }
.more-tools > summary::after {
    content: "▾";
    font-size: 10px;
    color: var(--vui-text-3, var(--vui-text-3));
}
.more-tools[open] > summary {
    border-color: #93c5fd;
    color: var(--vui-primary-active, var(--vui-primary-active));
    background: var(--vui-primary-softer, var(--vui-primary-softer));
}
.more-tools[open] > summary::after { content: "▴"; }
.more-tools-body {
    display: flex;
    align-items: center;
    gap: 6px;
    flex-wrap: wrap;
    margin-top: 6px;
    padding: 8px;
    border: 1px solid var(--vui-border-muted, var(--vui-border-muted));
    border-radius: var(--vui-radius-sm, 8px);
    background: var(--vui-surface-muted, var(--vui-surface-muted));
    box-sizing: border-box;
    min-width: 0;
    max-width: min(100vw - 32px, 420px);
}
.dtable td.wrap,
.dtable th.wrap { white-space: normal; }
.dtable thead th,
.app-data-table th {
    color: var(--vui-text-3, var(--vui-text-3));
    background: var(--vui-surface-muted, var(--vui-surface-muted));
    font-size: var(--vui-font-xs, 12px);
    font-weight: 750;
}
.dtable tbody tr:last-child td { border-bottom: 0; }
.dtable tbody tr:hover,
.app-data-table tbody tr:hover { background: var(--vui-surface-muted, var(--vui-surface-muted)); }
.dtable .tbl-empty { text-align: center; color: var(--vui-text-3); padding: 28px 12px; font-size: var(--vui-font-sm, 13px); white-space: normal; }
.dtable .mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: var(--vui-font-xs, 12px); }
.dtable .muted { color: var(--vui-text-3); }

/* 分页器 ui_pager */

/* layui 按钮/输入：高度/圆角/主色与 .btn 同档（不删功能） */
.layui-btn {
    height: var(--vui-ctrl-h, 32px) !important;
    line-height: calc(var(--vui-ctrl-h, 32px) - 2px) !important;
    padding: 0 14px !important;
    font-size: var(--vui-font-sm, 13px) !important;
    border-radius: var(--vui-radius-sm, 8px) !important;
}
.layui-btn-normal {
    background-color: var(--vui-primary) !important;
}
.layui-btn-normal:hover {
    background-color: var(--vui-primary-hover) !important;
}
.layui-btn-primary {
    border-color: var(--vui-control-border) !important;
    color: var(--vui-text) !important;
}
.layui-btn-xs,
.layui-btn-sm {
    height: var(--vui-ctrl-h-sm, 28px) !important;
    line-height: calc(var(--vui-ctrl-h-sm, 28px) - 2px) !important;
    padding: 0 10px !important;
    font-size: var(--vui-font-xs, 12px) !important;
}
.layui-btn-lg {
    height: var(--vui-ctrl-h-lg, 36px) !important;
    line-height: calc(var(--vui-ctrl-h-lg, 36px) - 2px) !important;
    padding: 0 16px !important;
    font-size: var(--vui-font-md, 14px) !important;
}
.layui-input,
.layui-select,
.layui-textarea {
    height: var(--vui-ctrl-h, 32px) !important;
    line-height: var(--vui-ctrl-h, 32px) !important;
    padding: 0 10px !important;
    font-size: var(--vui-font-sm, 13px) !important;
    border-radius: var(--vui-radius-sm, 8px) !important;
    border-color: var(--vui-control-border) !important;
}
.layui-textarea {
    height: auto !important;
    min-height: 80px !important;
    line-height: 1.5 !important;
    padding: 8px 10px !important;
}

/* ==========================================================================
   中台公共组件（#273 / #278 视觉基线 · 供 business builder 复用）
   不碰 Element Plus / Layui 内部功能选择器。
   ========================================================================== */

/* ---------- Metric cards ---------- */
.metric-grid {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 12px;
    min-width: 0;
}

.metric-card {
    display: grid;
    gap: 6px;
    min-width: 0;
    padding: 14px 16px;
    border: 1px solid var(--vui-border, var(--vui-border));
    border-radius: var(--vui-radius-md, 10px);
    background: var(--vui-surface, var(--vui-surface));
    color: inherit;
    text-decoration: none;
    box-shadow: var(--vui-shadow-sm);
    box-sizing: border-box;
}

.metric-card.is-monitor {
    border-top: 3px solid var(--vui-primary);
}

.metric-card.is-video {
    border-top: 3px solid #7c3aed;
}

.metric-card.is-dymanage {
    border-top: 3px solid #0d9488;
}

.metric-card.is-xhs {
    border-top: 3px solid #e11d48;
}

.metric-card .metric-label {
    color: var(--vui-text-3, var(--vui-text-3));
    font-size: 12px;
    font-weight: 700;
    line-height: 1.3;
}

.metric-card .metric-value {
    color: var(--vui-text, var(--vui-text));
    font-size: 22px;
    font-weight: 850;
    line-height: 1.15;
    letter-spacing: -.02em;
    font-variant-numeric: tabular-nums;
}

.metric-card .metric-meta {
    color: var(--vui-text-3, var(--vui-text-3));
    font-size: 11px;
    font-weight: 600;
    line-height: 1.4;
}

/* ---------- Workflow strip ---------- */
.workflow-strip {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
    gap: 10px;
    min-width: 0;
}

.workflow-step {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    align-items: start;
    gap: 10px;
    min-width: 0;
    padding: 12px 14px;
    border: 1px solid var(--vui-border, var(--vui-border));
    border-radius: var(--vui-radius-md, 10px);
    background: var(--vui-surface, var(--vui-surface));
    color: inherit;
    text-decoration: none;
    box-shadow: var(--vui-shadow-sm);
    box-sizing: border-box;
    transition: border-color .15s ease, box-shadow .15s ease, background .15s ease;
}

.workflow-step:hover {
    border-color: #c5d8f5;
    background: #f8fbff;
}

.workflow-step.active,
.workflow-step.is-active {
    border-color: #bcd3ff;
    background: var(--vui-primary-soft, var(--vui-primary-soft));
    box-shadow: 0 0 0 1px var(--console-focus-ring), var(--vui-shadow-sm);
}

.workflow-index {
    display: inline-grid;
    place-items: center;
    width: 24px;
    height: 24px;
    flex: 0 0 auto;
    border-radius: 999px;
    background: var(--vui-primary-soft, var(--vui-primary-soft));
    color: var(--vui-primary-active, var(--vui-primary-active));
    font-size: 12px;
    font-weight: 850;
    line-height: 1;
}

.workflow-step.active .workflow-index,
.workflow-step.is-active .workflow-index {
    background: var(--vui-primary, #1667d9);
    color: var(--vui-surface);
}

.workflow-title {
    margin: 0;
    color: var(--vui-text, var(--vui-text));
    font-size: 13px;
    font-weight: 800;
    line-height: 1.35;
}

.workflow-desc {
    grid-column: 2;
    margin: 3px 0 0;
    color: var(--vui-text-3, var(--vui-text-3));
    font-size: 11px;
    font-weight: 600;
    line-height: 1.45;
}

/* ---------- Hub entry cards ---------- */
.hub-grid {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 14px;
    min-width: 0;
}

.hub-card {
    min-height: 168px;
    display: grid;
    align-content: space-between;
    gap: 16px;
    min-width: 0;
    padding: 18px 18px 16px;
    border: 1px solid var(--vui-border, var(--vui-border));
    border-radius: var(--vui-radius-md, 10px);
    background: var(--vui-surface, var(--vui-surface));
    color: inherit;
    text-decoration: none;
    box-shadow: var(--vui-shadow-sm);
    box-sizing: border-box;
    transition: border-color .15s ease, box-shadow .15s ease, transform .15s ease;
}

/* 与 .card 并存时：hub-card 自管内边距，不依赖改全局 .card */
a.hub-card.card,
.hub-card.card {
    padding: 18px 18px 16px;
}

.hub-card:hover {
    border-color: #bcd3ff;
    box-shadow: var(--vui-shadow-md);
    transform: translateY(-1px);
}

.hub-card-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 10px;
    min-width: 0;
}

.hub-card-title {
    margin: 0;
    color: var(--vui-text, var(--vui-text));
    font-size: 17px;
    font-weight: 850;
    line-height: 1.35;
}

.hub-card-subtitle {
    margin: 6px 0 0;
    color: var(--vui-text-3, var(--vui-text-3));
    font-size: 13px;
    line-height: 1.55;
}

.hub-card-kpis {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(64px, 1fr));
    gap: 8px;
    min-width: 0;
}

.hub-kpi {
    display: grid;
    gap: 2px;
    min-width: 0;
}

.hub-kpi-value {
    color: var(--vui-text, var(--vui-text));
    font-size: 16px;
    font-weight: 850;
    font-variant-numeric: tabular-nums;
    line-height: 1.2;
}

.hub-kpi-label {
    color: var(--vui-text-3, var(--vui-text-3));
    font-size: 11px;
    font-weight: 650;
    line-height: 1.3;
}

.hub-card-links {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    min-width: 0;
}

.hub-card-links a {
    color: var(--vui-primary-active, var(--vui-primary-active));
    font-size: 12px;
    font-weight: 750;
    text-decoration: none;
}

.hub-card-links a:hover {
    text-decoration: underline;
}

/* ---------- Hub dashboard panels ---------- */
.hub-dashboard-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 14px;
    min-width: 0;
}

.hub-panel {
    min-width: 0;
    border: 1px solid var(--vui-border, var(--vui-border));
    border-radius: var(--vui-radius-md, 10px);
    background: var(--vui-surface, var(--vui-surface));
    box-shadow: var(--vui-shadow-sm);
    overflow: hidden;
    box-sizing: border-box;
}

.hub-panel-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    min-height: 44px;
    padding: 10px 14px;
    border-bottom: 1px solid var(--vui-border-muted, var(--vui-border-muted));
    background: var(--vui-surface-muted, var(--vui-surface-muted));
}

.hub-panel-head h2,
.hub-panel-head h3 {
    margin: 0;
    font-size: 14px;
    font-weight: 800;
    color: var(--vui-text, var(--vui-text));
    line-height: 1.3;
}

.hub-list {
    display: grid;
    gap: 0;
    margin: 0;
    padding: 0;
    list-style: none;
}

.hub-list-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    min-width: 0;
    padding: 10px 14px;
    border-bottom: 1px solid var(--vui-border-muted, var(--vui-border-muted));
    color: var(--vui-text-2, var(--vui-text-2));
    font-size: 13px;
    font-weight: 650;
    text-decoration: none;
    box-sizing: border-box;
}

.hub-list-item:last-child {
    border-bottom: 0;
}

.hub-card-title a {
    color: inherit;
    text-decoration: none;
}

.hub-card-title a:hover {
    color: var(--vui-primary-active, var(--vui-primary-active));
}

.hub-list-item > a {
    flex: 1 1 auto;
    min-width: 0;
    display: grid;
    gap: 2px;
    color: inherit;
    text-decoration: none;
}

.hub-list-item > a > span,
.hub-list-item > a > small {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.hub-list-item > a > small {
    color: var(--vui-text-3, var(--vui-text-3));
    font-size: 11px;
    font-weight: 600;
}

a.hub-list-item:hover {
    background: var(--vui-surface-muted, var(--vui-surface-muted));
    color: var(--vui-primary-active, var(--vui-primary-active));
}

.hub-list-item:hover {
    background: var(--vui-surface-muted, var(--vui-surface-muted));
}

.hub-list-item > a:hover {
    color: var(--vui-primary-active, var(--vui-primary-active));
}

.hub-list-count {
    flex: 0 0 auto;
    color: var(--vui-text-3, var(--vui-text-3));
    font-size: 12px;
    font-weight: 800;
    font-variant-numeric: tabular-nums;
}

/* ---------- 响应式：1440 / 1280 / 1024 / 390 ---------- */
@media (max-width: 1440px) {
    .ui-page-wide {
        width: min(1280px, calc(100% - 28px));
    }

    .metric-card .metric-value {
        font-size: 20px;
    }
}

@media (max-width: 1280px) {
    .ui-page-wide {
        width: min(1100px, calc(100% - 24px));
        gap: 12px;
    }

    .metric-grid {
        grid-template-columns: repeat(3, minmax(0, 1fr));
        gap: 10px;
    }

    .hub-grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 12px;
    }

    .workflow-strip {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@media (max-width: 1024px) {
    .ui-page-wide {
        width: calc(100% - 20px);
        margin: 12px auto 20px;
    }

    .metric-grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }

    .hub-dashboard-grid {
        grid-template-columns: 1fr;
    }

    .hub-card {
        min-height: 148px;
        padding: 16px;
    }

    a.hub-card.card,
    .hub-card.card {
        padding: 16px;
    }

    .workflow-strip {
        grid-template-columns: 1fr;
    }
}

@media (max-width: 390px) {
    .ui-page-wide {
        width: calc(100% - 16px);
        margin: 10px auto 16px;
        gap: 10px;
    }

    .metric-grid,
    .hub-grid {
        grid-template-columns: 1fr;
        gap: 8px;
    }

    .metric-card {
        padding: 12px 14px;
    }

    .metric-card .metric-value {
        font-size: 18px;
    }

    .hub-card {
        min-height: 0;
        padding: 14px;
        gap: 12px;
    }

    a.hub-card.card,
    .hub-card.card {
        padding: 14px;
    }

    .hub-card-title {
        font-size: 15px;
    }

    .hub-card-subtitle {
        font-size: 12px;
    }

    .workflow-step {
        padding: 10px 12px;
    }

    .hub-panel-head {
        min-height: 40px;
        padding: 8px 12px;
    }

    .hub-list-item {
        padding: 9px 12px;
        font-size: 12px;
    }

    /* 触控按钮底线 + 页面工具栏自然换行（不碰公共顶栏） */
    .btn,
    .btn.sm,
    .btn-small,
    .btn.compact {
        min-height: 28px;
    }

    .ui-toolbar {
        align-items: flex-start;
        gap: 8px;
        padding: 8px 10px;
    }

    .toolbar-actions,
    .more-tools-body {
        max-width: 100%;
    }
}

/* ---------- Namespaced atomic components ---------- */
.ui-surface {
    min-width: 0;
    overflow: hidden;
    border: 1px solid var(--vui-border);
    border-radius: var(--vui-radius-md);
    background: var(--vui-surface);
    box-shadow: var(--vui-shadow-sm);
    box-sizing: border-box;
}

.ui-surface[data-tone="muted"] { border-color: var(--vui-border-muted); background: var(--vui-surface-muted); }
.ui-surface[data-tone="info"] { border-color: var(--vui-primary-soft); background: var(--vui-info-bg); }
.ui-surface[data-tone="success"] { border-color: var(--vui-success-bg); background: var(--vui-success-bg); }
.ui-surface[data-tone="warn"] { border-color: var(--vui-warn-bg); background: var(--vui-warn-bg); }
.ui-surface[data-tone="danger"] { border-color: var(--vui-danger-bg); background: var(--vui-danger-bg); }

.ui-surface__head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--vui-gap);
    padding: 12px 14px;
    border-bottom: 1px solid var(--vui-border-muted);
}

.ui-surface__body { min-width: 0; padding: 14px; }
.ui-surface__title { margin: 0; color: var(--vui-text); font-size: var(--vui-font-md); font-weight: 800; line-height: 1.35; }
.ui-surface__hint { margin: 4px 0 0; color: var(--vui-text-3); font-size: var(--vui-font-xs); line-height: 1.45; }

.ui-stat {
    display: grid;
    gap: 4px;
    min-width: 0;
    padding: 14px 16px;
    border: 1px solid var(--vui-border);
    border-radius: var(--vui-radius-md);
    background: var(--vui-surface);
    box-shadow: var(--vui-shadow-sm);
    box-sizing: border-box;
}

.ui-stat__label { color: var(--vui-text-3); font-size: var(--vui-font-xs); font-weight: 700; line-height: 1.35; }
.ui-stat__value { color: var(--vui-text); font-size: 22px; font-weight: 850; line-height: 1.15; font-variant-numeric: tabular-nums; }
.ui-stat__meta { color: var(--vui-text-3); font-size: var(--vui-font-xs); line-height: 1.4; }
.ui-stat[data-tone="default"] .ui-stat__value { color: var(--vui-text); }
.ui-stat[data-tone="info"] .ui-stat__value { color: var(--vui-info); }
.ui-stat[data-tone="success"] .ui-stat__value { color: var(--vui-success); }
.ui-stat[data-tone="warn"] .ui-stat__value { color: var(--vui-warn); }
.ui-stat[data-tone="danger"] .ui-stat__value { color: var(--vui-danger); }

.ui-pill {
    display: inline-flex;
    align-items: center;
    min-height: 22px;
    padding: 0 8px;
    border: 1px solid var(--vui-border);
    border-radius: 999px;
    background: var(--vui-surface-muted);
    color: var(--vui-text-3);
    font-size: var(--vui-font-xs);
    font-weight: 750;
    line-height: 1;
    white-space: nowrap;
}

.ui-pill[data-tone="neutral"] { border-color: var(--vui-border); background: var(--vui-surface-muted); color: var(--vui-text-3); }
.ui-pill[data-tone="info"] { border-color: var(--vui-primary-soft); background: var(--vui-info-bg); color: var(--vui-info); }
.ui-pill[data-tone="success"] { border-color: var(--vui-success-bg); background: var(--vui-success-bg); color: var(--vui-success); }
.ui-pill[data-tone="warn"] { border-color: var(--vui-warn-bg); background: var(--vui-warn-bg); color: var(--vui-warn); }
.ui-pill[data-tone="danger"] { border-color: var(--vui-danger-bg); background: var(--vui-danger-bg); color: var(--vui-danger); }

.ui-empty {
    grid-column: 1 / -1;
    display: grid;
    place-items: center;
    align-content: center;
    gap: 8px;
    min-height: 180px;
    padding: 24px;
    border: 1px dashed var(--vui-border);
    border-radius: var(--vui-radius-md);
    background: var(--vui-surface-muted);
    color: var(--vui-text-3);
    text-align: center;
    box-sizing: border-box;
}

.ui-empty__title { color: var(--vui-text); font-size: var(--vui-font-lg); font-weight: 800; line-height: 1.35; }
.ui-empty__description { color: var(--vui-text-3); font-size: var(--vui-font-sm); line-height: 1.5; }
.ui-empty__actions { display: inline-flex; align-items: center; justify-content: center; gap: 6px; flex-wrap: wrap; }
.ui-empty--compact { min-height: 120px; padding: 16px; }

.ui-table { width: 100%; min-width: 0; color: var(--vui-text); }
.ui-table__scroll { max-width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; }
.ui-table table { width: 100%; border-collapse: collapse; background: var(--vui-surface); }
.ui-table th,
.ui-table td { padding: 10px 12px; border-bottom: 1px solid var(--vui-border-muted); text-align: left; vertical-align: middle; font-size: var(--vui-font-sm); }
.ui-table th { background: var(--vui-surface-muted); color: var(--vui-text-3); font-size: var(--vui-font-xs); font-weight: 750; }
.ui-table tbody tr:hover { background: var(--vui-surface-muted); }
.ui-table tbody tr:last-child td { border-bottom: 0; }
.ui-table[data-density="compact"] th,
.ui-table[data-density="compact"] td { padding: 7px 10px; }

/* Searchable select: one visible combobox, native select remains the form value and no-JS fallback. */
.ui-combobox {
    position: relative;
    min-width: 0;
    width: 100%;
    color: var(--vui-text);
    font-size: var(--vui-font-sm);
}

.ui-combobox__native {
    position: absolute !important;
    width: 1px !important;
    height: 1px !important;
    margin: -1px !important;
    padding: 0 !important;
    overflow: hidden !important;
    clip: rect(0 0 0 0) !important;
    clip-path: inset(50%) !important;
    white-space: nowrap !important;
}

.ui-combobox__input {
    width: 100%;
    min-width: 0;
    min-height: 38px;
    padding: 0 58px 0 11px !important;
    border: 1px solid var(--vui-control-border-soft);
    border-radius: var(--vui-radius-sm);
    background: var(--vui-surface);
    color: var(--vui-text);
    font: inherit;
    text-overflow: ellipsis;
    outline: 0;
    cursor: pointer;
    caret-color: var(--vui-text);
    box-sizing: border-box;
}

/* Chromium gives input[type="search"] its own clear button. The combobox already
   owns an accessible clear action, so keeping both creates two overlapping × controls. */
.ui-combobox__input::-webkit-search-cancel-button,
.ui-combobox__input::-webkit-search-decoration,
.ui-combobox__input::-webkit-search-results-button,
.ui-combobox__input::-webkit-search-results-decoration {
    display: none;
    -webkit-appearance: none;
    appearance: none;
}

.ui-combobox__input::-ms-clear,
.ui-combobox__input::-ms-reveal { display: none; width: 0; height: 0; }

.ui-combobox__input:hover { border-color: var(--vui-control-border); }
.ui-combobox__input:focus,
.ui-combobox.is-open .ui-combobox__input {
    border-color: var(--vui-primary);
    box-shadow: 0 0 0 3px rgba(37, 99, 235, .12);
}

.ui-combobox__controls {
    position: absolute;
    top: 1px;
    right: 1px;
    height: 36px;
    display: inline-flex;
    align-items: center;
    z-index: 2;
}

.ui-combobox__clear,
.ui-combobox__toggle {
    width: 27px;
    height: 34px;
    display: grid;
    place-items: center;
    padding: 0;
    border: 0;
    background: transparent;
    color: var(--vui-text-3);
    cursor: pointer;
}

.ui-combobox__clear { font-size: 17px; line-height: 1; }
.ui-combobox__clear[hidden] { display: none; }
.ui-combobox__clear:hover,
.ui-combobox__toggle:hover { color: var(--vui-primary-active); }
.ui-combobox__toggle span {
    width: 7px;
    height: 7px;
    margin-top: -4px;
    border-right: 1.5px solid currentColor;
    border-bottom: 1.5px solid currentColor;
    transform: rotate(45deg);
    transition: transform .16s ease, margin .16s ease;
}
.ui-combobox.is-open .ui-combobox__toggle span { margin-top: 4px; transform: rotate(225deg); }

.ui-combobox__popup {
    position: absolute;
    top: calc(100% + 5px);
    left: 0;
    z-index: 80;
    width: max(100%, 220px);
    max-height: 260px;
    overflow: auto;
    padding: 5px;
    border: 1px solid var(--vui-border);
    border-radius: var(--vui-radius-sm);
    background: var(--vui-surface);
    box-shadow: 0 14px 34px rgba(15, 23, 42, .14);
    box-sizing: border-box;
}

.ui-combobox__option {
    width: 100%;
    min-height: 34px;
    display: block;
    padding: 7px 9px;
    overflow: hidden;
    border: 0;
    border-radius: 6px;
    background: transparent;
    color: var(--vui-text-2);
    font: inherit;
    line-height: 1.35;
    text-align: left;
    text-overflow: ellipsis;
    white-space: nowrap;
    cursor: pointer;
}

.ui-combobox__option:hover,
.ui-combobox__option.is-active { background: var(--vui-primary-soft); color: var(--vui-primary-active); }
.ui-combobox__option.is-selected { color: var(--vui-primary-active); font-weight: 800; }
.ui-combobox__option.is-selected::after { float: right; content: '\2713'; }
.ui-combobox__empty { padding: 16px 10px; color: var(--vui-text-3); font-size: var(--vui-font-xs); text-align: center; }
.ui-combobox.is-disabled { opacity: .65; }

@media (max-width: 800px) {
    .ui-combobox__input { min-height: 44px; }
    .ui-combobox__controls { height: 42px; }
    .ui-combobox__clear, .ui-combobox__toggle { height: 40px; }
    .ui-combobox__option { min-height: 42px; padding-block: 10px; }
}

/* ── 图标基线 ──
   现状是 187 个内联 svg 各画各的：8 种 viewBox、6 种描边宽度，同一行里的图标
   画布和线粗都不一样，对不齐也不匀。取用得最多的那套（24×24、1.7）当基准，
   新图标一律用 viewBox="0 0 24 24" 加这个类；尺寸由 --ui-icon-size 控制，
   颜色跟着文字走，按钮变色时图标自动跟上。 */
.ui-icon {
    width: var(--ui-icon-size, 16px);
    height: var(--ui-icon-size, 16px);
    flex: none;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.7;
    stroke-linecap: round;
    stroke-linejoin: round;
}
/* 表态类图标：没表态是描边，表过态实心——一眼看出自己投没投。 */
.ui-icon.is-filled { fill: currentColor; stroke: none; }

/* ── 滚动条 ──
   内层容器（弹窗信息栏、记录列表这类）默认是 17px 宽的系统滚动条，
   一条灰杠横在内容边上，比它承载的信息还显眼。压细、常态透明、
   鼠标进容器才显形。

   全站 48 个滚动容器散在 18 个 CSS 文件里，逐个挂类既慢又必漏，所以直接对
   body 的后代生效——页面主滚动条挂在 html/body 上，不在这个范围里，照旧一直看得见。
   :where() 让特异性归零，任何业务规则想覆盖都压得过它。
   .ui-scroll 保留，给需要显式声明意图的地方用。 */
:where(body *) { scrollbar-width: thin; scrollbar-color: transparent transparent; }
:where(body *):hover, :where(body *):focus-within { scrollbar-color: #d0d7de transparent; }
:where(body *)::-webkit-scrollbar { width: 8px; height: 8px; }
:where(body *)::-webkit-scrollbar-track { background: transparent; }
:where(body *)::-webkit-scrollbar-thumb { border: 2px solid transparent; border-radius: 999px; background-clip: content-box; background-color: transparent; }
:where(body *):hover::-webkit-scrollbar-thumb, :where(body *):focus-within::-webkit-scrollbar-thumb { background-color: #d0d7de; }
:where(body *)::-webkit-scrollbar-thumb:hover { background-color: #afb8c1; }

/* 用不了的选项：列出来但点不动。原生 select 里 disabled 是灰着显示的，
   过滤掉会让人以为选项少了——接口健康检查没过的那几条，标签里写着原因，
   本来就是要给人看的。 */
.ui-combobox__option.is-option-disabled {
    color: var(--vui-text-4);
    cursor: not-allowed;
    background: transparent;
}
.ui-combobox__option.is-option-disabled:hover { background: transparent; }

/* ==========================================================================
   通用列表组件（cx-*）——全站任何列表页都能用，不绑任何页面。
   放在 base.css 是因为它是唯一被所有布局加载的样式表；
   之前这套挂在只给四个创作页加载的 creator-ui.css 里，别的列表页够不着，
   等于又是一份私有实现。
   ========================================================================== */

/* 列表工具条组件：一份实现，四处在用（短视频作品墙 / 图文库 / 文章库 / 创作模板）。
 *
 * 之前这四处各写各的排版，同一个东西四种间距四种高度。收成两层结构：
 *
 *   <div class="cx-listbar">        第一行：范围/状态 tab + 右侧动作
 *     …范围 / 状态 tab…
 *     <div class="cx-listbar__actions">右侧动作，自己贴到最右
 *   <div class="cx-filters">        第二行：一排 .cx-filter 胶囊 + 清空 + 状态文字
 *
 * 不放标题，也不放条数：标题页面顶上的 h1 已经写了，条数一律落在底部的 .cx-pager。
 *
 * 页面只负责往里放自己的条目。钩子一律用组件类，不绑任何页面类或 data 属性——
 * 创作模板页就不在 [data-creation-workbench] 下，但用的是同一个工具条。
 *
 * 视觉参照抖音创作者中心：tab 是文字型不画胶囊；筛选控件不描边，
 * 只用一层比页面底色重一点的填充圈出可点区域，标签在控件内。
 * 主色不跟着改——按钮、选中态仍是本站的 --vui-primary。
 */

.cx-listbar {
    display: flex;
    align-items: center;
    gap: var(--vui-space-2);
    flex-wrap: wrap;
    /* 和下面的列表、分页取同一档左右缩进，三行左边缘才在一条线上；
       10 / 14 不在 design.md 的间距档里。 */
    padding: var(--vui-space-3) var(--vui-space-4);
    border: 0;
    background: transparent;
}

/* 右侧动作自己贴到最右，不用占位元素撑 */
/* 这一组是「工具」：放不下时整组换到下一行并保持靠右，组内自己不再拆行——
   刷新按钮单独掉到第三行是最难看的一种换行。宽度不够就让搜索框先缩。 */
.cx-listbar__actions {
    position: relative;
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: nowrap;
    min-width: 0;
    margin-left: auto;
}

/* 筛选项就放在同一条 bar 里，排不下自然换行——本来就是一件事。
   只有「筛选行要能单独折叠」的页面（短视频作品墙、创作模板）才需要第二个元素，
   那时用同一个组件加这个修饰符，不要再造一个类。 */
.cx-listbar--filters { padding-top: 0; }

/* 一行放不下怎么办：不换行、不横向滚，把次级筛选收进「筛选」下拉。
   抖音创作者中心也是这个路子——bar 上只留主轴 tab、搜索和动作，
   其余条件点开一个面板选。用 <details> 实现，纯 HTML，不依赖脚本，
   任何页面直接抄这段结构就能用。 */
.cx-filters-drop { position: relative; flex: 0 0 auto; }

.cx-filters-drop > summary {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    height: 34px;
    padding: 0 12px;
    border-radius: var(--vui-radius-sm);
    background: var(--vui-bg);
    color: var(--vui-text-2);
    font-size: 13px;
    font-weight: 500;
    list-style: none;
    cursor: pointer;
    user-select: none;
}

.cx-filters-drop > summary::-webkit-details-marker { display: none; }
.cx-filters-drop > summary::after {
    width: 0;
    height: 0;
    border-top: 4px solid currentColor;
    border-right: 3.5px solid transparent;
    border-left: 3.5px solid transparent;
    opacity: .55;
    content: "";
    transition: transform .15s ease;
}
.cx-filters-drop[open] > summary::after { transform: rotate(180deg); }
.cx-filters-drop > summary:hover { background: var(--vui-border-muted); color: var(--vui-text); }

/* 收起时也要让人知道有几条条件在生效，否则等于把筛选藏了 */
.cx-filters-drop__count {
    min-width: 16px;
    height: 16px;
    padding: 0 4px;
    border-radius: 8px;
    background: var(--vui-primary);
    color: var(--vui-surface);
    font-size: 11px;
    font-weight: 700;
    line-height: 16px;
    text-align: center;
}
.cx-filters-drop__count:empty { display: none; }

/* 面板样式单独成类：有的页面的显隐已经由自己的脚本/Vue 控制（作品墙、创作模板），
   它们不方便换成 <details>，直接套这个类即可，位置和长相仍是同一套。 */
.cx-filters-drop__panel,
.cx-filters-panel {
    position: absolute;
    z-index: 60;
    top: calc(100% + 6px);
    left: 0;
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    width: max-content;
    max-width: min(420px, calc(100vw - 48px));
    padding: 12px;
    border-radius: var(--vui-radius-md);
    background: var(--vui-surface);
    box-shadow: 0 12px 32px rgba(15, 23, 42, .14);
}

/* 放在右对齐的动作组里时，面板也从右边缘展开——否则往右撑会顶出屏幕。
   靠结构判断，不用脚本量位置。 */
.cx-listbar__actions .cx-filters-panel,
.cx-listbar__actions .cx-filters-drop__panel,
.cx-filters-drop.is-right .cx-filters-drop__panel { left: auto; right: 0; }

/* 七八个条件塞进 420 会摞成四行，这一档给它们用。 */
.cx-filters-panel--wide { max-width: min(680px, calc(100vw - 48px)); }



/* 工具条右上角那排动作按钮：白底、一圈发丝边、32 高、14 字。
   和内容经营右上角那排（Element Plus 默认按钮：32px / 1px 边 / 白底 / 14px）
   同一套形制——两边都是控制台的「本页能干什么」，长成两种语言，来回切就看得出来。
   这里不引 Element Plus（pageV3 连 vue 都不加载），只用项目自己的 token 复刻它的形。

   原来是浅底无描边的灰方块。那套是给筛选控件用的，动作按钮跟着穿，
   结果控制台里同一层级的东西有两副面孔。

   32 也顺带和同一条上的范围切换（.task-scope-option，32px）对齐了，原来高出 2px。
   圆角仍用 --vui-radius-sm（8px）而不是 EP 的 4px：pageV3 整页的输入框、面板都是 8，
   只把这几颗改成 4 会跟自己页面打架——对齐的是形制，不是逐像素复制另一套组件库。

   写成后代选择器而不是新类名，页面里照旧写 .btn 就自动跟上，不用改 markup。 */
.cx-listbar__actions .btn,
.cx-listbar__actions button:not(.cx-filter__clear) {
    height: var(--vui-ctrl-h);
    min-height: var(--vui-ctrl-h);
    padding: 0 15px;
    border: 1px solid var(--vui-control-border);
    border-radius: var(--vui-radius-sm);
    background: var(--vui-surface);
    color: var(--vui-text);
    font-size: var(--vui-font-md);
    font-weight: 500;
    box-shadow: none;
}

/* 悬停按 EP 的路数：描边和字都转成主色，底上铺一层最浅的蓝。 */
.cx-listbar__actions .btn:hover,
.cx-listbar__actions button:not(.cx-filter__clear):hover {
    border-color: var(--vui-primary);
    background: var(--vui-info-bg);
    color: var(--vui-primary);
}

/* 主操作仍是品牌蓝，只是尺寸跟着这一排走 */
.cx-listbar__actions .btn-primary,
.cx-listbar__actions .btn-primary:hover {
    border-color: var(--vui-primary);
    background: var(--vui-primary);
    color: var(--vui-surface);
    font-weight: 700;
}

.cx-listbar__actions .btn-primary:hover {
    border-color: var(--vui-primary-hover);
    background: var(--vui-primary-hover);
}

/* 图标按钮（刷新）保持方形，边长跟着这一排走 */
.cx-listbar__actions .wb3-icon-btn {
    width: var(--vui-ctrl-h);
    padding: 0;
}


/* ---------- 两层 tab ----------
 * 范围（我的 / 本企业 / 全平台）和状态（可发布 / 任务大厅 / 已发布）是两个轴，
 * 原来长得一模一样，两排并排看不出谁管谁。按抖音创作者中心那套分两层：
 *   一层「范围」= 选中时填一块浅底胶囊，没选中就是纯文字；
 *   二层「状态」= 纯文字 + 细分隔线，只靠字重和深浅区分。
 */
.cx-listbar .task-scope-switch,
.cx-listbar .task-status-tabs {
    gap: 8px;
    padding: 0;
    border: 0;
    border-radius: 0;
    background: transparent;
    box-shadow: none;
}

.cx-listbar .task-status-tabs {
    gap: 0;
}

.cx-listbar .task-scope-option,
.cx-listbar .task-status-tab {
    position: relative;
    min-height: 32px;
    height: 32px;
    border: 0;
    border-radius: 0;
    background: transparent;
    box-shadow: none;
    color: var(--vui-text-3);
    font-size: 13px;
    font-weight: 400;
    white-space: nowrap;
}

.cx-listbar .task-scope-option:hover,
.cx-listbar .task-status-tab:hover {
    background: transparent;
    color: var(--vui-text);
}

/* 一层：范围。选中填浅底胶囊。 */
.cx-listbar .task-scope-option {
    padding: 0 14px;
    border-radius: var(--vui-radius-sm);
}

.cx-listbar .task-scope-option.active {
    background: var(--vui-border-muted);
    color: var(--vui-text);
    font-weight: 600;
}

.cx-listbar .task-scope-option:hover {
    background: var(--vui-bg);
}

/* 二层：状态。纯文字，分隔线画在相邻项的左边缘——容器 gap 归零，线正好落在中间。 */
.cx-listbar .task-status-tab {
    padding: 0 12px;
}

.cx-listbar .task-status-tab.active {
    background: transparent;
    color: var(--vui-text);
    font-weight: 600;
}

.cx-listbar .task-status-tab + .task-status-tab::before {
    content: "";
    position: absolute;
    left: 0;
    top: 50%;
    width: 1px;
    height: 12px;
    background: var(--vui-border);
    transform: translateY(-50%);
}

/* tab 上的计数是附注，不该和标签抢重量。 */
.cx-listbar .task-scope-option strong,
.cx-listbar .task-status-tab strong {
    min-width: 0;
    color: var(--vui-text-4);
    font-size: 12px;
    font-weight: 400;
}

.cx-listbar .task-scope-option.active strong,
.cx-listbar .task-status-tab.active strong {
    color: var(--vui-text-3);
}

/* ---------- 筛选控件：一颗浅底胶囊 ----------
 * 三个创作页用的是同一个组件：[标签] [控件]，无描边，等高 34。
 * 以前短视频页是裸下拉（11px，v3 里用 !important 锁死）、图文页是胶囊、
 * 文章页又是另一套，三页并排像三个产品。
 */
.cx-listbar .cx-filter {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    flex: 0 0 auto;
    height: 34px;
    padding: 0 10px;
    border: 0;
    border-radius: var(--vui-radius-sm);
    background: var(--vui-bg);
}

.cx-listbar .cx-filter > span {
    flex: 0 0 auto;
    color: var(--vui-text-3);
    font-size: 12px;
    font-weight: 600;
    white-space: nowrap;
}

/* :not(...) 那两段是为了对齐 video-ui.css 里 input[type="search"]:not([class^="el-"])… 的权重——
   它是 (0,3,1)，朴素的 .cx-filter input 只有 (0,1,2)，压不住，输入框会退回白底带框。 */
.cx-listbar .cx-filter select:not([class^="el-"]):not([class*=" el-"]),
.cx-listbar .cx-filter input:not([class^="el-"]):not([class*=" el-"]) {
    height: 100%;
    min-width: 0;
    padding: 0;
    border: 0;
    border-radius: 0;
    background: transparent;
    box-shadow: none;
    color: var(--vui-text);
    font-size: 13px;
    font-weight: 500;
}

.cx-listbar .cx-filter select { max-width: 170px; cursor: pointer; }
.cx-listbar .cx-filter input[type="date"]:not([class^="el-"]) { width: 116px; }
.cx-listbar .cx-filter select:focus,
.cx-listbar .cx-filter input:focus { outline: 0; box-shadow: none; }
.cx-listbar .cx-filter:focus-within { box-shadow: inset 0 0 0 1px var(--vui-primary); }

.cx-listbar .cx-filter__range { gap: 4px; }
.cx-listbar /* 搜索框在动作组里跟着一起换行，不再自己 margin-left:auto 单飞 */
.cx-filter__search { flex: 0 1 240px; min-width: 150px; }
.cx-listbar > .cx-filter__search { margin-left: auto; }
.cx-listbar .cx-filter__search svg {
    flex: 0 0 auto;
    width: 15px;
    height: 15px;
    fill: none;
    stroke: var(--vui-text-4);
    stroke-width: 1.8;
    stroke-linecap: round;
}
.cx-listbar .cx-filter__search input { flex: 1 1 auto; }

.cx-listbar .cx-filter__clear {
    flex: 0 0 auto;
    height: 34px;
    min-height: 34px;
    padding: 0 12px;
    border: 0;
    border-radius: var(--vui-radius-sm);
    background: var(--vui-bg);
    color: var(--vui-text-2);
    font: inherit;
    font-size: 13px;
    font-weight: 500;
    cursor: pointer;
}
.cx-listbar .cx-filter__clear:hover { background: var(--vui-border-muted); color: var(--vui-text); }
.cx-listbar .cx-filter__state { flex: 0 0 auto; width: 100%; color: var(--vui-text-4); font-size: 11px; }

/* 没有包在胶囊里的零散控件（旧筛选条还有几个）也按同一套画。 */
.cx-listbar .task-filterbar > select,
.cx-listbar .task-filterbar .task-filter-primary > select,
.cx-listbar .task-filterbar .task-filter-primary > input {
    height: 34px;
    padding: 0 10px;
    border: 0;
    border-radius: var(--vui-radius-sm);
    background: var(--vui-bg);
    box-shadow: none;
    color: var(--vui-text);
    font-size: 13px;
}

/* 筛选条本身不再是一块灰面板：它是一排控件，底色交给外层卡片。 */
.cx-listbar .task-filterbar {
    border-bottom: 0;
    background: transparent;
}

/* ---------- [hidden] 兜底 ---------- */
/* 类上写了 display 就盖掉了 UA 的 [hidden]{display:none}，元素照旧显示。
   这三页的显隐几乎全靠 hidden 属性切，同一个坑已经踩过三次
   （图文库列表、文章列表、文章编辑器），在共用层一次性兜住，别再逐个补。 */
[data-creation-workbench] [hidden] { display: none !important; }

/* 统一破图占位：资源失效后保持原容器尺寸，不显示浏览器的裂图图标。 */
img.is-image-fallback {
    object-fit: contain !important;
    background: var(--vui-bg, #f3f5f8);
}

/* ---------- 分页器 ----------
 * 同一个控件全站原本有六份写法。这里收成一份：总数在左，
 * 每页条数 + 上一页/页码/下一页在右。
 *
 * 内容经营那一处不并进来：它是 el-pagination，带页码和跳页框，是另一种控件；
 * 强行合并要么把它降级成上一页/下一页，要么把这里撑成一个分页库。
 */
/* 列表页外壳：工具条 + 列表本体 + 分页，三段装在一个面板里。
 *
 * 四个页面原来各套各的：文章库自己画了个 lg 圆角的白块（没有描边也没有阴影），
 * 图文库和作品墙套 .ui-surface（md + 描边 + sm 阴影），创作模板压根没有外壳，
 * 列表直接摊在页面底色上。同一种页面三种壳，边距和圆角自然对不上。
 * 统一成 .cx-list + .ui-surface：面板归 .ui-surface（全站那一个），
 * 三段的纵向节奏归这里，列表本体自己的网格排布仍由各页自己给。
 */
.cx-list {
    display: grid;
    align-content: start;
    min-width: 0;
}

.cx-list__body { min-width: 0; }

.cx-list > .cx-pager {
    padding: var(--vui-space-3) var(--vui-space-4);
    border-top: 1px solid var(--vui-border-muted);
}

.cx-pager {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
}

.cx-pager__total { margin-right: auto; color: var(--vui-text-3); font-size: 12px; }

.cx-pager__size {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    color: var(--vui-text-3);
    font-size: 12px;
    white-space: nowrap;
}

.cx-pager__size select {
    height: 32px;
    min-width: 68px;
    padding: 0 9px;
    border: 0;
    border-radius: var(--vui-radius-sm);
    background: var(--vui-bg);
    color: var(--vui-text);
    font: inherit;
    font-size: 12px;
    cursor: pointer;
}

.cx-pager__btn {
    height: 32px;
    padding: 0 14px;
    border: 0;
    border-radius: var(--vui-radius-sm);
    background: var(--vui-bg);
    color: var(--vui-text-2);
    font: inherit;
    font-size: 12px;
    font-weight: 500;
    cursor: pointer;
    transition: background .15s ease, color .15s ease;
}

.cx-pager__btn:hover:not(:disabled) { background: var(--vui-border-muted); color: var(--vui-text); }
.cx-pager__btn:disabled { color: var(--vui-text-4); cursor: not-allowed; }
.cx-pager__state { min-width: 56px; color: var(--vui-text-2); font-size: 12px; font-weight: 600; text-align: center; }
