:root {
    --bg: #f5f6f8;
    --surface: #ffffff;
    --border: #e3e6ec;
    --text: #1a1d24;
    --muted: #6b7280;
    --brand: #2f6df6;
    --brand-dark: #1f52cc;
    --chrome: #ffffff;
    --chrome-text: #5a6373;
    --ok-bg: #e7f6ed;
    --ok-text: #12683c;
    --warn-bg: #fdf3e3;
    --warn-text: #8a5a10;
    --danger: #d0453f;
    --radius: 10px;
    /* 侧边栏宽度(展开/折叠),供需要按主内容区精确对位的效果引用,
       如仪表盘三卡横向撑满主区。折叠态在 html.is-collapsed 里覆盖。 */
    --side-w: 232px;
}

* { box-sizing: border-box; }

/* Windows 经典滚动条「占位」:切换左侧菜单时,新页面高度变化会让文档
   滚动条出现/消失,视口宽度瞬间突变约 17px,侧边栏与内容区整体左右抖一下
   (macOS 是 overlay 滚动条,悬浮不占位,所以无此现象)。
   scrollbar-gutter 让浏览器始终预留滚动条槽位:内容短的时候槽位空着,
   内容长的时候滚动条画在槽位里,视口宽度全程不变,抖动消失。 */
html {
    scrollbar-gutter: stable;
}

body {
    margin: 0;
    background: var(--bg);
    color: var(--text);
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
        "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
    font-size: 14px;
    line-height: 1.6;
    -webkit-font-smoothing: antialiased;
}

a { color: inherit; text-decoration: none; }
h1, h2, h3 { margin: 0; }
.muted { color: var(--muted); }

/* ---------- 后台外壳 ---------- */
/* 布局:顶部 60px 通栏页头,侧边栏从页头下方开始。 */

.shell {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
}

.topbar {
    height: 60px;
    flex: 0 0 60px;
    display: flex;
    align-items: stretch;
    background: var(--chrome);
    color: var(--text);
    border-bottom: 1px solid var(--border);
    position: sticky;
    top: 0;
    z-index: 20;
}

/* 品牌区宽度与侧边栏一致,那条竖线才能跟侧边栏接上。
   左内边距归零、用子元素 margin 定位:logo/占位块的**中心**两态都钉在
   折叠条(64px)的中线 32px 上,折叠时水平方向零位移,不会左右跳。 */
.topbar-brand {
    width: 232px;
    flex: 0 0 232px;
    min-width: 0;
    display: flex;
    align-items: center;
    /* logo 与企业名称间距 5px:logo 不动,名称向左收拢。 */
    gap: 5px;
    padding: 0 18px 0 0;
    font-size: 15px;
    font-weight: 600;
    border-right: 1px solid var(--border);
}

.brand-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* 占位块/图片的中心都钉在 32px(折叠条中线),两态水平零位移。 */
.brand-mark {
    width: 28px;
    height: 28px;
    flex: 0 0 28px;
    margin-left: 18px;
    border-radius: 8px;
    background: var(--brand);
    display: grid;
    place-items: center;
    font-size: 14px;
    font-weight: 700;
}

/* 内容列的开头:折叠按钮 + 面包屑。左内边距 5px 与主内容区左缘对齐。 */
.topbar-crumbs {
    display: flex;
    align-items: center;
    gap: 10px;
    min-width: 0;
    padding: 0 16px 0 5px;
}

.crumbs {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
    overflow: hidden;
    white-space: nowrap;
    font-size: 13.5px;
    color: var(--muted);
}

.crumb-sep { color: #c3c9d4; }
.crumb-current { color: var(--text); font-weight: 500; }

.topbar-user {
    display: flex;
    align-items: center;
    gap: 16px;
    margin-left: auto;
    padding: 0 18px;
}

.who { line-height: 1.35; text-align: right; }
.topbar-user .who-name { color: var(--text); font-size: 13px; font-weight: 500; }
.topbar-user .who-role { color: var(--muted); font-size: 12px; }

/* ---------- 顶栏用户下拉 ---------- */
.user-menu { position: relative; }
.user-trigger {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 4px 8px;
    border: none;
    border-radius: 8px;
    background: transparent;
    cursor: pointer;
    color: var(--text);
    font-size: 13px;
}
.user-trigger:hover { background: rgba(0, 0, 0, 0.04); }
.user-caret { width: 14px; height: 14px; color: var(--muted); }

.user-avatar {
    position: relative;
    width: 30px;
    height: 30px;
    border-radius: 50%;
    overflow: hidden;
    flex: 0 0 auto;
    background: var(--brand);
    color: #fff;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 14px;
    font-weight: 600;
}
.user-avatar-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.user-avatar-lg { width: 44px; height: 44px; font-size: 18px; }

.user-panel {
    position: absolute;
    top: calc(100% + 6px);
    right: 12px;
    z-index: 60;
    min-width: 240px;
    padding: 6px;
    border: 1px solid var(--border);
    border-radius: 10px;
    background: var(--surface);
    box-shadow: 0 10px 30px rgba(15, 23, 42, 0.14);
}
.user-panel-head {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 10px 12px;
    border-bottom: 1px solid var(--border);
    margin-bottom: 6px;
}
.user-panel-meta { line-height: 1.4; min-width: 0; }
.user-panel-meta .who-name { font-size: 14px; font-weight: 600; color: var(--text); }
.user-panel-meta .who-role { font-size: 12px; color: var(--muted); }

.user-item {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 9px 10px;
    border: none;
    border-radius: 8px;
    background: transparent;
    color: var(--text);
    font-size: 13px;
    text-align: left;
    cursor: pointer;
    text-decoration: none;
}
.user-item:hover { background: #f1f4f9; }
.user-item svg { width: 16px; height: 16px; color: var(--muted); flex: 0 0 auto; }
.user-sub-trigger { position: relative; }
.user-sub-caret { width: 14px; height: 14px; margin-left: auto; }
.user-logout { color: #c53030; }
.user-logout svg { color: #c53030; }

.user-submenu {
    margin: 2px 0 4px 26px;
    padding: 4px;
    border-left: 2px solid var(--border);
}
.user-role-item {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    padding: 7px 10px;
    border: none;
    border-radius: 6px;
    background: transparent;
    color: var(--text);
    font-size: 13px;
    text-align: left;
    cursor: pointer;
}
.user-role-item:hover { background: #f1f4f9; }
.user-role-item .user-role-name { flex: 1; }
.user-role-check { width: 15px; height: 15px; color: var(--brand); }

/* ---------- 个人中心(参考稿:左身份卡 + 右逐行设置) ---------- */
/* 左右两块间距 5px、顶部对齐(通用 .card+.card 的 18px 外边距会打乱对齐,
   这里清零),stretch 让左卡高度与右侧保持一致。 */
.pf-layout {
    display: grid;
    grid-template-columns: 280px minmax(0, 1fr);
    gap: 5px;
    align-items: stretch;
    margin-top: 5px;
}
/* 通用 .card+.card 的间距(含宽页规则 10px)会打乱对齐,用更高特异度清零。 */
.content:has(.page-wide) .pf-layout .card + .card { margin-top: 0; }
.pf-identity { display: flex; flex-direction: column; }
.pf-avatar-box {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 10px;
    padding: 26px 16px 20px;
}
.pf-avatar {
    position: relative;
    width: 104px;
    height: 104px;
    border-radius: 50%;
    overflow: hidden;
    background: #eef1f5;
    color: #7c8698;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 38px;
    font-weight: 600;
}
.pf-avatar-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.pf-nick-line { display: flex; align-items: center; gap: 6px; }
.pf-nick { font-size: 16px; font-weight: 600; color: var(--text); }
.pf-meta { border-top: 1px solid var(--border); padding: 6px 18px 14px; margin-top: 4px; }
.pf-meta-item { display: flex; align-items: flex-start; gap: 10px; padding: 10px 0; }
.pf-meta-item > svg { width: 16px; height: 16px; color: var(--muted); flex: 0 0 auto; margin-top: 2px; }
.pf-meta-text { line-height: 1.45; min-width: 0; }
.pf-meta-label { font-size: 12px; color: var(--muted); }
.pf-meta-value { font-size: 13px; color: var(--text); word-break: break-all; }

.pf-settings-head {
    display: flex;
    align-items: center;
    gap: 18px;
    padding: 16px 22px 12px;
}
.pf-settings-titles { min-width: 0; }
.pf-settings-title { font-size: 15px; font-weight: 600; color: var(--text); }
.pf-settings-sub { font-size: 12.5px; color: var(--muted); margin-top: 3px; }
/* 标题区与性别单选之间的竖线分隔(固定高度,不顶到卡片上下边)。 */
.pf-head-divider {
    flex: 0 0 1px;
    width: 1px;
    height: 34px;
    background: var(--border);
}
.pf-gender { display: flex; align-items: center; gap: 14px; flex: 0 0 auto; }
.pf-gender-label { font-size: 13.5px; font-weight: 600; color: var(--text); }
.pf-radio {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 13px;
    color: var(--text);
    cursor: pointer;
}
.pf-radio input { accent-color: var(--brand); width: 15px; height: 15px; }

/* ---------- 系统设置:与个人中心同规格(页头卡 + 5px 间距双卡等高) ---------- */
.settings-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 360px;
    gap: 5px;
    align-items: stretch;
    margin-top: 5px;
}
/* 通用 .card+.card 的间距会打乱对齐,与 pf-layout 同样清零。 */
.content:has(.page-wide) .settings-grid .card + .card { margin-top: 0; }
/* 卡体紧跟 pf-settings-head,顶部不再留大内边距(同 pf-rows 的处理)。 */
.settings-body { padding: 0 22px 20px; }
.settings-brand-hint { margin: 0 0 12px; line-height: 1.6; }
/* 瓦片纵向排列,每张一行:左预览、右名称与按钮,360px 卡片内不拥挤。 */
.brand-grid { display: grid; gap: 10px; }
.brand-tile {
    display: grid;
    grid-template-columns: 88px minmax(0, 1fr);
    align-items: center;
    gap: 12px;
    padding: 10px;
    border: 1px solid var(--border);
    border-radius: 10px;
    background: var(--surface);
}
.brand-preview {
    height: 64px;
    display: grid;
    place-items: center;
    border: 1px dashed var(--border);
    border-radius: 8px;
    background: #fafbfd;
    overflow: hidden;
}
.brand-preview img { max-width: 100%; max-height: 100%; object-fit: contain; }
.brand-tile[data-shape="round"] .brand-preview img { width: 52px; height: 52px; border-radius: 50%; object-fit: cover; }
.brand-empty { font-size: 12px; color: var(--muted); }
.brand-meta { display: flex; flex-direction: column; align-items: flex-start; gap: 8px; }
.brand-label { font-size: 13px; font-weight: 600; color: var(--text); white-space: nowrap; }
.brand-actions { display: flex; gap: 6px; }
.brand-actions .btn { padding: 3px 10px; font-size: 12px; }
/* 顶栏 logo 图(替代 D 字占位块)。中心同样钉在 32px 竖线。 */
.brand-logo {
    width: 51px;
    height: 51px;
    flex: 0 0 51px;
    margin-left: 6.5px;
    border-radius: 8px;
    object-fit: contain;
}
.pf-rows { padding: 0 22px 8px; }
/* 三列网格:标签列自适应、值列定宽(所有行同一起点)、按钮列右对齐。 */
.pf-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 220px auto;
    align-items: center;
    gap: 16px;
    padding: 15px 0;
    border-top: 1px solid var(--border);
}
.pf-row-label { min-width: 0; }
.pf-row-name { font-size: 13.5px; font-weight: 600; color: var(--text); }
.pf-row-desc { font-size: 12px; color: var(--muted); margin-top: 2px; }
.pf-row-value { min-width: 0; text-align: left; }
.pf-row-text { font-size: 13px; color: var(--text); word-break: break-all; }
.pf-row .btn { justify-self: end; }

.pf-pane-error { color: #c53030; font-size: 12.5px; margin: 4px 0 0; }
.pf-pane .modal-actions { padding: 12px 0 0; }
.pf-layout .hint, .pf-pane .hint { font-size: 12px; color: var(--muted); }

.pf-toast {
    position: fixed;
    left: 50%;
    bottom: 28px;
    transform: translateX(-50%);
    z-index: 80;
    padding: 9px 18px;
    border-radius: 8px;
    background: rgba(26, 29, 36, 0.92);
    color: #fff;
    font-size: 13px;
    box-shadow: 0 8px 24px rgba(15, 23, 42, 0.24);
}

@media (max-width: 880px) {
    .pf-layout { grid-template-columns: 1fr; }
    .pf-row { flex-wrap: wrap; }
    .pf-row-value { min-width: 0; }
}

.shell-body {
    display: flex;
    flex: 1;
    min-height: 0;
}

.sidebar {
    width: 232px;
    flex: 0 0 232px;
    background: var(--chrome);
    color: var(--chrome-text);
    border-right: 1px solid var(--border);
    /* 底部圆角:侧栏底缘悬浮在页面底色上方 5px,视觉上是一块圆角面板。 */
    border-radius: 0 0 10px 10px;
    display: flex;
    flex-direction: column;
    position: sticky;
    top: 60px;
    /* 顶栏 60 + 底净距 5:侧栏底缘距浏览器底端 5px。 */
    height: calc(100vh - 65px);
    align-self: flex-start;
    overflow-y: auto;
}

.nav {
    display: flex;
    -ms-flex-direction: column;
    flex-direction: column;
    gap: 2px;
    padding: 12px 10px 14px;
    flex: 1;
    overflow-y: auto;
    /* Windows 经典滚动条是「占位型」(不像 Mac overlay 悬浮),内容高度变化
       会瞬间撑开 scrollbar、挤压 flex 子项宽度,造成 active 高亮 pill 被挤扁
       的视觉抖动。预留 gutter:浏览器原生支持就用 stable,不支持也无害。 */
    scrollbar-gutter: stable;
}

/* 侧边栏底部版本号:菜单区 flex:1 撑满后钉在最下方,浅灰水平居中;
   折叠态(64px)下 11px 字号的 "v0.2.x" 仍放得下,不做隐藏。 */
.sidebar-version {
    flex: 0 0 auto;
    padding: 4px 0 10px;
    text-align: center;
    font-size: 11px;
    color: #9aa3af;
    white-space: nowrap;
}

.nav-item {
    display: flex;
    align-items: center;
    gap: 10px;
    /* 左内边距 14px:图标中心 = 导航内边距 10 + 14 + 8 = 32px,
       与折叠态钉在同一竖直线上,两态水平零偏移。 */
    padding: 9px 12px 9px 14px;
    border-radius: 8px;
    font-size: 13.5px;
    font-weight: 600;
    color: var(--text);
    /* 行高钉死为图标高度:折叠态文字隐藏后行高不变,图标垂直位置两态一致不抖动。 */
    line-height: 16px;
    /* 展开动画进行到一半时侧边栏还不够宽,禁止标签折行撑高行盒(纵向抖动的来源)。 */
    white-space: nowrap;
    overflow: hidden;
    transition: background 0.15s, color 0.15s;
    /* Windows 下 flex column 的 item 默认允许 shrink:1,当兄弟 <details>
       展开重新布局时会把 active 蓝色 pill 挤扁;这里钉死不收缩。 */
    flex: 0 0 auto;
    -ms-flex: 0 0 auto;
    width: 100%;
}

.nav-item:hover { background: #f1f3f7; color: var(--brand-dark); }
.nav-item.active { background: var(--brand); color: #fff; font-weight: 600; }

.nav-icon {
    width: 16px;
    height: 16px;
    flex: 0 0 16px;
    opacity: 0.55;
}

.nav-item:hover .nav-icon { opacity: 0.8; }
.nav-item.active .nav-icon { opacity: 1; }

/* 菜单行文字占满剩余空间:把折叠箭头推到行的右内边框一侧。 */
.nav-item .nav-label { flex: 1 1 auto; }

/* ---------- 菜单折叠 ---------- */
/* 折叠状态记在 <html> 上,热更新只替换 body 内容也不会丢。 */

/* 宽度过渡只在页面加载完成后启用(html.collapse-anim 由 COLLAPSE_SCRIPT 挂上)。
   首屏的折叠态由 <head> 里的前置脚本定好,这里不再参与动画 —— 否则每次点菜单
   整页重载,浏览器都会重放一遍「展开 232px → 折叠 64px」的宽度过渡,
   侧边栏和主内容区肉眼可见地滑一下(Windows / macOS 现象相同)。 */
html.collapse-anim .sidebar,
html.collapse-anim .topbar-brand {
    transition: width 0.18s ease, flex-basis 0.18s ease;
}

/* 跨文档视图过渡(root_layout 的 <meta name="view-transition"> 开启):
   点菜单整页重载时,浏览器保持旧页显示直到新页就绪,再交叉淡入,
   消除「内容卷走 → 白屏 → 突然蹦出来」的闪动;默认 0.25s 收敛到 0.15s,
   过渡更干脆。不支持该 meta 的内核(IE / 旧 Edge)自动忽略,行为同前。 */
::view-transition-old(root),
::view-transition-new(root) {
    animation-duration: 0.15s;
}

/* 折叠态同步收紧侧栏宽度变量,供仪表盘三卡等横向撑满效果引用。 */
html.is-collapsed {
    --side-w: 64px;
}

html.is-collapsed .topbar-brand {
    width: 64px;
    flex-basis: 64px;
    padding: 0;
}

html.is-collapsed .brand-name { display: none; }

html.is-collapsed .sidebar {
    width: 64px;
    flex-basis: 64px;
}

/* 折叠态只收窄右内边距,左内边距(10px)与展开态一致,蓝色高亮块左缘不跳位。 */
html.is-collapsed .nav { padding: 12px 8px 14px 10px; }

/* 图标中心钉在 nav.padding-left(10) + item.padding-left(14) + 8 = 32px。 */
html.is-collapsed .nav-item {
    padding-left: 14px;
    padding-right: 0;
}

/* 折叠态子图标 13px:10 + 15.5 + 6.5 = 32,中心同线;
   上下内边距各补 1.5px(9→10.5),抵消图标从 16px 缩到 13px 的 3px,
   行高两态钉死 34px,折叠过程中子菜单行不缩放、下方菜单不纵移。 */
html.is-collapsed .nav-sub-item { padding-left: 15.5px; padding-top: 10.5px; padding-bottom: 10.5px; }

html.is-collapsed .nav-label { display: none; }

/* 顶栏里的方形图标按钮 */
.icon-btn {
    width: 32px;
    height: 32px;
    flex: 0 0 32px;
    display: grid;
    place-items: center;
    padding: 0;
    border: 1px solid transparent;
    border-radius: 8px;
    background: transparent;
    color: var(--chrome-text);
    cursor: pointer;
    transition: background 0.15s, color 0.15s;
}

.icon-btn:hover { background: #f1f3f7; color: var(--text); }
.icon-btn svg { width: 19px; height: 19px; transition: transform 0.18s ease; }

.collapse-toggle { color: var(--text); }

/* 顶栏消息铃铛:未读角标 */
.msg-bell { position: relative; color: var(--chrome-text); }
.msg-badge {
    position: absolute;
    top: -4px;
    right: -4px;
    min-width: 16px;
    height: 16px;
    padding: 0 4px;
    display: grid;
    place-items: center;
    border-radius: 8px;
    background: var(--danger);
    color: #fff;
    font-size: 11px;
    line-height: 1;
}

/* 折叠后箭头掉头,提示可以再展开 */
html.is-collapsed .collapse-toggle svg { transform: rotate(180deg); }

.main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
/* 主内容区:四周统一 5px 边距(上离顶栏、左离菜单栏、右离浏览器、底),
   去掉 1120px 宽度截断,宽屏吃满。 */
.content { padding: 5px; width: 100%; max-width: none; }

/* 分组菜单(系统管理):用 <details>/<summary> 折叠,无需脚本。
   <details> 在 Windows 上默认 display:block 但没有显式宽度,
   在 flex column 容器里作为子项时,它的内部 children(块流)可能把
   整个 details 撑出非预期宽度,进而挤压兄弟项(蓝色仪表板 pill 变形
   的元凶)。显式 width:100% + flex-shrink:0 + 独立 stacking 把它钉住。
   同时给 .nav-item 也补上 flex-shrink:0,active 高亮块不允许被挤扁。 */
.nav-group {
    display: block;
    width: 100%;
    flex: 0 0 auto;
    border-radius: 8px;
    overflow: hidden;
}

.nav-group > summary.nav-item {
    display: flex;
    width: 100%;
    list-style: none;
    cursor: pointer;
    user-select: none;
}

.nav-group > summary.nav-item::-webkit-details-marker { display: none; }

.nav-caret {
    width: 14px;
    height: 14px;
    flex: 0 0 14px;
    /* 不用 margin-left:auto:行内 label 已 flex:1 把箭头推到行的右缘。 */
    opacity: 0.55;
    transition: transform 0.15s ease;
}

.nav-group[open] .nav-caret { transform: rotate(180deg); }

/* 展开时分组标题用正文色加粗,跟普通菜单项区分开 —— 别再用白色,
   侧边栏现在是白底,白字等于隐形。 */
.nav-group[open] > summary.nav-item { color: var(--text); font-weight: 600; }

/* 子菜单缩进一级,视觉上从属于分组标题 */
.nav-sub-item { padding-left: 38px; font-size: 13px; }

/* 侧边栏收窄成图标条时:分组保持折叠,只显示根菜单图标;
   悬停时在图标右侧弹出该组的子菜单(.nav-flyout)。 */
html.is-collapsed .nav-group { display: block; position: relative; }

/* 图标条里不需要展开箭头;未展开过的组,行内子项藏掉(子项走悬浮层),
   折叠前已展开的组则在图标条里继续露出子项图标。 */
html.is-collapsed .nav-caret { display: none; }
html.is-collapsed .nav-group:not([data-was-open="1"]) > .nav-sub-item { display: none; }

/* 图标条里的子项图标缩小一档,和根菜单图标区分开。 */
/* 折叠态子图标缩小,但行内边距保持与展开态一致(9px):
   行高两态相同,展开/折叠过程中下方菜单不会纵向跳动。 */
html.is-collapsed .nav-sub-item .nav-icon {
    width: 13px;
    height: 13px;
    flex-basis: 13px;
    opacity: 0.45;
}

/* 折叠态悬浮层要溢出侧边栏显示,放开两层的滚动裁剪;
   侧边栏(sticky)自成层叠上下文,抬高层级避免悬浮层被内容区盖住。 */
html.is-collapsed .sidebar,
html.is-collapsed .nav { overflow: visible; }
html.is-collapsed .sidebar { z-index: 40; }

/* 展开态永远不出现悬浮层;折叠态悬停分组时才弹出。 */
.nav-flyout { display: none; }

html.is-collapsed .nav-group:hover > .nav-flyout {
    display: flex;
}

html.is-collapsed .nav-flyout {
    position: absolute;
    left: calc(100% + 8px);
    /* -11 = 边框 1 + 内边距 10:标题行的行盒正好压在根图标行同一水平线,
       子项行与图标条子项行逐行对齐。 */
    top: -11px;
    /* 注意:这里不设 display —— 显示只由 :hover 规则控制,
       否则折叠态下所有组的悬浮层会同时叠在右侧。 */
    flex-direction: column;
    min-width: 172px;
    /* 左右内边距与侧栏 .nav 一致(10px),行内边距一致(14px):
       弹出行的蓝色背景与图标条行的蓝色背景同宽同位,视觉上对齐。 */
    padding: 10px;
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 10px;
    box-shadow: 0 12px 32px rgba(16, 21, 31, 0.16);
    z-index: 60;
}

/* 图标与悬浮层之间垫一条隐形桥接区,鼠标移过去时不断触。 */
html.is-collapsed .nav-flyout::before {
    content: "";
    position: absolute;
    left: -10px;
    top: 0;
    bottom: 0;
    width: 10px;
}

/* 标题行:带组图标,行高钉成与侧栏行一致的 34px,弹出后逐行等距不错乱。 */
.nav-flyout-title {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 9px 14px;
    border-radius: 8px;
    line-height: 16px;
    font-size: 13.5px;
    font-weight: 600;
    color: var(--text);
}

.nav-flyout-title .nav-icon { opacity: 0.7; }

/* 悬浮层子项相对标题行缩进(14→28):图标与文字右移,
   与主菜单错开层级,不再同一条垂直线。 */
html.is-collapsed .nav-flyout .nav-item {
    justify-content: flex-start;
    padding-left: 28px;
    padding-right: 14px;
}

html.is-collapsed .nav-flyout .nav-label { display: inline; }

/* ---------- 页头操作行(标题与提示已全站移除,只留右侧按钮) ---------- */

.page-head {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 16px;
    margin-bottom: 20px;
}

.page-actions-row { justify-content: flex-end; }
.page-head-actions { display: flex; gap: 8px; flex: 0 0 auto; }

/* ---------- 卡片与表格 ---------- */

.card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    overflow: hidden;
}

.card-pad { padding: 20px 22px; }

.card + .card { margin-top: 18px; }

.card-title {
    font-size: 14px;
    font-weight: 600;
    padding: 14px 18px;
    border-bottom: 1px solid var(--border);
}

.table { width: 100%; border-collapse: collapse; }

.table th {
    text-align: left;
    padding: 11px 16px;
    background: #fafbfc;
    border-bottom: 1px solid var(--border);
    font-size: 12px;
    font-weight: 600;
    color: var(--muted);
}

.table td {
    padding: 13px 16px;
    border-bottom: 1px solid var(--border);
    vertical-align: middle;
}

.table tbody tr:last-child td { border-bottom: none; }
/* 斑马纹:偶数行背景加重;hover 保持浅色(写在后面,同特异性时后者生效)。 */
.table tbody tr:nth-child(even) td { background: #f1f3f7; }
.table tbody tr:hover td { background: #fafbfc; }

.cell-title { font-weight: 500; }
/* 操作列:flex 只作用在内层 div 上。td 一旦被 display:flex 会脱离
   table-cell 布局,行高/斑马纹/sticky 背景与主行错位(割裂感的根因)。
   td/th 保持表格单元格,靠 text-align 对齐按钮组所在的行。 */
.table td.cell-actions,
.table th.cell-actions { text-align: right; }
div.cell-actions { display: flex; gap: 6px; justify-content: flex-end; }
.empty { text-align: center; color: var(--muted); padding: 36px 16px; font-size: 13px; }
.meta { color: var(--muted); font-size: 12.5px; }

/* ---------- 徽章 ---------- */

.badge {
    display: inline-block;
    padding: 2px 9px;
    border-radius: 999px;
    font-size: 12px;
    font-weight: 500;
    white-space: nowrap;
}

.badge-ok { background: var(--ok-bg); color: var(--ok-text); }
.badge-info { background: #e8f0fe; color: #1a56c4; }
.badge-warn { background: var(--warn-bg); color: var(--warn-text); }
.badge-plain { background: #eef1f5; color: #4a5160; }

/* ---------- 统计卡 ---------- */

.stats {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 14px;
    margin-bottom: 20px;
}

/* 仪表盘三卡:卡间距 8px;.content 四周已统一 5px 内边距,
   卡片自然贴主内容区四缘各 5px,不再需要负外边距撑位。
   行高按 CSS 计算分三段:首行小卡固定 134px;中间行(走势 + 快捷菜单)
   与末行(产品更新 + 公告栏)按 0.85 : 1.15 分剩余高度。
   中间行只拿小份的原因:走势图是 960×220 的等比画布,卡片比图高出的
   部分会在图上下留白(旧口径 328px 硬下限 + 1fr 吃满富余,笔记本视口
   下卡片下方能空出上百像素);末行要放「表头 + 5 行 + 页码」,给足更实。
   两行都用 minmax(0, …fr) 且不设 328px 硬下限:行高之和恒等于
   height,视口偏矮(笔记本、Windows 125%/150% 缩放)时两行同比收缩,
   不会再顶出容器把整页撑出纵向滚动条;极端偏矮时快捷菜单/表格各自
   内部滚动或裁切,但页面本身始终不滚。 */
.stats-3 {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    grid-template-rows: 134px minmax(0, 0.85fr) minmax(0, 1.15fr);
    height: calc(100vh - 70px);
    gap: 8px;
    /* 抵掉 .stats 的 20px 下外边距:60 顶栏 + 5 上净距 + 网格 + 5 底净距
       正好 100vh,多 1px 都会把整页顶出纵向滚动条。 */
    margin-bottom: 0;
}

/* 网格里的卡统一收窄内边距:三张小卡在 134px 行高内放得下
   (标签 + 数值 + 提示),四张大卡同步受益。 */
.stats-3 .stat { padding: 12px 16px; }

/* 四张大卡显式定位(首行三张小卡占第 1 行):
   走势大卡居左占两列,快捷菜单卡在其右等高,同处第 2 行;
   产品更新卡在走势卡正下方(第 3 行),公告栏卡在右下角等高。
   两行行高由上面的 0.85fr / 1.15fr 分配(见 .stats-3)。
   min-width:0:卡内 nowrap/ellipsis 文本的 min-content 很宽,
   不放开收缩权会把 1fr 列撑出横向滚动。 */
.chart-card {
    grid-column: 1 / 3;
    grid-row: 2 / 3;
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.menu-card {
    grid-column: 3;
    grid-row: 2 / 3;
    display: flex;
    flex-direction: column;
    min-height: 0;
    min-width: 0;
}

.release-card {
    grid-column: 1 / 3;
    grid-row: 3 / 4;
    display: flex;
    flex-direction: column;
    min-height: 0;
    min-width: 0;
}

.board-card {
    grid-column: 3;
    grid-row: 3 / 4;
    display: flex;
    flex-direction: column;
    min-height: 0;
    min-width: 0;
}

/* 大卡(走势/快捷菜单/产品更新/公告栏)统一标题:比小卡的弱色标签醒目,居中。 */
.stat-title { font-size: 14px; font-weight: 600; text-align: center; }

/* 大卡空态:在剩余空间里垂直居中。 */
.stat-empty {
    margin: auto 0;
    padding: 10px 2px;
    color: var(--muted);
    font-size: 12.5px;
}

/* 走势卡头部:标题保持大卡居中的观感,左图例、右月/季/年切换按钮组对称贴边。 */
.chart-head { position: relative; display: flex; align-items: center; justify-content: center; min-height: 20px; }
/* 分渠道图例:贴左排布,与右侧月/季/年切换按钮对称。 */
.chart-legend { position: absolute; left: 0; top: 50%; transform: translateY(-50%); display: flex; gap: 10px; font-size: 11.5px; color: var(--muted); }
.chart-legend-item { display: inline-flex; align-items: center; gap: 4px; }
.chart-legend-dot { width: 8px; height: 8px; border-radius: 50%; }
.chart-switch { position: absolute; right: 0; top: 50%; transform: translateY(-50%); display: flex; gap: 4px; }
.chart-range {
    padding: 3px 8px;
    font-size: 11.5px;
    line-height: 1.4;
    color: var(--muted);
    background: transparent;
    border: 1px solid var(--border);
    border-radius: 4px;
    cursor: pointer;
    white-space: nowrap;
}
.chart-range.is-active { color: var(--brand); border-color: var(--brand); background: #f0f6ff; font-weight: 600; }

.chart-body { flex: 1; min-height: 0; margin-top: 8px; position: relative; }
/* 月/年两张图互斥显隐:作者样式会盖过 UA 的 [hidden],这里显式补回。 */
.chart-body[hidden] { display: none; }
.chart-body svg { width: 100%; height: 100%; display: block; }

/* 走势卡悬停提示:跟随鼠标的浮层,pointer-events:none 不挡十字线拾取。 */
.chart-tip {
    position: absolute;
    z-index: 6;
    display: none;
    padding: 6px 10px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 6px;
    box-shadow: 0 4px 14px rgba(15, 23, 42, 0.12);
    font-size: 12px;
    line-height: 1.65;
    color: var(--text);
    white-space: nowrap;
    pointer-events: none;
}
.chart-tip strong { font-size: 12.5px; }

/* 快捷菜单:两列入口网格(图标块 + 菜单名),按使用次数倒序;
   第 2 行随视口拉伸,条目贴顶排布,高度富余时自然留白。
   条目内边距与网格上边距收 1~2px:第 2 行只分到 0.85 份高度,
   8 个入口(4 行)要正好塞下,留不出内部滚动条。 */
.menu-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 6px;
    align-content: start;
    margin-top: 8px;
    min-height: 0;
    overflow-y: auto;
}

.menu-entry {
    display: flex;
    align-items: center;
    gap: 10px;
    min-width: 0;
    padding: 5px 10px;
    border: 1px solid var(--border);
    border-radius: 8px;
    transition: border-color 0.15s, background 0.15s;
}

.menu-entry:hover { border-color: var(--brand); background: #f7f9ff; }

.menu-entry-icon {
    width: 30px;
    height: 30px;
    flex: 0 0 30px;
    border-radius: 8px;
    background: #eef1f5;
    color: #4a5160;
    display: grid;
    place-items: center;
    transition: background 0.15s, color 0.15s;
}

/* 悬停反色:图标底色变品牌蓝、描边(svg stroke=currentColor)变白。 */
.menu-entry:hover .menu-entry-icon { background: var(--brand); color: #fff; }

.menu-entry-icon svg { width: 16px; height: 16px; }

.menu-entry-name {
    font-size: 12.5px;
    font-weight: 500;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* 产品更新「优化与修复」表:时间/类型/要点三列,斑马行,
   一页 5 条整卡展示,翻页走 /dashboard/page/N 路径段,卡内不出滚动条。 */
.release-table {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
    margin-top: 6px;
    font-size: 13px;
}

.release-head {
    display: flex;
    gap: 12px;
    padding: 6px 8px;
    border-bottom: 1px solid var(--border);
    font-size: 12px;
    font-weight: 600;
    color: var(--muted);
}

.release-body {
    flex: 1;
    min-height: 0;
    /* 内容按 5 行设计,极端情况下裁掉溢出也不出滚动条 */
    overflow: hidden;
}

.release-row {
    display: flex;
    align-items: center;
    gap: 12px;
    min-width: 0;
    /* 与 .stats-3 的行高分配配套:末行只分到 1.15 份高度,
       5 行内边距各收 1px,整页正好放下,不出裁切。 */
    padding: 8px;
}

/* 斑马纹:偶数行浅灰底 */
.release-row:nth-child(even) { background: #f7f8fa; }

.col-time { flex: 0 0 150px; }
.col-kind { flex: 0 0 64px; }
.col-point { flex: 1; min-width: 0; }

.release-time { font-variant-numeric: tabular-nums; }

.release-point {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* 类型徽章:浅底 + 描边的圆角小标签 */
.release-kind {
    display: inline-block;
    padding: 2px 10px;
    border: 1px solid transparent;
    border-radius: 6px;
    font-size: 12px;
    line-height: 18px;
}

.release-kind-add { background: #f2fbf5; border-color: #b4dcc0; color: #1a7f37; }
.release-kind-opt { background: #f0f5ff; border-color: #b3c9f0; color: #1a56c4; }
.release-kind-fix { background: #fdf3f2; border-color: #ecb4b0; color: #c53030; }

/* 紧凑页码:右下角一排小方按钮,复用 .pager-btn 样式。 */
.release-pager {
    display: flex;
    justify-content: flex-end;
    gap: 6px;
    margin-top: auto;
    padding-top: 8px;
}

/* 公告栏:全局公告(平台发布、全租户可见)最新 5 条,一行一条贴顶排布
   (彩色类型名 + 标题省略 + 详细 + 日期)。标题放不下时亮出「详细」,
   点开在卡内显示全文详情(放不下再分页,上一页/下一页),不出滚动条。 */
.board-list {
    flex: 1;
    min-height: 0;
    margin-top: 10px;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

.board-row {
    display: flex;
    align-items: center;
    gap: 10px;
    min-width: 0;
    padding: 9px 2px;
    border-bottom: 1px dashed var(--border);
    font-size: 13px;
}

.board-row:last-child { border-bottom: none; }

/* 置顶标识:红底白字小圆签,与平台侧公告列表的「置顶」同款。 */
.board-pin {
    flex: 0 0 auto;
    padding: 1px 7px;
    border-radius: 999px;
    background: #d64545;
    color: #fff;
    font-size: 11px;
    line-height: 16px;
}

/* 类型名按类型着色:系统蓝 / 产品紫 / 活动绿 / 安全橙 / 规则灰。 */
.board-type { flex: 0 0 auto; font-weight: 600; }
.ntype-system { color: #1a56c4; }
.ntype-product { color: #7c3aed; }
.ntype-activity { color: #1f9d55; }
.ntype-security { color: #c2620a; }
.ntype-rule { color: #64748b; }

.board-title {
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* 「详细」:默认不显示,标题被截断的行由脚本亮出。 */
.board-more {
    display: none;
    flex: 0 0 auto;
    padding: 0;
    border: none;
    background: none;
    color: #1a56c4;
    font-size: 12px;
    cursor: pointer;
}
.board-row.is-truncated .board-more { display: inline; }
.board-more:hover { text-decoration: underline; }

/* 标题悬停提示:浮层展示公告全文(类型/日期/标题/正文);
   超长正文卡内滚动,鼠标移入提示内保持显示(脚本延迟关闭)。 */
.board-tip {
    position: fixed;
    z-index: 60;
    max-width: 460px;
    max-height: 55vh;
    overflow: auto;
    padding: 10px 12px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 8px;
    box-shadow: 0 6px 24px rgba(15, 23, 42, 0.12);
    font-size: 12.5px;
    line-height: 1.65;
    color: var(--text);
}
.board-tip[hidden] { display: none; }
.board-tip-head { display: flex; justify-content: space-between; gap: 12px; margin-bottom: 4px; }
.board-tip-type { font-weight: 600; }
.board-tip-date { color: var(--muted); white-space: nowrap; }
.board-tip-title { font-weight: 600; margin-bottom: 4px; }
.board-tip-body { white-space: pre-wrap; word-break: break-word; }

.board-date {
    flex: 0 0 auto;
    color: var(--muted);
    font-size: 12px;
    font-variant-numeric: tabular-nums;
}

/* 详情视图:默认隐藏,打开时整卡从列表切换为详情(列表藏起,详情占满)。 */
.board-detail {
    display: none;
    flex: 1;
    min-height: 0;
    margin-top: 10px;
    flex-direction: column;
}
.board-card.is-detail .board-list { display: none; }
.board-card.is-detail .board-detail { display: flex; }

.board-detail-head {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 12px;
}
.board-detail-type { font-weight: 600; }
.board-detail-date { margin-left: auto; color: var(--muted); }

.board-back {
    padding: 0;
    border: none;
    background: none;
    color: #1a56c4;
    font-size: 12px;
    cursor: pointer;
}
.board-back:hover { text-decoration: underline; }

.board-detail-title {
    margin-top: 6px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 13.5px;
    font-weight: 600;
}

.board-detail-body {
    flex: 1;
    min-height: 0;
    margin-top: 6px;
    overflow: hidden;
    font-size: 12.5px;
    line-height: 1.7;
    color: var(--text);
    white-space: pre-wrap;
    word-break: break-word;
}

.board-pager {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    margin-top: 6px;
    font-size: 12px;
    color: var(--muted);
}
.board-pager-btn {
    padding: 2px 10px;
    border: 1px solid var(--border);
    border-radius: 5px;
    background: var(--surface);
    color: var(--text);
    font-size: 12px;
    cursor: pointer;
}
.board-pager-btn:disabled { opacity: 0.45; cursor: default; }

/* 公告栏空态:在剩余空间里垂直居中。 */
.board-body {
    flex: 1;
    display: grid;
    place-items: center;
    color: var(--muted);
    font-size: 13px;
}

.stat {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 16px 18px;
}

.stat-label { font-size: 12.5px; color: var(--muted); }
.stat-value { font-size: 26px; font-weight: 600; margin-top: 6px; letter-spacing: -0.02em; }
.stat-clock { font-size: 17px; font-variant-numeric: tabular-nums; }
.stat-hint { font-size: 12px; color: var(--muted); margin-top: 2px; }

/* 业务登记卡细分:普通蓝 / 较急橙 / 紧急红。 */
.hint-normal { color: #1a56c4; }
.hint-high { color: #d97706; }
.hint-top { color: #c53030; }
/* 租户统计卡细分:活跃绿 / 休眠灰(非活跃复用橙色 hint-high)。 */
.hint-active { color: #16a34a; }
.hint-dormant { color: #9ca3af; }

/* ---------- 按钮 ---------- */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: 8px 14px;
    border: 1px solid transparent;
    border-radius: 8px;
    background: var(--brand);
    color: #fff;
    font-family: inherit;
    font-size: 13.5px;
    font-weight: 500;
    cursor: pointer;
    transition: background 0.15s, border-color 0.15s, color 0.15s;
}

.btn:hover { background: var(--brand-dark); }

.btn-secondary { background: #fff; color: var(--text); border-color: var(--border); }
.btn-secondary:hover { background: #f1f3f7; }

.btn-danger { background: transparent; color: var(--danger); }
.btn-danger:hover { background: #fdecec; }

.btn-sm { padding: 5px 10px; font-size: 12.5px; border-radius: 7px; }
.btn-block { width: 100%; }
.inline-form { display: inline; }

/* ---------- 表单 ---------- */

.form { display: flex; flex-direction: column; gap: 18px; max-width: 780px; }
.field { display: flex; flex-direction: column; gap: 6px; }
.field label { font-size: 13px; font-weight: 500; }
.field .hint { font-size: 12px; color: var(--muted); }

.input, .select, .textarea {
    width: 100%;
    padding: 9px 12px;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: #fff;
    color: var(--text);
    font-family: inherit;
    font-size: 13.5px;
}

.input:focus, .select:focus, .textarea:focus {
    outline: none;
    border-color: var(--brand);
    box-shadow: 0 0 0 3px rgba(47, 109, 246, 0.12);
}

/* 下拉:去掉浏览器原生外观(Safari 的原生外观会无视 padding 导致高度比输入框矮),
   换成统一的自定义箭头,箭头距右缘 12px(比 Chrome 原生位置内移 4px)。 */
.select {
    appearance: none;
    -webkit-appearance: none;
    padding-right: 32px;
    background: #fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%239aa3b2' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E") no-repeat right 12px center / 14px;
}

.textarea {
    min-height: 260px;
    resize: vertical;
    line-height: 1.7;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 13px;
}

.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
.form-actions { display: flex; gap: 10px; padding-top: 2px; }
.check { display: flex; align-items: center; gap: 8px; font-size: 13.5px; }
.check input { width: 15px; height: 15px; accent-color: var(--brand); }

.textarea-sm { min-height: 84px; font-family: inherit; font-size: 13.5px; line-height: 1.6; }
.input-narrow { max-width: 160px; }
/* 列表搜索框:占位文案较长,给足宽度,避免提示文字被截断。 */
.input-search { width: 240px; max-width: 100%; }
/* 搜索区固定宽度的筛选下拉:不被 flex 拉伸,宽度稳定。 */
.select-fixed { width: 130px; flex: 0 0 auto; }

.btn:disabled { opacity: 0.45; cursor: not-allowed; }
.btn:disabled:hover { background: transparent; }

code {
    padding: 1px 6px;
    border-radius: 5px;
    background: #eef1f5;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 12.5px;
}

/* ---------- 区域设置 ---------- */

/* 查询栏与工具栏:一行排开 */
.toolbar-search {
    display: flex;
    align-items: center;
    gap: 10px;
    max-width: 620px;
    margin-bottom: 12px;
}

.toolbar-search .input { flex: 1; }

.toolbar {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 14px;
}

/* 树:去掉列表圆点,用缩进 + 左侧虚线表达层级 */
.tree, .tree ul {
    list-style: none;
    margin: 0;
    padding: 0;
}

.tree ul {
    margin-left: 14px;
    padding-left: 12px;
    border-left: 1px dashed var(--border);
}

.tree-row { margin: 2px 0; }

.tree-item {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 3px 6px;
    border-radius: 6px;
}

.tree-item:hover { background: #f1f3f7; }

.tree-item input[type="radio"] {
    width: 14px;
    height: 14px;
    accent-color: var(--brand);
    flex: 0 0 14px;
}

.tree-cell { display: flex; align-items: center; gap: 6px; }

.tree-name { font-weight: 500; }
.tree-code {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 12.5px;
    color: var(--muted);
}

/* ---------- 数据列表页(区域设置等) ---------- */

/* 宽页:表格类页面吃满内容区,四周都只留 5px(与 .content 统一) */
.content:has(.page-wide) {
    max-width: none;
    padding: 5px;
}

/* 查询区与下面的表格之间也是 10px */
.content:has(.page-wide) .card + .card { margin-top: 10px; }

/* 查询卡:字段名 + 输入框 + 查询/重置,一行排开 */
.filter-bar { padding: 14px 16px; }

.filter-form {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 12px 20px;
}

.filter-form label {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 13px;
    color: var(--text);
}

.filter-form .input { width: 230px; }
.filter-form .select { width: 230px; }
.filter-form .btn { flex: 0 0 auto; }

/* 单行筛选(会员审核页):三个条件框等比伸缩,始终和按钮同在一行,
   浏览器变窄时只缩小输入框、不换行。 */
.filter-form.filter-form-inline { flex-wrap: nowrap; gap: 12px; }
.filter-form.filter-form-inline label {
    flex: 1 1 0;
    min-width: 0;
    white-space: nowrap;
}
.filter-form.filter-form-inline .input,
.filter-form.filter-form-inline .select {
    width: 100%;
    min-width: 0;
    flex: 1 1 auto;
}
.filter-buttons {
    display: flex;
    flex: 0 0 auto;
    gap: 10px;
}

/* 收起按钮里的箭头,收起时翻转 */
.collapse-filter svg {
    width: 14px;
    height: 14px;
    transition: transform 0.15s ease;
}

.filter-bar.is-collapsed .collapse-filter svg { transform: rotate(180deg); }
.filter-bar.is-collapsed label { display: none; }
.filter-bar.is-collapsed .filter-buttons { margin-left: auto; }

/* 列多的表格:放不下才横向滚动,不把页面撑破 */
.table-scroll { overflow-x: auto; }
.table-scroll th,
.table-scroll td { white-space: nowrap; }

/* 0.3.86:宽表横向滚动条改细改淡(原生粗黑条观感差);Firefox 走
   scrollbar-* 标准属性,Chrome/Safari 走 webkit 伪元素。 */
.table-scroll,
.recovery-list-scroll {
    scrollbar-width: thin;
    scrollbar-color: #d6dee8 transparent;
}

.table-scroll::-webkit-scrollbar,
.recovery-list-scroll::-webkit-scrollbar { height: 8px; }

.table-scroll::-webkit-scrollbar-track,
.recovery-list-scroll::-webkit-scrollbar-track { background: transparent; }

.table-scroll::-webkit-scrollbar-thumb,
.recovery-list-scroll::-webkit-scrollbar-thumb {
    background: #d6dee8;
    border-radius: 4px;
}

.table-scroll::-webkit-scrollbar-thumb:hover,
.recovery-list-scroll::-webkit-scrollbar-thumb:hover { background: #b9c6d4; }

/* 操作列固定在右侧:其它列横向滚动时它始终可见。
   单元格要给不透明底色,否则下面的内容会透出来。 */
.table-fixed-actions { width: 100%; }

.table-fixed-actions th:last-child,
.table-fixed-actions td:last-child {
    position: sticky;
    right: 0;
    background: #fff;
    box-shadow: -10px 0 10px -10px rgba(16, 21, 31, 0.18);
}

.table-fixed-actions thead th:last-child {
    background: #fafbfc;
    z-index: 2;
}

.table-fixed-actions tbody tr:hover td:last-child { background: #fafbfc; }

/* 详情弹窗。
   规则:弹窗原则上不允许出现纵向滚动条 —— 弹窗本体永远是「整块」:
   高度封顶在视口内、overflow 隐藏,标题与按钮条恒定可见;
   极端情况下内容放不下时,只有 .modal-body 内层兜底滚动,
   任何弹窗都不应该把自身或页面撑出滚动条(表单要排紧凑,靠两列栅格压缩高度)。
   display:flex 只作用在 [open] 态:关闭态必须留给 UA 的 display:none,
   否则关掉的弹窗会以空壳形式留在页面上(像多出一个窗口)。 */
.modal {
    width: min(680px, calc(100vw - 40px));
    max-height: calc(100vh - 40px);
    padding: 0;
    border: none;
    border-radius: 12px;
    box-shadow: 0 24px 60px rgba(16, 21, 31, 0.24);
    color: var(--text);
    overflow: hidden;
}

.modal[open] {
    display: flex;
    flex-direction: column;
}

/* 弹窗打开时 UA 会给 <dialog> 本体画焦点圈(蓝框),关闭之。 */
.modal:focus { outline: none; }

/* 标题条与按钮条固定不压缩;内容注入壳([data-dialog-content])在布局上视为
   不存在,让注入进来的 .modal-head / .modal-body 直接成为 .modal 的 flex
   子项 —— 这样「超高时只有 body 内层滚」的兜底规则对整壳注入的弹窗同样生效。 */
.modal > .modal-head,
.modal > .modal-actions { flex: 0 0 auto; }

.modal > [data-dialog-content] { display: contents; }

/* 弹窗内容走「先开框再注入」流程:占位加载期间隐藏弹窗本体,内容就绪后
   一次性按最终高度显示,消除「先高后缩」的抖动。遮罩不受影响照常出现。 */
.modal:has(.app-loading) { visibility: hidden; }

.modal::backdrop { background: rgba(16, 21, 31, 0.42); }

.modal-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 14px 18px;
    border-bottom: 1px solid var(--border);
}

.modal-title { margin: 0; font-size: 15px; font-weight: 600; }

.modal-close {
    width: 30px;
    height: 30px;
    border: none;
    border-radius: 7px;
    background: transparent;
    color: var(--muted);
    font-size: 20px;
    line-height: 1;
    cursor: pointer;
}

.modal-close:hover { background: #f1f3f7; color: var(--text); }

.modal-body {
    flex: 1 1 auto;
    min-height: 0;
    padding: 8px 18px 18px;
    /* 兜底通道:内容极端超高时只在 body 内层滚,弹窗本体与页面不出滚动条。 */
    overflow-y: auto;
    overscroll-behavior: contain;
}

/* 全局加载态:圆环旋转 + 下方文字,在容器内居中(弹窗内容加载占位)。 */
.app-loading {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 12px;
    min-height: 220px;
    padding: 40px 0;
    color: var(--muted);
    font-size: 13px;
}

.app-loading-spinner {
    width: 36px;
    height: 36px;
    border-radius: 50%;
    border: 3px solid var(--border);
    border-top-color: var(--brand);
    animation: app-loading-spin 0.8s linear infinite;
}

@keyframes app-loading-spin { to { transform: rotate(360deg); } }

/* 详情字段表 */
.detail-list {
    display: grid;
    grid-template-columns: 96px 1fr;
    gap: 0;
    margin: 0;
    font-size: 13.5px;
}

.detail-list dt {
    padding: 9px 0;
    color: var(--muted);
    border-bottom: 1px solid #f1f3f7;
}

.detail-list dd {
    margin: 0;
    padding: 9px 0;
    border-bottom: 1px solid #f1f3f7;
    word-break: break-all;
}

/* 详情字段表 · 两列版:一行放两个字段,弹窗高度减半(字段为偶数个时正好对齐) */
.detail-list.cols-2 {
    grid-template-columns: 88px 1fr 88px 1fr;
}

/* 长文本字段(如驳回理由)独占一行:值横跨右侧三格 */
.detail-list.cols-2 dd.detail-wide {
    grid-column: 2 / span 3;
}

/* 列表上方的动作区:左侧搜索条件一行排开、右侧新建按钮,
   窄屏放不下时自动换行,搜索控件垂直居中对齐。 */
.list-toolbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 10px;
    padding: 12px 16px;
}

/* 工具栏里的搜索表单:不再依赖 inline 流排布,统一 flex 行对齐。 */
.list-toolbar .inline-form {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
}

/* 行内文字按钮(新增下级 / 编辑 / 删除)。
   注意 <button> 有浏览器默认边框和底色,必须显式清掉,否则删除按钮会带着一个框。 */
.link-action {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 0;
    border: none;
    background: transparent;
    font-family: inherit;
    font-size: 13px;
    color: var(--brand);
    cursor: pointer;
}

.link-action:hover { text-decoration: underline; }
.link-action.danger { color: var(--danger); }
.link-action svg { width: 14px; height: 14px; }

/* 状态开关 */
.switch {
    position: relative;
    width: 38px;
    height: 21px;
    flex: 0 0 38px;
    padding: 0;
    border: none;
    border-radius: 999px;
    background: #cdd3dd;
    cursor: pointer;
    transition: background 0.15s ease;
}

.switch::after {
    content: "";
    position: absolute;
    top: 2px;
    left: 2px;
    width: 17px;
    height: 17px;
    border-radius: 50%;
    background: #fff;
    box-shadow: 0 1px 2px rgba(16, 21, 31, 0.25);
    transition: transform 0.15s ease;
}

.switch.is-on { background: var(--brand); }
.switch.is-on::after { transform: translateX(17px); }

/* 会员等级 */
.member-level { font-variant-numeric: tabular-nums; }

/* 分页器:共 N 条 · 条/页 · |< · ‹ · 页码(≤5) · › · >| · 前往 N 页。
   平台规则:分页器统一放表格右下角,整条右对齐。 */
.pager {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    flex-wrap: wrap;
    gap: 6px;
    padding: 12px 16px;
    border-top: 1px solid var(--border);
}

.pager-total { margin-right: 8px; font-size: 13px; color: var(--muted); }

.pager-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 30px;
    height: 30px;
    padding: 0 9px;
    border: 1px solid var(--border);
    border-radius: 7px;
    background: #fff;
    color: var(--text);
    font-size: 13px;
}

.pager-btn:hover { background: #f1f3f7; }
.pager-btn.is-current {
    background: var(--brand);
    border-color: var(--brand);
    color: #fff;
    cursor: default;
}

.pager-btn.is-disabled {
    color: #b9c0cc;
    background: #fafbfc;
    cursor: not-allowed;
}

.pager-size { margin-left: 6px; }
.pager-size .select { width: auto; min-width: 104px; padding: 6px 10px; font-size: 13px; }

.pager-jump {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-left: 10px;
    font-size: 13px;
    color: var(--muted);
}

.pager-input {
    width: 60px;
    padding: 5px 8px;
    text-align: center;
}

/* 状态徽章:驳回用红色 */
.badge-danger { background: #fdecec; color: #9b2c2c; }

/* 表格里的树 */
.table-tree td { padding: 9px 12px; }

.tree-toggle {
    width: 20px;
    height: 20px;
    flex: 0 0 20px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: none;
    border-radius: 4px;
    background: transparent;
    color: var(--muted);
    cursor: pointer;
}

.tree-toggle:hover { background: #e8ebf1; color: var(--text); }
.tree-toggle svg { width: 14px; height: 14px; transition: transform 0.12s ease; }
.tree-toggle.open svg { transform: rotate(90deg); }

.tree-toggle-leaf { visibility: hidden; }

.tree-cell { display: flex; align-items: center; gap: 6px; }

/* 菜单管理:行更紧凑,操作列不折行 */
.table-menu-tree td { padding: 8px 12px; }
.table-menu-tree .cell-actions { flex-wrap: nowrap; white-space: nowrap; }
.table-menu-tree .link-action { white-space: nowrap; }

/* 除「展示名称」外都收缩到内容宽度,多出来的横向空间全给它,
   这样 排序 和 操作 之间不会裂开一道空隙。 */
.table-menu-tree th:nth-child(n + 3),
.table-menu-tree td:nth-child(n + 3) {
    width: 1px;
    white-space: nowrap;
}

/* 拖动手柄列 */
.table-menu-tree .cell-drag { width: 30px; padding: 8px 2px 8px 8px; }
.drag-handle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    color: var(--muted);
    cursor: grab;
    border-radius: 4px;
}
.drag-handle:hover { background: #e8ebf1; color: var(--text); }
.drag-handle svg { width: 14px; height: 14px; }
tr.dragging { opacity: 0.45; }
tr.drop-blocked .drag-handle { cursor: not-allowed; }
tr.drop-above td { border-top: 2px solid var(--brand); }
tr.drop-below td { border-bottom: 2px solid var(--brand); }
tr.drop-child { background: #eaf1fe; }

.tree-name { font-weight: 500; }

.tree-code {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 12.5px;
    color: var(--muted);
}

/* ---------- 提示条 ---------- */

.notice {
    padding: 10px 14px;
    border: 1px solid transparent;
    border-radius: 8px;
    font-size: 13px;
    margin-bottom: 18px;
}

.notice-ok { background: var(--ok-bg); color: var(--ok-text); border-color: #cbe9d8; }
.notice-warn { background: var(--warn-bg); color: var(--warn-text); border-color: #f2e0c2; }
.notice-error { background: #fdecec; color: #9b2c2c; border-color: #f6d5d5; }

/* ---------- 顶部下滑提示(toast)与居中确认弹窗 ---------- */

.app-toast {
    position: fixed;
    top: 14px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 12000;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    pointer-events: none;
}

.app-toast .toast-item {
    min-width: 220px;
    max-width: 520px;
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 10px 18px;
    font-size: 13px;
    color: var(--text);
    box-shadow: 0 8px 24px rgba(15, 23, 42, 0.18);
    opacity: 0;
    transform: translateY(-14px);
    transition: opacity .25s, transform .25s;
    pointer-events: auto;
    word-break: break-all;
}

.app-toast .toast-item.is-in { opacity: 1; transform: none; }
.app-toast .toast-item.toast-err { color: #9b2c2c; }

.app-modal-mask {
    position: fixed;
    inset: 0;
    background: rgba(15, 23, 42, 0.45);
    z-index: 11000;
    display: flex;
    align-items: center;
    justify-content: center;
}

.app-modal {
    width: 380px;
    max-width: calc(100vw - 40px);
    background: #fff;
    border-radius: 12px;
    box-shadow: 0 20px 50px rgba(15, 23, 42, 0.3);
    padding: 20px 22px;
}

.app-modal-title { font-size: 15px; font-weight: 600; margin-bottom: 10px; color: var(--text); }
.app-modal-text { font-size: 13px; line-height: 1.7; color: var(--muted); margin-bottom: 18px; word-break: break-all; }
.app-modal-actions { display: flex; justify-content: flex-end; gap: 10px; }
.app-modal-actions button {
    font-size: 13px;
    padding: 7px 18px;
    border-radius: 8px;
    border: 1px solid var(--border);
    background: #fff;
    color: var(--text);
    cursor: pointer;
}
.app-modal-actions button.app-modal-ok {
    background: var(--brand);
    border-color: var(--brand);
    color: #fff;
}
.app-modal-actions button.app-modal-ok:hover { filter: brightness(1.06); }
.app-modal-actions button.app-modal-cancel:hover { background: var(--bg, #f3f5f9); }

/* ---------- 登录页 ---------- */

.auth {
    min-height: 100vh;
    display: grid;
    place-items: center;
    padding: 24px;
    background: radial-gradient(900px 460px at 50% -8%, #dbe6ff 0%, var(--bg) 62%);
}

.auth-card {
    width: 100%;
    max-width: 420px;
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 14px;
    padding: 34px 32px 26px;
    box-shadow: 0 14px 34px rgba(16, 21, 31, 0.09);
}

.auth-title { font-size: 24px; font-weight: 700; margin: 0 0 26px; }

/* 表单行:左侧固定宽标签(带红色星号),右侧输入框。 */
.auth-form { gap: 20px; max-width: none; }
.auth-field { display: flex; align-items: center; gap: 10px; }
.auth-field > label {
    flex: 0 0 58px;
    display: flex;
    align-items: center;
    gap: 2px;
    font-size: 13.5px;
}
.req { color: var(--danger); }

.auth-input { position: relative; flex: 1; display: flex; align-items: center; }
.auth-input .input { padding-left: 34px; }
.auth-icon {
    position: absolute;
    left: 11px;
    width: 16px;
    height: 16px;
    color: #9aa3b2;
    pointer-events: none;
}

/* 密码框的小眼睛 */
.auth-eye {
    position: absolute;
    right: 6px;
    display: grid;
    place-items: center;
    width: 28px;
    height: 28px;
    padding: 0;
    border: none;
    border-radius: 6px;
    background: transparent;
    color: #9aa3b2;
    cursor: pointer;
}
.auth-eye:hover { background: #f1f3f7; color: var(--text); }
.auth-eye-icon { width: 16px; height: 16px; }

/* 验证码:输入框 + 右侧图片,点击图片刷新。 */
.auth-captcha .input { padding-right: 8px; }
.auth-captcha-img {
    position: absolute;
    right: 2px;
    height: 34px;
    /* 左缘贴输入框直角衔接,右缘保持圆角。 */
    border-radius: 0 7px 7px 0;
    cursor: pointer;
}

.auth-submit { margin-top: 4px; padding: 11px; font-size: 15px; letter-spacing: 8px; }

.auth-links {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-top: 20px;
    font-size: 12.5px;
}
.auth-link { color: var(--brand); }
.auth-link:hover { text-decoration: underline; }

/* ---------- 会员入驻申请 ---------- */

/* 整页内容收进一张居中的圆角卡片,跟登录页同一套视觉。 */
.reg {
    max-width: 880px;
    margin: 40px auto;
    padding: 28px 34px 34px;
    background: #fff;
    border: 1px solid var(--border);
    border-radius: 14px;
    box-shadow: 0 14px 34px rgba(16, 21, 31, 0.09);
}

.reg-topbar {
    display: flex;
    justify-content: space-between;
    align-items: center;
    font-size: 13.5px;
}
.reg-brand { color: var(--brand); font-weight: 600; }

.reg-title { font-size: 22px; font-weight: 700; margin-top: 16px; }
.reg-sub { margin: 4px 0 18px; font-size: 13px; color: var(--muted); }

.reg-notice {
    display: flex;
    align-items: center;
    gap: 8px;
    background: #f4f6fa;
    border-radius: 8px;
    padding: 12px 14px;
    font-size: 13px;
    color: #4a5160;
    margin-bottom: 24px;
}
.reg-notice-icon { width: 16px; height: 16px; flex: 0 0 16px; color: #9aa3b2; }

/* 表单栅格:标签 96px,内容两列均分;整行内容用 .reg-full 跨列。 */
.reg-form { display: flex; flex-direction: column; gap: 16px; }
.reg-row {
    display: grid;
    grid-template-columns: 96px 1fr 96px 1fr;
    gap: 12px;
    align-items: center;
}
.reg-row > label { display: flex; align-items: center; gap: 2px; font-size: 13.5px; }

.reg-inline { display: flex; align-items: center; gap: 18px; }
/* 所在区域:横跨到表单右边缘,出来的几级下拉始终等分整行宽度。 */
.reg-region { grid-column: 2 / -1; gap: 10px; }
.reg-region .select { flex: 1 1 0; min-width: 0; }
.reg-hidden { display: none !important; }

.reg-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 6px; }

/* 注册协议块:总勾选 + 两篇文章链接 + 联动提示。 */
.reg-agree { margin-top: 2px; }
.reg-agree-link { color: var(--brand); }
.reg-agree-link:hover { text-decoration: underline; }
.reg-agree-tip { margin: 6px 0 0; font-size: 12px; color: var(--muted); }

/* 协议文档页:分节正文 + 底部「我已阅读并同意」勾选。 */
.reg-doc { max-width: 760px; }
.reg-doc-body { margin-top: 14px; font-size: 13.5px; line-height: 1.9; color: #303643; }
.reg-doc-intro { margin: 0 0 10px; color: #4a5160; }
.reg-doc-h2 { font-size: 15px; font-weight: 600; margin: 18px 0 6px; }
.reg-doc-p { margin: 0 0 6px; }
.reg-doc-agree { margin-top: 22px; }
.reg-doc-tip { margin: 6px 0 0; font-size: 12px; color: var(--muted); }

.reg-done { max-width: 460px; margin: 80px auto 0; text-align: center; }
.reg-done .reg-sub { margin-bottom: 22px; }

@media (max-width: 760px) {
    /* 窄屏退回单列,标签压到输入框上方 */
    .reg-row { grid-template-columns: 1fr; gap: 6px; }
    .reg-inline { flex-wrap: wrap; gap: 10px 16px; }
}

/* ---------- 窄屏 ---------- */

@media (max-width: 880px) {
    /* 窄屏不再吸顶,省下一屏高度给内容 */
    .topbar { position: static; }
    .topbar-user { gap: 10px; padding: 0 12px; }
    .who { display: none; }

    /* 侧边栏保持竖排,由脚本挂上 is-collapsed 自动折叠成图标条
       (与点折叠按钮同款样式);这里只修吸顶基准。 */
    /* 顶栏在窄屏是普通流(60px),侧栏自然位置在其下:高度同样扣
       顶栏 60 + 底净距 5,底缘才悬浮在视口上方 5px(用 100vh-5 会
       整体下坠 60px 溢出视口)。 */
    .sidebar { top: 0; height: calc(100vh - 65px); }

    .stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    /* 窄屏四张大卡退常规流:全部单列,不再逃出 content;顶部距页头 10px。
       走势大卡高度改为内容自适应(最小 240px 放得下图表),
       其余三张同样放开锁行高,按各自内容撑开。 */
    .stats-3 {
        grid-template-columns: 1fr;
        grid-template-rows: none;
        height: auto;
        width: auto;
        margin: 0 0 16px;
    }
    .chart-card { grid-column: 1 / -1; grid-row: auto; min-height: 240px; }
    .menu-card, .board-card { grid-column: 1 / -1; grid-row: auto; min-height: 200px; }
    /* 产品更新卡按「表头 + 5 行 + 页码」的高度给足,窄屏同样不出滚动条 */
    .release-card { grid-column: 1 / -1; grid-row: auto; min-height: 340px; }
    .form-row { grid-template-columns: 1fr; }
    .content { padding: 5px; }
}

/* ---------- 图标选择器(菜单表单) ---------- */

/* 一行 15 个,纵向按需要排;行数多了这块区域自己滚,不把表单撑长。
   16px 的横向内边距 + 6px 的间隙正好让 15 列排满。 */
.icon-picker {
    display: grid;
    grid-template-columns: repeat(15, minmax(0, 1fr));
    gap: 6px;
    padding: 10px;
    max-height: 180px;
    overflow-y: auto;
    background: #fafbfc;
    border: 1px solid var(--border);
    border-radius: 9px;
}

.icon-tile {
    display: flex;
    align-items: center;
    justify-content: center;
    aspect-ratio: 1 / 1;
    padding: 0;
    color: var(--chrome-text);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 7px;
    cursor: pointer;
}
.icon-tile:hover { border-color: var(--brand); color: var(--brand); }
.icon-tile.is-selected {
    color: var(--brand);
    background: #eef3ff;
    border-color: var(--brand);
    box-shadow: inset 0 0 0 1px var(--brand);
}
.icon-tile-svg { width: 18px; height: 18px; }
.icon-tile-none { font-size: 11px; color: var(--muted); }

/* 菜单列表「图标」列:图标 + 名字 */
.icon-cell { display: inline-flex; align-items: center; gap: 7px; }
.icon-cell-svg { width: 16px; height: 16px; flex: 0 0 16px; color: var(--chrome-text); }

/* ---------- 页面设置:整屏编辑工作台 ---------- */
/* 编辑器在 `_admin` 之外,拿不到后台外壳,所以这里自己撑满一屏。 */

.ed {
    display: flex;
    flex-direction: column;
    height: 100vh;
    overflow: hidden;
    background: var(--bg);
}

.ed-bar {
    height: 56px;
    flex: 0 0 56px;
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 0 16px;
    background: var(--surface);
    border-bottom: 1px solid var(--border);
}

.ed-bar-title { display: flex; align-items: baseline; gap: 10px; font-size: 15px; }
.ed-bar-title .muted { font-size: 12px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.ed-bar-actions { margin-left: auto; display: flex; align-items: center; gap: 8px; }

.ed-hint { font-size: 12px; color: var(--muted); }
.ed-hint.is-dirty { color: var(--warn-text); }

/* 属性面板「数据源」分组里的占位说明 */
.ed-ds-hint { margin: 0; font-size: 12.5px; line-height: 1.7; color: var(--muted); }

.ed-body { flex: 1; min-height: 0; display: flex; }

.ed-canvas-wrap {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
}

/* 横向禁滚:页面编辑器把画布等比缩小到可用宽度内(applyZoom),
   正常情况下不会溢出;overflow-x: hidden 兜底浮点误差的亚像素溢出。 */
.ed-canvas-scroll { flex: 1; overflow-x: hidden; overflow-y: auto; padding: 5px; }

.ed-canvas {
    margin: 0 auto;
    /* 编辑模式专属:10x10 浅灰点阵底纹,方便看清组件位置和大小。
       预览页(.pv-page)没有这个背景。 */
    background-color: #fff;
    background-image: radial-gradient(circle, #d9dee6 1.2px, transparent 1.2px);
    background-size: 10px 10px;
    border: 1px solid var(--border);
    border-radius: 10px;
    box-shadow: 0 8px 28px rgba(16, 21, 31, 0.06);
    position: relative;
    padding: 10px;
}

.ed-side {
    width: 300px;
    flex: 0 0 300px;
    display: flex;
    flex-direction: column;
    min-height: 0;
    gap: 5px;
    background: var(--bg);
}

.ed-pane {
    display: flex;
    flex-direction: column;
    min-height: 0;
    border-radius: 10px;
    overflow: hidden;
    background: var(--surface);
    /* 和画布同一款柔和投影:白面板直接贴在浅灰底上时,5px 间距读起来
       会比画布周围那条带投影的缝细,补上投影让三处间距视觉一致。 */
    box-shadow: 0 8px 28px rgba(16, 21, 31, 0.06);
}
/* 上面属性区:高度随内容自适应,内容太多时自己收缩出滚动条,
   不会把下面组件库挤出底边。上/下边距用 6px 做光学补偿:
   边缘处的缝缺少双侧圆角和参照物,同样 5px 看起来比中间的缝窄。 */
.ed-pane-props { flex: 0 1 auto; margin-top: 6px; }
/* 下面组件库:吃掉剩余空间,min-height 保底,上边距由 gap 提供。 */
.ed-pane-palette { flex: 1 1 0; min-height: 120px; margin-bottom: 6px; }

.ed-pane-head {
    flex: 0 0 auto;
    padding: 10px 14px;
    font-size: 13px;
    font-weight: 600;
    color: var(--text);
    background: #fafbfc;
    border-bottom: 1px solid var(--border);
}

.ed-pane-body { flex: 1; overflow: auto; padding: 12px 14px 5px; }

/* 属性区(「元素属性」所在面板)四边距统一 5px,底部 4px:
   最后一个手风琴已去掉 margin-bottom,这里 padding-bottom 就是最终距离。 */
.ed-pane-props .ed-pane-body { padding: 5px 5px 4px; }

/* 两个面板头同款:透明底、无下边框,用左右内缩 5px 的细分隔线,
   「画布属性」和「UI 组件库」页签的文字位置、线条完全一致。 */
.ed-pane-props .ed-pane-head,
.ed-pane-palette .ed-pane-head {
    background: transparent;
    border-bottom: none;
    position: relative;
    padding-bottom: 5px;
}
.ed-pane-props .ed-pane-head::after,
.ed-pane-palette .ed-pane-head::after {
    content: "";
    position: absolute;
    left: 5px;
    right: 5px;
    bottom: 0;
    height: 1px;
    background: var(--border);
}
.ed-pal-tabs { display: flex; align-items: center; gap: 8px; }
.ed-pal-tab {
    border: none;
    background: none;
    padding: 0;
    font: inherit;
    font-size: 13px;
    color: var(--muted);
    cursor: pointer;
}
.ed-pal-tab:hover { color: var(--text); }
.ed-pal-tab.is-active { color: var(--text); font-weight: 600; }
.ed-pal-sep { color: var(--muted); }
.ed-pal-custom { position: relative; padding-right: 26px; }
.ed-pal-del {
    position: absolute;
    top: 50%;
    right: 6px;
    transform: translateY(-50%);
    border: none;
    background: none;
    padding: 0;
    color: var(--muted);
    cursor: pointer;
    font-size: 12px;
}
.ed-pal-del:hover { color: var(--danger, #c0392b); }

/* 属性面板 */
/* 选中区块时的头部(组件名 + 操作按钮),现在嵌在 .ed-pane-head 里,
   不再要自己撑间距,字重回到正常免得按钮跟着标题加粗。 */
.ed-props-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 0;
    font-size: 13px;
    font-weight: normal;
}

/* 属性面板手风琴:和后台菜单一样可折叠,默认展开常用分组。 */
.ed-acc {
    margin-bottom: 3px;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: var(--surface);
    overflow: hidden;
}
/* 最后一个手风琴不再加 margin,与面板底边的距离由 .ed-pane-body 的 4px 底 padding 决定。 */
.ed-acc:last-child { margin-bottom: 0; }
.ed-acc summary {
    cursor: pointer;
    list-style: none;
    padding: 9px 12px;
    font-size: 13px;
    font-weight: 600;
    color: var(--text);
    background: #f7f8fa;
    border-bottom: 1px solid var(--border);
    display: flex;
    align-items: center;
    user-select: none;
}
.ed-acc summary::-webkit-details-marker { display: none; }
/* 折叠箭头:和后台菜单 nav-caret 一致,展开时翻转 180°。 */
.ed-acc-caret {
    width: 14px;
    height: 14px;
    flex: 0 0 14px;
    margin-left: auto;
    opacity: 0.55;
    transition: transform 0.15s ease;
}
.ed-acc[open] .ed-acc-caret { transform: rotate(180deg); }
.ed-acc:not([open]) summary { border-bottom: none; }
.ed-acc-body { padding: 12px; }

.ed-field { display: block; margin-bottom: 10px; }
.ed-field-label {
    display: block;
    margin-bottom: 4px;
    font-size: 12px;
    color: var(--muted);
}
/* 字段名旁的问号提示:红色小圆圈,悬停时由挂在 body 上的浮层
   (.ed-hint-pop)显示解释 —— 浮层用 position: fixed,不受右侧
   面板滚动容器 overflow 裁剪的影响,贴边也不会被截断。 */
.ed-field-hint {
    display: inline-block;
    width: 14px;
    height: 14px;
    margin-left: 5px;
    border-radius: 50%;
    background: #fdecec;
    color: var(--danger);
    font-size: 10px;
    font-weight: 600;
    line-height: 14px;
    text-align: center;
    vertical-align: 2px;
    cursor: help;
    user-select: none;
}
.ed-hint-pop {
    position: fixed;
    z-index: 999;
    width: max-content;
    max-width: 260px;
    padding: 7px 10px;
    border-radius: 8px;
    background: #2b3243;
    color: #fff;
    font-size: 12px;
    font-weight: 400;
    line-height: 1.55;
    white-space: normal;
    text-align: left;
    box-shadow: 0 6px 18px rgba(16, 21, 31, 0.18);
    pointer-events: none;
}
.ed-hint-pop[hidden] { display: none; }
.ed-field input[type="text"],
.ed-field input[type="number"],
.ed-field select,
.ed-field textarea {
    width: 100%;
    padding: 6px 9px;
    border: 1px solid var(--border);
    border-radius: 7px;
    font: inherit;
    font-size: 13px;
    background: var(--surface);
    color: var(--text);
}
.ed-field textarea { resize: vertical; }
/* 下拉箭头:原生箭头位置不可控,换成内联 SVG 背景,右缘固定留 8px。 */
.ed-field select {
    appearance: none;
    -webkit-appearance: none;
    padding-right: 24px;
    background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="%2394a3b8" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>');
    background-repeat: no-repeat;
    background-position: right 8px center;
    background-size: 12px;
}
.ed-field-inline { display: flex; align-items: center; gap: 8px; }
.ed-field-inline .ed-field-label { margin: 0; }

/* 数字字段:标签在前、输入框居中、px 单位跟在后面的一行式布局。 */
.ed-field-row { display: flex; align-items: center; gap: 8px; }
.ed-field-row .ed-field-label { margin: 0; white-space: nowrap; }
/* 行式布局里的 select:占满剩余宽度,不被 width:100% 撑出 flex 行。 */
.ed-field-row select { flex: 1; min-width: 0; width: auto; }
.ed-field-input { position: relative; flex: 1; min-width: 0; }
.ed-field-unit {
    position: absolute;
    right: 9px;
    top: 50%;
    transform: translateY(-50%);
    font-size: 12px;
    color: var(--muted);
    pointer-events: none;
}
.ed-field-input input[type="number"] { padding-right: 26px; }
/* 无单位数字字段(z 坐标:只是层叠顺序数量,不是 px):去掉右侧单位占位,
   字段本身在并排行里占窄,把空间让给带 px 的成对字段。 */
.ed-field-nounit .ed-field-input input[type="number"] { padding-right: 8px; }
.ed-field-nounit .ed-field-input { flex: 0 0 auto; }
.ed-field-nounit input[type="number"] { width: 56px; }
/* 成对字段(宽/高、X/Y)并排一行。带 px 单位的字段平分剩余空间;
   z 坐标这种无单位字段占窄(在 .ed-field-nounit 上设了固定宽度)。 */
.ed-field-pair { display: flex; gap: 8px; }
.ed-field-pair .ed-field { flex: 1 1 0; min-width: 0; }
.ed-field-pair .ed-field.ed-field-nounit { flex: 0 0 auto; }
/* 网格/容器的边距属性:每组(外边距/内边距)占两行,每行「左/上」「右/下」
   两个带 px 单位的数字框,行名在首行,次行用空名称列对齐。 */
.ed-margin-grid { display: flex; flex-direction: column; gap: 4px; }
.ed-mg-row { display: flex; align-items: center; gap: 8px; }
.ed-mg-row .ed-field { flex: 1 1 0; margin-bottom: 0; gap: 4px; min-width: 0; }
.ed-mg-name {
    flex: 0 0 38px;
    font-size: 12px;
    color: var(--muted);
    white-space: nowrap;
}
.ed-color { display: flex; align-items: center; gap: 6px; }
.ed-color input[type="color"] {
    width: 44px;
    height: 30px;
    padding: 2px;
    border: 1px solid var(--border);
    border-radius: 7px;
    background: var(--surface);
}

/* 组件库 */
.ed-pal-group { margin-bottom: 14px; }
.ed-pal-title { margin-bottom: 6px; font-size: 12px; color: var(--muted); }
.ed-pal-items { display: flex; flex-wrap: wrap; gap: 6px; }

.ed-pal-item {
    padding: 5px 10px;
    font: inherit;
    font-size: 12.5px;
    color: var(--text);
    background: #f6f7f9;
    border: 1px solid var(--border);
    border-radius: 7px;
    cursor: grab;
}
.ed-pal-item:hover { border-color: var(--brand); color: var(--brand); background: #f2f6ff; }

/* 画布上的区块:
   用 outline 而不是 border,不增加布局尺寸。预览/正式页走的是 `.pv-block`,
   不会带这套编辑态样式。 */
.ed-block {
    position: relative;
    /* 十字箭头:提示块可以按住拖动移动 */
    cursor: move;
}
/* 块内预览出来的表单控件不跟着块变十字:文本输入恢复文本光标,
   按钮/下拉/链接恢复手型 —— 光标形状只在「可拖动的块本体」上是十字。 */
.ed-block input, .ed-block textarea { cursor: text; }
.ed-block select, .ed-block button, .ed-block a { cursor: pointer; }
/* 选中态:不再画蓝色实线框。
   原本有可见边框的块(.ed-has-border,如带 border 的容器 / fieldset)只靠 8 个手柄提示;
   没有可见边框的块在编辑态补一条虚线 outline 让用户能看到边界,
   正式页(.pv-block)不带这套样式,所以虚线只在编辑器画布里出现。
   标题/段落(.ed-block-texty)未选中时常驻虚线,方便定位。 */
.ed-block.is-selected { outline: 0; }
.ed-block.is-selected:not(.ed-has-border),
.ed-block.ed-block-texty:not(.is-selected) {
    outline: 1px dashed var(--muted);
    outline-offset: 0;
}

/* 多选(Ctrl+点击):第一个选中的是基准块,红色「行军蚁」虚线框;其余选中的块用
   深一档灰色虚线框。虚线沿周长顺时针流动,多选更醒目;带可见边框的块平时靠缩放
   手柄提示,多选时手柄已隐藏,这里统一补上虚线,否则看不到选中范围。
   has-multi 规则特异性更高,覆盖上面的常规虚线。 */
.ed-block.is-selected.is-anchor { outline: 0; outline-offset: 0; }
.ed-canvas.has-multi .ed-block.is-selected { outline: 0; outline-offset: 0; }
.ed-canvas.has-multi .ed-block.is-selected::before {
    content: "";
    position: absolute;
    inset: -2px;
    z-index: 5;
    pointer-events: none;
    background-repeat: no-repeat;
    background-size: 100% 2px, 100% 2px, 2px 100%, 2px 100%;
    background-position: 0 0, 0 100%, 0 0, 100% 0;
    animation: ed-ants 0.7s linear infinite;
}
.ed-canvas.has-multi .ed-block.is-selected:not(.is-anchor)::before {
    background-image:
        repeating-linear-gradient(90deg, #374151 0 6px, transparent 6px 12px),
        repeating-linear-gradient(90deg, #374151 0 6px, transparent 6px 12px),
        repeating-linear-gradient(0deg, #374151 0 6px, transparent 6px 12px),
        repeating-linear-gradient(0deg, #374151 0 6px, transparent 6px 12px);
}
.ed-canvas.has-multi .ed-block.is-selected.is-anchor::before {
    background-image:
        repeating-linear-gradient(90deg, var(--danger) 0 6px, transparent 6px 12px),
        repeating-linear-gradient(90deg, var(--danger) 0 6px, transparent 6px 12px),
        repeating-linear-gradient(0deg, var(--danger) 0 6px, transparent 6px 12px),
        repeating-linear-gradient(0deg, var(--danger) 0 6px, transparent 6px 12px);
}
@keyframes ed-ants {
    to {
        background-position: 12px 0, -12px 100%, 0 -12px, 100% 12px;
    }
}
/* 多选期间隐藏 8 向缩放手柄:那是单选交互,多选时挤在一起没有意义。 */
.ed-canvas.has-multi .ed-resize { display: none !important; }

/* 多选面板:水平/垂直两行图标按钮,悬停用 title 提示名称。 */
.ed-multi-hint { margin: 0 0 10px; font-size: 12px; color: var(--muted); line-height: 1.6; }
.ed-multi-row { display: flex; align-items: center; gap: 6px; margin-bottom: 6px; }
.ed-multi-row > span { flex: none; width: 28px; font-size: 11px; color: var(--muted); }

/* 交互功能分组:每种交互一张卡片,标题行右侧「移除」,下面角色下拉 + 应用按钮。 */
.ed-ix-kind { border: 1px solid var(--border); border-radius: 8px; padding: 10px; margin-bottom: 10px; }
.ed-ix-kind:last-child { margin-bottom: 0; }
.ed-ix-kind-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.ed-ix-kind-head strong { font-size: 13px; }
.ed-ix-role-row { display: flex; align-items: center; gap: 8px; font-size: 12px; padding: 3px 0; }
.ed-ix-role-name { flex: none; width: 72px; color: var(--muted); }
.ed-ix-missing { color: #d33; }
.ed-multi-row button {
    flex: 1;
    height: 28px;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: 1px solid var(--border);
    border-radius: 7px;
    background: var(--surface);
    color: var(--text);
    cursor: pointer;
    transition: border-color 0.15s, color 0.15s, background 0.15s;
}
.ed-multi-row button svg { display: block; }
.ed-multi-row button:hover {
    border-color: var(--brand);
    color: var(--brand);
    background: #f2f6ff;
}

/* 容器/网格:让 .pv-container/.pv-grid 撑满整个 .ed-block 高度,背景色铺满。
   .ed-block 是 flex column,.ed-block-body flex:1 撑满,
   .pv-container/.pv-grid 再 flex:1 撑满 .ed-block-body。 */
.ed-block.ed-block-container,
.ed-block.ed-block-grid { display: flex; flex-direction: column; }
.ed-block.ed-block-container > .ed-block-body,
.ed-block.ed-block-grid > .ed-block-body {
    flex: 1;
    display: flex;
    flex-direction: column;
    min-height: 0;
}
.ed-block.ed-block-container > .ed-block-body > .pv-container,
.ed-block.ed-block-grid > .ed-block-body > .pv-grid { flex: 1; }

/* 圆角接力:样式 → 圆角写在外层块(.ed-block/.pv-block)上,但容器的背景色、
   分组框的边框等视觉都画在内层面板(.pv-container/.pv-grid/.pv-fieldset)上,
   不继承的话外层圆角下面是内层直角背景,设置圆角看不出来。画布链路中间
   还隔着 .ed-block-body,也得接力继承,圆角才能从外层传到内层。 */
.ed-block-body,
.pv-container,
.pv-grid,
.pv-fieldset { border-radius: inherit; }

/* 编辑态下 grid 默认带白色背景,让 grid 区域可见(看不出范围就没法定位)。
   用户在属性面板设了 style.background 时,renderInner 会把颜色写到 .pv-grid
   inline style 上覆盖此默认色;正式页 .pv-block-grid 不带这套样式,保持透明。
   用 background-color 而不是 background 简写,否则会清掉列间虚竖线 background-image。 */
.ed-block.ed-block-grid > .ed-block-body > .pv-grid { background-color: #fff; }

/* 编辑态独有:按列数画 N-1 条虚竖线,把各列分开(用户视觉上对齐列结构用)。
   2 列 → 中间 1 条;3 列 → 1/3、2/3 处 2 条;4 列 → 1/4、1/2、3/4 处 3 条。
   虚线颜色与画布点阵同色(#d9dee6),只编辑态显示;正式页 .pv-block 不带 .ed-block-grid
   父级,自然不触发这些规则。background-position 用百分比近似列分隔位置,
   gap 不为 0 时会有小偏差但视觉上不影响。 */
.ed-block.ed-block-grid > .ed-block-body > .pv-grid.pv-cols-2 {
    background-image: repeating-linear-gradient(to bottom, #d9dee6 0 4px, transparent 4px 8px);
    background-size: 1px 100%;
    background-position: 50% 0;
    background-repeat: no-repeat;
}
.ed-block.ed-block-grid > .ed-block-body > .pv-grid.pv-cols-3 {
    background-image:
        repeating-linear-gradient(to bottom, #d9dee6 0 4px, transparent 4px 8px),
        repeating-linear-gradient(to bottom, #d9dee6 0 4px, transparent 4px 8px);
    background-size: 1px 100%, 1px 100%;
    background-position: 33.33% 0, 66.67% 0;
    background-repeat: no-repeat, no-repeat;
}
.ed-block.ed-block-grid > .ed-block-body > .pv-grid.pv-cols-4 {
    background-image:
        repeating-linear-gradient(to bottom, #d9dee6 0 4px, transparent 4px 8px),
        repeating-linear-gradient(to bottom, #d9dee6 0 4px, transparent 4px 8px),
        repeating-linear-gradient(to bottom, #d9dee6 0 4px, transparent 4px 8px);
    background-size: 1px 100%, 1px 100%, 1px 100%;
    background-position: 25% 0, 50% 0, 75% 0;
    background-repeat: no-repeat, no-repeat, no-repeat;
}

/* 8 方向缩放手柄:统一 2x2 像素空心小方块,贴在 .ed-block 外缘。
   手柄本身是 10x10 的透明容器(扩大点击区),可见的小方块用 ::before 画在中央。
   方向只通过 cursor 区分,N/S=ns、E/W=ew、NW/SE=nwse、NE/SW=nesw。 */
.ed-resize {
    display: none;
    position: absolute;
    width: 10px;
    height: 10px;
    background: transparent;
    z-index: 6;
    box-sizing: border-box;
}
.ed-resize::before {
    content: "";
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    width: 2px;
    height: 2px;
    border: 1px solid var(--brand);
    background: #fff;
    box-sizing: content-box;
}
/* 只画直接子级的手柄:后代选择器会让「选中窗口」时把窗口内所有子组件的
   8 个手柄一起点亮,看起来像全部被选中了。 */
.ed-block.is-selected > .ed-resize { display: block; }

/* 4 条边的中点手柄:横/竖方向光标。 */
.ed-resize-n, .ed-resize-s {
    left: 50%;
    margin-left: -5px;
    cursor: ns-resize;
}
.ed-resize-n { top: -5px; }
.ed-resize-s { bottom: -5px; }

.ed-resize-e, .ed-resize-w {
    top: 50%;
    margin-top: -5px;
    cursor: ew-resize;
}
.ed-resize-e { right: -5px; }
.ed-resize-w { left: -5px; }

/* 4 个角的手柄:斜向光标。 */
.ed-resize-nw { top: -5px; left: -5px; cursor: nwse-resize; }
.ed-resize-ne { top: -5px; right: -5px; cursor: nesw-resize; }
.ed-resize-sw { bottom: -5px; left: -5px; cursor: nesw-resize; }
.ed-resize-se { bottom: -5px; right: -5px; cursor: nwse-resize; }

/* 移动 / 缩放进行中全程锁定光标:HTML5 拖放期间浏览器会把光标改回默认,
   在 <html> 上挂 class 强制覆盖。
   缩放的光标方向跟着手柄走 -- JS 在 <html> 上设 --resize-cursor。 */
html.is-moving,
html.is-moving * { cursor: move !important; }
html.is-resizing,
html.is-resizing * { cursor: var(--resize-cursor, nwse-resize) !important; }

.ed-empty-page,
.ed-empty-slot,
/* 正式页里空容器的占位,和编辑器画布同款虚线框 */
.pv-empty-slot {
    padding: 22px 14px;
    text-align: center;
    font-size: 12.5px;
    color: var(--muted);
    background: repeating-linear-gradient(45deg, #fafbfd, #fafbfd 8px, #f4f6fa 8px, #f4f6fa 16px);
    border: 1px dashed var(--border);
    border-radius: 8px;
}
/* 空容器/网格的占位要撑满父容器内部整个空间(背景铺满)。
   .pv-container 是 flex 容器:用 flex:1 + align-self:stretch 在主轴和交叉轴都撑满。
   .pv-grid 是 grid 容器:用 grid-column/grid-row 跨所有 cell。 */
.pv-container > .ed-empty-slot,
.pv-container > .pv-empty-slot {
    flex: 1;
    align-self: stretch;
    min-height: 0;
}
.pv-grid > .ed-empty-slot,
.pv-grid > .pv-empty-slot {
    grid-column: 1 / -1;
    grid-row: 1 / -1;
}

/* 拖放反馈:蓝色虚线框只给容器(流式布局需要插入位置提示);
   根画布是自由布局,拖到画布上不该整块亮起蓝框。 */
.ed-canvas .is-drop-parent { outline: 2px dashed var(--brand); outline-offset: 2px; }
.drop-before { box-shadow: 0 -3px 0 0 var(--brand); }
.drop-after { box-shadow: 0 3px 0 0 var(--brand); }

/* 页面管理总览 */
.ed-index-name { font-weight: 500; }
.ed-index .table td .inline-form { display: inline; margin-left: 10px; }

/* ---------- 自定义页面(渲染出来的那一份) ---------- */

.custom-page { padding: 0; }
.custom-page .card { padding: 14px 16px; }

/* 自定义页面的预览不套后台默认的内容留白/居中宽度:
   外层只留 5px(与 .content 统一);页面宽度由文档自己决定(和编辑器画布一致),
   在内容区里居中。 */
.content:has(.custom-page) { padding: 5px; max-width: none; }

.pv-page {
    margin: 0 auto;
    background: var(--surface);
    border-radius: 10px;
    padding: 10px;
    /* 文档高度为 0 时的兜底(行内样式不设 min-height):至少 600px。 */
    min-height: 600px;
}

/* custom-page 里画布撑满浏览器可视区(顶栏 60px + 上下留白 10px×2),
   与编辑器画布「高度为 0 时撑满滚动区」的语义一致 —— 根级靠底/垂直居中的
   组件两边落点才相同。 */
.custom-page .pv-page { min-height: calc(100vh - 80px); }

.pv-heading { margin: 0 0 8px; line-height: 1.35; }
.pv-text { margin: 0 0 8px; line-height: 1.7; }
.pv-image { display: block; max-width: 100%; border-radius: 8px; }
.pv-divider { border: none; border-top: 1px solid var(--border); }

.pv-btn {
    display: inline-block;
    padding: 7px 16px;
    border-radius: 8px;
    font-size: 13.5px;
    text-decoration: none;
}
.pv-btn-primary { background: var(--brand); color: #fff; }
.pv-btn-primary:hover { background: var(--brand-dark); }
.pv-btn-secondary { background: #eef1f6; color: var(--text); border: 1px solid var(--border); }
/* 按钮外层包裹:撑满区块(区块 w/h 变化时按钮跟着变大小),
   min-height:inherit 把区块的 min-height 接力下来,内部 <a> 用 flex 填满并居中文字。 */
.pv-btn-wrap { display: flex; min-height: inherit; }
.pv-btn-wrap .pv-btn {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
}

.pv-label {
    display: inline-block;
    padding: 2px 9px;
    border-radius: 999px;
    font-size: 12px;
}
.pv-label-gray { background: #eef1f6; color: var(--chrome-text); }
.pv-label-blue { background: #e8f0fe; color: var(--brand-dark); }
.pv-label-green { background: var(--ok-bg); color: var(--ok-text); }
.pv-label-red { background: #fdeceb; color: var(--danger); }

.pv-container > .ed-block,
.pv-grid > .ed-block { margin-bottom: 0; }

.pv-grid > .ed-block,
.pv-container > .ed-block { min-width: 0; }

.pv-fieldset {
    border: 1px solid var(--border);
    border-radius: 10px;
    margin: 0 0 10px;
}
.pv-fieldset legend { padding: 0 6px; font-size: 13px; font-weight: 600; }

.pv-imagetext { display: flex; gap: 16px; align-items: flex-start; margin-bottom: 12px; }
.pv-imagetext-right { flex-direction: row-reverse; }
.pv-imagetext-media { width: 200px; border-radius: 8px; flex: 0 0 auto; }
.pv-imagetext-body { min-width: 0; }
.pv-imagetext-body h3 { margin: 0 0 6px; font-size: 15px; }
.pv-imagetext-body p { margin: 0; font-size: 13px; line-height: 1.7; color: var(--chrome-text); }

.pv-placeholder {
    padding: 22px;
    text-align: center;
    font-size: 12.5px;
    color: var(--muted);
    background: #f7f8fa;
    border: 1px dashed var(--border);
    border-radius: 8px;
}

/* 表格组件 */
.pv-table {
    width: 100%;
    border-collapse: separate;
    border-spacing: 0;
    font-size: 13.5px;
    border: 1px solid var(--border);
    border-radius: 8px;
    overflow: hidden;
}
.pv-table th,
.pv-table td {
    padding: 9px 12px;
    text-align: left;
    border-bottom: 1px solid var(--border);
    border-right: 1px solid var(--border);
}
.pv-table th:last-child,
.pv-table td:last-child { border-right: none; }
.pv-table tbody tr:last-child td { border-bottom: none; }
.pv-table th {
    background: #f6f7f9;
    font-weight: 600;
}
.pv-table-striped tbody tr:nth-child(even) td {
    background: #fafbfc;
}
.pv-table tbody tr:hover td {
    background: #f2f6ff;
}

.pv-field { display: block; margin-bottom: 12px; }
.pv-field-label { display: block; margin-bottom: 5px; font-size: 13px; }
.pv-control {
    width: 100%;
    padding: 7px 10px;
    border: 1px solid var(--border);
    border-radius: 8px;
    font: inherit;
    font-size: 13.5px;
    background: var(--surface);
    color: var(--text);
}
.pv-choices { display: flex; flex-wrap: wrap; gap: 14px; }
.pv-choice { display: inline-flex; align-items: center; gap: 5px; font-size: 13px; }

/* 开关组件:轨道 34×18、滑块 14,开=品牌蓝、关=灰,滑块位移带过渡。 */
.pv-switch {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    cursor: pointer;
    user-select: none;
}
.pv-switch-track {
    position: relative;
    flex: 0 0 auto;
    width: 34px;
    height: 18px;
    border-radius: 9px;
    background: #d1d5db;
    transition: background 0.15s;
}
.pv-switch-thumb {
    position: absolute;
    top: 2px;
    left: 2px;
    width: 14px;
    height: 14px;
    border-radius: 50%;
    background: #fff;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
    transition: transform 0.15s;
}
.pv-switch.is-on .pv-switch-track { background: var(--brand); }
.pv-switch.is-on .pv-switch-thumb { transform: translateX(16px); }
.pv-switch-text { font-size: 12px; color: var(--muted); }

/* 表格行内操作图标按钮:26×26 圆角方块,描边图标跟随文字色;
   hover 淡灰底 + 品牌色,删除按钮 hover 转危险红。title 显示原标签。 */
.pv-cell-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    margin: 0 2px;
    border-radius: 7px;
    color: var(--muted);
    text-decoration: none;
    transition: background 0.15s, color 0.15s;
}
.pv-cell-btn:hover { background: #eef1f5; color: var(--brand); }
.pv-cell-btn-danger:hover { background: rgba(208, 69, 63, 0.1); color: var(--danger); }

/* 窗口容器:画布预览里以卡片形态内联显示;正式页由后端加 hidden 变弹层。
   宽度铺满外层区块(.pv-block 的 w 决定),自身不再带 inline width。
   纵向 flex:head 固定,body 吃掉剩余高度并出滚动条 —— 窗口有确定高度时
   (区块 h>0,渲染输出 inline height)内容超出不会把窗口撑大;min-height:0
   是 flex 子项可收缩出滚动条的关键。h=0 自适应时由画布 inline max-height 兜底。 */
.pv-window {
    box-sizing: border-box;
    width: 100%;
    max-width: 100%;
    display: flex;
    flex-direction: column;
    max-height: 100%;
    background: #fff;
    border: 1px solid #e3e6ec;
    border-radius: 10px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
    overflow: hidden;
}
/* hidden 必须能真正藏住窗口:上面的 display:flex 会覆盖浏览器默认的
   [hidden]{display:none},不补这条的话运行时脚本关窗(置 hidden)毫无效果。 */
.pv-window[hidden] { display: none !important; }
/* 窗口外层区块(.pv-block-window)在浏览器模式只剩占位作用:窗口本体靠运行时
   开合,区块却一直占着 w×h 的绝对定位区域,会把叠在它下面/后面的按钮、分页
   全部拦成点不动。让区块指针穿透,窗口本体自己恢复可点(非模态窗口就地显示时
   仍需能交互;模态窗口打开时已被摘到 body,不受这条影响)。 */
.pv-block-window { pointer-events: none; }
.pv-block-window .pv-window { pointer-events: auto; }
/* 模态窗口打开态:居中悬浮层(运行时脚本按 data-modal 挂上),阴影加强。 */
.pv-window.pv-window-modal {
    position: fixed;
    left: 50%;
    top: 80px;
    transform: translateX(-50%);
    z-index: 1000;
    max-height: 80vh;
    overflow: auto;
    box-shadow: 0 16px 48px rgba(0, 0, 0, 0.28);
}
/* 模态遮罩:全屏半透明,点击等同关窗(运行时脚本开/关模态窗口时增删)。 */
.pv-backdrop {
    position: fixed;
    inset: 0;
    z-index: 999;
    background: rgba(15, 23, 42, 0.45);
}
.pv-window-head {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 5px 10px;
    background: #f8f9fb;
    border-bottom: 1px solid var(--border);
}
.pv-window-head > span { font-size: 13px; font-weight: 600; }
.pv-window-close {
    border: none;
    background: none;
    padding: 2px 5px;
    font-size: 16px;
    line-height: 1;
    color: var(--muted);
    border-radius: 4px;
}
.pv-window-close:hover { color: var(--danger); }
/* 窗口内容区:flex 子项,min-height:0 才允许收缩并出滚动条 ——
   窗口定高(h>0)时内容超出由这里滚动,而不是把窗口撑大。
   position:relative:窗口 body 是自由布局槽,子块按 x/y 绝对定位(与根画布同款),
   超出部分由这里的滚动条兜住。 */
.pv-window-body { position: relative; flex: 1 1 auto; overflow: auto; min-height: 0; padding: 10px; }
/* 编辑器「隐藏窗口」(hide_edit):画布上变透明占位,未勾「显示所有窗口」时
   不可选中、不可拖动 —— 外层盒子指针穿透,点击落到被它盖住的下方组件上
   (透明占位不再拦截选中,与浏览器模式 .pv-block-window 同理)。
   只影响编辑画布可见性,浏览器模式永远 hidden,与此属性无关。 */
.pv-window.ed-win-hidden { opacity: 0; }
.ed-block.ed-block-win-hidden { pointer-events: none; }
/* 画布属性「显示所有窗口」(show_windows):编辑器辅助开关,让被「隐藏窗口」
   遮掉的窗口在画布现形以便编辑(选中/拖动恢复可用);只作用于编辑器画布,
   保存后正式页仍按各窗口
   hide_edit/modal 行为(浏览器模式窗口永远 hidden,由按钮触发)。 */
.ed-canvas.ed-show-windows .ed-win-hidden { opacity: 1 !important; }
.ed-canvas.ed-show-windows .ed-block.ed-block-win-hidden { pointer-events: auto; }

/* 翻页组件:一行居中,按钮 hover 变品牌蓝底白字。 */
.pv-pager {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    font-size: 12px;
}
.pv-pager-info { color: var(--muted); }
.pv-pager-btn {
    height: 26px;
    padding: 0 10px;
    border: 1px solid #d9dee6;
    border-radius: 6px;
    background: #fff;
    color: var(--text);
    font-size: 12px;
    transition: background 0.12s, border-color 0.12s, color 0.12s;
}
.pv-pager-btn:hover { background: var(--brand); border-color: var(--brand); color: #fff; }

/* 树形组件:行高 26px;lines 时子级 ul 左侧画 1px 虚线竖线。 */
.pv-tree { list-style: none; margin: 0; padding: 0; font-size: 13px; }
.pv-tree ul { list-style: none; margin: 0; padding: 0 0 0 18px; }
.pv-tree li { line-height: 26px; }
.pv-tree-lines ul { border-left: 1px dashed #d9dee6; margin-left: 7px; padding-left: 11px; }
.pv-tree-caret {
    width: 16px;
    height: 16px;
    padding: 0;
    border: none;
    background: none;
    font-size: 12px;
    line-height: 16px;
    color: var(--muted);
    vertical-align: middle;
    transition: color 0.12s;
}
.pv-tree-caret:hover { color: var(--text); }
.pv-tree-node {
    display: inline-block;
    height: 26px;
    line-height: 26px;
    padding: 0 6px;
    border-radius: 4px;
    vertical-align: middle;
    cursor: pointer;
    transition: background 0.12s;
}
.pv-tree-node:hover { background: #f2f6ff; }
.pv-tree-check { margin-right: 5px; vertical-align: middle; accent-color: var(--brand); }

/* 树形表格组件:第一格按层级 padding-left 缩进,caret 复用独立选择器
   .pv-tree-caret 的外观(未被 .pv-tree 限定,无需补写);表格同时带
   .pv-table 类,行 hover 浅蓝 #f2f6ff 由 .pv-table tbody tr:hover td 兜底。 */
.pv-tt-cell-tree { white-space: nowrap; }
.pv-tt-label { margin-left: 4px; }

/* 窄屏:右侧面板挪到下面,别把画布挤没 */
@media (max-width: 1100px) {
    .ed-body { flex-direction: column; overflow: auto; }
    .ed-side { width: 100%; flex: 1 0 auto; border-left: none; border-top: 1px solid var(--border); }
    .ed-canvas-wrap { flex: 0 0 auto; border-right: none; }
    .ed-pane-props, .ed-pane-palette { flex: 0 0 auto; }
    .ed-canvas-scroll { max-height: 60vh; }
}

/* ================= 数据库设置画布 ================= */

/* 表以紧凑标签卡的形式铺在点阵画布上,卡片下有 SVG 连线层。
   高度由 .db-edit 下的 flex 约束链决定(撑满滚动容器),不写 100vh 魔法数;
   表卡片过多或被拖出底边时,纵向滚动条出现在画布自身上。 */
.db-canvas {
    position: relative;
    min-width: 100%;
    background-color: #fff;
    background-image: radial-gradient(circle, #d9dee6 1.2px, transparent 1.2px);
    background-size: 10px 10px;
    border-radius: 10px;
    overflow-x: hidden;
    overflow-y: auto;
}

/* 数据库页专用布局(不影响页面编辑器):
   - 滚动容器改成 flex,画布 flex 撑满,四边各留 5px(margin),
     底边因此恒定 5px;内容超出时滚动条只出现在画布上。
   - 右侧只有一个属性卡片,上/下边距同样收为 5px(原来是 6px/0px 贴底)。 */
.db-edit .ed-canvas-scroll { display: flex; padding: 0; /* 数据库画布不做等比缩放,保留横向滚动 */ overflow-x: auto; }
.db-edit .db-canvas {
    flex: 1 1 auto;
    min-width: 0;
    min-height: 0;
    margin: 5px;
}
.db-edit .ed-side { gap: 0; }
.db-edit .ed-pane-props { margin: 5px 0; }
#db-lines {
    position: absolute;
    top: 0;
    left: 0;
}
#db-labels {
    position: absolute;
    top: 0;
    left: 0;
    pointer-events: none;
}
#db-labels .db-edge-label { pointer-events: none; }
#db-cards { position: absolute; inset: 0; }

.db-table {
    position: absolute;
    width: 224px;
    height: 48px;
    padding: 6px 12px;
    background: #fff;
    border: 1.5px solid #9fb4d4;
    border-radius: 9px;
    box-shadow: 0 3px 10px rgba(16, 21, 31, 0.08);
    cursor: grab;
    user-select: none;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 1px;
    box-sizing: border-box;
    transition: border-color 0.12s, box-shadow 0.12s, opacity 0.12s;
}
.db-table:active { cursor: grabbing; }
.db-table:hover { border-color: #b9c6d8; }
.db-table.is-selected {
    border-color: var(--brand);
    box-shadow: 0 0 0 2px rgba(47, 109, 246, 0.22), 0 4px 12px rgba(16, 21, 31, 0.1);
}
/* Ctrl 多选的第二张表:绿色描边,和首张区分。 */
.db-table.is-selected-2 {
    border-color: #1a7f47;
    box-shadow: 0 0 0 2px rgba(26, 127, 71, 0.2), 0 4px 12px rgba(16, 21, 31, 0.1);
}
/* 连接(中间)表:紫色虚线,提示它只承担 M:N 关联。 */
.db-table.is-junction {
    border-style: dashed;
    border-color: #8f76d6;
    background: #faf8ff;
}
.db-table.is-dim { opacity: 0.35; }
.db-table-name {
    font-size: 13px;
    font-weight: 700;
    font-family: ui-monospace, Menlo, Consolas, monospace;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.db-table-comment {
    font-size: 11.5px;
    color: var(--muted);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* 悬停浮出的详细信息:引擎、列数、行数、注释。 */
.db-table-tip {
    display: none;
    position: absolute;
    top: calc(100% + 6px);
    left: 0;
    z-index: 30;
    max-width: 260px;
    padding: 6px 10px;
    border-radius: 7px;
    background: #26303f;
    color: #f3f6fa;
    font-size: 11.5px;
    line-height: 1.5;
    white-space: normal;
    box-shadow: 0 6px 18px rgba(16, 21, 31, 0.22);
    pointer-events: none;
}
.db-table:hover .db-table-tip { display: block; }

/* 连线状态。 */
.db-edge { transition: opacity 0.12s, stroke-width 0.12s; }
.db-edge.is-dim, .db-edge-label.is-dim { opacity: 0.15; }
.db-edge.is-active, .db-edge.is-hover { stroke-width: 2.8; }
/* 恰好连接当前所选两张表的关系:再加粗一档并提亮标签。 */
.db-edge.is-pair { stroke-width: 3.4; }
.db-edge-label.is-pair rect { stroke-width: 1.6; }
.db-edge-label { transition: opacity 0.12s; }
.db-edge-label text { font-size: 11px; font-family: ui-monospace, Menlo, Consolas, monospace; }

/* 键标记:主键 / 唯一 / 普通索引。 */
.db-key-badge {
    display: inline-block;
    margin-right: 5px;
    padding: 0 5px;
    border-radius: 4px;
    background: #e4edff;
    color: #2357c8;
    font-size: 10.5px;
    font-weight: 700;
    font-style: normal;
    vertical-align: 1px;
}
.db-key-uni { background: #e6f7ee; color: #1a7f47; }
.db-key-mul { background: #f1ecff; color: #6741c6; }

/* 右侧表属性面板。 */
.db-hint { font-size: 12.5px; padding: 4px 2px; }
.db-prop-meta { font-size: 12.5px; color: var(--muted); margin-bottom: 8px; }
.db-prop-cols { display: flex; flex-direction: column; gap: 6px; }
.db-prop-col {
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 7px 9px;
    background: var(--surface);
}
.db-prop-col-name {
    font-size: 12.5px;
    font-weight: 600;
    font-family: ui-monospace, Menlo, Consolas, monospace;
}
.db-prop-col-type {
    font-size: 11.5px;
    color: var(--muted);
    font-family: ui-monospace, Menlo, Consolas, monospace;
    margin-top: 2px;
}

/* 编辑表结构弹窗。 */
.db-struct { width: min(820px, calc(100vw - 40px)); }
.db-struct.db-create { width: min(900px, calc(100vw - 40px)); }
.db-create-fields {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0 14px;
}
.db-struct-field {
    display: block;
    font-size: 12.5px;
    color: var(--muted);
    margin: 6px 0 12px;
}
.db-struct-field .input { margin-top: 4px; width: 100%; box-sizing: border-box; }
.db-struct-grid {
    border: 1px solid var(--border);
    border-radius: 8px;
    max-height: 52vh;
    overflow: auto;
}
.db-struct-row {
    display: grid;
    grid-template-columns: minmax(120px, 1.1fr) minmax(130px, 1fr) 48px minmax(150px, 1.5fr) 34px;
    gap: 8px;
    align-items: center;
    padding: 6px 10px;
    border-bottom: 1px solid #eef1f5;
}
.db-struct-row:last-child { border-bottom: none; }
/* 新建表多两列(主键/自增)。 */
.db-create-row {
    grid-template-columns:
        minmax(104px, 1fr) minmax(118px, 1fr)
        40px 40px 40px minmax(120px, 1.3fr) 30px;
}
.db-struct-row .ct-null,
.db-struct-row .ct-pk,
.db-struct-row .ct-ai { justify-self: center; width: 15px; height: 15px; }
.db-struct-head {
    position: sticky;
    top: 0;
    z-index: 1;
    background: #f5f7fa;
    font-size: 12px;
    color: var(--muted);
    font-weight: 600;
}
.db-struct-row .input {
    width: 100%;
    box-sizing: border-box;
    padding: 5px 8px;
    font-size: 12.5px;
    font-family: ui-monospace, Menlo, Consolas, monospace;
}
.db-struct-row .db-cell-comment { font-family: inherit; }
.db-struct-row .db-cell-null { justify-self: center; width: 15px; height: 15px; }
.db-struct-del {
    width: 24px;
    height: 24px;
    border: none;
    border-radius: 6px;
    background: transparent;
    color: #b3413a;
    font-size: 16px;
    line-height: 1;
    cursor: pointer;
}
.db-struct-del:hover { background: #fdeceb; }
.db-struct-add { margin-top: 10px; }
.db-struct-error { color: #b3413a; font-size: 12.5px; min-height: 16px; margin-top: 8px; }
.db-struct-warn { font-size: 11.5px; margin: 6px 0 0; }
/* 新建表/编辑表结构弹窗里按钮条是 <dialog> 的直接子元素(不在 .modal-body 内),
   通用 .modal-actions 只有 padding-top,会贴右、贴底;
   这里补齐左右 18px(与 head/body 对齐)和底部 18px。 */
.db-struct .modal-actions { padding: 16px 18px 18px; }

/* ================= 关联编辑(右侧双选面板) ================= */
.db-hint kbd {
    display: inline-block;
    padding: 0 5px;
    border: 1px solid var(--border);
    border-bottom-width: 2px;
    border-radius: 5px;
    background: #f5f7fa;
    font-family: ui-monospace, Menlo, Consolas, monospace;
    font-size: 11px;
}
.db-rel-pair {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 10px;
    margin-bottom: 12px;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: #f7f9fc;
}
.db-rel-table {
    flex: 1;
    min-width: 0;
    text-align: center;
    font-family: ui-monospace, Menlo, Consolas, monospace;
    font-size: 12.5px;
    font-weight: 700;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    color: var(--brand);
}
.db-rel-arrows { color: var(--muted); font-size: 14px; }
.db-rel-section { margin-bottom: 14px; }
.db-rel-section-title {
    font-size: 12px;
    font-weight: 700;
    color: var(--muted);
    margin-bottom: 6px;
}
.db-rel-empty { font-size: 12.5px; padding: 4px 2px 8px; }
.db-rel-list { display: flex; flex-direction: column; gap: 8px; }

.db-rel-item {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    padding: 8px 10px;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: #fff;
}
.db-rel-badge {
    flex: 0 0 auto;
    margin-top: 1px;
    padding: 2px 7px;
    border-radius: 10px;
    font-size: 11px;
    font-weight: 700;
    color: #fff;
}
.db-rel-badge.mto { background: #2f6df6; }
.db-rel-badge.oto { background: #1a7f47; }
.db-rel-badge.mtm { background: #6741c6; }
.db-rel-body { flex: 1; min-width: 0; }
.db-rel-line {
    font-size: 12.5px;
    font-family: ui-monospace, Menlo, Consolas, monospace;
    word-break: break-all;
}
.db-rel-via { font-size: 11.5px; margin-top: 2px; }
.db-rel-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    margin-top: 6px;
}
.db-rel-check {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 11.5px;
    color: var(--muted);
    margin-right: 2px;
}
.db-rel-check input { width: 14px; height: 14px; }
.db-rel-danger { color: #b3413a; border-color: #e3b4b0; }
.db-rel-danger:hover { background: #fdeceb; }

.db-rel-form { display: flex; flex-direction: column; gap: 10px; }
.db-rel-field {
    display: block;
    font-size: 12.5px;
    color: var(--muted);
}
.db-rel-field .input {
    width: 100%;
    box-sizing: border-box;
    margin-top: 4px;
    padding: 6px 8px;
    font-size: 12.5px;
}
.db-rel-inline {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: 12px;
    color: var(--muted);
    margin-left: 10px;
}
.db-rel-inline input { width: 14px; height: 14px; }
.db-rel-tip { font-size: 11.5px; margin: 4px 0 0; }

/* ================= 角色管理 ================= */

.page-wide { max-width: 100%; }

/* 工具栏:左侧批量删除,右侧新增(统一口径:新建类按钮一律在右) */
.role-toolbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
}

/* 只有新增按钮、没有左侧动作的工具栏:靠右对齐。 */
.list-toolbar-right {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 10px;
}

.role-toolbar form { display: inline-flex; align-items: center; }

.role-toolbar .btn-danger {
    background: #fdeeee;
    border-color: #f8d9d8;
}

.role-toolbar .btn-danger:hover { background: #fbe0df; }

.role-toolbar-right { display: inline-flex; align-items: center; gap: 10px; }

.btn-primary { background: var(--brand); color: #fff; }
.btn-primary:hover { background: var(--brand-dark); }

/* 列表 */
.table-roles th,
.table-roles td { vertical-align: middle; }

.cell-check { width: 44px; text-align: center; }

.cell-check input[type="checkbox"] {
    width: 16px;
    height: 16px;
    accent-color: var(--brand);
    cursor: pointer;
    vertical-align: middle;
}

.role-name-link { color: var(--text); font-weight: 500; }
.role-name-link:hover { color: var(--brand); }

/* 表头排序 */
.sort-link {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    color: var(--muted);
    font-weight: 600;
}

.sort-link:hover { color: var(--text); }
.sort-link.is-active { color: var(--brand); }
.sort-glyph { font-size: 11px; line-height: 1; }

/* 适用范围标签(列表行内、下拉框内共用) */
.scope-tag {
    display: inline-block;
    padding: 3px 12px;
    border-radius: 6px;
    font-size: 13px;
    line-height: 1.5;
    white-space: nowrap;
}

.scope-tag-gray { background: #f1f2f4; color: #8a909c; }
.scope-tag-orange { background: #fdf1e0; color: #c9822b; }

/* 行内开关按设计稿放大一点 */
.table-roles .switch { width: 44px; height: 24px; flex-basis: 44px; }
.table-roles .switch::after { width: 20px; height: 20px; }
.table-roles .switch.is-on::after { transform: translateX(20px); }

/* 弹窗 */
.modal-lg { width: min(860px, calc(100vw - 48px)); }
.modal-notice { margin: 4px 0 14px; }

.modal-actions {
    display: flex;
    justify-content: flex-end;
    gap: 10px;
    margin-top: 6px;
    padding-top: 16px;
    border-top: 1px solid var(--border);
}

/* 角色表单:label 在左、控件在右 */
.role-form { max-width: none; gap: 16px; }

.role-field {
    flex-direction: row;
    align-items: flex-start;
}

.role-field > label {
    width: 110px;
    flex: 0 0 110px;
    padding-top: 8px;
    padding-right: 14px;
    text-align: right;
    color: var(--text);
}

.role-field > .input,
.role-field > .select,
.role-field > .scope-select,
.role-field > .menu-tree-box,
.role-field > textarea { flex: 1 1 auto; }

.role-field .hint { margin-top: 6px; }

.role-field-inline { align-items: center; }
.role-field-inline > label { padding-top: 0; }

.req-label .req { margin-right: 2px; }

/* 角色多选下拉:按钮复用 .select 外观,勾选浮层绝对定位于按钮下方。 */
.role-field > .multiselect { flex: 1 1 auto; position: relative; }

.multiselect-toggle {
    display: block;
    width: 100%;
    text-align: left;
    cursor: pointer;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* 浮层用 position:fixed 脱离弹窗的裁剪链:弹窗比视口矮时,若仍用 absolute
   定位,向下展开的浮层会戳出弹窗底部、把 modal-body 撑出滚动条。
   打开时由脚本按触发器的视口坐标设置 left/top/width/max-height。 */
.multiselect-menu {
    position: fixed;
    z-index: 30;
    max-height: 220px;
    overflow: auto;
    background: #fff;
    border: 1px solid var(--border, #ccc);
    border-radius: 4px;
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
    padding: 4px 0;
}

.multiselect-item {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 12px;
    cursor: pointer;
    font-size: 14px;
}

.multiselect-item:hover { background: #f2f5f7; }

.multiselect-item input { margin: 0; flex: 0 0 auto; }

.role-field .input-narrow { width: 220px; flex: 0 0 auto; }

/* 状态单选 */
.radio-pill {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    margin: 0 26px 0 0;
    font-size: 14px;
    cursor: pointer;
    /* 文字不折行:选中加粗变宽时整颗 pill 换到下一行,而非文字断行(如「U盘/存储卡」)。 */
    white-space: nowrap;
}

.radio-pill input {
    width: 17px;
    height: 17px;
    accent-color: var(--brand);
    cursor: pointer;
}

.radio-pill:has(input:checked) span { color: var(--brand); font-weight: 600; }

/* 置灰选项(如不能停用自己当前的角色):不可点、降低对比度。 */
.radio-pill.is-disabled {
    cursor: not-allowed;
    opacity: 0.45;
}

.radio-pill.is-disabled input { cursor: not-allowed; }

/* 适用范围多选下拉 */
.scope-select { position: relative; }

.scope-box {
    /* caret 的 absolute 定位基准:不加 relative 会相对更高的 .scope-select
       定位,箭头戳到框外右下角。 */
    position: relative;
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    min-height: 40px;
    padding: 6px 34px 6px 12px;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: var(--surface);
    text-align: left;
    cursor: pointer;
    font: inherit;
}

.scope-box:hover { border-color: #c4ccd8; }
.scope-select:has(.scope-panel:not([hidden])) .scope-box {
    border-color: var(--brand);
    box-shadow: 0 0 0 2px rgba(47, 109, 246, 0.12);
}

.scope-tags {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    flex: 1 1 auto;
}

.scope-tags .scope-tag { padding: 2px 10px; font-size: 12.5px; }

.scope-placeholder { color: #9aa1ad; font-size: 13.5px; }

.scope-clear {
    flex: 0 0 auto;
    width: 18px;
    height: 18px;
    border: none;
    border-radius: 50%;
    background: #b7bdc8;
    color: #fff;
    font-size: 12px;
    line-height: 18px;
    text-align: center;
    padding: 0;
    cursor: pointer;
}

.scope-clear:hover { background: #98a0ad; }

.scope-caret {
    position: absolute;
    right: 12px;
    top: 50%;
    width: 16px;
    height: 16px;
    margin-top: -8px;
    color: var(--muted);
    pointer-events: none;
}

.scope-select:has(.scope-panel:not([hidden])) .scope-caret { transform: rotate(180deg); }

/* 与 .multiselect-menu 同口径:fixed 脱离弹窗裁剪,几何由脚本按触发器
   的视口坐标计算(left/top/width/max-height),不再戳出弹窗撑滚动条。 */
.scope-panel {
    position: fixed;
    z-index: 30;
    padding: 4px;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: var(--surface);
    box-shadow: 0 12px 32px rgba(16, 21, 31, 0.16);
    max-height: 320px;
    overflow-y: auto;
}

.scope-list { list-style: none; margin: 0; padding: 0; }

.scope-option {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 8px 12px;
    border-radius: 6px;
    font-size: 13.5px;
    cursor: pointer;
}

.scope-option:hover { background: #f5f8ff; }
.scope-option input { width: 16px; height: 16px; accent-color: var(--brand); cursor: pointer; }
.scope-option:has(input:checked) { color: var(--brand); font-weight: 600; }

.scope-group-title {
    padding: 9px 12px 3px;
    font-size: 12.5px;
    color: var(--muted);
}

.scope-tenant .scope-option { padding-left: 28px; }

/* 菜单权限树:高度压矮(200px),配合弹窗两列表单,保证「添加角色」
   弹窗整块落在视口内、不出纵向滚动条;树自身放不下时在框内滚动。 */
.menu-tree-box {
    border: 1px solid var(--border);
    border-radius: 8px;
    max-height: 200px;
    overflow-y: auto;
    background: var(--surface);
}

.menu-tree-box-tall { max-height: 460px; }

.menu-tree,
.menu-tree-children { list-style: none; margin: 0; padding: 4px; }
.menu-tree-children { padding-left: 0; }

.menu-tree-row {
    display: flex;
    align-items: center;
    gap: 2px;
    padding: 5px 8px;
    border-radius: 6px;
}

.menu-tree-row:hover { background: #f6f7f9; }

.menu-caret {
    flex: 0 0 22px;
    width: 22px;
    height: 24px;
    padding: 0;
    border: none;
    background: transparent;
    color: var(--muted);
    cursor: pointer;
}

.menu-caret svg {
    width: 14px;
    height: 14px;
    transition: transform 0.12s ease;
    transform: rotate(90deg);
}

.menu-caret-leaf { flex: 0 0 22px; }

.menu-tree-item.is-collapsed > .menu-tree-row .menu-caret svg { transform: rotate(0deg); }
.menu-tree-item.is-collapsed > .menu-tree-children { display: none; }

/* 超出上级角色权限范围的节点:置灰不可勾选。 */
.menu-tree-item.is-disabled > .menu-tree-row .menu-check { color: #9aa1ad; cursor: not-allowed; }
.menu-tree-item.is-disabled > .menu-tree-row .menu-check input { cursor: not-allowed; }

.menu-check {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-size: 13.5px;
    cursor: pointer;
}

.menu-check input { width: 16px; height: 16px; accent-color: var(--brand); cursor: pointer; }

/* 弹窗里横排表单在窄屏下退化成竖排 */
@media (max-width: 720px) {
    .role-field { flex-direction: column; align-items: stretch; }
    .role-field > label {
        width: auto;
        flex: none;
        padding: 0;
        text-align: left;
    }
    .role-field .input-narrow { width: 100%; }
}

/* ---------------------------------------------------------------- 网站模板卡片页 */

/* 模板列表不套外层卡片:页头 + 一行卡片的平铺布局,宽度吃满内容区。 */
.tpl-page > .page-head { margin-bottom: 16px; }

.tpl-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: 16px;
    align-items: start;
}

.tpl-card {
    display: flex;
    flex-direction: column;
    border: 1px solid var(--border);
    border-radius: 10px;
    background: var(--surface);
    transition: box-shadow 0.15s ease, border-color 0.15s ease;
}

.tpl-card:hover { box-shadow: 0 6px 18px rgba(15, 23, 42, 0.08); }

/* 使用中的模板整卡蓝框高亮,一眼区分「已在用的」和「闲置的」。 */
.tpl-card.is-used { border-color: var(--brand); }

/* 启用开关:卡片线框外围下方一行「启用 + 开关」,所有开关互斥只能开一个。 */
.tpl-cell {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.tpl-enable {
    display: flex;
    align-items: center;
    gap: 8px;
    padding-left: 4px;
}

.tpl-enable-label { font-size: 12px; color: var(--muted); }

/* 被激活(启用)的模板整卡高亮。 */
.tpl-card.is-active { border-color: var(--brand); box-shadow: 0 0 0 1px var(--brand); }

/* 缩略图:固定 105px 高,上方彩色头图 + 下方三个占位方块。
   删除按钮要探出圆角,故不裁剪(头图自带圆角上缘贴合)。 */
.tpl-thumb {
    position: relative;
    height: 105px;
    margin: 12px 12px 0;
    border: 1px solid var(--border);
    border-radius: 8px;
    display: flex;
    flex-direction: column;
}

.tpl-band {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 8px;
    flex: 1;
    min-height: 0;
    padding: 0 16px;
    border-radius: 7px 7px 0 0;
    background: linear-gradient(135deg, #94a3b8, #7b8ba6);
}

.tpl-band > i {
    display: block;
    height: 8px;
    border-radius: 4px;
    background: rgba(255, 255, 255, 0.72);
}

.tpl-band > i.is-short { width: 58%; }

/* 头图配色改为拾色器任意色值,渐变由卡片内联 style 下发到 .tpl-band。 */
.tpl-tone-field { flex-direction: row; align-items: center; gap: 12px; }
.tpl-tone-field > label { width: 84px; text-align: right; flex: none; }

.tpl-tone-color {
    width: 44px;
    height: 34px;
    padding: 2px;
    border: 1px solid var(--border);
    border-radius: 8px;
    background: var(--surface);
    cursor: pointer;
}

.tpl-tone-color::-webkit-color-swatch-wrapper { padding: 0; }
.tpl-tone-color::-webkit-color-swatch { border: none; border-radius: 5px; }
.tpl-tone-color::-moz-color-swatch { border: none; border-radius: 5px; }

.tpl-sqs {
    display: flex;
    gap: 8px;
    padding: 8px 12px;
    background: var(--surface);
    flex: 0 0 34px;
    border-radius: 0 0 7px 7px;
}

.tpl-sqs > i {
    flex: 1;
    border-radius: 4px;
    background: #eef1f5;
}

/* 删除按钮:圆形 × 对齐缩略图右上角圆角的圆心,并向右上再探出 2px。
   缩略图圆角 8px,按钮 22px,圆心在 (8,8);额外右上移 2px → -5。 */
.tpl-del {
    position: absolute;
    top: -5px;
    right: -5px;
    margin: 0;
    z-index: 2;
}

.tpl-x {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    padding: 0;
    border: none;
    border-radius: 50%;
    background: rgba(15, 23, 42, 0.5);
    color: #fff;
    font-size: 14px;
    line-height: 1;
    cursor: pointer;
    box-shadow: 0 1px 3px rgba(15, 23, 42, 0.3);
    transition: background 0.15s ease;
}

.tpl-x:hover { background: rgba(15, 23, 42, 0.78); }

.tpl-body {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 10px 14px 0;
}

.tpl-head {
    display: flex;
    align-items: center;
    gap: 8px;
}

.tpl-name {
    flex: 1;
    min-width: 0;
    font-size: 15px;
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.tpl-name a { color: var(--text); text-decoration: none; }
.tpl-name a:hover { color: var(--brand); }

/* 「（预览）」:蓝色文字链接,和名称同一行跟在后面。 */
.tpl-preview { font-size: 13px; font-weight: 400; color: var(--brand); }
.tpl-preview a { color: var(--brand); text-decoration: none; }
.tpl-preview a:hover { text-decoration: underline; }

/* 模板简介不再单列一行,完整说明挂在标题的 title tooltip 上。 */

/* 底部操作行:修改 · 授权 · 节日主题 · 克隆 · 进入编辑。离卡片底缘留 7px。 */
.tpl-foot {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    margin-bottom: 7px;
}

.tpl-actions {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 4px;
    font-size: 13px;
}

.tpl-link {
    color: var(--brand);
    text-decoration: none;
    white-space: nowrap;
}

.tpl-link:hover { text-decoration: underline; }

.tpl-sep { color: var(--muted); opacity: 0.5; }

.tpl-edit { font-weight: 500; }

/* 授权信息区在卡片上隐藏,改走弹窗。 */
.tpl-grant { display: none; }

/* 模板弹窗宽度比默认 modal 略宽,装下主题表格。 */
.tpl-dialog { width: min(820px, calc(100vw - 48px)); }

/* 模板弹窗底部的说明文字。 */
.tpl-new-hint {
    margin: 4px 0 0;
    font-size: 13px;
    line-height: 1.7;
    color: var(--muted);
}

/* 导出模板弹窗:模板清单行(名称 + 导出链接)。 */
.tpl-export-list {
    display: flex;
    flex-direction: column;
    gap: 2px;
    max-height: 320px;
    overflow-y: auto;
}
.tpl-export-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 7px 10px;
    border-radius: 6px;
}
.tpl-export-row:nth-child(odd) { background: var(--bg-soft, #f6f7fa); }
.tpl-export-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 14px;
}

/* 模板名称字数计数:右下角小字。 */
.char-counter {
    position: absolute;
    right: 10px;
    bottom: 7px;
    font-size: 12px;
    color: var(--muted);
    pointer-events: none;
}

/* 分段按钮组:授权范围。 */
.seg-group {
    display: inline-flex;
    border: 1px solid var(--border);
    border-radius: 8px;
    overflow: hidden;
}

.seg-group .seg {
    padding: 7px 14px;
    border: none;
    border-right: 1px solid var(--border);
    background: var(--surface);
    font-size: 13px;
    color: var(--text);
    cursor: pointer;
    transition: background 0.15s ease, color 0.15s ease;
}

.seg-group .seg:last-child { border-right: none; }

.seg-group .seg.is-active {
    background: var(--brand);
    color: #fff;
}

.seg-group .seg:hover:not(.is-active) { background: #f1f3f7; }

/* 授权弹窗字段。 */
.grant-field .hint { display: block; margin-top: 6px; }

.grant-switch-label {
    display: flex;
    align-items: center;
    gap: 10px;
    cursor: pointer;
}

.grant-switch-label .switch { flex: 0 0 auto; }

.grant-tenant-list {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 10px 12px;
    border: 1px solid var(--border);
    border-radius: 8px;
    max-height: 200px;
    overflow-y: auto;
}

.grant-tenant {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 13px;
    cursor: pointer;
}

.grant-tenant input { accent-color: var(--brand); }

/* 租户层级小标签。 */
.tag {
    display: inline-block;
    padding: 1px 7px;
    border-radius: 4px;
    background: #eef1f5;
    color: #4a5160;
    font-size: 12px;
}

/* 节日主题表格。 */
/* 主题表格弹窗右上角的「新增」工具条。 */
.theme-toolbar {
    display: flex;
    justify-content: flex-end;
    margin-bottom: 10px;
}

/* 主题表格弹窗:高度随内容变化,但顶部锚定在视口固定位置,
   翻页导致高度变化时窗口顶部不跟着跳动。 */
.tpl-dialog.theme-dialog {
    top: 12vh;
    bottom: auto;
    margin: 0 auto;
}

/* 翻页局部刷新时表格区域轻微变暗,不重建整窗。 */
[data-theme-region] { transition: opacity 0.12s ease; }
[data-theme-region].is-loading { opacity: 0.55; }

/* 操作列表头左对齐,内容也要跟着左对齐(通用 .cell-actions 是右对齐)。 */
.theme-table .cell-actions { justify-content: flex-start; }

/* 主题表格下方的分页器,靠右。 */
.theme-pager {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 6px;
    margin-top: 10px;
}

.theme-table-wrap {
    margin-bottom: 16px;
    border: 1px solid var(--border);
    border-radius: 8px;
    overflow: hidden;
}

.theme-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 13px;
}

.theme-table th {
    padding: 10px 12px;
    text-align: left;
    font-weight: 600;
    background: #f7f8fa;
    border-bottom: 1px solid var(--border);
}

.theme-table td {
    padding: 10px 12px;
    border-bottom: 1px solid #f1f3f7;
    vertical-align: middle;
}

.theme-table tr:last-child td { border-bottom: none; }

.theme-params-cell {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 12px;
    color: var(--muted);
    max-width: 200px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.theme-field-inline {
    display: flex;
    align-items: center;
    gap: 10px;
}

.theme-field-inline .switch { flex: 0 0 auto; }

/* 编辑器顶栏的模板名称/说明输入框 */
.ed-bar-meta {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
}

.ed-name,
.ed-desc {
    border: 1px solid transparent;
    border-radius: 6px;
    background: transparent;
    color: inherit;
    font-size: 14px;
    padding: 4px 8px;
    transition: border-color 0.12s ease, background 0.12s ease;
}

.ed-name { width: 160px; font-weight: 600; }
.ed-desc { width: 240px; font-size: 13px; }

.ed-name:hover,
.ed-desc:hover { background: rgba(255, 255, 255, 0.08); }

.ed-name:focus,
.ed-desc:focus {
    outline: none;
    border-color: var(--brand);
    background: rgba(255, 255, 255, 0.1);
}

/* ---------- 页签条(恢复登记/字典/入库等页签页共用) ----------
   原先随各页 body 行内 <style> 注入:流式首绘早于样式解析,页签卡片
   先按纯 inline 链接塌成一条窄线、样式到达后瞬间弹回,点任何页签跳转
   都会整体抖一下。上移到全局 head 样式后首帧即有版式。 */
.recovery-tabs {
    display: flex;
    align-items: center;
    gap: 28px;
    padding: 0 4px;
    border-bottom: 1px solid var(--border);
}

.recovery-tabs-card { padding: 0 16px; }

.recovery-tab {
    display: inline-flex;
    align-items: center;
    padding: 10px 2px;
    font-size: 14px;
    color: var(--text);
    text-decoration: none;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
}

.recovery-tab:hover { color: var(--brand); }

.recovery-tab.is-active {
    color: var(--brand);
    font-weight: 600;
    border-bottom-color: var(--brand);
}

.recovery-section + .recovery-section { margin-top: 5px; }

.recovery-filter .input,
.recovery-filter .select { width: 170px; }

.recovery-filter .checkbox-line {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    white-space: nowrap;
}
/* 门户布局设计器(/editor/design/{id})。
   布局:整屏无顶栏(保存/撤销/退出在属性卡片头部) + 工作区(白底点阵画布,
   右侧 288px 两张独立的白底圆角卡:属性面板在上、组件库在下,两张卡间隔 5px,
   中间露出页面灰底,所以 5px 间距与组件库的圆角都看得见。
   区块用 .dsn-block,组件形似渲染用 .pk-*;右键菜单/编辑条/弹窗模拟各一节。 */

.dsn {
    display: flex;
    flex-direction: column;
    height: 100vh;
    background: #f3f4f6;
    color: #1f2937;
    font-size: 13px;
}

/* —— 主体(整屏,无顶栏;保存/撤销/退出在属性卡片头部) —— */
.dsn-body {
    flex: 1;
    display: flex;
    gap: 5px;
    padding: 5px;
    min-height: 0;
    position: relative;
    box-sizing: border-box;
}
.dsn-work {
    flex: 1;
    /* 本身透明、无边框:白底与边框画在画布(.dsn-stage)上,白色区域永远与画布等大。
       竖直滚动条不占常驻槽位:需要时才出现,贴着画布右边框外侧(即画布与属性面板
       之间那条 5px 间隙的左边),不出滚动条时画布直接铺满工作区、没有空槽。
       出/收滚动条会变 clientWidth,由 JS 的 ResizeObserver 重算缩放补上。 */
    overflow-x: hidden;
    overflow-y: auto;
    padding: 0;
}
/* 工作区滚动条:细、浅灰、圆角,悬停加深。只用 ::-webkit-scrollbar(Chrome/Safari):
   一旦声明 scrollbar-width,浏览器会忽略 ::-webkit-scrollbar,退回 11px 原生细条,
   又粗又黑 —— 之前就是这个原因。Firefox 不支持伪元素,退回系统默认细条,可接受。 */
.dsn-work::-webkit-scrollbar { width: 6px; height: 6px; }
.dsn-work::-webkit-scrollbar-track { background: transparent; }
.dsn-work::-webkit-scrollbar-thumb { background: #d1d5db; border-radius: 3px; }
.dsn-work::-webkit-scrollbar-thumb:hover { background: #9ca3af; }
.dsn-stage {
    position: relative;
    margin: 0 auto;
    /* 白底 + 点阵 + 边框都在 stage 上:stage 不被 scale,点阵视觉大小跨浏览器恒为
       10px;白色区域与画布永远等大(折叠后画布限宽 1200px 居中,白底随之收窄)。
       边框用 inset 阴影而不是 border:stage 尺寸就是 JS 按画布缩放算出来的内容盒,
       加了真边框(border-box)会让里面的 page 溢出 2px,工作区平白多出一根滚动条。 */
    box-sizing: border-box;
    background-color: #fff;
    background-image: radial-gradient(circle, #d1d5db 1.2px, transparent 1.2px);
    background-size: 10px 10px;
    border: none;
    /* 圆角 8px(屏幕像素):多层容器铺满整块画布,它的四角圆角按这个值反算
       (见 designer_canvas.js 的 boxRadius,值 = 8 / ZOOM 页面像素),四角与画布
       完全重合,既不露画布底色、画布自己的圆角也保留。 */
    border-radius: 8px;
    box-shadow: inset 0 0 0 1px #e5e7eb, 0 1px 4px rgba(0, 0, 0, 0.12);
}
.dsn-page {
    position: absolute;
    left: 0;
    top: 0;
    transform-origin: top left;
    /* page 始终透明:白底与点阵由 stage 提供,page 只承载缩放后的组件内容。 */
    background-color: transparent;
}

/* —— 区块 —— */
.dsn-block {
    position: absolute;
    cursor: move;
    user-select: none;
    -webkit-user-select: none;
}
/* 多层容器铺满整块画布、位置尺寸由画布决定,拖不动,指针不给"移动"光标。 */
.dsn-block.t-layerBox { cursor: default; }
/* 0.3.34 租户锁定区块(页头/页脚/导航):置灰提示只读,拖拽/缩放/删除在 JS 侧拦截。 */
.dsn-block.is-locked { cursor: default; opacity: 0.55; }
/* 页面模式摊平画出的层内子组件:只是"预览"这一层的内容,不可选中/拖动 ——
   指针穿透到下层的多层容器上,进层编辑才能改它们。 */
.dsn-block.is-layerfloat { pointer-events: none; }
.dsn-block.is-selected {
    outline: 2px dashed #2f6df6;
    outline-offset: -2px;
    border-radius: 4px;
}
/* 8 向缩放把手 */
.dsn-h {
    position: absolute;
    width: 9px;
    height: 9px;
    background: #fff;
    border: 1.5px solid #2f6df6;
    border-radius: 2px;
    z-index: 20;
}
.dsn-h-nw { left: -5px; top: -5px; cursor: nwse-resize; }
.dsn-h-n  { left: calc(50% - 5px); top: -5px; cursor: ns-resize; }
.dsn-h-ne { right: -5px; top: -5px; cursor: nesw-resize; }
.dsn-h-e  { right: -5px; top: calc(50% - 5px); cursor: ew-resize; }
.dsn-h-se { right: -5px; bottom: -5px; cursor: nwse-resize; }
.dsn-h-s  { left: calc(50% - 5px); bottom: -5px; cursor: ns-resize; }
.dsn-h-sw { left: -5px; bottom: -5px; cursor: nesw-resize; }
.dsn-h-w  { left: -5px; top: calc(50% - 5px); cursor: ew-resize; }

/* 网格:格线 + 子区块 */
.dsn-gridline { position: absolute; background: rgba(47, 109, 246, 0.25); pointer-events: none; }
.dsn-gridline.is-v { left: 0; right: 0; height: 1px; }
.dsn-gridline.is-h { top: 0; bottom: 0; width: 1px; }
.dsn-grid-child { position: absolute; }

/* —— 右侧栏:288px。本身透明无边框,只负责竖向排两张卡、卡间留 5px —— */
.dsn-side {
    width: 288px;
    flex: none;
    display: flex;
    flex-direction: column;
    /* 组件库与上面的属性面板之间固定 5px:侧栏透明,这段间距直接露出页面灰底,
       所以能看见;同时组件库成为独立圆角卡,四个角(含上边两个)都圆。 */
    gap: 5px;
    padding: 0;
    min-height: 0;
    box-sizing: border-box;
    overflow: hidden;
}
.dsn-body.is-folded .dsn-side { display: none; }
.dsn-card {
    background: #fff;
    border: 1px solid #e5e7eb;
    border-radius: 8px;
    padding: 12px;
    display: flex;
    flex-direction: column;
    min-height: 0;
    overflow: hidden;
    box-sizing: border-box;
}
/* 属性卡:高度自适应内容 —— 内容少就矮,不撑满;内容多到超出可用高度才收缩,
   由 .dsn-card-body 在内部纵向滚动(flex-shrink 1 保证它先于组件库被压缩) */
.dsn-card-props { flex: 0 1 auto; }
/* 组件库:取内容自身高度、永不压缩(flex: none 既不伸也不缩),保证整个组件库
   始终完整显示在浏览器里,不会被上面的属性面板挤掉或裁掉 */
.dsn-card-palette { flex: none; }
.dsn-card-head {
    display: flex;
    align-items: center;
    gap: 6px;
    padding-bottom: 8px;
    flex: none;
}
.dsn-card-title { font-size: 14px; font-weight: 500; white-space: nowrap; }
.dsn-head-hint { margin-left: auto; color: #9ca3af; font-size: 12px; font-weight: 400; white-space: nowrap; }
.dsn-hint { flex: 1; min-width: 0; text-align: right; color: #059669; font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dsn-card-body {
    flex: 1;
    /* 横向永不滚动(拖入组件后面板曾出现横向滚动条);纵向溢出时才滚动。 */
    overflow-x: hidden;
    overflow-y: auto;
    min-height: 0;
}
/* 属性卡滚动区(0.3.47):不再显示滚动条 —— 内容超出时直接用滚轮/触摸板滑动即可,
   滚动条(含 scrollbar-gutter 预留槽位)会把树形菜单编辑器的行挤窄、裁掉行尾按钮。
   Chrome/Safari 用 ::-webkit-scrollbar 归零,Firefox 用 scrollbar-width: none。 */
.dsn-card-props .dsn-card-body { scrollbar-width: none; }
.dsn-card-props .dsn-card-body::-webkit-scrollbar { width: 0; height: 0; display: none; }

/* 头部图标按钮:预览 / 保存(主色)/ 撤销 / 退出(红)。
   一律等宽(26px + flex:none),按钮间距拉开到 8px。 */
.dsn-head-btns { display: flex; align-items: center; gap: 8px; flex: none; }
.dsn-iconbtn {
    position: relative;
    flex: none;
    width: 26px;
    height: 26px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1px solid #e5e7eb;
    border-radius: 4px;
    background: #fff;
    color: #6b7280;
    cursor: pointer;
    padding: 0;
}
.dsn-iconbtn svg { width: 14px; height: 14px; }
.dsn-iconbtn:hover { border-color: #2f6df6; color: #2f6df6; }
.dsn-iconbtn.is-primary { background: #2f6df6; border-color: #2f6df6; color: #fff; }
.dsn-iconbtn.is-primary:hover { background: #255ed6; color: #fff; }
.dsn-iconbtn.is-danger { background: #f56c6c; border-color: #f56c6c; color: #fff; }
.dsn-iconbtn.is-danger:hover { background: #e55555; color: #fff; }
.dsn-iconbtn:disabled { opacity: 0.45; cursor: default; }
/* 退出按钮上的层编辑深度徽标:进一层=1、嵌套子层=2,弹一层减 1,栈空不显示。 */
.dsn-badge {
    position: absolute;
    top: -7px;
    right: -7px;
    min-width: 14px;
    height: 14px;
    padding: 0 3px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 7px;
    background: #f59e0b;
    color: #fff;
    font-size: 10px;
    font-style: normal;
    line-height: 1;
    pointer-events: none;
}

/* 折叠按钮:贴在属性面板(侧栏)左缘线上的箭头,固定视口垂直居中。
   侧栏左缘线 = body 右内缘 5px + 侧栏宽 288px = 293px;按钮 18px 宽、右缘落在
   线上(right = 293 - 18 = 275),尾巴(左缘)贴线、箭头在面板一侧;收起后侧栏
   隐藏,箭头改贴窗口右缘。 */
.dsn-fold {
    position: absolute;
    right: 275px;
    top: 50%;
    transform: translateY(-50%);
    width: 18px;
    height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: flex-start;
    background: transparent;
    border: none;
    color: #6b7280;
    cursor: pointer;
    z-index: 30;
    font-size: 20px;
    line-height: 1;
    padding: 0;
}
.dsn-fold:hover { color: #2f6df6; }
.dsn-body.is-folded .dsn-fold { right: 0; }

/* —— 属性分组:手风琴折叠(与源项目 el-collapse 同款,› 展开时转 90°) —— */
.dsn-group { border-top: 1px solid #ebeef5; }
/* 每个分组只有 border-top,最后一组下方本就无线;补一条收口横线。
   用 :last-of-type 而非 :last-child —— 分组(section)后面还跟着删除按钮/页脚,
   最后一个 section 不是容器的最后一个子元素。 */
.dsn-group:last-of-type { border-bottom: 1px solid #ebeef5; }
.dsn-group h4 {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin: 0;
    padding: 8px 2px;
    font-size: 13px;
    font-weight: 500;
    color: #374151;
    cursor: pointer;
    user-select: none;
}
.dsn-group h4:hover { color: #2f6df6; }
.dsn-chev { color: #9ca3af; font-size: 12px; transition: transform 0.15s; }
.dsn-group.is-open .dsn-chev { transform: rotate(90deg); }
.dsn-group-body { display: none; padding: 2px 2px 10px; }
.dsn-group.is-open .dsn-group-body { display: block; }

/* 页面属性行 / 提示 / 引导框 / 统计 / 删除按钮 / 多选对齐框 */
.dsn-prow { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: 8px; font-size: 13px; color: #4b5563; }
.dsn-prow-ctl { display: flex; align-items: center; gap: 4px; flex: none; }
.dsn-prow-ctl .dsn-num { width: 96px; flex: none; }
.dsn-prow-ctl em { font-style: normal; color: #9ca3af; font-size: 12px; }
.dsn-tip { margin-top: 16px; border-top: 1px solid #e5e7eb; padding-top: 12px; font-size: 12px; color: #9ca3af; }
.dsn-count { margin-top: 16px; border-top: 1px solid #e5e7eb; padding-top: 12px; font-size: 12px; color: #6b7280; }
.dsn-danger {
    margin-top: 16px;
    border: none;
    border-radius: 4px;
    background: #f56c6c;
    color: #fff;
    font-size: 12px;
    padding: 6px 12px;
    cursor: pointer;
}
.dsn-danger:hover { background: #e55555; }
.dsn-alignbox { margin-top: 12px; border: 1px solid #e5e7eb; border-radius: 6px; padding: 8px; }
.dsn-row {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-bottom: 5px;
    min-width: 0;
}
.dsn-row > span {
    flex: none;
    width: 76px;
    color: #4b5563;
    font-size: 12px;
}
.dsn-row-top { align-items: flex-start; }
.dsn-check { display: flex; align-items: center; gap: 5px; margin: 2px 0; }
.dsn-check span { font-size: 12px; color: #374151; }
.dsn-in {
    flex: 1;
    min-width: 0;
    border: 1px solid #d1d5db;
    border-radius: 4px;
    padding: 4px 6px;
    font-size: 12px;
    font-family: inherit;
    color: inherit;
    background: #fff;
}
.dsn-in:focus { outline: none; border-color: #2f6df6; }
.dsn-ta { resize: vertical; }
.dsn-num { flex: 1; }
.dsn-pick { width: 28px; height: 26px; padding: 0; border: 1px solid #d1d5db; border-radius: 4px; background: #fff; flex: none; }
.dsn-btnrow { display: flex; flex-wrap: wrap; gap: 4px; margin: 4px 0; }
.dsn-pad { padding: 2px 4px; margin: 6px 0; }
.dsn-muted { color: #9ca3af; font-size: 12px; }

/* 列表编辑器(链接/图片/案例/层) */
.dsn-editor { border-top: 1px dashed #eef0f3; padding: 6px 0 2px; margin-top: 2px; }
.dsn-editor > b { display: block; font-size: 12px; color: #4b5563; margin-bottom: 4px; }
.dsn-editor-nested { border: 1px solid #eef0f3; border-radius: 4px; padding: 4px; margin-bottom: 4px; }
.dsn-list-row {
    display: flex;
    align-items: center;
    gap: 4px;
    margin-bottom: 4px;
}
.dsn-list-row-slim { padding: 2px 4px; border-radius: 4px; }
.dsn-list-row-slim.is-active { background: #eef4ff; }
.dsn-x {
    flex: none;
    width: 22px;
    height: 22px;
    border: none;
    border-radius: 4px;
    background: transparent;
    color: #9ca3af;
    cursor: pointer;
}
.dsn-x:hover { background: #fee2e2; color: #dc2626; }

/* 菜单编辑器(0.3.37):行与标题按 dsn-list-row 同口径 flex 排布,
   行尾符号链接 ↑/↓ 调序、✕ 删除、+ 添加(一级项加子菜单/标题后加一级项)。 */
.dsn-menu-head { display: flex; align-items: center; gap: 6px; margin-bottom: 4px; }
.dsn-menu-head > b { margin-bottom: 0; }
.dsn-menu-row { display: flex; align-items: center; gap: 4px; margin-bottom: 4px; }
.dsn-menu-sym {
    flex: none;
    width: 20px;
    height: 20px;
    padding: 0;
    border: none;
    border-radius: 4px;
    background: transparent;
    color: #9ca3af;
    font-size: 13px;
    line-height: 20px;
    cursor: pointer;
}
.dsn-menu-sym:hover { background: #eef4ff; color: #2f6df6; }
.dsn-menu-sym.is-danger:hover { background: #fee2e2; color: #dc2626; }

/* ===== 0.3.46 菜单树编辑器 =====
   一级项 = 圆角卡片(节点),子项缩进进左侧竖线连接区;拖动时半透明 +
   前/后插入蓝线、并入父项时子级区高亮。配色沿用全站品牌蓝 #2f6df6。 */
.dsn-mtree-node { border: 1px solid #e5e7eb; border-radius: 10px; margin-bottom: 6px; background: #fff; overflow: hidden; transition: border-color .15s; }
.dsn-mtree-node:hover { border-color: #c7d7ff; }
/* 0.3.47 紧凑排版:侧栏只有 288px,行内间距与输入框固定宽收紧、全部可收缩,
   保证「↙ + ✕」行尾按钮在任何 slug 长度下都完整可见(此前被裁掉)。 */
.dsn-mtree-row { display: flex; align-items: center; gap: 4px; padding: 5px 6px; min-width: 0; }
.dsn-mtree-row[draggable="true"] { cursor: grab; }
.dsn-mtree-row[draggable="true"]:hover { background: #f6f9ff; }
.dsn-mtree-grip { display: inline-flex; color: #c3cad6; flex: 0 0 auto; }
.dsn-mtree-grip:hover { color: #2f6df6; }
.dsn-mtree-name { flex: 1 1 52px; min-width: 40px; }
.dsn-mtree-url { flex: 0 0 auto; color: #9ca3af; font-size: 12px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.dsn-mtree-path { flex: 1 1 76px; min-width: 52px; width: auto; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; }
.dsn-mtree-href { flex: 1 1 84px; min-width: 56px; width: auto; font-size: 12px; }
.dsn-mtree-row .dsn-page-edit { margin-left: 0; flex: 0 0 auto; }
.dsn-mtree-chip { flex: 0 0 auto; padding: 1px 8px; border-radius: 999px; font-size: 11px; color: #2f6df6; background: #eef4ff; }
.dsn-mtree-kids { display: flex; flex-direction: column; gap: 4px; margin: 0 5px 5px 14px; padding: 3px 4px 3px 10px; border-left: 2px solid #e8eeff; border-radius: 0 0 0 8px; }
.dsn-mtree-row.is-kid { background: #f8faff; border-radius: 8px; padding: 3px 4px; }
.dsn-mtree-row.is-kid:hover { background: #eef4ff; }
.dsn-mtree-row.is-dragging { opacity: .4; }
.dsn-mtree-row.is-drop-before { box-shadow: inset 0 2px 0 0 #2f6df6; }
.dsn-mtree-row.is-drop-after { box-shadow: inset 0 -2px 0 0 #2f6df6; }
.dsn-mtree-kids.is-drop-into { background: #eef4ff; border-left-color: #2f6df6; }

/* 层管理行:序号在最前(#001 起,等宽数字;当前显示层为红色),层名称输入框
   **固定宽度**(104px,比最初的 96px 稍长):用 flex 自适应时,滚动条出现与否会
   吃掉/腾出 6px,输入框会跟着变长变短;写死宽度后长度恒定。
   整行从左往右顺排、件件固定宽(见 .dsn-layer-acts 注释),滚动条出现/消失时
   输入框与图标按钮的位置都不变。 */
.dsn-layer-no {
    flex: none;
    width: 30px;
    font-size: 12px;
    color: #9ca3af;
    font-variant-numeric: tabular-nums;
}
.dsn-layer-no.is-shown { color: #dc2626; }
.dsn-layer-name { flex: none; width: 104px; }
/* 图标组**不贴右**(不再用 margin-left:auto):贴右时它的右缘锚在行内容右缘上,
   滚动条一出现可用宽度变窄,输入框后面的整组按钮就跟着左右跳。
   改成从左往右顺排:序号 30 + 间隙 + 输入框 104 + 间隙 + 图标组 97,全部固定宽,
   左缘只锚在行的左内缘(竖直滚动条永远不动左缘),多出的空隙留在行尾右侧。 */
.dsn-layer-acts { display: inline-flex; align-items: center; gap: 3px; }
.dsn-icobtn {
    flex: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    padding: 0;
    border: none;
    border-radius: 4px;
    background: transparent;
    color: #9ca3af;
    cursor: pointer;
}
.dsn-icobtn:hover:not(:disabled) { background: #eef4ff; color: #2f6df6; }
.dsn-icobtn.is-on { color: #2f6df6; }
.dsn-icobtn svg { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
/* 添加层(跟在每行「✕」后面):每行都放一个,这样各行的图标竖排能对齐;但层只能
   往后追加,所以只有**最后一行**那个可点,其余置灰禁用。必须放在 .dsn-icobtn
   之后 —— 它要把 .dsn-icobtn 的 border:none 盖回虚线。 */
.dsn-addlayer { border: 1px dashed #d1d5db; font-size: 14px; line-height: 1; }
.dsn-addlayer:hover:not(:disabled) { border-color: #2f6df6; }
.dsn-addlayer:disabled { border-color: #eceef1; color: #d1d5db; cursor: default; }

/* —— 组件库:紧凑虚线小标签(与源项目同款,拖出即新增) —— */
.dsn-card-palette .dsn-card-body {
    display: flex;
    flex-wrap: wrap;
    align-content: flex-start;
    gap: 4px;
    overflow-x: hidden;
    overflow-y: auto;
}
.dsn-chip {
    cursor: grab;
    user-select: none;
    border: 1px dashed #d1d5db;
    border-radius: 4px;
    background: rgba(243, 244, 246, 0.4);
    padding: 2px 6px;
    font-size: 12px;
    color: #6b7280;
}
.dsn-chip:hover { border-color: #2f6df6; color: #2f6df6; }

/* —— 右键菜单 —— */
.dsn-menu {
    position: fixed;
    display: none;
    min-width: 128px;
    background: #fff;
    border: 1px solid #e5e7eb;
    border-radius: 6px;
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.14);
    padding: 4px;
    z-index: 999;
}
.dsn-menu button {
    display: block;
    width: 100%;
    text-align: left;
    border: none;
    background: transparent;
    padding: 5px 10px;
    border-radius: 4px;
    font: inherit;
    font-size: 12px;
    cursor: pointer;
}
.dsn-menu button:hover { background: #f3f4f6; }
.dsn-menu button.is-danger { color: #dc2626; }

/* —— 编辑模式提示条(仅弹窗编辑)/ 弹窗模拟 —— */
.dsn-editbar {
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 5px 8px;
    background: #1d4ed8;
    color: #fff;
    font-size: 12px;
    z-index: 950;
}
.dsn-editbar button {
    border: none;
    border-radius: 4px;
    background: #fff;
    color: #1d4ed8;
    padding: 3px 10px;
    font: inherit;
    font-size: 12px;
    cursor: pointer;
    flex: none;
}
.dsn-overlay { position: absolute; inset: 0; background: rgba(15, 23, 42, 0.42); z-index: 800; }
.dsn-dialog {
    position: absolute;
    z-index: 900;
    background: #fff;
    border-radius: 8px;
    box-shadow: 0 8px 30px rgba(0, 0, 0, 0.35);
}
.dsn-dialog > .dsn-block { cursor: move; }

/* 层页签(多层容器):容器铺满整块画布,页签改到容器内部左上角,不再顶到画布外 */
.dsn-tabs { position: absolute; left: 6px; top: 6px; display: flex; gap: 4px; z-index: 30; }
.dsn-tab {
    border: 1px solid #d1d5db;
    border-radius: 4px;
    background: #fff;
    color: #4b5563;
    font-size: 11px;
    padding: 1px 6px;
    cursor: pointer;
    white-space: nowrap;
}
.dsn-tab.is-active { border-color: #2f6df6; color: #2f6df6; background: #eef4ff; }

/* ================================================================= 组件形似渲染 */

.pk-logo { display: flex; align-items: center; gap: 8px; width: 100%; height: 100%; overflow: hidden; }
.pk-nav {
    display: flex;
    align-items: center;
    gap: 18px;
    width: 100%;
    height: 100%;
    padding: 0 14px;
    overflow: visible;
}
/* color: inherit —— 显式 author color,覆盖浏览器 UA :visited 灰色(点击后变灰)。 */
.pk-nav-link { text-decoration: none; cursor: pointer; white-space: nowrap; color: inherit; }
/* 导航下拉:有二级项的一级项包进 pk-nav-item,二级默认隐藏、悬停展开
   (画布只渲染一级 + ▾ 角标,与浏览器静止态一致);外框 z-index 由渲染端抬升,
   保证下拉盖住后续区块。 */
.pk-nav-item { position: relative; display: inline-flex; align-items: center; }
.pk-nav-caret { font-style: normal; font-size: .75em; margin-left: 4px; opacity: .85; }
.pk-nav-drop {
    display: none;
    position: absolute;
    top: 100%;
    left: 0;
    z-index: 10;
    flex-direction: column;
    min-width: 100%;
    padding: 6px 0;
    background: #ffffff;
    border: 1px solid #e5e7eb;
    border-radius: 6px;
    box-shadow: 0 8px 20px rgba(0, 0, 0, .12);
    white-space: nowrap;
}
.pk-nav-item:hover .pk-nav-drop { display: flex; }
.pk-nav-drop-link { display: block; padding: 6px 16px; font-size: 14px; line-height: 1.4; color: #111827; text-decoration: none; }
.pk-nav-drop-link:hover { background: #eef4ff; color: #2f6df6; }
.pk-carousel { position: relative; width: 100%; height: 100%; overflow: hidden; background: #f9fafb; }
/* 轮播切片:全部叠进容器,首张作流内底图(动画被禁用时兜底可见),其余按动画显隐。 */
.pk-carousel .pk-slide { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; opacity: 0; }
.pk-carousel .pk-slide:first-of-type { position: static; opacity: 1; }
.pk-dots { position: absolute; left: 0; right: 0; bottom: 8px; display: flex; justify-content: center; gap: 5px; }
.pk-dots i { width: 7px; height: 7px; border-radius: 50%; background: rgba(255, 255, 255, 0.85); }
.pk-dots i:first-child { background: #2f6df6; }
/* 设计视图轮播角标:提示画布是静态首帧,真实轮播在预览/门户里播放。 */
.dsn-carousel-badge {
    position: absolute; top: 8px; right: 8px; padding: 2px 8px; border-radius: 10px;
    background: rgba(47, 109, 246, 0.85); color: #fff; font-size: 11px; line-height: 1.4;
    pointer-events: none;
}
.pk-block { padding: 12px; width: 100%; height: 100%; overflow: hidden; box-sizing: border-box; }
.pk-block strong { display: block; font-size: 16px; margin-bottom: 6px; }
.pk-block p { margin: 0; line-height: 1.6; white-space: pre-wrap; word-break: break-word; }
/* 案例区数字与字母优先等宽:西文先走等宽字体栈(型号、日期逐字对齐),
   中文不在等宽字体内自然回退后文黑体,Safari/Chrome 行为一致。 */
.pk-case-line, .pk-case-table, .pk-pager {
    font-family: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, "Courier New", monospace, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
}
/* 案例行:脱敏姓名+型号 / 故障现象 / 结果+日期(常规字重,不粗黑);
   一行放不下时仅故障段省略号截断,hover 由原生 title 出全文 tooltip。
   行高 20 + 间距 4 = 行步长 24,与门户/画布的行数测量公式严格同口径。 */
.pk-case-line { display: flex; gap: 8px; align-items: baseline; white-space: nowrap; font-size: 13px; line-height: 20px; margin-bottom: 4px; }
/* 纵向对齐:姓名列/结果列固定列宽(按面板宽度百分比,任意面板尺寸都成立),
   故障列占余,各行三段起点一致;超长省略号截断,悬停 title 出全文。 */
.pk-case-pre { flex: 0 0 40%; overflow: hidden; text-overflow: ellipsis; }
.pk-case-fault { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.pk-case-post { flex: 0 0 33%; }
/* 面板底部「更多案例」小链接:右对齐;高 16 + 上间距 10 = 链接行 26(测量口径)。 */
.pk-case-more { display: block; text-align: right; font-size: 12px; line-height: 16px; margin-top: 10px; color: #2f6df6; text-decoration: none; }
.pk-case-more:hover { text-decoration: underline; }
/* 更多案例页:轮播以下表格卡(绝对定位撑满剩余高度,不超首页高度、无纵向滚动)。 */
.pk-case-page { display: flex; flex-direction: column; box-sizing: border-box; background: #fff; border: 1px solid #e5e7eb; border-radius: 8px; padding: 12px; overflow: hidden; }
.pk-case-table { width: 100%; table-layout: fixed; border-collapse: collapse; font-size: 13px; color: #374151; }
.pk-case-table th, .pk-case-table td { padding: 0 8px; text-align: left; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pk-case-table th { height: 36px; background: #f9fafb; color: #6b7280; font-weight: 600; border-bottom: 1px solid #e5e7eb; }
.pk-case-table td { height: 30px; border-bottom: 1px solid #f3f4f6; }
.pk-case-table tbody tr:last-child td { border-bottom: none; }
.pk-case-table th:nth-child(1) { width: 6%; }
.pk-case-table th:nth-child(2) { width: 14%; }
.pk-case-table th:nth-child(3) { width: 26%; }
.pk-case-table th:nth-child(4) { width: 24%; }
.pk-case-table th:nth-child(5) { width: 12%; }
/* 分页器:贴表格卡底部右侧(按钮右下口径)。 */
.pk-pager { margin-top: auto; display: flex; align-items: center; justify-content: flex-end; gap: 4px; padding-top: 8px; font-size: 13px; }
.pk-pager-total { color: #6b7280; margin-right: 8px; }
.pk-pager-btn { display: inline-flex; align-items: center; justify-content: center; min-width: 26px; height: 26px; padding: 0 6px; border: 1px solid #d1d5db; border-radius: 4px; background: #fff; color: #374151; text-decoration: none; }
.pk-pager-btn:hover { border-color: #2f6df6; color: #2f6df6; }
.pk-pager-btn.is-current { background: #2f6df6; border-color: #2f6df6; color: #fff; }
.pk-pager-btn.is-disabled { color: #c0c7d1; pointer-events: none; }
.pk-pager-btn.is-gap { border: none; background: none; color: #9ca3af; pointer-events: none; }
.pk-lang { display: flex; align-items: center; justify-content: center; gap: 10px; width: 100%; height: 100%; }
.pk-lang span { cursor: pointer; padding: 2px 8px; border-radius: 4px; color: #6b7280; }
.pk-lang .is-on { color: #2f6df6; font-weight: 600; }
.pk-hotline { display: flex; align-items: center; width: 100%; height: 100%; padding: 0 10px; box-sizing: border-box; overflow: hidden; white-space: nowrap; }
.pk-upload {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 6px;
    width: 100%;
    height: 100%;
    border: 1.5px dashed #d1d5db;
    border-radius: 8px;
    box-sizing: border-box;
    color: #6b7280;
}
.pk-upload b { font-size: 22px; }
.pk-tag {
    display: inline-flex;
    align-items: center;
    max-width: 100%;
    height: 100%;
    padding: 2px 10px;
    box-sizing: border-box;
    overflow: hidden;
    white-space: nowrap;
}
.pk-caselist { width: 100%; height: 100%; padding: 10px; box-sizing: border-box; overflow: hidden; border: 1px solid #d1d5db; border-radius: 8px; }
.pk-caselist strong { display: block; margin-bottom: 6px; }
.pk-case-item { display: flex; gap: 8px; align-items: baseline; margin-bottom: 4px; }
.pk-case-item b { flex: none; max-width: 40%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pk-case-item span { color: #6b7280; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 联系我们:联系信息 + 百度地图 + 交通算路(画布静态与门户实时共用同一套 class) */
.pk-contactmap { position: relative; display: flex; flex-direction: column; width: 100%; height: 100%; padding: 16px; box-sizing: border-box; overflow: hidden; }
.pk-cmap-head { flex: none; margin-bottom: 12px; }
.pk-cmap-head b { display: block; font-size: 22px; line-height: 1.3; color: #111827; }
.pk-cmap-head span { display: block; margin-top: 4px; font-size: 14px; line-height: 1.6; color: #6b7280; }
.pk-cmap-body { flex: 1; display: flex; gap: 16px; min-height: 0; }
.pk-cmap-info { flex: none; width: 260px; padding: 14px 16px; box-sizing: border-box; background: #f8fafc; border: 1px solid #e5e7eb; border-radius: 6px; overflow: auto; }
.pk-cmap-info-row { display: flex; flex-direction: column; gap: 2px; padding: 8px 0; border-bottom: 1px dashed #e5e7eb; }
.pk-cmap-info-row:last-child { border-bottom: 0; }
.pk-cmap-info-row span { font-size: 12px; color: #9ca3af; }
.pk-cmap-info-row b { font-size: 14px; line-height: 1.6; color: #111827; font-weight: 600; word-break: break-all; }
.pk-cmap-empty { padding: 14px 16px; font-size: 13px; line-height: 1.6; color: #9ca3af; background: #f8fafc; border: 1px dashed #e5e7eb; border-radius: 6px; }
.pk-cmap-main { flex: 1; display: flex; flex-direction: column; min-width: 0; }
.pk-cmap-canvas { position: relative; flex: 1; min-height: 200px; width: 100%; background: #eef2f7; border-radius: 6px; overflow: hidden; }
.pk-cmap-loading, .pk-cmap-fallback { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; gap: 8px; padding: 12px; box-sizing: border-box; text-align: center; font-size: 14px; line-height: 1.6; color: #6b7280; }
.pk-cmap-pin { display: inline-block; width: 10px; height: 10px; border-radius: 50%; background: #2f6df6; }
.pk-cmap-tabs { flex: none; display: flex; align-items: center; gap: 8px; margin-top: 10px; }
.pk-cmap-tabs button { padding: 4px 14px; border: 1px solid #d1d5db; border-radius: 999px; background: #ffffff; color: #374151; font-size: 13px; line-height: 1.4; cursor: pointer; }
.pk-cmap-tabs button.is-on { color: #2f6df6; border-color: #2f6df6; background: #eef4ff; font-weight: 600; }
.pk-cmap-route { flex: none; display: flex; align-items: center; gap: 8px; margin-top: 10px; }
.pk-cmap-input, .pk-cmap-start { flex: 1; min-width: 0; height: 36px; padding: 0 10px; box-sizing: border-box; border: 1px solid #d1d5db; border-radius: 6px; background: #ffffff; color: #374151; font-size: 14px; line-height: 34px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.pk-cmap-input:focus { outline: none; border-color: #2f6df6; }
.pk-cmap-start { display: flex; align-items: center; color: #9ca3af; cursor: default; }
.pk-cmap-go { flex: none; height: 36px; padding: 0 18px; border: 0; border-radius: 6px; background: #2f6df6; color: #ffffff; font-size: 14px; cursor: pointer; }
.pk-cmap-result { flex: 1; min-height: 0; margin-top: 8px; overflow: auto; font-size: 13px; line-height: 1.7; color: #374151; }
.pk-cmap-result p { margin: 0 0 4px; }
.pk-cmap-result .pk-cmap-sum { font-weight: 600; color: #111827; margin-bottom: 6px; }
.pk-cmap-result .pk-cmap-err { color: #b91c1c; }
.pk-layerbox { position: relative; width: 100%; height: 100%; overflow: visible; box-sizing: border-box; }
.pk-layerbadge { display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; color: #c0c7d1; }
.pk-button {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    box-sizing: border-box;
    overflow: hidden;
    white-space: nowrap;
    cursor: pointer;
}
.pk-window {
    position: relative;
    width: 100%;
    height: 100%;
    box-sizing: border-box;
    border: 1px solid #e5e7eb;
    overflow: hidden;
}
.pk-window-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 6px 10px;
    background: #f3f4f6;
    border-bottom: 1px solid #e5e7eb;
    font-weight: 600;
}
.pk-window-bar i { font-style: normal; color: #9ca3af; }
.pk-window-body { position: relative; width: 100%; height: calc(100% - 31px); }
.pk-window .pk-window-body:only-child { height: 100%; }
.pk-textblock { position: relative; width: 100%; height: 100%; overflow: hidden; box-sizing: border-box; line-height: 1.6; }
.pk-textblock::after { content: ''; display: block; clear: both; }
.pk-field { display: flex; align-items: center; gap: 8px; width: 100%; height: 100%; padding: 4px 8px; box-sizing: border-box; }
.pk-field label { flex: none; color: #374151; white-space: nowrap; }
.pk-field-top { flex-direction: column; align-items: stretch; justify-content: flex-start; }
.pk-select, .pk-input {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: space-between;
    min-height: 28px;
    border: 1px solid #d1d5db;
    border-radius: 4px;
    padding: 2px 8px;
    background: #fff;
    color: #6b7280;
    overflow: hidden;
}
.pk-textarea { min-height: 100%; height: 100%; align-items: flex-start; }
.pk-opts { display: none; }
.pk-form { width: 100%; height: 100%; padding: 8px; box-sizing: border-box; overflow: hidden; }
.pk-form-row { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
.pk-form-row label { flex: none; width: 30%; color: #374151; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pk-form-row .pk-input { flex: 1; }
.pk-fieldset { width: 100%; height: 100%; border: 1px solid #d1d5db; border-radius: 6px; box-sizing: border-box; margin: 0; padding: 8px; }
.pk-fieldset legend { padding: 0 6px; color: #374151; }
.pk-choice-row { display: flex; align-items: center; gap: 12px; width: 100%; height: 100%; padding: 4px 8px; box-sizing: border-box; overflow: hidden; }
.pk-choice-row label { flex: none; color: #374151; }
.pk-choice-row.is-col { flex-direction: column; align-items: flex-start; justify-content: flex-start; }
.pk-choice { white-space: nowrap; }
.pk-banner { width: 100%; height: 100%; overflow: hidden; }
.pk-copyright { display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; color: #6b7280; }
.pk-card {
    position: relative;
    width: 100%;
    height: 100%;
    padding: 12px;
    box-sizing: border-box;
    border: 1px solid #d1d5db;
    border-radius: 8px;
    overflow: hidden;
    line-height: 1.6;
}
/* 导航菜单项之间的「|」分隔线(画布与门户渲染同口径)。 */
.pk-nav-sep { opacity: .35; margin: 0 2px; user-select: none; }
.pk-card strong { display: block; margin-bottom: 4px; }
.pk-card p { margin: 0; color: #4b5563; white-space: pre-wrap; word-break: break-word; }
.pk-card-btn {
    position: absolute;
    right: 10px;
    bottom: 10px;
    display: inline-flex;
    align-items: center;
    gap: 5px;
    border: none;
    border-radius: 6px;
    padding: 4px 12px;
    cursor: pointer;
    font: inherit;
}
/* 按钮内嵌 SVG 图标:固定 14px 不随文字换行挤压。 */
.pk-btn-ico { flex: none; display: block; }
/* 三大业务组件卡片左上角标图像:float 让标题/正文环绕,大小位置由面板内联样式控制。 */
.pk-card-icon { float: left; border-radius: 6px; object-fit: cover; }
.pk-container {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    border: 1px solid #d1d5db;
    border-radius: 6px;
    background: #fff;
    color: #9ca3af;
    box-sizing: border-box;
}
.pk-grid { position: relative; width: 100%; height: 100%; }
.pk-unknown { display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; color: #c0c7d1; }
.dsn-img { display: block; }
.dsn-img-empty {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: #eef0f3;
    color: #9ca3af;
    border-radius: 4px;
}

/* 图文控件「绑定控件」:属性行触发按钮 + 横向三列级联浮层 */
.dsn-bind-btn {
    text-align: left;
    cursor: pointer;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 4px;
    color: #6b7280;
}
.dsn-bind-btn.is-on { color: #1f2937; border-color: #2f6df6; }
.dsn-bind-caret { flex: none; color: #9ca3af; font-size: 10px; }

/* 图文控件「上传图像」:加号块 / 缩略图(点击更换, 悬停右上角移除)。
   一行 3 个的紧凑网格(.dsn-row > span 的 76px 标签宽度规则会波及容器,需放开)。 */
.dsn-row > span.dsn-thumbs { flex: 1 1 auto; width: auto; min-width: 0; }
.dsn-thumbs {
    display: grid; grid-template-columns: repeat(3, 56px);
    gap: 8px; justify-content: start; align-items: start;
}
.dsn-thumb {
    position: relative; width: 56px; height: 56px; border-radius: 6px;
    border: 1px solid #e5e7eb; background: #f3f4f6; padding: 0; cursor: pointer;
    overflow: hidden; display: flex; align-items: center; justify-content: center;
    flex: none;
}
.dsn-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.dsn-thumb-add {
    border-style: dashed; color: #9ca3af; font-size: 11px; line-height: 1;
    flex-direction: column; gap: 3px;
}
.dsn-thumb-add svg { display: block; }
.dsn-thumb-add:hover { border-color: #2f6df6; color: #2f6df6; background: #f0f5ff; }
.dsn-thumb-hint { grid-column: 1 / -1; font-style: normal; font-size: 10px; color: #9ca3af; }
.dsn-thumb-del {
    position: absolute; top: 0; right: 0; width: 18px; height: 18px;
    border: none; border-radius: 0 0 0 6px; background: rgba(17,24,39,.62);
    color: #fff; font-size: 10px; line-height: 18px; padding: 0; cursor: pointer;
    opacity: 0; transition: opacity .12s;
}
.dsn-thumb:hover .dsn-thumb-del { opacity: 1; }
/* 0.3.55 诊断窗口面板:窗口序号/名称行、向导预览浮层。
   0.3.64 起窗口选项(名称与参考图)取动态字典,面板不再有档位清单,
   档位小缩略图与档位列表样式随之移除。 */
.dsn-wiz-no { flex: none; font-size: 11px; color: #2f6df6; background: #eef4ff; border-radius: 3px; padding: 0 5px; }
.dsn-wiz-name { flex: 1; min-width: 0; font-size: 12px; font-weight: 500; color: #374151; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dsn-wiz-name em { margin-left: 6px; font-style: normal; font-weight: 400; font-size: 11px; color: #9ca3af; }
.dsn-wiz-mask {
    position: fixed; inset: 0; z-index: 70; background: rgba(17, 24, 39, 0.45);
    display: flex; align-items: center; justify-content: center;
}
.dsn-wiz-box {
    width: min(760px, 92vw); max-height: 86vh; overflow: auto;
    background: #fff; border-radius: 10px; padding: 14px 16px; box-sizing: border-box;
    box-shadow: 0 12px 40px rgba(0, 0, 0, 0.25);
}
.dsn-wiz-head { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
.dsn-wiz-head b { font-size: 14px; white-space: nowrap; }
.dsn-wiz-x { margin-left: auto; border: none; background: none; color: #9ca3af; font-size: 20px; line-height: 1; padding: 0; cursor: pointer; }
.dsn-wiz-x:hover { color: #ef4444; }
.dsn-wiz-tabs { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
.dsn-wiz-tab {
    border: 1px solid #e5e7eb; border-radius: 999px; background: #fff;
    color: #4b5563; font-size: 12px; padding: 3px 10px; cursor: pointer;
}
.dsn-wiz-tab:hover { border-color: #2f6df6; color: #2f6df6; }
.dsn-wiz-tab.is-on { border-color: #2f6df6; background: #eef4ff; color: #2f6df6; }
.dsn-wiz-view { border: 1px solid #eef0f3; border-radius: 8px; padding: 10px; }
.dsn-wiz-meta { color: #9ca3af; font-size: 12px; margin-bottom: 10px; }
.dsn-bind-panel {
    position: fixed;
    z-index: 60;
    display: flex;
    background: #fff;
    border: 1px solid #d1d5db;
    border-radius: 6px;
    box-shadow: 0 8px 24px rgba(17, 24, 39, .16);
    overflow: hidden;
}
.dsn-bind-col {
    width: 168px;
    max-height: 280px;
    overflow-y: auto;
    border-right: 1px solid #eef0f3;
    scrollbar-width: thin;
}
.dsn-bind-col:last-child { border-right: none; }
.dsn-bind-col:empty { display: none; }
.dsn-bind-item {
    display: flex;
    align-items: center;
    gap: 6px;
    width: 100%;
    padding: 6px 10px;
    border: none;
    background: transparent;
    font: inherit;
    font-size: 12px;
    color: #374151;
    text-align: left;
    cursor: pointer;
}
.dsn-bind-item:hover { background: #f3f4f6; }
.dsn-bind-item.is-picked { background: #eef4ff; color: #2f6df6; }
.dsn-bind-item.is-active { color: #2f6df6; font-weight: 600; }
.dsn-bind-item > span { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dsn-bind-item > i { flex: none; font-style: normal; color: #9ca3af; }
.dsn-bind-item > em {
    flex: none;
    max-width: 72px;
    font-style: normal;
    color: #9ca3af;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ============================================================
   免费门户三大业务组件(智能诊断 / 自助登记 / 交互查询)。
   静态卡片直接复用 .pk-card 形似(标题 + 说明 + 右下角绝对定位主按钮,
   designer_canvas.js 与 portal_layout.rs 两边 HTML 逐字一致);
   后面各节是预览端运行时 portal_biz.js 的弹窗/向导/结果页样式
   (类前缀 pk-diag- / pk-reg- / pk-inq-)。
   窄屏响应式规则在 portal_layout.rs 的 PREVIEW_FIX_CSS。
   ============================================================ */

/* —— 弹窗(模态):遮罩铺满视口,同时只开一个;点遮罩不关,Esc 关。
   宽高自适应内容:JS 只把配置宽度写成 max-width 上限,高度由内容撑开;
   内边距统一 10px;min-width 340px 保证表单/栅格有稳定可用宽度(窄屏由
   PREVIEW_FIX_CSS 放开)。 —— */
.pk-dialog-mask { position: fixed; inset: 0; z-index: 100; background: rgba(17, 24, 39, .45); display: flex; align-items: center; justify-content: center; }
.pk-dialog { display: flex; flex-direction: column; width: auto; height: auto; max-width: 94vw; max-height: 92vh; background: #fff; border-radius: 6px; box-shadow: 0 12px 32px rgba(17, 24, 39, .18); overflow: hidden; }
.pk-dialog-head { display: flex; align-items: center; padding: 10px; border-bottom: 1px solid #eef0f3; flex: none; }
.pk-dialog-title { font-weight: 600; color: #1f2937; }
.pk-dialog-x { margin-left: auto; width: 26px; height: 26px; border: none; border-radius: 4px; background: transparent; color: #9ca3af; font-size: 18px; line-height: 1; cursor: pointer; padding: 0; }
.pk-dialog-x:hover { background: #f3f4f6; color: #374151; }
.pk-dialog-body { flex: 1; min-height: 0; display: flex; flex-direction: column; gap: 10px; padding: 10px; min-width: 340px; box-sizing: border-box; overflow-x: hidden; overflow-y: auto; }

/* —— 向导:步骤条 / 内容面板 / 底部按钮 —— */
.pk-wiz-steps { display: flex; align-items: center; gap: 6px; flex: none; }
.pk-wiz-step { display: inline-flex; align-items: center; gap: 5px; color: #9ca3af; font-size: 12px; white-space: nowrap; }
.pk-wiz-step b { width: 18px; height: 18px; display: inline-flex; align-items: center; justify-content: center; border-radius: 50%; background: #eef0f3; color: #6b7280; font-size: 11px; font-weight: 500; }
.pk-wiz-step.is-on { color: #2f6df6; font-weight: 500; }
.pk-wiz-step.is-on b { background: #2f6df6; color: #fff; }
.pk-wiz-step.is-done b { background: #dbe7ff; color: #2f6df6; }
.pk-wiz-sep { flex: 1; min-width: 12px; height: 1px; background: #e5e7eb; }
.pk-wiz-pane { flex: 1; min-height: 0; overflow-x: hidden; overflow-y: auto; }
.pk-wiz-hint { color: #6b7280; font-size: 12px; margin-bottom: 8px; }
.pk-wiz-loading { color: #9ca3af; font-size: 12px; padding: 18px 0; text-align: center; }
.pk-wiz-error { color: #f56c6c; font-size: 12px; padding: 6px 0; }
.pk-wiz-tip { color: #f56c6c; font-size: 12px; text-align: center; padding-top: 4px; flex: none; }
.pk-wiz-foot { display: flex; justify-content: flex-end; gap: 8px; flex: none; padding-top: 4px; }
.pk-wiz-btn { border: 1px solid #d1d5db; border-radius: 6px; background: #fff; color: #374151; padding: 6px 16px; font: inherit; font-size: 13px; cursor: pointer; }
.pk-wiz-btn:hover { border-color: #2f6df6; color: #2f6df6; }
.pk-wiz-btn.is-primary { background: #2f6df6; border-color: #2f6df6; color: #fff; }
.pk-wiz-btn.is-primary:hover { background: #255ed6; color: #fff; }
.pk-wiz-btn:disabled { opacity: .5; cursor: default; }
.pk-wiz-btn:disabled:hover { border-color: #d1d5db; color: #374151; }
.pk-wiz-btn.is-primary:disabled:hover { background: #2f6df6; border-color: #2f6df6; color: #fff; }

/* —— 诊断:选项栅格 + 翻页 + 结果页(手绘 SVG 图表) —— */
.pk-diag-grid { display: flex; flex-wrap: wrap; justify-content: center; }
/* 灰线框只包图像区(thumb),radio+标签行裸露在框外下方居中;选中高亮也画在图像框上。
   框高 110px(边框含在内,border-box),框宽可配(0=随格宽;图文并排时按框高取正方形);
   图像等比例缩放完整显示(contain,不裁剪),与框比例不符时留灰底。 */
.pk-diag-cell { cursor: pointer; }
.pk-diag-thumb { height: 110px; display: flex; align-items: center; justify-content: center; background: #f8fafc; border: 1px solid #e5e7eb; border-radius: 6px; padding: 0; box-sizing: border-box; overflow: hidden; }
.pk-diag-cell:hover .pk-diag-thumb { border-color: #2f6df6; }
.pk-diag-cell.is-on .pk-diag-thumb { border-color: #2f6df6; box-shadow: inset 0 0 0 1px #2f6df6; }
.pk-diag-thumb img { width: 100%; height: 100%; object-fit: contain; display: block; }
.pk-diag-thumb-ph { color: #c0c7d1; font-size: 22px; }
/* radio 圆点 + 标签整行居中放在图像灰框外侧下方,离框 3px;行间距 10px(栅格 gap)。 */
.pk-diag-cap { display: flex; align-items: center; justify-content: center; gap: 6px; margin-top: 3px; }
.pk-diag-dot { flex: none; width: 12px; height: 12px; border-radius: 50%; border: 2px solid #d1d5db; box-sizing: border-box; }
.pk-diag-cell.is-on .pk-diag-dot { border-color: #2f6df6; background: radial-gradient(circle, #2f6df6 0 4px, transparent 4.5px); }
.pk-diag-captext { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: #374151; font-size: 12px; }
/* 0.3.55 按窗口配置的图文位置:is-h = 图与 radio 同一行(左/右),
   is-textonly = 关掉「显示图片」后的纯文字单选卡(只有单选圈 + 文字,不留占位框)。 */
.pk-diag-cell.is-h { display: flex; align-items: center; gap: 8px; }
.pk-diag-cell.is-h .pk-diag-thumb { flex: none; }
.pk-diag-cell.is-h .pk-diag-cap { margin-top: 0; flex: 1; min-width: 0; }
.pk-diag-cell.is-left .pk-diag-cap { justify-content: flex-start; }
.pk-diag-cell.is-right .pk-diag-cap { justify-content: flex-end; }
.pk-diag-cell.is-textonly .pk-diag-cap { margin-top: 0; justify-content: flex-start; }
.pk-diag-cell.is-textonly { padding: 2px 0; }
/* 翻页箭头:绝对定位、不占文档流 —— 出现/消失不挤动图像;无背景无边框,裸的 > / < 字符。
   「>」悬在最右侧图右缘,「<」悬在最左侧图左缘(提示左侧还有图)。 */
.pk-diag-gridwrap { position: relative; margin-top: -5px; }
.pk-diag-pager { position: absolute; inset: 0; pointer-events: none; }
.pk-diag-arrow { position: absolute; top: 50%; transform: translateY(-50%); border: none; background: none; padding: 0; color: #2f6df6; font-size: 18px; line-height: 1; cursor: pointer; pointer-events: auto; }
.pk-diag-arrow:hover { color: #1a4fd6; }
.pk-diag-arrow-prev { left: 0; }
/* 「>」整体移出最右图像框右缘 2px:基准 right:0 再按自身宽度右移 + 2px 间隙,
   字形不再压在画框上;落在弹窗 body 右内边距(10px)范围内,不会被裁剪。 */
.pk-diag-arrow-next { right: 0; transform: translateY(-50%) translateX(calc(100% + 2px)); }
/* 翻页滑入:下一页整行从右侧快速滑入,上一页整行从左侧快速滑入。 */
@keyframes pk-grid-in-r { from { transform: translateX(36px); opacity: 0; } to { transform: none; opacity: 1; } }
@keyframes pk-grid-in-l { from { transform: translateX(-36px); opacity: 0; } to { transform: none; opacity: 1; } }
.pk-diag-grid.pk-in-r { animation: pk-grid-in-r .25s ease-out; }
.pk-diag-grid.pk-in-l { animation: pk-grid-in-l .25s ease-out; }
.pk-diag-sum { color: #374151; font-size: 13px; margin-bottom: 8px; }
.pk-diag-total { color: #9ca3af; font-size: 12px; margin-left: 8px; }
.pk-diag-charts { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 10px; }
.pk-diag-chart { flex: 1 1 220px; min-width: 0; border: 1px solid #eef0f3; border-radius: 6px; padding: 8px; box-sizing: border-box; }
.pk-diag-charttitle { color: #6b7280; font-size: 12px; margin-bottom: 4px; }
.pk-diag-bar { width: 100%; height: auto; display: block; }
.pk-diag-axis { stroke: #d1d5db; stroke-width: 1; }
.pk-diag-blbl { fill: #6b7280; font-size: 10px; text-anchor: middle; }
.pk-diag-bval { fill: #374151; font-size: 10px; text-anchor: middle; }
.pk-diag-pie { width: 140px; height: 140px; display: block; margin: 0 auto; }
.pk-diag-legend { display: flex; flex-wrap: wrap; gap: 4px 10px; margin-top: 6px; }
.pk-diag-lg { display: inline-flex; align-items: center; gap: 4px; font-size: 11px; color: #4b5563; }
.pk-diag-lg i { width: 8px; height: 8px; border-radius: 2px; display: inline-block; }
.pk-diag-list { border: 1px solid #eef0f3; border-radius: 6px; overflow: hidden; }
.pk-diag-lrow { display: flex; align-items: center; gap: 10px; padding: 6px 10px; font-size: 12px; border-bottom: 1px solid #f3f4f6; }
.pk-diag-lrow:last-child { border-bottom: none; }
.pk-diag-lc { flex: 1; min-width: 0; color: #374151; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pk-diag-lp { flex: none; width: 4em; text-align: right; color: #2f6df6; }
.pk-diag-la { flex: none; width: 7em; text-align: right; color: #6b7280; }
.pk-diag-rec { margin-top: 10px; }
.pk-diag-rectitle { font-size: 12px; color: #6b7280; margin-bottom: 6px; }
.pk-diag-rcrow { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 10px; padding: 6px 10px; border: 1px solid #eef0f3; border-radius: 6px; margin-bottom: 6px; font-size: 12px; }
.pk-diag-rcrow.is-top { border-color: #2f6df6; background: #f0f5ff; }
.pk-diag-rcname { flex: 1 1 120px; min-width: 0; color: #1f2937; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pk-diag-badge { font-style: normal; margin-left: 6px; padding: 1px 6px; border-radius: 7px; background: #f59e0b; color: #fff; font-size: 10px; }
.pk-diag-rcscore { flex: none; color: #4b5563; }
.pk-diag-rcsamp { flex: none; color: #9ca3af; }
/* 0.3.66 推荐商家分项名次(chips)+ 每行「我要登记」按钮。 */
.pk-diag-rcrank { flex: none; padding: 1px 6px; border-radius: 7px; background: #f3f4f6; color: #6b7280; font-size: 11px; }
.pk-diag-rcbtn { flex: none; border: 1px solid #2f6df6; border-radius: 6px; background: #fff; color: #2f6df6; padding: 2px 10px; font-size: 12px; line-height: 1.5; cursor: pointer; }
.pk-diag-rcbtn:hover { background: #2f6df6; color: #fff; }
/* 结果页成功率块:与「损坏概率/概率分布」图表并列展示,大数字 + 样本数小字;
   无样本时出一行「暂无样本」占位(样式从简)。配色沿用本区既有口径。 */
.pk-diag-rate { flex: 0 1 150px; min-width: 120px; border: 1px solid #eef0f3; border-radius: 6px; padding: 8px; box-sizing: border-box; text-align: center; }
.pk-diag-ratenum { color: #2f6df6; font-size: 26px; font-weight: 700; line-height: 1.3; }
/* 0.3.66 失败率:成功率块内第二行,红色小字(成功/失败同屏对照)。 */
.pk-diag-ratefail { color: #ef4444; font-size: 12px; margin-top: 2px; }
.pk-diag-ratesamp { color: #9ca3af; font-size: 12px; margin-top: 2px; }
.pk-diag-rate-na { font-size: 13px; font-weight: 500; color: #9ca3af; }
/* 0.3.65 零样本提示:故障现象窗口与诊断结果窗口在样本数为 0 时的一屏说明。 */
.pk-diag-empty { border: 1px dashed #e5e7eb; border-radius: 8px; background: #fafbfc; padding: 28px 16px; text-align: center; }
.pk-diag-emptytitle { color: #4b5563; font-size: 14px; font-weight: 500; line-height: 1.6; }
.pk-diag-emptysub { color: #9ca3af; font-size: 12px; margin-top: 8px; line-height: 1.6; }

/* —— 登记:标签固定 4 字宽,逐字 span + flex space-between 分散对齐;
   必填星号悬浮在标签文字左外侧(::before 绝对定位,不占宽、不挤压标签);
   表单左侧留 1em 给星号。禁用 text-align:justify(Chromium 单行中文不分配字距)。 —— */
.pk-reg-form { padding-left: 1em; }
.pk-reg-row { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
/* 登记面板(与后台恢复登记弹窗同构):两个 fieldset + 两列栅格,窄屏折成一列。 */
.pk-reg-fieldset { border: 1px solid #e5e7eb; border-radius: 8px; padding: 12px 14px 2px; margin: 0 0 12px; min-width: 0; }
.pk-reg-legend { font-size: 13px; font-weight: 600; color: #1f2937; padding: 0 6px; }
.pk-reg-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: 16px; }
.pk-reg-combo-row .pk-reg-field { position: relative; }
.pk-reg-combo-row .pk-quick-list { top: calc(100% + 2px); }
/* 行内配对(数值+单位、姓名+称谓):主输入撑满,右侧下拉固定窄宽。
   align-items: stretch 让 select 与 input 严格等高 —— select 在 Chrome/Safari
   有各自 UA 最小高度,靠 padding 对齐不可靠,交叉轴拉伸两端一致。 */
.pk-reg-pair { display: flex; gap: 6px; align-items: stretch; min-width: 0; }
.pk-reg-pair .pk-reg-input:first-child { flex: 1; min-width: 0; }
/* 单位/称谓下拉:必须叠 .pk-reg-input 提特异度,否则被后文 .pk-reg-input{width:100%}
   同特异度覆盖,下拉变全宽、主输入被挤小、必填星号叠到邻列。 */
.pk-reg-input.pk-reg-unit { flex: none; width: 76px; }
/* 分区/卷步:数量步进条 + 明细表(与后台登记同构)。 */
.pk-part-bar { display: flex; align-items: center; justify-content: space-between; border: 1px solid #e5e7eb; border-radius: 8px; padding: 8px 12px; margin-bottom: 10px; }
.pk-part-title { font-size: 13px; font-weight: 600; color: #1f2937; }
.pk-part-ctl { display: flex; align-items: center; gap: 8px; }
.pk-part-btn { width: 26px; height: 26px; border: 1px solid #d1d5db; border-radius: 6px; background: #fff; color: #374151; font-size: 15px; line-height: 1; cursor: pointer; }
.pk-part-btn:hover { border-color: #2f6df6; color: #2f6df6; }
.pk-part-num { min-width: 1.5em; text-align: center; font-size: 14px; color: #1f2937; }
.pk-part-table { width: 100%; border-collapse: collapse; font-size: 13px; table-layout: fixed; }
.pk-part-table th { text-align: left; color: #6b7280; font-weight: 500; padding: 4px 6px; border-bottom: 1px solid #e5e7eb; white-space: nowrap; }
.pk-part-table td { padding: 5px 6px; border-bottom: 1px solid #f3f4f6; }
/* 定宽列:序号列固定,大小/文件系统按比例,备注吃剩余;两表(介质/分区)共用。 */
.pk-part-table th:nth-child(1), .pk-part-table td:nth-child(1) { width: 84px; }
.pk-part-table th:nth-child(2), .pk-part-table td:nth-child(2) { width: 38%; }
.pk-part-table th:nth-child(3), .pk-part-table td:nth-child(3) { width: 24%; }
/* 分区表「大小」列(数值+单位配对)收窄约 40px,让出的宽度落到「备注」列。 */
.pk-part-table.is-part th:nth-child(2), .pk-part-table.is-part td:nth-child(2) { width: 31%; }
/* 分区步「未知」勾选:明细表下方一行,浅灰小字。 */
.pk-part-unknown { display: inline-flex; align-items: center; gap: 4px; margin-top: 8px; font-size: 12px; color: #6b7280; cursor: pointer; user-select: none; }
.pk-part-unknown input { margin: 0; accent-color: #2f6df6; }
.pk-part-table .pk-part-idx { white-space: nowrap; color: #374151; }
.pk-part-empty { color: #6b7280; font-size: 12px; padding: 6px 2px 10px; }
@media (max-width: 620px) {
    .pk-reg-grid { grid-template-columns: 1fr; }
    .pk-part-table { font-size: 12px; }
    .pk-part-table th, .pk-part-table td { padding: 4px; }
    .pk-reg-input.pk-reg-unit { width: 58px; }
}
/* 型号步「快速选择」:居中输入框 + AJAX 下拉建议(库内型号忽略大小写前缀匹配)。 */
.pk-quick { display: flex; justify-content: center; align-items: center; gap: 8px; margin-top: 10px; }
.pk-quick-label { flex: none; color: #374151; font-size: 13px; }
.pk-quick-box { position: relative; width: 260px; }
.pk-quick-input { width: 100%; box-sizing: border-box; min-height: 30px; padding: 4px 8px; border: 1px solid #d1d5db; border-radius: 6px; font-size: 13px; color: #111827; }
.pk-quick-list { position: absolute; left: 0; right: 0; top: 34px; z-index: 30; max-height: 150px; overflow: auto; background: #ffffff; border: 1px solid #d1d5db; border-radius: 6px; box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12); }
.pk-quick-item { display: block; width: 100%; padding: 6px 10px; border: 0; background: none; text-align: left; font-size: 13px; color: #111827; cursor: pointer; }
.pk-quick-item:hover { background: #eef2ff; }
.pk-reg-label { position: relative; flex: none; width: 4em; color: #374151; font-size: 13px; }
.pk-reg-row.is-req .pk-reg-label::before,
.pk-inq-row.is-req .pk-reg-label::before { content: '*'; position: absolute; left: -.75em; top: 0; color: #f56c6c; }
.pk-reg-spread { display: flex; justify-content: space-between; width: 100%; }
.pk-reg-spread i { font-style: normal; }
.pk-reg-field { flex: 1; min-width: 0; }
.pk-reg-input { width: 100%; box-sizing: border-box; border: 1px solid #d1d5db; border-radius: 4px; padding: 6px 8px; font: inherit; font-size: 13px; color: #1f2937; background: #fff; }
.pk-reg-input:focus { outline: none; border-color: #2f6df6; }

/* —— 查询:工单号/PIN 表单 + 状态/二维码/逐步办理 + 改 PIN —— */
.pk-inq-form { padding-left: 1em; }
.pk-inq-row { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
/* 查询验证码行:输入框 + 可点击刷新的 SVG 图并排。 */
.pk-cap-field { display: flex; align-items: center; gap: 8px; }
.pk-cap-input { width: 120px; flex: none; }
.pk-cap-img { flex: none; line-height: 0; border: 1px solid #d1d5db; border-radius: 4px; overflow: hidden; cursor: pointer; }
.pk-cap-img svg { display: block; height: 32px; width: auto; }
.pk-inq-result { text-align: center; }
.pk-inq-status { font-size: 14px; color: #1f2937; margin-bottom: 4px; }
.pk-inq-status b { color: #059669; }
.pk-inq-no { color: #6b7280; font-size: 12px; margin-bottom: 10px; }
.pk-inq-qr { display: flex; justify-content: center; }
.pk-inq-qr svg { width: 140px; height: 140px; }
.pk-inq-qrtip { color: #9ca3af; font-size: 12px; margin-top: 4px; }
.pk-inq-flow { margin-top: 10px; }
.pk-inq-flowlink { color: #2f6df6; font-size: 13px; text-decoration: none; }
.pk-inq-flowlink:hover { text-decoration: underline; }
.pk-inq-pin { border-top: 1px solid #eef0f3; margin-top: 12px; padding-top: 10px; text-align: left; }
.pk-inq-pintitle { font-size: 12px; color: #6b7280; margin-bottom: 6px; }
.pk-inq-pinrow { display: flex; gap: 8px; }
/* 两个 PIN 输入以 110px 为基准可伸可缩,窄屏随弹窗收窄,不撑出横向滚动。 */
.pk-inq-pinrow .pk-reg-input { flex: 1 1 110px; min-width: 0; }
.pk-inq-pinmsg { font-size: 12px; margin-top: 6px; min-height: 16px; color: #9ca3af; }
.pk-inq-pinmsg.is-err { color: #f56c6c; }
.pk-inq-pinmsg.is-ok { color: #059669; }

/* ===== 0.2.98 门户菜单页面化 ===== */
/* 页面区容器:页面模式下画布只承载页面区(置顶摆放),淡虚线上缘标识编辑区起点。 */
.dsn-pagearea {
    position: absolute;
    box-shadow: inset 0 0 0 1px rgba(47, 109, 246, 0.12);
    border-top: 1px dashed rgba(47, 109, 246, 0.35);
}
/* 页面区底边拖高把手:横条、上下拖动实时改 pages[editPage].height。 */
.dsn-pagearea-handle {
    position: absolute;
    height: 8px;
    margin-top: -4px;
    cursor: ns-resize;
    background: rgba(47, 109, 246, 0.35);
    border-radius: 4px;
    z-index: 950;
}
.dsn-pagearea-handle:hover { background: rgba(47, 109, 246, 0.6); }
/* 0.3.38 菜单编辑器叶子项行尾的「↙」编辑图标(进该菜单页编辑;0.3.36 前是「编辑」文字链接)。 */
.dsn-page-edit { margin-left: 6px; font-size: 12px; color: #2f6df6; text-decoration: none; white-space: nowrap; }
.dsn-page-edit:hover { text-decoration: underline; }
/* 0.3.34 租户侧栏顶部审核状态条:配色与后台徽标同口径(琥珀/红/绿),none 由 hidden 隐藏。 */
.dsn-review-status {
    flex: none;
    padding: 8px 12px;
    border-radius: 8px;
    font-size: 12px;
    line-height: 1.6;
    box-sizing: border-box;
    border: 1px solid transparent;
}
.dsn-review-status[hidden] { display: none; }
.dsn-review-status.is-pending { background: #fffbeb; border-color: #fde68a; color: #92400e; }
.dsn-review-status.is-rejected { background: #fef2f2; border-color: #fecaca; color: #991b1b; }
.dsn-review-status.is-approved { background: #f0fdf4; border-color: #bbf7d0; color: #166534; }
/* 富文本编辑器(0.3.31 租户门户编辑):工具条 + contenteditable 编辑区,
   配色沿用面板既有口径(边框 #e5e7eb、主色 #2f6df6)。 */
.dsn-rich-box { display: flex; flex-direction: column; gap: 6px; }
.dsn-rich-bar { display: flex; flex-wrap: wrap; gap: 4px; }
.dsn-rich-btn {
    border: 1px solid #e5e7eb;
    background: #ffffff;
    color: #374151;
    font-size: 12px;
    line-height: 1;
    padding: 5px 8px;
    border-radius: 6px;
    cursor: pointer;
}
.dsn-rich-btn:hover { border-color: #2f6df6; color: #2f6df6; }
.dsn-rich {
    border: 1px solid #e5e7eb;
    border-radius: 6px;
    background: #ffffff;
    color: #111827;
    font-size: 14px;
    line-height: 1.8;
    padding: 8px 10px;
    min-height: 120px;
    max-height: 320px;
    overflow-y: auto;
    outline: none;
}
.dsn-rich:focus { border-color: #2f6df6; }
.dsn-rich a { color: #2f6df6; }
/* 带子菜单的父项(门户 0.3.92 起渲染为可点链接,走自身 href;画布仍是形似 span)。 */
.pk-nav-parent { cursor: pointer; white-space: nowrap; }
/* 当前页高亮:下划线 + 加粗,文字颜色跟随导航自身配色 —— 固定蓝色在
   深色/彩色底(如蓝底白字导航)上几乎不可见,看起来像「变灰且变不回去」。 */
.pk-nav-link.is-active { font-weight: 600; text-decoration: underline; text-underline-offset: 4px; text-decoration-thickness: 2px; }
.pk-nav-drop-link.is-active { font-weight: 600; text-decoration: underline; text-underline-offset: 3px; }
/* 0.3.47:当前页是某根菜单的子页时,根项(下拉触发器)同样标下划线,
   收起下拉后也能看出当前页属于哪个根菜单。 */
.pk-nav-parent.is-active { font-weight: 600; text-decoration: underline; text-underline-offset: 4px; text-decoration-thickness: 2px; }
