@charset "UTF-8";
/* ==========================================================================
   NFarms 前端共享设计系统 · nf.css · v2「森境 Forest Glass」
   纯 CSS，无外部依赖（无 CDN / 无外链字体 / 无图片），暗色优先 + 浅色主题。
   --------------------------------------------------------------------------
   v2 变更摘要（2026-09-16）：
     · 全新视觉：深林墨绿底 + 双团极光氛围光；浮层磨砂玻璃 + 背景模糊；
       主按钮/品牌/进度条绿→青渐变；主色夜光生态绿；hover 浮起 + 泛光微动效。
     · 硬兼容：301 个类名、全部 v1 --nf-* 令牌名、布局/滚动模型、
       响应式断点、可访问性、打印与 reduced-motion 规则全部保留。
     · 新增令牌均为 additive（--nf-glass / --nf-glass-strong / --nf-glass-brd /
       --nf-glow-accent / --nf-grad-accent / --nf-aurora-*）。
   --------------------------------------------------------------------------
   目录
     1.  主题令牌（:root 暗色 / [data-theme="light"] 浅色）
     2.  基础与重置（盒模型 / 原生控件配色 / 选中 / 滚动条 / 焦点）
     3.  排版、字体栈与工具类（.nf-num .nf-scroll .nf-sticky .nf-split .nf-tip）
     4.  应用外壳（.nf-app .nf-side .nf-nav .nf-main .nf-top .nf-page）
     5.  容器与网格（.nf-card .nf-grid.cols-*）
     6.  统计卡片（.nf-stat-*）
     7.  表格与分页（.nf-table-wrap .nf-table .nf-th-sort .nf-pager）
     8.  表单（.nf-form .nf-field-* .nf-input .nf-select .nf-switch .nf-tags）
     9.  按钮（.nf-btn 及 primary/ghost/danger/warn/sm）
    10.  标签、徽标、等级（.nf-badge .nf-tag .nf-tone-* .nf-level.lv-* .nf-badge-demo）
    11.  弹层（.nf-modal-* .nf-drawer-* .nf-toast*）
    12.  数据展示（.nf-kv .nf-timeline .nf-tree .nf-terminal .nf-progress .nf-code .nf-tabs）
    13.  状态与反馈（.nf-empty .nf-loading .nf-skeleton .nf-error-banner .nf-login）
    14.  通知与用户菜单（.nf-notify-* .nf-user-menu）
    15.  响应式（≤1200px 图标栏 / ≤820px 抽屉 + 单列）
    16.  布局与尺寸一致性（页面滚动 / 控件高度 / 折叠轨 / 卡片等高）
    17.  减少动效与打印
   --------------------------------------------------------------------------
   动效约定：交互过渡 ≤200ms，一次性进入动画 nf-fade-in / nf-pop，
   仅骨架屏为 2.4s 慢循环微光（reduced-motion 下全部静止）。
   ========================================================================== */

/* ==== 1. 主题令牌 ==== */
:root {
  color-scheme: dark light;
  /* 底色：深林墨绿（非纯黑），叠加极光氛围光后呈现"夜间山林"质感 */
  --nf-bg: #0a100e; --nf-bg-soft: #101715; --nf-panel: #141d19; --nf-panel-2: #1b2723;
  --nf-line: #27352f; --nf-line-soft: #1d2a25;
  /* 文字：正文/次要/弱化 三级（带轻微绿倾向，与底色同源） */
  --nf-text: #e9f4ee; --nf-text-dim: #a8bfb4; --nf-text-mute: #6d867b;
  /* 主色：夜光生态绿；--nf-accent-2 为薄荷青，用于渐变终点与 hover 提亮 */
  --nf-accent: #35d07f; --nf-accent-2: #6ee7b7; --nf-accent-soft: rgba(53, 208, 127, 0.15);
  /* 语义色 */
  --nf-ok: #3ecf8e; --nf-warn: #f0b445; --nf-danger: #f0615a; --nf-info: #58b0f5;
  /* 四档告警级别新增的 high：橙/琥珀，夹在 warning(琥珀) 与 critical(红) 之间。
     warning 用偏黄的琥珀(#f0b445)，high 用偏橙的琥珀(#f08a3c)，两者一眼可区分。 */
  --nf-high: #f08a3c;
  --nf-ok-soft: rgba(62, 207, 142, 0.14); --nf-warn-soft: rgba(240, 180, 69, 0.14);
  --nf-high-soft: rgba(240, 138, 60, 0.16);
  --nf-danger-soft: rgba(240, 97, 90, 0.14); --nf-info-soft: rgba(88, 176, 245, 0.14);
  --nf-mute-soft: rgba(109, 134, 123, 0.18);
  /* 语义色描边（不依赖 color-mix，兼容 Safari 15） */
  --nf-accent-line: rgba(53, 208, 127, 0.45); --nf-ok-line: rgba(62, 207, 142, 0.45);
  --nf-warn-line: rgba(240, 180, 69, 0.45); --nf-high-line: rgba(240, 138, 60, 0.5);
  --nf-danger-line: rgba(240, 97, 90, 0.45);
  --nf-info-line: rgba(88, 176, 245, 0.45); --nf-mask-soft: rgba(10, 16, 14, 0.72);
  /* 玻璃与光效（v2 新增） */
  --nf-glass: rgba(20, 29, 25, 0.72); --nf-glass-strong: rgba(15, 22, 19, 0.88);
  --nf-glass-brd: rgba(255, 255, 255, 0.09);
  --nf-glow-accent: 0 0 22px rgba(53, 208, 127, 0.18);
  --nf-grad-accent: linear-gradient(135deg, #35d07f, #4cc9a0 55%, #6ee7b7);
  --nf-aurora-1: rgba(53, 208, 127, 0.10); --nf-aurora-2: rgba(94, 234, 212, 0.07);
  /* 形状与层级 */
  --nf-radius: 12px; --nf-radius-sm: 8px;
  --nf-shadow: 0 18px 44px rgba(0, 0, 0, 0.5); --nf-shadow-sm: 0 2px 10px rgba(0, 0, 0, 0.32);
  --nf-side-w: 240px; --nf-side-rail-w: 64px; --nf-top-h: 56px;
  /* 字体栈：仅系统字体，不引用任何外链字体 */
  --nf-font: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", "Noto Sans SC", Roboto, "Helvetica Neue", Arial, sans-serif;
  --nf-font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", "Courier New", monospace;
  /* 尺寸节奏 */
  --nf-gap: 14px; --nf-pad: 16px; --nf-speed: 180ms;
}
[data-theme="light"] {
  color-scheme: light;
  /* 浅色主题：微绿灰底 + 白色玻璃面板，正文对比度 ≥ 12:1 */
  --nf-bg: #eef4f1; --nf-bg-soft: #e3ece8; --nf-panel: #ffffff; --nf-panel-2: #f2f7f4;
  --nf-line: #c9d8d2; --nf-line-soft: #dde8e3;
  --nf-text: #12211b; --nf-text-dim: #46595a; --nf-text-mute: #71867d;
  --nf-accent: #14915c; --nf-accent-2: #1fae74; --nf-accent-soft: rgba(20, 145, 92, 0.13);
  --nf-ok: #148a56; --nf-warn: #a96f0c; --nf-danger: #c93f36; --nf-info: #1f6fbf;
  --nf-high: #c2620f;
  --nf-ok-soft: rgba(20, 138, 86, 0.12); --nf-warn-soft: rgba(169, 111, 12, 0.13);
  --nf-high-soft: rgba(194, 98, 15, 0.14);
  --nf-danger-soft: rgba(201, 63, 54, 0.12); --nf-info-soft: rgba(31, 111, 191, 0.12);
  --nf-mute-soft: rgba(113, 134, 125, 0.16);
  --nf-accent-line: rgba(20, 145, 92, 0.42); --nf-ok-line: rgba(20, 138, 86, 0.42);
  --nf-warn-line: rgba(169, 111, 12, 0.42); --nf-high-line: rgba(194, 98, 15, 0.46);
  --nf-danger-line: rgba(201, 63, 54, 0.42);
  --nf-info-line: rgba(31, 111, 191, 0.42); --nf-mask-soft: rgba(238, 244, 241, 0.75);
  /* 玻璃与光效（浅色） */
  --nf-glass: rgba(255, 255, 255, 0.72); --nf-glass-strong: rgba(255, 255, 255, 0.92);
  --nf-glass-brd: rgba(18, 33, 27, 0.1);
  --nf-glow-accent: 0 4px 16px rgba(20, 145, 92, 0.22);
  --nf-aurora-1: rgba(20, 145, 92, 0.08); --nf-aurora-2: rgba(56, 189, 248, 0.07);
  /* 形状与尺寸：与暗色一致，仅阴影换成浅色投影 */
  --nf-radius: 12px; --nf-radius-sm: 8px;
  --nf-shadow: 0 14px 34px rgba(18, 33, 27, 0.14); --nf-shadow-sm: 0 2px 8px rgba(18, 33, 27, 0.08);
  --nf-side-w: 240px; --nf-side-rail-w: 64px; --nf-top-h: 56px;
  --nf-gap: 14px; --nf-pad: 16px; --nf-speed: 180ms;
}

/* ==== 2. 基础与重置 ==== */
*, *::before, *::after { box-sizing: border-box; }
html { background: var(--nf-bg); -webkit-text-size-adjust: 100%; }
body {
  margin: 0; min-height: 100vh; background: var(--nf-bg); color: var(--nf-text); font: 14px/1.6 var(--nf-font);
  font-variant-numeric: tabular-nums; -webkit-font-smoothing: antialiased;
  transition: background-color var(--nf-speed) ease, color var(--nf-speed) ease;
}
/* 极光氛围光：固定附着，玻璃层的通透来源（打印时关闭） */
body::before {
  content: ""; position: fixed; inset: 0; z-index: -1; pointer-events: none;
  background:
    radial-gradient(52% 42% at 12% -4%, var(--nf-aurora-1), transparent 62%),
    radial-gradient(44% 38% at 88% 6%, var(--nf-aurora-2), transparent 60%),
    radial-gradient(62% 52% at 50% 112%, var(--nf-aurora-1), transparent 64%);
}
h1, h2, h3, h4, h5, h6 { margin: 0; font-weight: 600; line-height: 1.35; }
p { margin: 0 0 8px; }
a { color: var(--nf-accent); text-decoration: none; transition: color var(--nf-speed) ease; }
a:hover { color: var(--nf-accent-2); text-decoration: underline; }
code, kbd, pre, samp { font-family: var(--nf-font-mono); font-size: 0.92em; }
hr { height: 1px; margin: 14px 0; border: 0; background: var(--nf-line-soft); }
::selection { background: var(--nf-accent); color: #ffffff; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: var(--nf-bg-soft); }
::-webkit-scrollbar-thumb { border: 2px solid transparent; border-radius: 999px; background: var(--nf-line); background-clip: padding-box; }
::-webkit-scrollbar-thumb:hover { background: var(--nf-text-mute); background-clip: padding-box; }
::-webkit-scrollbar-corner { background: var(--nf-bg-soft); }
/* 原生控件跟随主题 */
input, select, textarea, button { font: inherit; color: inherit; accent-color: var(--nf-accent); }
input, select, textarea { background: var(--nf-bg-soft); border: 1px solid var(--nf-line); border-radius: var(--nf-radius-sm); }
input[type="checkbox"], input[type="radio"], input[type="file"] { accent-color: var(--nf-accent); cursor: pointer; }
textarea { resize: vertical; min-height: 84px; }
::placeholder { color: var(--nf-text-mute); opacity: 1; }
:-ms-input-placeholder { color: var(--nf-text-mute); }
/* 焦点可见性：可访问性硬要求，键盘用户必须能看清落点 */
:focus-visible, .nf-focus-visible { outline: 2px solid var(--nf-accent); outline-offset: 2px; }
input:focus-visible, select:focus-visible, textarea:focus-visible { outline: 2px solid var(--nf-accent); outline-offset: 0; border-color: var(--nf-accent); }
button:focus-visible, a:focus-visible, [tabindex]:focus-visible { outline: 2px solid var(--nf-accent); outline-offset: 2px; border-radius: var(--nf-radius-sm); }

/* ==== 3. 排版、字体栈与工具类 ==== */
.nf-mono { font-family: var(--nf-font-mono); }
.nf-num, .nf-table th.nf-num, .nf-table td.nf-num { text-align: right; font-variant-numeric: tabular-nums; font-family: var(--nf-font-mono); }
.nf-dim { color: var(--nf-text-dim); }
.nf-mute { color: var(--nf-text-mute); }
.nf-nowrap { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.nf-scroll {
  overflow: auto; overscroll-behavior: contain; scrollbar-width: thin;
  scrollbar-color: var(--nf-line) transparent;
}
.nf-sticky { position: sticky; top: calc(var(--nf-top-h) + 10px); z-index: 4; }
.nf-split { display: grid; grid-template-columns: minmax(0, 1fr) 320px; gap: var(--nf-gap); align-items: start; }
.nf-split.wide-aside { grid-template-columns: minmax(0, 1fr) 420px; }
.nf-split > .nf-aside, .nf-split > aside { min-width: 0; }
.nf-tip { border-bottom: 1px dashed var(--nf-line); cursor: help; }
.nf-hide { display: none !important; }
.nf-right { margin-left: auto; }

/* ==== 4. 应用外壳 ==== */
.nf-app {
  display: grid; grid-template-columns: var(--nf-side-w) minmax(0, 1fr); min-height: 100vh;
  background: var(--nf-bg);
}
.nf-boot {
  grid-column: 1 / -1; display: flex; align-items: center; justify-content: center; min-height: 100vh;
  color: var(--nf-text-mute); letter-spacing: 0.08em;
}
/* 非外壳视图（登录页 / 错误页 / 占位页）必须脱离侧栏网格。
   2026-09-15 故障复盘：app.js 渲染登录视图时给外壳加了 `nf-app-login`，
   但 CSS 里从未定义它 ⇒ 登录内容仍然落在 `.nf-app` 网格的**第一列**（=侧栏宽度，
   约 232px）里，`.nf-login` 又是 flex 横排，三个子块互相挤压，
   长中文说明被压成一字一行（用户截图里的"竖排乱码"）。
   修法：任何 nf-app-* 变体视图都退化为普通块级布局，占满整屏。 */
.nf-app.nf-app-login,
.nf-app.nf-app-plain { display: block; grid-template-columns: none; min-height: 100vh; }
/* 侧栏：磨砂玻璃浮层（不支持 backdrop-filter 时退回接近不透明的玻璃底） */
.nf-side {
  position: sticky; top: 0; align-self: start; display: flex; flex-direction: column; width: var(--nf-side-w);
  height: 100vh; overflow-x: hidden; overflow-y: auto;
  background: var(--nf-glass); border-right: 1px solid var(--nf-glass-brd);
  box-shadow: inset -1px 0 0 rgba(255, 255, 255, 0.02);
  backdrop-filter: blur(16px) saturate(1.25); -webkit-backdrop-filter: blur(16px) saturate(1.25);
  scrollbar-width: thin; scrollbar-color: var(--nf-line) transparent;
}
@supports not ((backdrop-filter: blur(2px)) or (-webkit-backdrop-filter: blur(2px))) {
  .nf-side, .nf-top, .nf-page-head, .nf-modal, .nf-drawer, .nf-notify-list, .nf-user-menu, .nf-search-results {
    background: var(--nf-glass-strong);
  }
}
.nf-side-brand {
  display: flex; align-items: center; gap: 10px; flex: 0 0 auto; height: var(--nf-top-h); padding: 0 14px;
  border-bottom: 1px solid var(--nf-glass-brd); white-space: nowrap; overflow: hidden;
}
.nf-brand-mark {
  display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto; width: 28px; height: 28px;
  border-radius: 8px; background: var(--nf-grad-accent); color: #052b1b;
  font-size: 15px; font-weight: 700; box-shadow: var(--nf-glow-accent);
}
.nf-brand-text { display: flex; flex-direction: column; min-width: 0; line-height: 1.25; }
.nf-brand-name { font-size: 14px; font-weight: 600; letter-spacing: 0.04em; }
.nf-brand-sub { font-size: 11px; color: var(--nf-text-mute); overflow: hidden; text-overflow: ellipsis; }
.nf-nav { flex: 1 1 auto; padding: 10px 8px 20px; }
.nf-nav-group { margin: 0 0 12px; }
.nf-nav-group-title, .nf-nav-group > .title {
  padding: 8px 10px 6px; color: var(--nf-text-mute); font-size: 11px; letter-spacing: 0.12em;
  text-transform: uppercase; white-space: nowrap; overflow: hidden;
}
.nf-nav-item {
  display: flex; align-items: center; gap: 10px; width: 100%; margin: 0 0 2px; padding: 8px 10px; border: 0;
  border-radius: var(--nf-radius-sm); background: transparent; color: var(--nf-text-dim); font-size: 13.5px;
  text-align: left; text-decoration: none; white-space: nowrap; overflow: hidden; cursor: pointer;
  transition: background-color var(--nf-speed) ease, color var(--nf-speed) ease, box-shadow var(--nf-speed) ease;
}
.nf-nav-item:hover { background: rgba(255, 255, 255, 0.045); color: var(--nf-text); text-decoration: none; }
.nf-nav-item.active, .nf-nav-item[aria-current="page"] {
  background: linear-gradient(90deg, var(--nf-accent-soft), rgba(53, 208, 127, 0.02) 140%);
  color: var(--nf-text); box-shadow: inset 2px 0 0 var(--nf-accent), 0 0 14px rgba(53, 208, 127, 0.07);
}
.nf-nav-item.active .nf-nav-icon, .nf-nav-item[aria-current="page"] .nf-nav-icon { color: var(--nf-accent); }
.nf-nav-icon { flex: 0 0 auto; width: 20px; text-align: center; font-size: 15px; line-height: 1; }
.nf-nav-label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.nf-nav-badge {
  flex: 0 0 auto; min-width: 18px; padding: 0 5px; border-radius: 999px; background: var(--nf-panel-2);
  color: var(--nf-text-dim); font-size: 11px; line-height: 16px; text-align: center;
}
.nf-nav-item.active .nf-nav-badge { background: var(--nf-accent); color: #052b1b; font-weight: 600; }
.nf-side-foot {
  flex: 0 0 auto; padding: 10px 12px; border-top: 1px solid var(--nf-glass-brd); color: var(--nf-text-mute);
  font-size: 11.5px;
}
/* 页脚内的备案号链接：与页脚文字同色，hover 提亮（低调但可点击核验） */
.nf-side-foot a { color: var(--nf-text-mute); text-decoration: none; transition: color var(--nf-speed) ease; }
.nf-side-foot a:hover { color: var(--nf-accent); text-decoration: underline; }
.nf-main { display: flex; flex-direction: column; min-width: 0; height: 100vh; overflow: hidden; }
/* 顶栏：磨砂玻璃 + 底部高光 */
.nf-top {
  position: sticky; top: 0; z-index: 20; display: flex; align-items: center; gap: 10px; flex: 0 0 auto;
  height: var(--nf-top-h); padding: 0 var(--nf-pad);
  background: var(--nf-glass); border-bottom: 1px solid var(--nf-glass-brd);
  backdrop-filter: blur(16px) saturate(1.25); -webkit-backdrop-filter: blur(16px) saturate(1.25);
}
.nf-top-title { font-size: 15px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.nf-crumbs {
  display: flex; align-items: center; gap: 6px; min-width: 0; color: var(--nf-text-mute); font-size: 12.5px;
  white-space: nowrap; overflow: hidden;
}
.nf-crumbs a { color: var(--nf-text-dim); }
.nf-crumbs a:hover { color: var(--nf-accent); }
.nf-crumbs-sep { color: var(--nf-line); }
.nf-crumbs-current { color: var(--nf-text); font-size: 14.5px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; }
.nf-search {
  position: relative; display: flex; align-items: center; gap: 6px; min-width: 180px; max-width: 320px;
  flex: 0 1 320px; padding: 0 10px; height: 32px;
  background: rgba(255, 255, 255, 0.04); border: 1px solid var(--nf-line); border-radius: var(--nf-radius-sm);
  transition: border-color var(--nf-speed) ease, box-shadow var(--nf-speed) ease;
}
.nf-search:focus-within { border-color: var(--nf-accent); box-shadow: 0 0 0 2px var(--nf-accent-soft), var(--nf-glow-accent); }
.nf-search-icon { color: var(--nf-text-mute); font-size: 13px; }
.nf-search input { flex: 1 1 auto; min-width: 0; height: 100%; padding: 0; border: 0; background: transparent; outline: none; }
.nf-search-clear { padding: 0 2px; border: 0; background: transparent; color: var(--nf-text-mute); cursor: pointer; }
.nf-search-clear:hover { color: var(--nf-danger); }
.nf-search-kbd {
  padding: 1px 5px; border: 1px solid var(--nf-line); border-radius: 4px; color: var(--nf-text-mute);
  font-family: var(--nf-font-mono); font-size: 11px;
}
.nf-top-actions { display: flex; align-items: center; gap: 6px; margin-left: auto; }
.nf-icon-btn {
  position: relative; display: inline-flex; align-items: center; justify-content: center; width: 32px;
  height: 32px; padding: 0; border: 1px solid transparent; border-radius: var(--nf-radius-sm);
  background: transparent; color: var(--nf-text-dim); font-size: 15px; cursor: pointer;
  transition: background-color var(--nf-speed) ease, color var(--nf-speed) ease;
}
.nf-icon-btn:hover { background: rgba(255, 255, 255, 0.06); color: var(--nf-text); }
.nf-icon-btn.active { background: var(--nf-accent-soft); color: var(--nf-accent); }
.nf-page {
  display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0;
  animation: nf-fade-in var(--nf-speed) ease-out both;
}
.nf-page-head {
  display: flex; align-items: flex-end; gap: 12px; flex-wrap: wrap; padding: 14px var(--nf-pad) 10px;
  border-bottom: 1px solid var(--nf-glass-brd);
}
.nf-page-head h1, .nf-page-title { margin: 0; font-size: 17px; font-weight: 600; }
.nf-page-desc { margin: 2px 0 0; color: var(--nf-text-mute); font-size: 12.5px; }
.nf-page-actions { display: flex; align-items: center; gap: 8px; margin-left: auto; }
.nf-page-body {
  flex: 1 1 auto; min-height: 0; padding: var(--nf-pad); overflow: auto; scrollbar-width: thin;
  scrollbar-color: var(--nf-line) transparent;
}
.nf-toolbar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: var(--nf-gap); }
.nf-toolbar-sep { width: 1px; height: 20px; background: var(--nf-line-soft); }

/* ==== 5. 容器与网格 ==== */
/* 卡片：深色玻璃质感——微透明渐变 + 顶部内高光；hover 浮起泛光 */
.nf-card {
  display: flex; flex-direction: column; min-width: 0;
  background: linear-gradient(165deg, rgba(255, 255, 255, 0.03), rgba(255, 255, 255, 0) 38%), var(--nf-panel);
  border: 1px solid var(--nf-line-soft); border-radius: var(--nf-radius);
  box-shadow: var(--nf-shadow-sm), inset 0 1px 0 rgba(255, 255, 255, 0.04);
  transition: border-color var(--nf-speed) ease, box-shadow var(--nf-speed) ease, transform var(--nf-speed) ease;
}
.nf-card:hover { border-color: var(--nf-line); }
.nf-card-head {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding: 11px 14px;
  border-bottom: 1px solid var(--nf-line-soft);
}
.nf-card-title { margin: 0; font-size: 14px; font-weight: 600; }
.nf-card-extra {
  display: flex; align-items: center; gap: 8px; margin-left: auto; color: var(--nf-text-mute);
  font-size: 12.5px;
}
.nf-card-body { flex: 1 1 auto; min-width: 0; padding: 14px; }
.nf-card-body.flush { padding: 0; }
.nf-card-foot {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap; padding: 10px 14px;
  border-top: 1px solid var(--nf-line-soft); color: var(--nf-text-mute); font-size: 12.5px;
}
.nf-card-foot .nf-btn { font-size: 12.5px; }
.nf-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: var(--nf-gap);
  align-items: start;
}
.nf-grid.cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.nf-grid.cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.nf-grid.cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.nf-grid.cols-6 { grid-template-columns: repeat(6, minmax(0, 1fr)); }
.nf-grid.tight { gap: 10px; }
.nf-grid > .span-2 { grid-column: span 2; }
.nf-grid > .span-all { grid-column: 1 / -1; }

/* ==== 6. 统计卡片 ==== */
.nf-stat {
  display: flex; flex-direction: column; gap: 4px; min-width: 0; padding: 13px 14px;
  background: linear-gradient(165deg, rgba(255, 255, 255, 0.035), rgba(255, 255, 255, 0) 42%), var(--nf-panel);
  border: 1px solid var(--nf-line-soft); border-radius: var(--nf-radius);
  box-shadow: var(--nf-shadow-sm), inset 0 1px 0 rgba(255, 255, 255, 0.05);
  transition: border-color var(--nf-speed) ease, transform var(--nf-speed) ease, box-shadow var(--nf-speed) ease;
}
.nf-stat:hover { border-color: var(--nf-accent-line); transform: translateY(-2px); box-shadow: var(--nf-shadow-sm), var(--nf-glow-accent); }
.nf-stat-label { display: flex; align-items: center; gap: 6px; color: var(--nf-text-mute); font-size: 12.5px; }
.nf-stat-value {
  display: flex; align-items: baseline; gap: 4px; color: var(--nf-text); font-size: 25px; font-weight: 600;
  line-height: 1.2; font-variant-numeric: tabular-nums;
}
.nf-stat-unit { color: var(--nf-text-dim); font-size: 12.5px; font-weight: 400; }
.nf-stat-delta { display: inline-flex; align-items: center; gap: 3px; font-size: 12px; color: var(--nf-text-dim); }
.nf-stat-delta.up { color: var(--nf-ok); }
.nf-stat-delta.down { color: var(--nf-danger); }
.nf-stat-hint { color: var(--nf-text-mute); font-size: 11.5px; }
.nf-stat-spark { height: 34px; margin-top: 4px; }
.nf-stat.nf-tone-ok { box-shadow: inset 3px 0 0 var(--nf-ok), var(--nf-shadow-sm); }
.nf-stat.nf-tone-warn { box-shadow: inset 3px 0 0 var(--nf-warn), var(--nf-shadow-sm); }
.nf-stat.nf-tone-danger { box-shadow: inset 3px 0 0 var(--nf-danger), var(--nf-shadow-sm); }
.nf-stat.nf-tone-info { box-shadow: inset 3px 0 0 var(--nf-info), var(--nf-shadow-sm); }
.nf-stat.nf-tone-accent { box-shadow: inset 3px 0 0 var(--nf-accent), var(--nf-shadow-sm); }
.nf-stat.nf-tone-ok:hover { box-shadow: inset 3px 0 0 var(--nf-ok), var(--nf-shadow-sm), 0 0 18px rgba(62, 207, 142, 0.16); }
.nf-stat.nf-tone-warn:hover { box-shadow: inset 3px 0 0 var(--nf-warn), var(--nf-shadow-sm), 0 0 18px rgba(240, 180, 69, 0.16); }
.nf-stat.nf-tone-danger:hover { box-shadow: inset 3px 0 0 var(--nf-danger), var(--nf-shadow-sm), 0 0 18px rgba(240, 97, 90, 0.16); }
.nf-stat.nf-tone-info:hover { box-shadow: inset 3px 0 0 var(--nf-info), var(--nf-shadow-sm), 0 0 18px rgba(88, 176, 245, 0.16); }
.nf-stat.nf-tone-accent:hover { box-shadow: inset 3px 0 0 var(--nf-accent), var(--nf-shadow-sm), var(--nf-glow-accent); }

/* ==== 7. 表格与分页 ==== */
.nf-table-wrap {
  max-width: 100%; overflow: auto; overscroll-behavior-x: contain; border-radius: var(--nf-radius-sm);
  scrollbar-width: thin; scrollbar-color: var(--nf-line) var(--nf-bg-soft);
}
.nf-table-wrap::-webkit-scrollbar { height: 9px; width: 9px; }
.nf-table-wrap::-webkit-scrollbar-thumb { border-radius: 999px; background: var(--nf-line); }
.nf-table-wrap::-webkit-scrollbar-thumb:hover { background: var(--nf-text-mute); }
.nf-table { width: 100%; border-collapse: separate; border-spacing: 0; font-size: 13px; white-space: nowrap; }
.nf-table th, .nf-table td { padding: 10px 12px; border-bottom: 1px solid var(--nf-line-soft); text-align: left; vertical-align: middle; }
.nf-table thead th {
  position: sticky; top: 0; z-index: 2; background: var(--nf-panel-2); color: var(--nf-text-dim);
  font-size: 12.5px; font-weight: 600; letter-spacing: 0.02em; border-bottom: 1px solid var(--nf-line);
}
.nf-table tbody tr { transition: background-color var(--nf-speed) ease; }
.nf-table tbody tr:hover { background: rgba(255, 255, 255, 0.028); }
.nf-table tbody tr.selected, .nf-table tbody tr.nf-row-sel { background: var(--nf-accent-soft); }
.nf-table tbody tr:last-child td { border-bottom: 0; }
.nf-table td.nf-cell-main, .nf-table th.nf-cell-main { color: var(--nf-text); font-weight: 500; white-space: normal; }
.nf-table .nf-col-sel, .nf-table .nf-row-sel-cell { width: 36px; padding-right: 0; text-align: center; }
.nf-th-sort {
  display: inline-flex; align-items: center; gap: 4px; padding: 0; border: 0; background: transparent;
  color: inherit; font: inherit; font-weight: 600; cursor: pointer; transition: color var(--nf-speed) ease;
}
.nf-th-sort:hover { color: var(--nf-text); }
.nf-th-sort::after { content: "⇅"; color: var(--nf-text-mute); font-size: 11px; opacity: 0.7; }
.nf-th-sort.asc::after { content: "↑"; color: var(--nf-accent); opacity: 1; }
.nf-th-sort.desc::after { content: "↓"; color: var(--nf-accent); opacity: 1; }
.nf-td-actions { display: flex; align-items: center; gap: 6px; justify-content: flex-end; white-space: nowrap; }
.nf-pager {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap; padding: 10px 12px;
  border-top: 1px solid var(--nf-line-soft); color: var(--nf-text-mute); font-size: 12.5px;
}
.nf-pager-info { margin-right: auto; color: var(--nf-text-dim); }
.nf-pager-btn, .nf-pager-page {
  min-width: 28px; height: 28px; padding: 0 8px; border: 1px solid var(--nf-line);
  border-radius: var(--nf-radius-sm); background: var(--nf-panel); color: var(--nf-text-dim); font-size: 12.5px;
  cursor: pointer; transition: border-color var(--nf-speed) ease, color var(--nf-speed) ease, background-color var(--nf-speed) ease, box-shadow var(--nf-speed) ease;
}
.nf-pager-btn:hover:not(:disabled), .nf-pager-page:hover { border-color: var(--nf-accent); color: var(--nf-text); }
.nf-pager-page.active { border-color: transparent; background: var(--nf-grad-accent); color: #052b1b; font-weight: 600; box-shadow: var(--nf-glow-accent); }
.nf-pager-btn:disabled { opacity: 0.45; cursor: not-allowed; }

/* ==== 8. 表单 ==== */
.nf-form { display: block; max-width: 100%; }
.nf-form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px 14px; }
.nf-form-grid.one { grid-template-columns: minmax(0, 1fr); }
.nf-form-grid.three { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.nf-field { display: flex; flex-direction: column; gap: 5px; min-width: 0; margin-bottom: 12px; }
.nf-form-grid .nf-field { margin-bottom: 0; }
.nf-field.full { grid-column: 1 / -1; }
.nf-field-label { display: flex; align-items: center; gap: 5px; color: var(--nf-text-dim); font-size: 12.5px; font-weight: 500; }
.nf-field-label .req, .nf-field.required .nf-field-label::after { content: "*"; color: var(--nf-danger); }
.nf-field-help { color: var(--nf-text-mute); font-size: 11.5px; }
.nf-field-error { color: var(--nf-danger); font-size: 11.5px; }
.nf-field.has-error .nf-input, .nf-field.has-error .nf-select, .nf-field.has-error .nf-textarea { border-color: var(--nf-danger); box-shadow: 0 0 0 2px var(--nf-danger-soft); }
.nf-field-static { padding: 7px 0; color: var(--nf-text); font-size: 13.5px; }
.nf-input, .nf-select, .nf-textarea {
  width: 100%; min-width: 0; padding: 7px 10px; background: var(--nf-bg-soft); border: 1px solid var(--nf-line);
  border-radius: var(--nf-radius-sm); color: var(--nf-text); font-size: 13.5px; line-height: 1.5;
  transition: border-color var(--nf-speed) ease, box-shadow var(--nf-speed) ease;
}
.nf-input:hover, .nf-select:hover, .nf-textarea:hover { border-color: var(--nf-text-mute); }
.nf-input:focus, .nf-select:focus, .nf-textarea:focus { border-color: var(--nf-accent); box-shadow: 0 0 0 2px var(--nf-accent-soft), var(--nf-glow-accent); outline: none; }
.nf-input:disabled, .nf-select:disabled, .nf-textarea:disabled { opacity: 0.55; cursor: not-allowed; background: var(--nf-panel-2); }
.nf-input[readonly] { background: var(--nf-panel-2); color: var(--nf-text-dim); }
.nf-select {
  -webkit-appearance: none; appearance: none; padding-right: 26px;
  background-image: linear-gradient(45deg, transparent 50%, var(--nf-text-mute) 50%), linear-gradient(135deg, var(--nf-text-mute) 50%, transparent 50%);
  background-position: calc(100% - 15px) 50%, calc(100% - 10px) 50%; background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat; cursor: pointer;
}
.nf-select option { background: var(--nf-panel); color: var(--nf-text); }
.nf-textarea { min-height: 88px; font-family: var(--nf-font); resize: vertical; }
.nf-input.sm, .nf-select.sm { padding: 4px 8px; font-size: 12.5px; }
.nf-input.mono, .nf-textarea.mono { font-family: var(--nf-font-mono); font-size: 12.5px; }
.nf-input[type="color"] { height: 32px; padding: 2px 4px; }
/* 开关：type=switch / 复选框样式化 */
.nf-switch {
  display: inline-flex; align-items: center; gap: 8px; cursor: pointer; -webkit-user-select: none;
  user-select: none; font-size: 13px; color: var(--nf-text-dim);
}
.nf-switch input { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
.nf-switch-track {
  position: relative; flex: 0 0 auto; width: 38px; height: 20px; border-radius: 999px;
  background: var(--nf-panel-2); border: 1px solid var(--nf-line);
  transition: background-color var(--nf-speed) ease, border-color var(--nf-speed) ease, box-shadow var(--nf-speed) ease;
}
.nf-switch-track::after {
  content: ""; position: absolute; top: 2px; left: 2px; width: 14px; height: 14px; border-radius: 50%;
  background: var(--nf-text-mute);
  transition: transform var(--nf-speed) ease, background-color var(--nf-speed) ease;
}
.nf-switch input:checked + .nf-switch-track { background: var(--nf-grad-accent); border-color: transparent; box-shadow: var(--nf-glow-accent); }
.nf-switch input:checked + .nf-switch-track::after { transform: translateX(18px); background: #052b1b; }
.nf-switch input:focus-visible + .nf-switch-track { outline: 2px solid var(--nf-accent); outline-offset: 2px; }
.nf-switch input:disabled + .nf-switch-track { opacity: 0.5; cursor: not-allowed; }
/* 多值输入（tags） */
.nf-tags {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap; min-height: 34px; padding: 4px 8px;
  background: var(--nf-bg-soft); border: 1px solid var(--nf-line); border-radius: var(--nf-radius-sm);
  transition: border-color var(--nf-speed) ease, box-shadow var(--nf-speed) ease;
}
.nf-tags:focus-within { border-color: var(--nf-accent); box-shadow: 0 0 0 2px var(--nf-accent-soft); }
.nf-tags-input { flex: 1 1 80px; min-width: 80px; padding: 2px 0; border: 0; background: transparent; outline: none; }
.nf-tag-chip, .nf-tags .nf-tag { display: inline-flex; align-items: center; gap: 4px; }
.nf-tag-chip-x {
  border: 0; background: transparent; color: inherit; font-size: 12px; line-height: 1; cursor: pointer;
  opacity: 0.75;
}
.nf-tag-chip-x:hover { opacity: 1; }
.nf-checkline {
  display: inline-flex; align-items: center; gap: 6px; color: var(--nf-text-dim); font-size: 13px;
  cursor: pointer;
}
/* 拖拽上传 */
.nf-upload { display: block; }
.nf-upload-zone {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  padding: 20px 14px; border: 1px dashed var(--nf-line); border-radius: var(--nf-radius);
  background: var(--nf-bg-soft); color: var(--nf-text-mute); text-align: center; cursor: pointer;
  transition: border-color var(--nf-speed) ease, color var(--nf-speed) ease, background-color var(--nf-speed) ease;
}
.nf-upload-zone:hover, .nf-upload-zone.drag { border-color: var(--nf-accent); color: var(--nf-text-dim); background: var(--nf-accent-soft); }
.nf-upload-hint { color: var(--nf-text-mute); font-size: 11.5px; }
.nf-upload-label { color: var(--nf-text-dim); font-size: 13px; }

/* ==== 9. 按钮 ==== */
.nf-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px; height: 32px; padding: 0 13px;
  border: 1px solid var(--nf-line); border-radius: var(--nf-radius-sm);
  background: rgba(255, 255, 255, 0.045); color: var(--nf-text); font-size: 13px; font-family: var(--nf-font);
  line-height: 1; white-space: nowrap; cursor: pointer;
  transition: background-color var(--nf-speed) ease, border-color var(--nf-speed) ease, color var(--nf-speed) ease,
    opacity var(--nf-speed) ease, box-shadow var(--nf-speed) ease, transform var(--nf-speed) ease;
}
.nf-btn:hover { border-color: var(--nf-text-mute); background: rgba(255, 255, 255, 0.08); }
.nf-btn:active { transform: translateY(1px); }
.nf-btn.primary {
  border-color: transparent; background: var(--nf-grad-accent); color: #052b1b; font-weight: 600;
  box-shadow: var(--nf-glow-accent);
}
.nf-btn.primary:hover { background: var(--nf-grad-accent); filter: brightness(1.07); border-color: transparent; box-shadow: 0 0 28px rgba(53, 208, 127, 0.28); }
.nf-btn.ghost { background: transparent; border-color: var(--nf-line); color: var(--nf-text-dim); }
.nf-btn.ghost:hover { background: rgba(255, 255, 255, 0.05); color: var(--nf-text); border-color: var(--nf-text-mute); }
.nf-btn.danger { border-color: transparent; background: var(--nf-danger); color: #ffffff; box-shadow: 0 0 16px rgba(240, 97, 90, 0.18); }
.nf-btn.danger:hover { background: var(--nf-danger); filter: brightness(1.08); border-color: transparent; }
.nf-btn.danger.ghost { background: transparent; border-color: var(--nf-danger); color: var(--nf-danger); box-shadow: none; }
.nf-btn.warn { border-color: transparent; background: var(--nf-warn); color: #241a04; box-shadow: 0 0 16px rgba(240, 180, 69, 0.16); }
.nf-btn.warn:hover { filter: brightness(1.06); border-color: transparent; }
.nf-btn.warn.ghost { background: transparent; border-color: var(--nf-warn); color: var(--nf-warn); box-shadow: none; }
.nf-btn.sm { height: 26px; padding: 0 9px; font-size: 12px; }
.nf-btn.lg { height: 38px; padding: 0 18px; font-size: 14px; }
.nf-btn.block { width: 100%; }
.nf-btn:disabled, .nf-btn[disabled], .nf-btn.disabled { opacity: 0.5; cursor: not-allowed; filter: grayscale(0.25); pointer-events: none; }
.nf-btn-icon { font-size: 13px; line-height: 1; }

/* ==== 10. 标签、徽标、等级 ==== */
.nf-badge, .nf-tag {
  display: inline-flex; align-items: center; gap: 4px; padding: 1px 8px; border: 1px solid var(--nf-line);
  border-radius: 999px; background: var(--nf-panel-2); color: var(--nf-text-dim); font-size: 11.5px;
  line-height: 18px; white-space: nowrap; vertical-align: middle;
}
.nf-badge { font-weight: 500; }
.nf-tag { background: transparent; border-style: solid; font-weight: 400; }
.nf-tone-ok, .nf-badge.nf-tone-ok, .nf-tag.nf-tone-ok { border-color: var(--nf-ok-line); background: var(--nf-ok-soft); color: var(--nf-ok); }
.nf-tag.nf-tone-ok { background: transparent; }
.nf-tone-warn, .nf-badge.nf-tone-warn, .nf-tag.nf-tone-warn { border-color: var(--nf-warn-line); background: var(--nf-warn-soft); color: var(--nf-warn); }
.nf-tag.nf-tone-warn { background: transparent; }
.nf-tone-danger, .nf-badge.nf-tone-danger, .nf-tag.nf-tone-danger { border-color: var(--nf-danger-line); background: var(--nf-danger-soft); color: var(--nf-danger); }
.nf-tag.nf-tone-danger { background: transparent; }
.nf-tone-info, .nf-badge.nf-tone-info, .nf-tag.nf-tone-info { border-color: var(--nf-info-line); background: var(--nf-info-soft); color: var(--nf-info); }
.nf-tag.nf-tone-info { background: transparent; }
.nf-tone-accent, .nf-badge.nf-tone-accent, .nf-tag.nf-tone-accent { border-color: var(--nf-accent-line); background: var(--nf-accent-soft); color: var(--nf-accent); }
.nf-tag.nf-tone-accent { background: transparent; }
.nf-tone-muted, .nf-badge.nf-tone-muted, .nf-tag.nf-tone-muted { border-color: var(--nf-line); background: var(--nf-mute-soft); color: var(--nf-text-mute); }
.nf-tag.nf-tone-muted { background: transparent; }
.nf-dot {
  display: inline-block; flex: 0 0 auto; width: 7px; height: 7px; border-radius: 50%;
  background: var(--nf-text-mute); box-shadow: 0 0 0 3px transparent; vertical-align: middle;
}
.nf-dot.nf-tone-ok { background: var(--nf-ok); box-shadow: 0 0 0 3px var(--nf-ok-soft), 0 0 8px rgba(62, 207, 142, 0.5); }
.nf-dot.nf-tone-warn { background: var(--nf-warn); box-shadow: 0 0 0 3px var(--nf-warn-soft), 0 0 8px rgba(240, 180, 69, 0.5); }
.nf-dot.nf-tone-danger { background: var(--nf-danger); box-shadow: 0 0 0 3px var(--nf-danger-soft), 0 0 8px rgba(240, 97, 90, 0.5); }
.nf-dot.nf-tone-info { background: var(--nf-info); box-shadow: 0 0 0 3px var(--nf-info-soft), 0 0 8px rgba(88, 176, 245, 0.5); }
.nf-dot.nf-tone-accent { background: var(--nf-accent); box-shadow: 0 0 0 3px var(--nf-accent-soft), 0 0 8px rgba(53, 208, 127, 0.5); }
.nf-status {
  display: inline-flex; align-items: center; gap: 6px; color: var(--nf-text-dim); font-size: 12.5px;
  white-space: nowrap;
}
.nf-level {
  display: inline-flex; align-items: center; gap: 4px; padding: 1px 7px; border: 1px solid var(--nf-line);
  border-radius: var(--nf-radius-sm); background: var(--nf-mute-soft); color: var(--nf-text-dim);
  font-size: 11px; font-weight: 600; letter-spacing: 0.03em; line-height: 17px; white-space: nowrap;
}
.nf-level.lv-critical { border-color: var(--nf-danger-line); background: var(--nf-danger-soft); color: var(--nf-danger); }
/* high（攻击失败 / 抵御成功）：橙/琥珀色，排在 warning 之上、critical 之下 */
.nf-level.lv-high { border-color: var(--nf-high-line); background: var(--nf-high-soft); color: var(--nf-high); }
.nf-level.lv-error { border-color: var(--nf-danger-line); background: var(--nf-danger-soft); color: var(--nf-danger); }
.nf-level.lv-warning { border-color: var(--nf-warn-line); background: var(--nf-warn-soft); color: var(--nf-warn); }
.nf-level.lv-info { border-color: var(--nf-info-line); background: var(--nf-info-soft); color: var(--nf-info); }
.nf-level.lv-debug { border-color: var(--nf-line); background: var(--nf-mute-soft); color: var(--nf-text-mute); }
.nf-badge-demo {
  display: inline-flex; align-items: center; gap: 4px; padding: 1px 8px; border: 1px dashed var(--nf-warn);
  border-radius: 999px; background: var(--nf-warn-soft); color: var(--nf-warn); font-size: 11px;
  font-weight: 500; line-height: 18px; white-space: nowrap;
}
.nf-badge-demo::before { content: "◈"; font-size: 10px; }

/* ==== 11. 弹层 ==== */
.nf-modal-mask {
  position: fixed; inset: 0; z-index: 1000; display: flex; align-items: center; justify-content: center;
  padding: 24px 16px; background: rgba(5, 9, 8, 0.6); backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px);
  animation: nf-fade-in var(--nf-speed) ease-out both;
}
.nf-modal {
  display: flex; flex-direction: column; width: min(560px, 100%); max-height: calc(100vh - 48px);
  background: var(--nf-glass-strong); border: 1px solid var(--nf-glass-brd); border-radius: var(--nf-radius);
  box-shadow: var(--nf-shadow), var(--nf-glow-accent); overflow: hidden;
  backdrop-filter: blur(18px) saturate(1.3); -webkit-backdrop-filter: blur(18px) saturate(1.3);
  animation: nf-pop var(--nf-speed) ease-out both;
}
.nf-modal.sm { width: min(400px, 100%); }
.nf-modal.lg { width: min(820px, 100%); }
.nf-modal.wide { width: min(1080px, 100%); }
.nf-modal-head {
  display: flex; align-items: center; gap: 10px; flex: 0 0 auto; padding: 12px 16px;
  border-bottom: 1px solid var(--nf-glass-brd); font-size: 14.5px; font-weight: 600;
}
.nf-modal-close {
  margin-left: auto; width: 26px; height: 26px; padding: 0; border: 0; border-radius: var(--nf-radius-sm);
  background: transparent; color: var(--nf-text-mute); font-size: 15px; cursor: pointer;
  transition: background-color var(--nf-speed) ease, color var(--nf-speed) ease;
}
.nf-modal-close:hover { background: rgba(255, 255, 255, 0.07); color: var(--nf-text); }
.nf-modal-body {
  flex: 1 1 auto; min-height: 0; padding: 16px; overflow: auto; color: var(--nf-text-dim);
  scrollbar-width: thin; scrollbar-color: var(--nf-line) transparent;
}
.nf-modal-body strong { color: var(--nf-text); }
.nf-modal-foot {
  display: flex; align-items: center; justify-content: flex-end; gap: 8px; flex: 0 0 auto; padding: 11px 16px;
  border-top: 1px solid var(--nf-glass-brd); background: rgba(255, 255, 255, 0.03);
}
.nf-modal-danger .nf-modal-head { color: var(--nf-danger); }
.nf-modal-require {
  margin-top: 10px; padding: 10px; border: 1px solid var(--nf-danger-line); border-radius: var(--nf-radius-sm);
  background: var(--nf-danger-soft);
}
.nf-drawer-mask {
  position: fixed; inset: 0; z-index: 1000; background: rgba(5, 9, 8, 0.55); backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px);
  animation: nf-fade-in var(--nf-speed) ease-out both;
}
.nf-drawer {
  position: fixed; top: 0; right: 0; bottom: 0; z-index: 1001; display: flex; flex-direction: column;
  width: min(460px, 92vw); background: var(--nf-glass-strong); border-left: 1px solid var(--nf-glass-brd);
  box-shadow: var(--nf-shadow); backdrop-filter: blur(18px) saturate(1.3); -webkit-backdrop-filter: blur(18px) saturate(1.3);
  animation: nf-pop var(--nf-speed) ease-out both;
}
.nf-drawer.left { right: auto; left: 0; border-left: 0; border-right: 1px solid var(--nf-glass-brd); }
.nf-drawer-head {
  display: flex; align-items: center; gap: 10px; flex: 0 0 auto; padding: 12px 16px;
  border-bottom: 1px solid var(--nf-glass-brd); font-size: 14.5px; font-weight: 600;
}
.nf-drawer-body {
  flex: 1 1 auto; min-height: 0; padding: 16px; overflow: auto; scrollbar-width: thin;
  scrollbar-color: var(--nf-line) transparent;
}
.nf-drawer-foot {
  display: flex; align-items: center; justify-content: flex-end; gap: 8px; flex: 0 0 auto; padding: 11px 16px;
  border-top: 1px solid var(--nf-glass-brd); background: rgba(255, 255, 255, 0.03);
}
.nf-toast-wrap {
  position: fixed; right: 16px; bottom: 16px; z-index: 1200; display: flex; flex-direction: column;
  align-items: flex-end; gap: 8px; max-width: min(380px, calc(100vw - 32px)); pointer-events: none;
}
.nf-toast {
  display: flex; align-items: flex-start; gap: 8px; width: 100%; padding: 10px 12px;
  border: 1px solid var(--nf-glass-brd); border-left: 3px solid var(--nf-text-mute);
  border-radius: var(--nf-radius-sm); background: var(--nf-glass-strong);
  box-shadow: var(--nf-shadow); backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
  color: var(--nf-text-dim); font-size: 13px; pointer-events: auto;
  animation: nf-pop var(--nf-speed) ease-out both;
}
.nf-toast-title { color: var(--nf-text); font-weight: 600; }
.nf-toast-body { margin-top: 2px; color: var(--nf-text-dim); font-size: 12.5px; word-break: break-word; }
.nf-toast.tone-ok, .nf-toast.tone-info, .nf-toast.tone-warn, .nf-toast.tone-danger, .nf-toast.tone-accent, .nf-toast.tone-muted { border-left-width: 3px; }
.nf-toast.tone-ok { border-left-color: var(--nf-ok); }
.nf-toast.tone-info { border-left-color: var(--nf-info); }
.nf-toast.tone-accent { border-left-color: var(--nf-accent); }
.nf-toast.tone-warn { border-left-color: var(--nf-warn); }
.nf-toast.tone-danger { border-left-color: var(--nf-danger); }
.nf-toast.tone-muted { border-left-color: var(--nf-text-mute); }
.nf-toast.tone-critical, .nf-toast.tone-high, .nf-toast.tone-error, .nf-toast.tone-warning, .nf-toast.tone-debug { border-left-width: 3px; }
.nf-toast.tone-critical, .nf-toast.tone-high, .nf-toast.tone-error { border-left-color: var(--nf-danger); }
.nf-toast.tone-warning { border-left-color: var(--nf-warn); }
.nf-toast.tone-debug { border-left-color: var(--nf-text-mute); }

/* ==== 12. 数据展示 ==== */
.nf-kv {
  display: grid; grid-template-columns: minmax(90px, max-content) minmax(0, 1fr); gap: 7px 14px; margin: 0;
  font-size: 13px;
}
.nf-kv dt, .nf-kv-k { color: var(--nf-text-mute); font-size: 12.5px; }
.nf-kv dd, .nf-kv-v { margin: 0; color: var(--nf-text); word-break: break-word; }
.nf-kv.nf-kv-inline { grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); }
.nf-timeline { position: relative; margin: 0; padding: 2px 0 2px 18px; list-style: none; }
.nf-timeline::before {
  content: ""; position: absolute; top: 6px; bottom: 6px; left: 5px; width: 1px;
  background: var(--nf-line-soft);
}
.nf-timeline-item { position: relative; padding: 0 0 14px; }
.nf-timeline-item:last-child { padding-bottom: 0; }
.nf-timeline-dot {
  position: absolute; top: 5px; left: -17px; width: 9px; height: 9px; border-radius: 50%;
  background: var(--nf-panel); border: 2px solid var(--nf-text-mute);
}
.nf-timeline-item.nf-tone-ok .nf-timeline-dot { border-color: var(--nf-ok); box-shadow: 0 0 8px rgba(62, 207, 142, 0.45); }
.nf-timeline-item.nf-tone-warn .nf-timeline-dot { border-color: var(--nf-warn); box-shadow: 0 0 8px rgba(240, 180, 69, 0.45); }
.nf-timeline-item.nf-tone-danger .nf-timeline-dot { border-color: var(--nf-danger); box-shadow: 0 0 8px rgba(240, 97, 90, 0.45); }
.nf-timeline-item.nf-tone-info .nf-timeline-dot { border-color: var(--nf-info); box-shadow: 0 0 8px rgba(88, 176, 245, 0.45); }
.nf-timeline-time { color: var(--nf-text-mute); font-size: 11.5px; font-family: var(--nf-font-mono); }
.nf-timeline-title { color: var(--nf-text); font-size: 13.5px; font-weight: 500; }
.nf-timeline-desc { color: var(--nf-text-dim); font-size: 12.5px; }
.nf-tree { padding: 2px 0; font-size: 13px; list-style: none; margin: 0; }
.nf-tree-node { display: block; color: var(--nf-text-dim); }
.nf-tree-row, .nf-tree-label {
  display: flex; align-items: center; gap: 6px; padding: 4px 6px; border-radius: var(--nf-radius-sm);
  cursor: pointer; transition: background-color var(--nf-speed) ease, color var(--nf-speed) ease;
}
.nf-tree-row:hover, .nf-tree-label:hover { background: rgba(255, 255, 255, 0.05); color: var(--nf-text); text-decoration: none; }
.nf-tree-node.active > .nf-tree-row, .nf-tree-node.active > .nf-tree-label { background: var(--nf-accent-soft); color: var(--nf-text); }
.nf-tree-toggle {
  flex: 0 0 auto; width: 14px; border: 0; background: transparent; color: var(--nf-text-mute); font-size: 10px;
  cursor: pointer; transition: transform var(--nf-speed) ease;
}
.nf-tree-toggle.open { transform: rotate(90deg); }
.nf-tree-badge { margin-left: auto; }
.nf-tree-children { margin: 0 0 0 14px; padding: 0; list-style: none; border-left: 1px dashed var(--nf-line-soft); }
.nf-code {
  display: block; max-width: 100%; margin: 0 0 10px; padding: 10px 12px; overflow: auto;
  background: var(--nf-bg-soft); border: 1px solid var(--nf-line-soft); border-radius: var(--nf-radius-sm);
  color: var(--nf-text); font-family: var(--nf-font-mono); font-size: 12.5px; line-height: 1.6;
  white-space: pre; tab-size: 2; scrollbar-width: thin; scrollbar-color: var(--nf-line) transparent;
}
.nf-terminal {
  display: flex; flex-direction: column; gap: 2px; min-height: 200px; max-height: 420px; padding: 10px 12px;
  overflow: auto; background: #081009; border: 1px solid var(--nf-line); border-radius: var(--nf-radius-sm);
  color: #cfe3d8; font-family: var(--nf-font-mono); font-size: 12.5px; line-height: 1.65; scrollbar-width: thin;
  scrollbar-color: #33454e transparent;
}
.nf-terminal-line { white-space: pre-wrap; word-break: break-word; color: #cfe3d8; }
.nf-terminal-line.cmd { color: #e7eef2; }
.nf-terminal-line.ok { color: var(--nf-ok); }
.nf-terminal-line.warn { color: var(--nf-warn); }
.nf-terminal-line.err { color: var(--nf-danger); }
.nf-terminal-line.dim { color: #6e828f; }
.nf-terminal-input-line { display: flex; align-items: center; gap: 6px; }
.nf-terminal-prompt { color: var(--nf-accent-2); white-space: pre; }
.nf-terminal input {
  flex: 1 1 auto; min-width: 0; padding: 0; border: 0; background: transparent; color: #e7eef2;
  font-family: var(--nf-font-mono); font-size: 12.5px; outline: none;
}
.nf-progress {
  position: relative; width: 100%; height: 7px; overflow: hidden; background: var(--nf-panel-2);
  border: 1px solid var(--nf-line-soft); border-radius: 999px;
}
.nf-progress-bar {
  height: 100%; width: 0; border-radius: 999px; background: var(--nf-grad-accent);
  box-shadow: 0 0 10px rgba(53, 208, 127, 0.4);
  transition: width var(--nf-speed) ease;
}
.nf-progress.nf-tone-ok .nf-progress-bar { background: var(--nf-ok); box-shadow: 0 0 10px rgba(62, 207, 142, 0.4); }
.nf-progress.nf-tone-warn .nf-progress-bar { background: var(--nf-warn); box-shadow: 0 0 10px rgba(240, 180, 69, 0.4); }
.nf-progress.nf-tone-danger .nf-progress-bar { background: var(--nf-danger); box-shadow: 0 0 10px rgba(240, 97, 90, 0.4); }
.nf-progress.nf-tone-info .nf-progress-bar { background: var(--nf-info); box-shadow: 0 0 10px rgba(88, 176, 245, 0.4); }
.nf-progress.nf-tone-muted .nf-progress-bar { background: var(--nf-text-mute); box-shadow: none; }
.nf-progress-row { display: flex; align-items: center; gap: 10px; }
.nf-progress-row .nf-progress-label { flex: 0 0 auto; min-width: 96px; color: var(--nf-text-dim); font-size: 12.5px; }
.nf-progress-row .nf-progress-text {
  flex: 0 0 auto; min-width: 42px; color: var(--nf-text-mute); font-size: 12px; text-align: right;
  font-family: var(--nf-font-mono);
}
.nf-tabs { display: flex; align-items: center; gap: 2px; flex-wrap: wrap; border-bottom: 1px solid var(--nf-line-soft); }
.nf-tab {
  position: relative; padding: 8px 12px; border: 0; border-bottom: 2px solid transparent;
  background: transparent; color: var(--nf-text-mute); font-size: 13.5px; font-family: var(--nf-font);
  cursor: pointer; transition: color var(--nf-speed) ease, border-color var(--nf-speed) ease;
}
.nf-tab:hover { color: var(--nf-text); }
.nf-tab.active { color: var(--nf-text); border-bottom-color: var(--nf-accent); }
.nf-tab-badge {
  margin-left: 5px; padding: 0 5px; border-radius: 999px; background: var(--nf-panel-2);
  color: var(--nf-text-dim); font-size: 11px;
}
.nf-tab.active .nf-tab-badge { background: var(--nf-accent); color: #052b1b; font-weight: 600; }
.nf-tab-panel { padding-top: var(--nf-gap); }
.nf-chart { position: relative; width: 100%; min-height: 80px; }
.nf-chart canvas { display: block; width: 100%; }
.nf-chart-empty {
  display: flex; align-items: center; justify-content: center; min-height: 120px; color: var(--nf-text-mute);
  font-size: 12.5px;
}
.nf-legend {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-top: 8px; color: var(--nf-text-dim);
  font-size: 12px;
}
.nf-legend-item { display: inline-flex; align-items: center; gap: 5px; }
.nf-legend-swatch { width: 9px; height: 9px; border-radius: 2px; background: var(--nf-accent); }

/* ==== 13. 状态与反馈 ==== */
.nf-empty {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  padding: 32px 18px; color: var(--nf-text-mute); text-align: center;
}
.nf-empty-icon { font-size: 26px; line-height: 1.2; opacity: 0.7; }
.nf-empty-title, .nf-empty-text { color: var(--nf-text-dim); font-size: 13.5px; }
.nf-empty-desc, .nf-empty-sub { color: var(--nf-text-mute); font-size: 12.5px; max-width: 420px; }
.nf-empty-denied .nf-empty-icon { color: var(--nf-warn); }
.nf-loading { position: relative; pointer-events: none; }
.nf-loading::after {
  content: "载入中…"; position: absolute; inset: 0; z-index: 5; display: flex; align-items: center;
  justify-content: center; background: var(--nf-mask-soft); color: var(--nf-text-dim); font-size: 12.5px;
  letter-spacing: 0.1em; border-radius: inherit;
}
.nf-loading-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: var(--nf-accent); box-shadow: 0 0 8px rgba(53, 208, 127, 0.5); }
/* 骨架屏：2.4s 慢循环微光（reduced-motion 下静止） */
.nf-skeleton {
  display: block; width: 100%; height: 12px; border-radius: var(--nf-radius-sm);
  background: linear-gradient(90deg, var(--nf-panel-2) 25%, rgba(255, 255, 255, 0.07) 50%, var(--nf-panel-2) 75%);
  background-size: 200% 100%; border: 1px solid var(--nf-line-soft);
  animation: nf-shimmer 2.4s ease-in-out infinite;
}
.nf-skeleton-row { display: flex; align-items: center; gap: 10px; padding: 9px 0; }
.nf-skeleton-row .nf-skeleton { flex: 1 1 auto; }
.nf-skeleton-row .nf-skeleton.sm { flex: 0 0 60px; }
.nf-error-banner {
  display: flex; align-items: flex-start; gap: 10px; margin: 0 0 12px; padding: 10px 12px;
  border: 1px solid var(--nf-danger-line); border-left: 3px solid var(--nf-danger);
  border-radius: var(--nf-radius-sm); background: var(--nf-danger-soft); color: var(--nf-text);
  font-size: 12.5px;
}
.nf-error-banner-title { color: var(--nf-danger); font-weight: 600; }
.nf-error-banner-detail {
  margin-top: 3px; color: var(--nf-text-dim); font-family: var(--nf-font-mono); font-size: 11.5px;
  word-break: break-word; white-space: pre-wrap;
}
.nf-error-banner-close { margin-left: auto; border: 0; background: transparent; color: var(--nf-text-mute); cursor: pointer; }
.nf-error-banner-close:hover { color: var(--nf-danger); }
.nf-login {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 14px;
  min-height: 100vh; padding: 24px 16px; background: transparent;
}
/* 登录视图的子块一律不收缩（此前默认 flex-shrink:1，窄屏时被压成一字一行） */
.nf-login > * { flex: 0 0 auto; max-width: 100%; }
.nf-login-logo { width: 34px; height: 34px; font-size: 17px; }
.nf-login-title { font-size: 16px; font-weight: 600; letter-spacing: 0.02em; }
.nf-login-sub { color: var(--nf-text-mute); font-size: 12px; }
.nf-login-pane { margin-top: 12px; }
.nf-login-error { margin-top: 10px; }
.nf-login-foot { line-height: 1.65; }
.nf-login-card {
  width: min(420px, 100%); padding: 22px;
  background: var(--nf-glass-strong); border: 1px solid var(--nf-glass-brd); border-radius: var(--nf-radius);
  box-shadow: var(--nf-shadow), var(--nf-glow-accent);
  backdrop-filter: blur(18px) saturate(1.3); -webkit-backdrop-filter: blur(18px) saturate(1.3);
}
.nf-login-brand { display: flex; align-items: center; gap: 10px; margin-bottom: 2px; font-size: 16px; font-weight: 600; }
.nf-login-desc { margin-bottom: 14px; color: var(--nf-text-mute); font-size: 12.5px; }
.nf-login-demo {
  width: min(720px, 100%); margin-top: 0; padding: 10px 12px; border: 1px solid var(--nf-line-soft);
  border-radius: var(--nf-radius-sm); background: var(--nf-bg-soft); color: var(--nf-text-dim); font-size: 12px;
}
/* 演示账号表在窄屏可横向滚动，而不是把单元格压成竖排 */
.nf-login-demo .nf-table-wrap { overflow-x: auto; }
.nf-login-hint { color: var(--nf-warn); font-size: 11.5px; }

/* 无障碍隐藏（屏幕阅读器可见、视觉隐藏）。此前未定义 ⇒ 顶栏"全局搜索"标签
   被当成普通文本渲染，在窄列里被压成一字一行。 */
.nf-sr-only {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden;
  clip: rect(0, 0, 0, 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}
/* ==== 13.1 外壳补充：app.js 引用但此前未定义样式的类 ==== */
/* 顶栏标题簇（面包屑 + 标题）：纵向排列并允许省略号收尾 */
.nf-top-titles { display: flex; flex-direction: column; justify-content: center; flex: 1 1 auto; min-width: 0; }
.nf-menu-btn { display: none; }                     /* 移动端"☰"汉堡键 */
.nf-side-toggle { display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto; min-width: 30px; padding: 0 8px; }
.nf-side-switch { display: inline-flex; align-items: center; flex: 0 0 auto; }
.nf-search-wrap { display: flex; align-items: center; gap: 6px; flex: 1 1 220px; min-width: 0; }
.nf-search-wrap .nf-input { min-width: 0; }
.nf-user { max-width: 220px; }
.nf-user > span:last-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nf-avatar {
  display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto; width: 22px; height: 22px;
  border-radius: 50%; background: var(--nf-grad-accent); color: #052b1b;
  font-size: 12px; font-weight: 700; box-shadow: 0 0 10px rgba(53, 208, 127, 0.25);
}
.nf-brand-platform { color: var(--nf-text-mute); font-size: 11px; }
.nf-page-head-text { min-width: 0; }
.nf-pop-head, .nf-pop-foot {
  display: flex; align-items: center; gap: 8px; padding: 8px 10px; color: var(--nf-text-dim); font-size: 12.5px;
}
.nf-pop-head { border-bottom: 1px solid var(--nf-glass-brd); font-weight: 600; }
.nf-pop-foot { border-top: 1px solid var(--nf-glass-brd); }
.nf-pop-group {
  padding: 8px 10px 4px; color: var(--nf-text-mute); font-size: 11px; letter-spacing: 0.12em; text-transform: uppercase;
}
/* 跨平台切换面板条目 */
.nf-platform-item {
  display: flex; align-items: center; gap: 8px; padding: 7px 10px; border-radius: var(--nf-radius-sm);
  color: var(--nf-text-dim); text-decoration: none;
}
.nf-platform-item:hover { background: rgba(255, 255, 255, 0.05); color: var(--nf-text); text-decoration: none; }
.nf-platform-text { display: flex; flex-direction: column; min-width: 0; line-height: 1.3; }
.nf-platform-text b { color: var(--nf-text); font-size: 13px; font-weight: 600; }
.nf-platform-text i { color: var(--nf-text-mute); font-size: 11px; font-style: normal; }
/* 全局搜索面板（app.js 动态创建） */
.nf-search-results {
  width: min(520px, 92vw); padding: 6px; background: var(--nf-glass-strong); border: 1px solid var(--nf-glass-brd);
  border-radius: var(--nf-radius); box-shadow: var(--nf-shadow);
  backdrop-filter: blur(16px) saturate(1.25); -webkit-backdrop-filter: blur(16px) saturate(1.25);
}
.nf-search-item {
  display: flex; align-items: center; gap: 8px; width: 100%; padding: 7px 10px; border: 0;
  border-radius: var(--nf-radius-sm); background: transparent; color: var(--nf-text-dim); font-size: 13px;
  text-align: left; cursor: pointer;
}
.nf-search-item:hover { background: rgba(255, 255, 255, 0.06); color: var(--nf-text); }
.nf-search-label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nf-search-sub { flex: 0 0 auto; color: var(--nf-text-mute); font-size: 11.5px; white-space: nowrap; }
/* 用户菜单：app.js 用的是 .nf-user-head/.nf-user-title/.nf-user-sub/.nf-user-foot，
   与 .nf-user-menu-* 同义，这里补上（此前菜单头脚没有分隔线与间距）。 */
.nf-user-head { padding: 8px 10px 10px; border-bottom: 1px solid var(--nf-glass-brd); margin-bottom: 6px; }
.nf-user-title { color: var(--nf-text); font-size: 13.5px; font-weight: 600; }
.nf-user-sub { color: var(--nf-text-mute); font-size: 11.5px; }
.nf-user-foot {
  display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; padding-top: 8px; border-top: 1px solid var(--nf-glass-brd);
}
/* 移动端抽屉打开时锁住背景滚动（app.js 给 .nf-app 加 nf-drawer-open） */
.nf-app.nf-drawer-open { overflow: hidden; }
/* 传感器页用的 5 列网格与工具条行 */
.nf-grid-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
.nf-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }

/* ==== 14. 通知与用户菜单 ==== */
.nf-notify-list {
  position: absolute; top: calc(100% + 8px); right: 0; z-index: 50; width: min(340px, calc(100vw - 32px));
  max-height: 380px; overflow: auto; background: var(--nf-glass-strong); border: 1px solid var(--nf-glass-brd);
  border-radius: var(--nf-radius); box-shadow: var(--nf-shadow);
  backdrop-filter: blur(16px) saturate(1.25); -webkit-backdrop-filter: blur(16px) saturate(1.25);
  scrollbar-width: thin; scrollbar-color: var(--nf-line) transparent;
}
.nf-notify-head {
  display: flex; align-items: center; gap: 8px; padding: 9px 12px; border-bottom: 1px solid var(--nf-glass-brd);
  color: var(--nf-text-dim); font-size: 12.5px; font-weight: 600;
}
.nf-notify-head .nf-btn { margin-left: auto; }
.nf-notify-item {
  display: flex; gap: 8px; padding: 9px 12px; border-bottom: 1px solid var(--nf-glass-brd);
  color: var(--nf-text-dim); font-size: 12.5px; cursor: pointer;
  transition: background-color var(--nf-speed) ease;
}
.nf-notify-item:hover { background: rgba(255, 255, 255, 0.05); }
.nf-notify-item:last-child { border-bottom: 0; }
.nf-notify-item.unread { background: var(--nf-accent-soft); }
.nf-notify-item.unread .nf-notify-title { color: var(--nf-text); font-weight: 600; }
.nf-notify-title { color: var(--nf-text-dim); }
.nf-notify-body { margin-top: 2px; color: var(--nf-text-mute); font-size: 11.5px; word-break: break-word; }
.nf-notify-time { color: var(--nf-text-mute); font-size: 11px; white-space: nowrap; font-family: var(--nf-font-mono); }
.nf-notify-foot { padding: 8px 12px; color: var(--nf-text-mute); font-size: 11.5px; text-align: center; }
.nf-bell-count {
  position: absolute; top: 2px; right: 2px; min-width: 15px; height: 15px; padding: 0 4px; border-radius: 999px;
  background: var(--nf-danger); color: #fff; font-size: 10px; line-height: 15px; text-align: center;
}
.nf-user-menu {
  position: absolute; top: calc(100% + 8px); right: 0; z-index: 50; width: min(240px, calc(100vw - 32px));
  padding: 6px; background: var(--nf-glass-strong); border: 1px solid var(--nf-glass-brd); border-radius: var(--nf-radius);
  box-shadow: var(--nf-shadow);
  backdrop-filter: blur(16px) saturate(1.25); -webkit-backdrop-filter: blur(16px) saturate(1.25);
}
.nf-user-menu-head { padding: 8px 10px 10px; border-bottom: 1px solid var(--nf-glass-brd); margin-bottom: 6px; }
.nf-user-menu-name { color: var(--nf-text); font-size: 13.5px; font-weight: 600; }
.nf-user-menu-meta { color: var(--nf-text-mute); font-size: 11.5px; }
.nf-user-menu-item {
  display: flex; align-items: center; gap: 8px; width: 100%; padding: 7px 10px; border: 0;
  border-radius: var(--nf-radius-sm); background: transparent; color: var(--nf-text-dim); font-size: 13px;
  text-align: left; cursor: pointer;
  transition: background-color var(--nf-speed) ease, color var(--nf-speed) ease;
}
.nf-user-menu-item:hover { background: rgba(255, 255, 255, 0.06); color: var(--nf-text); }
.nf-user-menu-item.danger { color: var(--nf-danger); }
.nf-user-menu-sep { height: 1px; margin: 6px 4px; background: var(--nf-glass-brd); }
.nf-pop-anchor { position: relative; }

/* ==== 15. 响应式 ==== */
/* ≤1200px：侧栏收成图标轨（也可由 .nf-side.collapsed 在任意宽度手动收起） */
@media (max-width: 1200px) {
  .nf-app { grid-template-columns: var(--nf-side-rail-w) minmax(0, 1fr); }
  .nf-side { width: var(--nf-side-rail-w); overflow-x: hidden; }
  .nf-side-brand { justify-content: center; padding: 0 8px; }
  .nf-brand-text, .nf-side-foot, .nf-nav-group-title, .nf-nav-group > .title, .nf-nav-label, .nf-nav-badge { display: none; }
  .nf-nav { padding: 10px 6px 20px; }
  .nf-nav-item { justify-content: center; gap: 0; padding: 9px 0; }
  .nf-split { grid-template-columns: minmax(0, 1fr); }
  .nf-grid.cols-6 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .nf-grid.cols-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .nf-grid-5 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
/* 侧栏手动折叠（任意宽度可用）：app.js 给 .nf-app 或 .nf-side 加 collapsed 均可。
   刻意不使用 :has()，以兼容 Chrome/Edge 100+。 */
.nf-app.collapsed { grid-template-columns: var(--nf-side-rail-w) minmax(0, 1fr); }
.nf-side.collapsed { width: var(--nf-side-rail-w); overflow-x: hidden; }
.nf-side.collapsed .nf-side-brand { justify-content: center; padding: 0 8px; }
.nf-side.collapsed .nf-brand-text, .nf-side.collapsed .nf-nav-group-title, .nf-side.collapsed .nf-nav-group > .title, .nf-side.collapsed .nf-nav-label, .nf-side.collapsed .nf-nav-badge, .nf-side.collapsed .nf-side-foot { display: none; }
.nf-side.collapsed .nf-nav { padding: 10px 6px 20px; }
.nf-side.collapsed .nf-nav-item { justify-content: center; gap: 0; padding: 9px 0; }
/* ≤820px：侧栏变离屏抽屉，顶栏搜索收起，所有多列网格单列 */
@media (max-width: 820px) {
  :root { --nf-top-h: 52px; --nf-pad: 12px; --nf-gap: 10px; }
  .nf-app, .nf-app.collapsed { grid-template-columns: minmax(0, 1fr); }
  .nf-side, .nf-side.collapsed {
    position: fixed; top: 0; left: 0; bottom: 0; z-index: 60; width: min(268px, 82vw); height: 100vh;
    transform: translateX(-100%); transition: transform var(--nf-speed) ease; box-shadow: none;
  }
  .nf-side.open, .nf-side.collapsed.open { transform: translateX(0); box-shadow: var(--nf-shadow); }
/* 抽屉打开时恢复完整标签（含被 .collapsed 收起的场景） */
  .nf-side .nf-side-brand, .nf-side.collapsed .nf-side-brand { justify-content: flex-start; padding: 0 14px; }
  .nf-side .nf-brand-text, .nf-side .nf-nav-label, .nf-side .nf-nav-group-title, .nf-side.collapsed .nf-brand-text, .nf-side.collapsed .nf-nav-label, .nf-side.collapsed .nf-nav-group-title { display: flex; }
  .nf-side .nf-nav, .nf-side.collapsed .nf-nav { padding: 10px 8px 20px; }
  .nf-side .nf-nav-item, .nf-side.collapsed .nf-nav-item { justify-content: flex-start; gap: 10px; padding: 9px 10px; }
  .nf-side .nf-nav-badge, .nf-side.collapsed .nf-nav-badge { display: inline-block; }
  .nf-side .nf-side-foot, .nf-side.collapsed .nf-side-foot { display: block; }
  .nf-main { height: auto; min-height: 100vh; overflow: visible; }
  .nf-top { padding: 0 12px; }
  .nf-top .nf-search, .nf-search, .nf-search-kbd { display: none; }
  .nf-search-wrap { display: none; }
  /* 移动端：用"☰"打开抽屉，"≡"折叠键与跨平台切换按钮收进抽屉语境 */
  .nf-menu-btn { display: inline-flex; }
  .nf-side-toggle, .nf-side-switch { display: none; }
  .nf-crumbs { display: none; }
  .nf-page-head { padding: 12px 12px 10px; }
  .nf-page-body { overflow: visible; padding: 12px; }
  .nf-page-actions { margin-left: 0; width: 100%; }
  .nf-grid, .nf-grid.cols-2, .nf-grid.cols-3, .nf-grid.cols-4, .nf-grid.cols-6, .nf-grid-5 { grid-template-columns: minmax(0, 1fr); }
  .nf-grid > .span-2, .nf-grid > .span-all { grid-column: auto; }
  .nf-form-grid, .nf-form-grid.three, .nf-split, .nf-split.wide-aside { grid-template-columns: minmax(0, 1fr); }
  .nf-kv { grid-template-columns: minmax(0, 1fr); gap: 2px 0; }
  .nf-kv dd, .nf-kv-v { margin-bottom: 8px; }
  .nf-toast-wrap { right: 12px; bottom: 12px; left: 12px; max-width: none; }
  .nf-drawer { width: 92vw; }
  /* 侧栏抽屉（z-index 60）必须盖在遮罩之上；弹层用的遮罩仍为 1000 */
  .nf-drawer-mask { z-index: 55; }
  .nf-modal-mask { align-items: flex-end; padding: 0; }
  .nf-modal { width: 100%; max-height: 88vh; border-radius: var(--nf-radius) var(--nf-radius) 0 0; }
  .nf-sticky { position: static; }
}
@media (max-width: 560px) {
  .nf-stat-value { font-size: 22px; }
  .nf-table th, .nf-table td { padding: 9px 10px; }
  .nf-modal-foot, .nf-drawer-foot { flex-wrap: wrap; }
  .nf-modal-foot .nf-btn, .nf-drawer-foot .nf-btn { flex: 1 1 auto; }
}

/* ==== 16. 布局与尺寸一致性（滚动模型 / 控件高度 / 折叠轨 / 卡片等高）==== */
:root { --nf-ctl-h: 34px; --nf-ctl-h-sm: 28px; }

/* 16.1 页面滚动（滚轮可用）+ 页头吸顶玻璃 */
.nf-page {
  overflow-y: auto; overflow-x: hidden; overscroll-behavior: contain;
  scrollbar-width: thin; scrollbar-color: var(--nf-line) transparent;
}
.nf-page::-webkit-scrollbar { width: 10px; height: 10px; }
.nf-page::-webkit-scrollbar-track { background: transparent; }
.nf-page::-webkit-scrollbar-thumb { border-radius: 999px; background: var(--nf-line); }
.nf-page::-webkit-scrollbar-thumb:hover { background: var(--nf-text-mute); }
/* 外层已经滚了，内层不再各自滚（否则出现双滚动条、滚轮先滚到内层到底的怪异手感） */
.nf-page > .nf-page-body { overflow: visible; min-height: 0; }
.nf-page-head {
  position: sticky; top: 0; z-index: 6;
  background: var(--nf-glass); border-bottom: 1px solid var(--nf-glass-brd);
  backdrop-filter: blur(14px) saturate(1.2); -webkit-backdrop-filter: blur(14px) saturate(1.2);
}
/* 表格横向滚动容器在页面滚动时不要被裁 */
.nf-table-wrap { max-width: 100%; }

/* 16.2 控件高度统一（工具栏不再高低不齐） */
.nf-input, .nf-select { height: var(--nf-ctl-h); padding-top: 0; padding-bottom: 0; }
.nf-input.sm, .nf-select.sm { height: var(--nf-ctl-h-sm); padding-top: 0; padding-bottom: 0; }
.nf-btn { height: var(--nf-ctl-h); }
.nf-btn.sm { height: var(--nf-ctl-h-sm); }
.nf-btn.lg { height: 40px; }
.nf-toolbar, .nf-page-actions, .nf-card-head, .nf-card-extra { align-items: center; }
/* 工具栏里的控件：给合理的基础宽度，而不是被基础规则的 width:100% 撑成等分 */
.nf-toolbar > .nf-input, .nf-toolbar > .nf-select { width: auto; flex: 0 1 200px; min-width: 132px; }
.nf-toolbar > .nf-input.grow, .nf-toolbar > .nf-search { flex: 1 1 260px; }
.nf-toolbar > .nf-btn { flex: 0 0 auto; }

/* 16.3 折叠轨一致性：网格与标签同时收起（两种 collapsed 写法都覆盖） */
.nf-app.collapsed .nf-side { width: var(--nf-side-rail-w); }
.nf-app.collapsed .nf-side-brand { justify-content: center; padding: 0 8px; }
.nf-app.collapsed .nf-brand-text,
.nf-app.collapsed .nf-nav-label,
.nf-app.collapsed .nf-nav-group-title,
.nf-app.collapsed .nf-nav-group > .title,
.nf-app.collapsed .nf-nav-badge,
.nf-app.collapsed .nf-side-foot,
.nf-app.collapsed .nf-side-switch,
.nf-side.collapsed .nf-side-switch { display: none; }
.nf-app.collapsed .nf-nav-item { justify-content: center; gap: 0; padding: 9px 0; }
/* 轨道里任何元素都不许出现换行文字（防"一字一行"） */
.nf-side.collapsed .nf-nav, .nf-app.collapsed .nf-nav { padding: 10px 6px 20px; }
.nf-side.collapsed .nf-nav-item, .nf-app.collapsed .nf-nav-item { white-space: nowrap; overflow: hidden; }

/* 16.4 卡片等高（同一网格内不再高矮不一） */
.nf-grid > .nf-card,
.nf-grid > .nf-stat,
.nf-grid > .span-2 > .nf-card,
.nf-grid > .span-all > .nf-card { height: 100%; }
.nf-grid > * { min-width: 0; }
.nf-stat { min-height: 96px; }

/* ==== 17. 减少动效与打印 ==== */
@keyframes nf-fade-in {
  from { opacity: 0; transform: translateY(4px); }
  to { opacity: 1; transform: none; }
}
@keyframes nf-pop {
  from { opacity: 0; transform: scale(0.985); }
  to { opacity: 1; transform: none; }
}
@keyframes nf-shimmer {
  from { background-position: 180% 0; }
  to { background-position: -20% 0; }
}
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important; animation-iteration-count: 1 !important;
    animation-delay: 0ms !important; animation: none !important; transition-duration: 0.001ms !important;
    transition: none !important; scroll-behavior: auto !important;
  }
  .nf-stat:hover, .nf-btn:active { transform: none; }
  .nf-side { transition: none; }
  .nf-skeleton { background: var(--nf-panel-2); }
}
@media print {
  :root, [data-theme="light"], [data-theme="dark"] {
    color-scheme: light; --nf-bg: #ffffff; --nf-panel: #ffffff; --nf-panel-2: #ffffff; --nf-bg-soft: #ffffff;
    --nf-text: #000000; --nf-text-dim: #333333; --nf-text-mute: #555555; --nf-line: #bbbbbb;
    --nf-line-soft: #dddddd; --nf-shadow: none; --nf-shadow-sm: none;
  }
  body::before { display: none; }
  .nf-side, .nf-top, .nf-toast-wrap, .nf-modal-mask, .nf-drawer-mask, .nf-drawer, .nf-notify-list, .nf-user-menu, .nf-error-banner-close, .nf-toolbar, .nf-pager { display: none !important; }
  .nf-app { display: block; min-height: 0; }
  .nf-main { display: block; height: auto; overflow: visible; }
  .nf-page, .nf-page-body { display: block; overflow: visible; padding: 0; }
  .nf-card, .nf-card-body, .nf-modal, .nf-table-wrap { border-color: #dddddd !important; box-shadow: none !important; break-inside: avoid; }
  .nf-table thead th { position: static; }
  body { background: #ffffff; color: #000000; font-size: 11pt; }
  a { color: #000000; text-decoration: underline; }
}

/* ===== WEB-UI-CLIP-FIX BEGIN (2026-09-26) ===== */
/* ==========================================================================
   WEB-UI-CLIP-FIX · 2026-09-26 —— 系统性修复「显示不全 / 被裁切 / 列错位」
   --------------------------------------------------------------------------
   生成者：.scratch/gen-css-fix.py（幂等，可重复运行）
   范围  ：只做"覆盖式补充"，上方既有设计系统一行未改，便于审计与回滚。
   每条规则都标注：对应截图哪一个现象 → 根因 → 为什么这样改能解决。
   ========================================================================== */

/* --------------------------------------------------------------------------
   [现象 1] 左侧导航被挤成"竖排单字"（盘/商/货/订/付/送/餐/销/员/析/微/设 竖着排）
            + 侧栏底部出现横向滚动条
   根因：导航项是 flex 容器（图标 + 文字 + 徽标），但 .nf-nav 自身没有
         `min-width:0`，也没有横向溢出兜底。flex 子项的自动最小尺寸等于
         max-content ⇒ 图标 + 整行文字把侧栏内容宽度"顶"出去；.nf-side 又是
         `overflow-x:hidden`，浏览器于是退回按最小内容宽度排布 ⇒ 逐字换行。
   修法：① .nf-nav 可收缩且横向绝不溢出；② .nf-side 给最小宽度地板，
            保证正常 240px 不被内层内容压缩；③ 图标/徽标禁止压扁，
            文字行占满剩余宽度并用省略号收尾（绝不逐字断行）。
   -------------------------------------------------------------------------- */
.nf-side { min-width: var(--nf-side-w); }
.nf-nav { min-width: 0; overflow-x: clip; }
.nf-nav .nf-nav-item { min-width: 0; }
.nf-nav .nf-nav-icon,
.nf-nav .nf-nav-badge { flex: 0 0 auto; }
.nf-nav .nf-nav-label { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
/* 顶栏面包屑同样禁止"被压成一字一行" */
.nf-crumbs { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.nf-crumbs-current { min-width: 0; overflow: hidden; text-overflow: ellipsis; }

/* --------------------------------------------------------------------------
   [现象 2] 顶部标签栏（hub 小程序 online store workflow …）被右边缘裁掉，
            最后一个标签只露出半个字
   根因：标签行既没换行也没能横向滚动，溢出部分被直接裁掉。
   修法：改为**横向滚动**（而不是换行）：页签是同级互斥导航，换行会让
         .nf-tab 的 2px 底部指示条与 .nf-tabs 的 1px 分隔线在视觉上错位；
         滚动则保持单行基线，与 .nf-table-wrap 的横向滚动行为一致。
         scrollbar 收细；标签自身 flex:0 0 auto 不被压扁；省略号兜底。
   -------------------------------------------------------------------------- */
.nf-tabs {
  overflow-x: auto; overflow-y: hidden; flex-wrap: nowrap; max-width: 100%;
  overscroll-behavior-x: contain; scrollbar-width: thin;
  scrollbar-color: var(--nf-line) transparent;
}
.nf-tabs::-webkit-scrollbar { height: 8px; }
.nf-tabs::-webkit-scrollbar-track { background: transparent; }
.nf-tabs::-webkit-scrollbar-thumb { border-radius: 999px; background: var(--nf-line); }
.nf-tabs > .nf-tab { flex: 0 0 auto; text-overflow: ellipsis; }
.nf-tabs > .nf-tab:only-child { max-width: 100%; }

/* --------------------------------------------------------------------------
   [现象 3 + 6] 主表最右列被卡片裁掉（"转化 13 2…"、"25 8…"）
                + 右边缘还有被裁掉的窄条（"D ×"）
   根因：祖先滚动宿主 `.nf-main{overflow:hidden}` 与 `.nf-page{overflow-x:hidden}`
         把后代的横向滚动一起裁掉了。`.nf-table-wrap` 虽然写了 `overflow:auto`，
         但祖先固定了内容盒宽度 ⇒ 滚动条永远不出现，超出部分只能被切；
         同时 flex/grid 子项默认 `min-width:auto`，不肯收缩，进一步把卡片撑破。
   修法：① `.nf-page` / `.nf-page-body` 改用 `overflow-x:clip`
            —— 只做视觉裁剪、**不建立滚动容器**，于是 `.nf-table-wrap`
            重新成为最近的横向滚动宿主，横向滚动条得以出现；纵向滚动完全保留。
         ② `.nf-main` 同理改 `clip`（不再裁掉后代的横向滚动能力）。
         ③ 所有 flex/grid 包裹层补齐 `min-width:0`（治"子项撑破父级"通病）。
   -------------------------------------------------------------------------- */
.nf-main { overflow-x: clip; }
.nf-page { overflow-x: clip; }
.nf-page > .nf-page-body,
.nf-page-body { min-width: 0; overflow-x: clip; }
.nf-split,
.nf-split > *,
.nf-grid,
.nf-grid > *,
.nf-card,
.nf-card > *,
.nf-sticky { min-width: 0; }

/* --------------------------------------------------------------------------
   [现象 4] 「版本记录」表列错位/重叠：版本号列里叠着"历史版本"、
            `1.4.20` 显示成 `1 4 20`、发布时间与状态挤在一起
   根因（实证，见 .scratch/WEB-UI-CLIP-FIX-REPORT.md）：
         **不是表头与单元格列数不一致** —— boot-pages.js 在真实 DOM 上断言过
         thead 单元格总数 == 每一行 tbody 单元格总数，store/小程序管理页两张表
         均为 8/8，全部 66 个注册页面 0 处不一致。真正原因是 `.nf-table` 的
         `white-space:nowrap` 被违背：祖先裁切把单元格压窄后，浏览器在"无滚动条
         可用"的前提下退回按最小内容宽度排布，`1.4.20` 于是在 `.`/数字之间断行
         ⇒ 视觉上成了 `1 4 20`，并与相邻「历史版本」标签挤在同一格宽度里，
         看上去就是"列错位/重叠"。
   修法：两条腿都要有 ——
         ① 上面 [现象 3] 让横向滚动重新可用，单元格不再被压窄（**主修法**）；
         ② 显式声明单元格行为：普通单元格 nowrap（靠横向滚动保证完整可读），
            长"文字列"（变更说明/说明/备注/地址/内容…）由 JS 打 `nf-cell-wrap`
            类后允许换行；需要单行+省略号的用 `nf-cell-ellipsis`。
   -------------------------------------------------------------------------- */
.nf-table-wrap { min-width: 0; max-width: 100%; overflow-x: auto; }
.nf-table { width: max-content; min-width: 100%; }
/* .nf-table 自身声明了 white-space:nowrap（不想改旧行），这里把"是否 nowrap"下移到
   单元格上，并给单元格显式声明溢出兜底 —— 于是 nowrap 的兜底责任明确落在
   .nf-table-wrap 的横向滚动上，不再出现"nowrap 却没有溢出策略"的悬空状态。 */
.nf-table thead, .nf-table tbody, .nf-table tr { white-space: normal; }
.nf-table th, .nf-table td { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.nf-table td.nf-cell-wrap, .nf-table th.nf-cell-wrap { white-space: normal; word-break: break-word; }
.nf-table td.nf-cell-ellipsis, .nf-table th.nf-cell-ellipsis {
  max-width: 320px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* --------------------------------------------------------------------------
   [现象 5] 右侧二维码卡片底部文字被裁
            （"矩阵 396×396 px … 示意：pages/index/index?share=1" 被切）
   根因：卡片位于 `.nf-split` 的 grid 子项（aside）里，grid/flex 子项默认
         `min-width:auto` 不肯收缩；卡片正文又是必然很长的中文 + 长英文路径。
   修法：① grid 子项 `min-width:0`（上面已补）让卡片宽度由容器决定；
         ② 卡片正文允许换行（中文按字断、长英文路径按词断）；
         ③ canvas 保持 `max-width:100%` 并补高度自适应，避免把卡片挤宽。
   -------------------------------------------------------------------------- */
.nf-aside, .nf-split > aside { min-width: 0; max-width: 100%; }
.nf-aside .nf-card, .nf-aside .nf-card-body { min-width: 0; max-width: 100%; }
.nf-aside .nf-card-body { overflow-wrap: anywhere; word-break: break-word; }
.nf-aside canvas { max-width: 100%; height: auto; }
.nf-aside .nf-toolbar { flex-wrap: wrap; }
/* 弹窗/抽屉正文同样允许长内容换行，避免右侧文字被裁 */
.nf-modal-body, .nf-drawer-body { overflow-wrap: anywhere; }
.nf-modal { overflow-x: clip; }
/* 顶栏搜索结果副标题 / 通知时间原本只有 nowrap+hidden，补省略号兜底 */
.nf-search-sub, .nf-notify-time { min-width: 0; overflow: hidden; text-overflow: ellipsis; }

/* --------------------------------------------------------------------------
   窄屏收尾：≤1200px 侧栏收成 64px 图标轨，绝不能再出现文字
   （否则就是"竖排单字"的另一条触发路径）；≤820px 分栏改单列。
   -------------------------------------------------------------------------- */
@media (max-width: 1200px) {
  .nf-side { min-width: var(--nf-side-rail-w); }
  .nf-nav { overflow-x: clip; }
  .nf-side .nf-nav-item { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
}
@media (max-width: 820px) {
  .nf-side { min-width: 0; }
  .nf-split, .nf-split.wide-aside { grid-template-columns: minmax(0, 1fr); }
  .nf-table { width: max-content; min-width: 100%; }
  .nf-tabs { flex-wrap: nowrap; }
}
/* ===== WEB-UI-CLIP-FIX END ===== */

/* ===== NFOS-CSS-JSCOMPAT BEGIN (2026-09-26) ===== */
/* 目的：候选 A 采用基线（原始墨绿玻璃主题）的头部，因此并发 agent 在 19:19 之后
   为「长文本列」新增的 JS 开关（apps/web/<site>/js/core/ui.js 的 col.wrap /
   col.ellipsis → 输出 class="nf-cell-wrap" / class="nf-cell-ellipsis"）在基线里
   没有对应定义。这里只补**显示相关**的最小类，不带任何配色/玻璃/圆角改动。
   依据（本地扫描结果，见报告「候选 A 的合成与验收」）：
     · store/js/core/ui.js  渲染列时按 col.wrap 输出 .nf-cell-wrap
     · store/js/core/ui.js  渲染列时按 col.ellipsis 输出 .nf-cell-ellipsis
     · store/js/pages/mall.js / delivery.js 里已有 wrap: true 的列在用 */
.nf-cell-wrap { white-space: normal; overflow-wrap: anywhere; word-break: break-word; }
.nf-cell-ellipsis { max-width: 320px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* .nf-wrapsafe：store 站 9 处「必须看全」的非表格字段在用（本次同批上线） */
.nf-wrapsafe { white-space: normal; overflow: visible; text-overflow: clip; overflow-wrap: anywhere; word-break: break-word; min-width: 0; }
/* ===== NFOS-CSS-JSCOMPAT END ===== */
/* ===== NFOS-UI-GAPFIX BEGIN (2026-09-26) ===== */
/* ==========================================================================
   NFOS-UI-GAPFIX · 2026-09-26 —— 「显示不全」缺口补齐（追加区，同级规则后者胜出）
   --------------------------------------------------------------------------
   与上方 WEB-UI-CLIP-FIX 区块**兼容叠加**：不涉及任何表格选择器，不改主题色、
   不改尺寸体系、不动动画。生成者：.scratch/patch-nf-css-gaps.py（幂等，可重跑；
   删掉本区块即可完整回滚）。配套 JS 改动：apps/web/store/js/pages/*.js 的 9 处
   .nf-nowrap → .nf-wrapsafe（见报告 3.2 节）。
   ========================================================================== */

/* ---- R1【回归修复，高优先】折叠侧栏被 min-width 撑开
   CLIP-FIX 给侧栏加了 `.nf-side { min-width: var(--nf-side-w) }`（=240px）地板，
   但折叠态只写了 `width: var(--nf-side-rail-w)`（=64px）——min-width 大于 width，
   于是 **>1200px 视口手动点顶栏"≡"折叠时**，侧栏实际仍占 240px，而网格列只有 64px，
   侧栏整块底色压在正文左侧约 176px 上（导航文字虽被 display:none 隐藏，遮挡仍在）。
   ≤1200px 因 CLIP-FIX 的媒体查询把 min-width 改成 64px，所以只有大窗口能撞上。
   修法：折叠态的 min-width 与 width 一起回到轨道宽度。 */
.nf-app.collapsed .nf-side,
.nf-side.collapsed { min-width: var(--nf-side-rail-w); }

/* ---- R2 新增 `.nf-wrapsafe`：给"信息必须完整"的非表格字段用。
   语义 = 放得下就一行、放不下就换行，并且允许在 IP/工单号/dedup_key/URL 这种
   没有空格的长串内部断词；软换行不会插入换行符，选中复制出来的值仍然完整。
   配套 JS 里同时给了 title 属性，悬停可看全。
   为什么不用现成的 `.nf-nowrap`：它是 `nowrap + overflow:hidden + text-overflow:ellipsis`，
   在表格单元格里是正确选择（列宽稳定、配合横向滚动），所以保留它的语义不动，
   只把 store 站那 9 处"必须看全"的调用点换成这个新类。 */
.nf-wrapsafe {
  white-space: normal; overflow: visible; text-overflow: clip;
  overflow-wrap: anywhere; word-break: break-word; min-width: 0;
}

/* ---- R3 `.nf-code`（长 JSON / 长 nginx 配置 / 长 URL 查看器）：
   保持 `white-space: pre` 语义（代码/JSON 缩进对齐不能被破坏），显式补齐
   「不撑破父容器 + 可横向滚动查看」。注意：基础规则里已经有 max-width:100% 与
   overflow:auto，这里显式复述一遍，防止后续有人改动基础规则时把这两条丢掉
   （`.nf-code` 也被 hub 的在线编辑器当行号栏用，pre 语义必须保留）。 */
.nf-code { max-width: 100%; overflow: auto; }

/* ---- R4 弹层/抽屉的窄屏宽度。
   ui.js:111 `box.style.width = px(opts.width || (drawer ? 420 : 480))` 写的是**内联样式**，
   优先级高于第 15 节媒体查询里的 `.nf-modal{width:100%}` / `.nf-drawer{width:92vw}`
   ⇒ 在 360~400px 的手机上，弹层仍按 480px（抽屉 420px）渲染，被推出屏幕两侧，
   右侧内容（含 footer 按钮）既看不到也点不到。max-width 能约束内联 width，
   且宽屏下默认宽度完全不变。 */
.nf-modal { max-width: 100%; }
.nf-drawer { max-width: 92vw; }

/* ---- R5 下拉浮层的高度上限。
   它们是 absolute 定位的浮层，页面滚动带不动；内容一多就越出视口，
   用户既滚不到也点不到。用 min() 夹进视口（文件里已有先例：`.nf-modal{width:min(560px,100%)}`），
   并让浮层自己纵向滚动。 */
.nf-search-results { max-height: min(60vh, 420px); overflow: auto; overscroll-behavior: contain; }
.nf-user-menu { max-height: min(60vh, 420px); overflow: auto; overscroll-behavior: contain; }
/* 原为固定 max-height:380px：在 <420px 高的窗口里，顶栏下拉的顶端会被推出屏幕外 */
.nf-notify-list { max-height: min(70vh, 380px); overflow: auto; overscroll-behavior: contain; }

/* ---- 边界声明（不写规则，只写契约）：本区块不包含任何 .nf-table / .nf-table-wrap /
   .nf-cell-* / .nf-kv 选择器，因此不会与 WEB-UI-CLIP-FIX 的表格策略产生层叠竞争。 */
/* ===== NFOS-UI-GAPFIX END ===== */

/* ===== NFOS-UI-SIDEBAR-STICKY-FIX BEGIN (2026-09-28) ===== */
/* ==========================================================================
   NFOS-UI-SIDEBAR-STICKY-FIX · 2026-09-28
   --------------------------------------------------------------------------
   修两个**用户截图里仍然存在**的现象。生成者：.scratch/gen-sidebar-sticky-fix.py
   （幂等，可重跑；删掉本块即完整回滚）。
   --------------------------------------------------------------------------
   [现象 A] 侧栏在「折叠 / 窄栏」状态下文字被压成"每行一个字"
     · 用户列出的 18 个字（盘监户管审布报警单障巡产访感视令则盾）就是
       js/core/ui.js 的 ICONS 单字图标表（dashboard盘 monitor监 audit审 user户 …），
       即"图标轨"本身；但**只要标签没被隐藏或没被 nowrap 兜住**，同一个位置就会
       退化成逐字换行。
     · 已确认的两条真实漏洞：
       (1) 第 15 节 @media (max-width:820px) 里有一条
           `.nf-side .nf-nav-label{display:flex}`（抽屉态恢复标签），它的层叠位置
           **晚于**折叠态那条 `display:none` ⇒ 窄屏 + 折叠时标签会被重新点亮；
       (2) CLIP-FIX 给 `.nf-side` 加了 `min-width:var(--nf-side-w)`(=240px) 地板，
           而折叠态只写了 width=64px ⇒ 宽视口下 min-width 仍按 240px 生效，
           64px 的网格列里塞 240px 的盒子（侧栏压住正文 / 内容被压扁）。
     · 修法：折叠态（含窄屏媒体查询）**显式**把文字元素压成 display:none，
       并给整条轨道加"绝不竖排"的硬兜底；轨道宽度与最小宽度成对声明、带地板。
   --------------------------------------------------------------------------
   [现象 B] 「发布模板管理」等页面的表头出现两层文字叠在一起
     · 根因 = 玻璃拟态的半透明吸顶底：
       `.nf-page-head{position:sticky;background:var(--nf-glass)}`，
       `--nf-glass = rgba(20,29,25,.72)`（浅色为 rgba(255,255,255,.72)），
       叠 `backdrop-filter:blur(14px)` ⇒ 正文滚过时 28% 的底色 + 模糊就是
       "两层文字叠在一起"（用户举的例子 `秒表农重作业通知模板` /
       `州委农重作业安排通知` 正好各差两个字，是模糊鬼影的典型特征）。
       `.nf-top`（顶栏）同病。
     · 表格侧另有一处结构缺陷：`js/core/ui.js` 给 `<thead>` 打了 `.nf-sticky`
       （`position:sticky;top:calc(--nf-top-h+10px)=66px;z-index:4;无背景`），
       同时 `.nf-table thead th` 自己也是 sticky(top:0,z-index:2,不透明底)
       ⇒ 一个表头上**两层吸顶、两个 top、两个 z-index**，其中一层还没有背景。
       修法：表头改用 `.nf-thead`（只有 th 一层吸顶、显式不透明底 + z-index），
       `.nf-sticky` 只管"侧栏卡片吸顶"这一类用法并补上不透明底。
     · 为什么不做"统一改不吸顶"：吸顶本身是用户可感知的价值（长表滚动时保留列名），
       而"叠字"的充分条件是**背景不透明**——把背景钉成实色即可根治，
       不必牺牲功能。因此本块选择 **保留吸顶 + 强制不透明底 + 明确 z-index 分层**，
       并给出经过实测的判据（见 .scratch/audit-shell-sticky.py 的 B1/B2）。
   ========================================================================== */

/* ---- 0. 实色回退令牌：玻璃拟态下所有吸顶/浮层元素都用它做底 ----
   为什么不用 color-mix()：目标浏览器是"现代但不保证最新"（项目注释里明确写了
   "不依赖 color-mix，兼容 Safari 15"），而后两个令牌本身就**已经是实色**
   （暗色 #141d19 / 浅色 #ffffff），直接引用最稳。 */
:root { --nf-solid: var(--nf-panel); --nf-solid-2: var(--nf-panel-2); }

/* ==== A. 折叠 / 窄栏状态：文字一律隐藏，绝不竖排 ==== */
/* A1 折叠态（两种写法都覆盖）：把文字元素彻底移出渲染树。
      !important 是刻意的 —— 必须压过第 15 节那条"抽屉态恢复标签"的 display:flex。 */
.nf-side.collapsed .nf-nav-label,
.nf-side.collapsed .nf-nav-badge,
.nf-side.collapsed .nf-nav-group-title,
.nf-side.collapsed .nf-nav-group > .title,
.nf-side.collapsed .nf-brand-text,
.nf-side.collapsed .nf-side-foot,
.nf-side.collapsed .nf-side-switch,
.nf-app.collapsed .nf-nav-label,
.nf-app.collapsed .nf-nav-badge,
.nf-app.collapsed .nf-nav-group-title,
.nf-app.collapsed .nf-nav-group > .title,
.nf-app.collapsed .nf-brand-text,
.nf-app.collapsed .nf-side-foot,
.nf-app.collapsed .nf-side-switch { display: none !important; }

/* A2 图标轨几何：宽度与最小宽度**成对**声明，并给一个 >=48px 的地板。
      只有"显式折叠"才收轨；≥820px 的抽屉态（.nf-side.open）不受影响。 */
.nf-side.collapsed,
.nf-app.collapsed .nf-side {
  width: var(--nf-side-rail-w) !important;
  min-width: var(--nf-side-rail-w) !important;
  max-width: var(--nf-side-rail-w) !important;
  overflow-x: hidden !important;
  overflow-y: auto;
}
.nf-side.collapsed .nf-nav,
.nf-app.collapsed .nf-nav { padding: 10px 6px 20px; min-width: 0; overflow-x: clip; }
.nf-side.collapsed .nf-nav-item,
.nf-app.collapsed .nf-nav-item {
  justify-content: center; gap: 0; padding: 9px 0; min-width: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* 图标本身也不许被压扁成两行（图标是单字，宽度 20px + text-align:center） */
.nf-side.collapsed .nf-nav-icon,
.nf-app.collapsed .nf-nav-icon {
  flex: 0 0 auto; width: 20px; min-width: 20px; line-height: 1;
  white-space: nowrap; overflow: hidden; text-align: center;
}
/* A3 兜底：任何情况下，轨道里的文字都不换行、超宽就裁 —— 不可能出现"一字一行" */
.nf-side.collapsed .nf-nav,
.nf-side.collapsed .nf-nav-item,
.nf-side.collapsed .nf-nav-item * ,
.nf-app.collapsed .nf-nav,
.nf-app.collapsed .nf-nav-item,
.nf-app.collapsed .nf-nav-item * { white-space: nowrap; overflow-wrap: normal; word-break: keep-all; }

/* A4 窄屏媒体查询里的"恢复标签"必须让位于折叠态：
      只在**抽屉真正打开**（.nf-side.open）时恢复，而不再无脑 `.nf-side .nf-nav-label`。 */
@media (max-width: 820px) {
  .nf-side.collapsed:not(.open) .nf-nav-label,
  .nf-side.collapsed:not(.open) .nf-nav-group-title,
  .nf-side.collapsed:not(.open) .nf-nav-group > .title,
  .nf-side.collapsed:not(.open) .nf-brand-text,
  .nf-side.collapsed:not(.open) .nf-nav-badge,
  .nf-side.collapsed:not(.open) .nf-side-foot { display: none !important; }
  .nf-side.collapsed:not(.open) .nf-nav-item { justify-content: center; gap: 0; }
}

/* A5 展开态基线：标签单行 + 省略号 + 合理最小宽度（切换折叠/展开不跳字） */
.nf-nav-item { min-width: 0; white-space: nowrap; }
.nf-nav-label { flex: 1 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.nf-nav-icon, .nf-nav-badge { flex: 0 0 auto; }

/* ==== B. 吸顶 / 浮层：一律不透明底 + 明确 z-index 分层 ==== */
/* B0 滚动宿主归一：`.nf-page-body` 的纵向滚动在 CLIP-FIX 里已被改成
      `overflow: visible`，于是全站真正的滚动宿主其实是外层 `.nf-page`。
      这里显式声明，避免"吸顶元素找不到最近滚动宿主、贴到视口上"这种
      不可预测的叠层（叠字的一大来源）。 */
.nf-page { position: relative; }
.nf-page-body { overflow: visible; }
.nf-table-wrap { overflow-x: auto; overflow-y: visible; }
/* B1 顶栏与页头：玻璃 `--nf-glass`(alpha .72) → 实色 `--nf-panel`。
      这是"两层文字叠在一起"的**直接根因**：滚动时 28% 透明 + 14px 模糊 = 鬼影叠字。 */
.nf-top {
  background: var(--nf-solid) !important; background-image: none !important;
  z-index: 20;
}
.nf-page-head {
  background: var(--nf-solid) !important; background-image: none !important;
  z-index: 10;
}
/* B2 侧栏（sticky，宽度 240/64 都可能有内容滚过）也给实色底 */
.nf-side { background: var(--nf-solid) !important; background-image: none !important; z-index: 8; }
/* B3 表格吸顶表头：只保留 th 一层。背景显式实色（不再依赖 --nf-panel-2 被谁改过），
      并把卡片上那层"左上到右下"的白色渐变也清掉（渐变会让底色不再是实色）。 */
.nf-table thead th,
.nf-table thead.nf-thead th {
  position: sticky !important; top: 0 !important; z-index: 6 !important;
  background: var(--nf-solid-2) !important; background-image: none !important;
  box-shadow: 0 1px 0 var(--nf-line), 0 6px 12px -8px rgba(0, 0, 0, 0.55);
}
/* B3b 旧的 `.nf-sticky` 挂在 `<thead>` 上时（历史写法/未更新的页面），
      它自带 top:66px + z-index:4 却没有任何背景 —— 这是"两层吸顶"的另一半。
      这里把它在表格语境下**显式停用**，让 th 的那一层成为唯一吸顶层。 */
.nf-table thead.nf-sticky,
.nf-table thead.nf-sticky th {
  position: static !important; top: auto !important; z-index: auto !important;
}
.nf-table thead th { position: sticky !important; top: 0 !important; z-index: 6 !important; }
.nf-table-wrap { background: var(--nf-solid); }
/* B4 `.nf-sticky` 现在只表示"侧栏卡片吸顶"（ui.js 的表格 thead 已改用 .nf-thead）。
      但旧的 `<thead class="nf-sticky">` 若还残留在某个页面上，也必须有不透明底，
      故这里一并给它实色 —— 双保险。 */
.nf-sticky {
  background: var(--nf-solid) !important; background-image: none !important;
  z-index: 8;
}
/* B5 抽屉是 fixed + 半透明玻璃，滚过正文同样会透字：给实色底 */
.nf-drawer { background: var(--nf-solid) !important; background-image: none !important; }
/* B6 打印/窄屏不许再吸顶（沿用既有语义，避免打印出叠字） */
@media print {
  .nf-table thead th { position: static !important; }
  .nf-sticky { position: static !important; }
}
/* ===== NFOS-UI-SIDEBAR-STICKY-FIX END ===== */
