/* ============================================================
   Nuono 控制台 · Apple 玻璃风设计系统
   单一 token 真源：本文件是唯一的视觉变量定义处。
   ============================================================ */

:root {
  /* 桌面背景（浅色：柔和径向渐变 + 苹果灰底） */
  --bg-desktop: radial-gradient(1200px 800px at 85% -10%, #e4ebff 0%, transparent 55%),
                radial-gradient(1000px 700px at -10% 110%, #ffe9f0 0%, transparent 50%),
                radial-gradient(900px 600px at 110% 80%, #e6fbf3 0%, transparent 45%),
                #f5f5f7;

  /* 玻璃表面。
     edge 是 0.5px 发丝描边（玻璃的切面），sheen 是顶部那道镜面反光；
     两者叠在 box-shadow 的 inset 层里，玻璃才有厚度而不是一块半透明色块。 */
  /* 后台工作界面不用实时 backdrop blur。
     页面上同时存在 topbar / panel / metric-card / dock / fanout 等十几个玻璃层，Chromium
     和 WebKit 会分别为它们分配 backdrop 纹理；叠加壁纸的 filter/transform 后，GPU tile
     缓存偶尔会错用裁剪区，产生整块粉蓝色矩形或上一帧残影。这个故障不属于 CSS 层叠，
     z-index / isolation / Portal 都只能改变触发概率，不能保证正确。

     通用后台改用高不透明 translucent surface + 发丝边 + 内高光 + 阴影表达玻璃层次，
     保留壁纸透色但不做实时高斯重采样。登录页的沉浸式单块玻璃使用独立的显式 blur，
     不受这个 token 影响。 */
  --glass: rgba(255, 255, 255, 0.84);
  --glass-strong: rgba(255, 255, 255, 0.96);
  --glass-blur: none;
  --glass-edge: rgba(255, 255, 255, 0.8);
  --glass-sheen: rgba(255, 255, 255, 0.9);
  --surface-solid: #ffffff;
  --bg-hover: rgba(0, 0, 0, 0.045);
  --bg-active: rgba(0, 122, 255, 0.1);
  --bg-inset: rgba(0, 0, 0, 0.035);

  /* 文字。
     壁纸可自定义，卡片背后的明暗不可控，所以二三级文字要留够对比度余量：
     text-2 在白底约 5.9:1，text-3 约 4.6:1，都过 WCAG AA 的 4.5:1。 */
  --text-1: #1d1d1f;
  --text-2: #5c5c61;
  --text-3: #78787d;
  --text-invert: #ffffff;

  /* 分隔线 */
  --hairline: rgba(0, 0, 0, 0.07);
  --hairline-strong: rgba(0, 0, 0, 0.12);

  /* Apple 调色板 */
  --blue: #007aff;
  --blue-hover: #0071e3;
  --blue-pressed: #0051b5;
  --green: #34c759;
  --orange: #ff9500;
  --red: #ff3b30;
  --purple: #af52de;
  --indigo: #5856d6;
  --teal: #5ac8fa;
  --pink: #ff2d55;
  --yellow: #ffcc00;

  --blue-soft: rgba(0, 122, 255, 0.12);
  --green-soft: rgba(52, 199, 89, 0.14);
  --orange-soft: rgba(255, 149, 0, 0.14);
  --red-soft: rgba(255, 59, 48, 0.12);
  --purple-soft: rgba(175, 82, 222, 0.13);
  --indigo-soft: rgba(88, 86, 214, 0.13);
  --teal-soft: rgba(90, 200, 250, 0.16);
  --pink-soft: rgba(255, 45, 85, 0.12);

  /* 图标渐变 */
  --grad-blue: linear-gradient(135deg, #0a84ff 0%, #5e5ce6 100%);
  --grad-green: linear-gradient(135deg, #30d158 0%, #0aa8a8 100%);
  --grad-orange: linear-gradient(135deg, #ff9f0a 0%, #ff453a 100%);
  --grad-pink: linear-gradient(135deg, #ff375f 0%, #bf5af2 100%);
  --grad-purple: linear-gradient(135deg, #bf5af2 0%, #5e5ce6 100%);
  --grad-teal: linear-gradient(135deg, #64d2ff 0%, #0a84ff 100%);

  /* 几何 */
  --radius-lg: 20px;
  --radius-md: 14px;
  --radius-sm: 10px;
  --radius-xs: 7px;

  /* 阴影 */
  --shadow-card: 0 1px 2px rgba(0, 0, 0, 0.04), 0 8px 24px rgba(0, 0, 0, 0.06);
  --shadow-pop: 0 12px 48px rgba(0, 0, 0, 0.16);
  --shadow-btn: 0 1px 3px rgba(0, 122, 255, 0.32);

  /* 字体 */
  --font: -apple-system, BlinkMacSystemFont, "SF Pro SC", "SF Pro Text",
          "PingFang SC", "Inter", "Helvetica Neue", "Microsoft YaHei", sans-serif;
  --mono: ui-monospace, "SF Mono", "JetBrains Mono", "Cascadia Code", Menlo, monospace;

  /* 动效 */
  --ease: cubic-bezier(0.25, 0.1, 0.25, 1);
  --t-fast: 0.15s var(--ease);
  --t-std: 0.24s var(--ease);

  /* 壁纸（运行时由外观设置覆盖） */
  --wallpaper-image: none;
  --wallpaper-blur: 0px;
  --wallpaper-opacity: 1;
}

[data-theme="dark"] {
  --bg-desktop: radial-gradient(1100px 700px at 80% -10%, #101a33 0%, transparent 55%),
                radial-gradient(900px 650px at -10% 110%, #221226 0%, transparent 50%),
                #0b0b0d;

  --glass: rgba(28, 28, 30, 0.84);
  --glass-strong: rgba(28, 28, 30, 0.96);
  /* 暗色下发丝边和反光都要压暗，否则白边会在深色玻璃上显得发光过头 */
  --glass-edge: rgba(255, 255, 255, 0.16);
  --glass-sheen: rgba(255, 255, 255, 0.1);
  --surface-solid: #1c1c1e;
  --bg-hover: rgba(255, 255, 255, 0.06);
  --bg-active: rgba(10, 132, 255, 0.2);
  --bg-inset: rgba(255, 255, 255, 0.05);

  --text-1: #f5f5f7;
  --text-2: #a1a1a6;
  --text-3: #6e6e73;

  --hairline: rgba(255, 255, 255, 0.09);
  --hairline-strong: rgba(255, 255, 255, 0.16);

  --blue: #0a84ff;
  --blue-hover: #409cff;
  --blue-pressed: #0071e3;
  --green: #30d158;
  --orange: #ff9f0a;
  --red: #ff453a;
  --purple: #bf5af2;
  --indigo: #5e5ce6;
  --teal: #64d2ff;
  --pink: #ff375f;
  --yellow: #ffd60a;

  --blue-soft: rgba(10, 132, 255, 0.2);
  --green-soft: rgba(48, 209, 88, 0.2);
  --orange-soft: rgba(255, 159, 10, 0.2);
  --red-soft: rgba(255, 69, 58, 0.2);
  --purple-soft: rgba(191, 90, 242, 0.2);
  --indigo-soft: rgba(94, 92, 230, 0.22);
  --teal-soft: rgba(100, 210, 255, 0.22);
  --pink-soft: rgba(255, 55, 95, 0.22);

  --shadow-card: 0 1px 2px rgba(0, 0, 0, 0.3), 0 10px 30px rgba(0, 0, 0, 0.35);
  --shadow-pop: 0 16px 56px rgba(0, 0, 0, 0.55);
}

/* ============ Reset ============ */

* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { height: 100%; }

/* 桌面底色挂在 html 上，body 保持透明，壁纸层（body::before）叠在底色之上。

   没有 `background-attachment: fixed`：文档本身根本不滚动（`.app-shell` 是
   `height: 100vh; overflow: hidden`，滚动发生在 `.main` 内部），所以 fixed 不产生任何
   视觉差异，只是让这三层 radial-gradient 进不了普通的背景缓存 —— 上面每块毛玻璃取样
   时都得重算一遍渐变。省掉它，渐变只在尺寸变化时算一次。 */
html {
  background: var(--bg-desktop);
}

body {
  font-family: var(--font);
  background: transparent;
  color: var(--text-1);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  font-size: 14px;
  transition: background 0.3s ease, color 0.3s ease;
}

::selection { background: rgba(10, 132, 255, 0.22); }

/* 壁纸层。
   不能用 z-index: -1：负层级会把伪元素压到祖先元素的背景之后，只要 html 或 body 有
   不透明底色就整层看不见。改成 z-index: 0 停在内容之下，由 .app-shell 抬到 1。 */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  background-image: var(--wallpaper-image);
  background-size: cover;
  background-position: center;
  filter: blur(var(--wallpaper-blur));
  opacity: var(--wallpaper-opacity);
  /* `translateZ(0)` 把壁纸提到独立合成层，让它的模糊结果被缓存成一张纹理。
     上面每块 `backdrop-filter` 取样时读这张纹理即可，不必重新跑一遍壁纸的 blur。
     `scale(1.04)` 是为了让 blur 的边缘羽化溢出视口，两者写在同一个 transform 里。 */
  transform: scale(1.04) translateZ(0);
  pointer-events: none;
}

a { color: var(--blue); text-decoration: none; }
button { font-family: inherit; }

::-webkit-scrollbar { width: 9px; height: 9px; }
::-webkit-scrollbar-thumb {
  background: var(--hairline-strong);
  border-radius: 9999px;
  border: 2px solid transparent;
  background-clip: padding-box;
}
::-webkit-scrollbar-track { background: transparent; }

/* ============ 布局骨架 ============ */

/* 用 height: 100vh + overflow: hidden 代替 min-height，让 .main 内部滚动。
   导航改为底部悬浮 Dock 后，横向不再有固定占位，内容区拿到整个视口宽度。 */
/* position + z-index 让内容层稳定停在壁纸层（body::before, z-index 0）之上 */
.app-shell {
  display: flex;
  height: 100vh;
  overflow: hidden;
  position: relative;
  z-index: 1;
}

.main {
  flex: 1;
  min-width: 0;
  /* 底部留出 Dock 高度（74px）+ 悬浮间距，避免最后一屏内容被挡住。
     横向用 max() 把内容收在 1140px 内并居中：窄屏取 28px 常规留白，宽屏取两侧均分的余量。
     居中做在 padding 而不是内层 max-width 容器上，滚动条才留在视口边缘。 */
  padding: 22px max(28px, calc((100% - 1140px) / 2)) 124px;
  display: flex;
  flex-direction: column;
  gap: 18px;
  overflow-y: auto;
}

/* ============ 底部分组 Dock ============ */

.dock {
  position: fixed;
  bottom: calc(26px + env(safe-area-inset-bottom, 0px));
  left: 50%;
  transform: translateX(-50%);
  z-index: 900;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 10px;
  border-radius: 24px;
  background: var(--glass);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  box-shadow:
    0 12px 48px rgba(0, 0, 0, 0.18),
    0 2px 6px rgba(0, 0, 0, 0.08),
    inset 0 0 0 0.5px var(--glass-edge),
    inset 0 1px 0 var(--glass-sheen);
}

.dock-group {
  position: relative;
  width: 60px;
  height: 70px;
  border-radius: 16px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  border: none;
  background: transparent;
  color: var(--text-1);
  cursor: pointer;
  transition: transform var(--t-fast), background var(--t-fast), color var(--t-fast);
}
.dock-group svg { width: 23px; height: 23px; }
.dock-group:hover { transform: translateY(-6px) scale(1.1); background: var(--bg-hover); }
.dock-group.active { color: var(--blue); background: rgba(0, 122, 255, 0.14); }
.dock-group.open { background: rgba(0, 122, 255, 0.1); }

/* 图标下的小字标签。10px 是还能保持清晰的下限；随按钮统一上浮，
   选中时跟图标一起变蓝。 */
.dock-label {
  font-size: 10px;
  line-height: 1;
  font-weight: 500;
  letter-spacing: 0.3px;
  color: var(--text-3);
  white-space: nowrap;
  pointer-events: none;
  transition: color var(--t-fast);
}
.dock-group:hover .dock-label { color: var(--text-1); }
.dock-group.active .dock-label,
.dock-group.open .dock-label { color: var(--blue); }

/* 多子项分组不再画底部小横条：选中态已经有底色和配色区分，横条只是又一层视觉噪声，
   而且它落在图标下方，看起来像二级菜单漏出来的一截。`data-multi` 属性保留 —— 它是
   语义信息，将来若要换一种提示方式（比如 hover 才显形）不用回头改 DOM。 */

.dock-sep {
  width: 1px;
  height: 42px;
  margin: 0 6px;
  background: var(--hairline);
}

/* 扇出面板：分组的二级项。
   left 与 --origin-x 由 shell.rs 按被点击图标的位置算出，所以展开动画是
   「从那个图标长出来」，而不是从屏幕中间凭空出现。 */
.fanout {
  position: fixed;
  /* Dock 顶边在离底 116px（26 + 70 + 2×10），这里留 12px 间隙 */
  bottom: 128px;
  left: 0;
  z-index: 899;
  min-width: 320px;
  padding: 12px;
  border-radius: 22px;
  background: var(--glass-strong, var(--glass));
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  box-shadow:
    0 16px 56px rgba(0, 0, 0, 0.2),
    inset 0 0 0 0.5px var(--glass-edge),
    inset 0 1px 0 var(--glass-sheen);
  opacity: 0;
  pointer-events: none;
  transform: translateY(10px) scale(0.94);
  transform-origin: var(--origin-x, 50%) bottom;
  /* 收起时不做位移/缩放，只淡出，而且比展开快得多。
     这块面板背景是 `--glass-strong`（白底 0.86 不透明），带着 scale 慢慢缩回去等于把一块
     接近纯白的板子在页面上拖 260ms —— 跳转时看起来就是「闪了一下白」。展开时保留那点
     回弹手感（用户在等面板出现，动效是反馈）；收起时用户已经在看新页面了，面板只该让路。
     `visibility` 一起过渡是为了淡出结束后立刻脱离命中测试和合成。 */
  visibility: hidden;
  transition:
    opacity 0.12s ease,
    visibility 0s linear 0.12s;
}
.fanout.open {
  opacity: 1;
  pointer-events: auto;
  visibility: visible;
  transform: translateY(0) scale(1);
  transition:
    opacity var(--t-fast),
    transform 0.26s cubic-bezier(0.32, 1.35, 0.5, 1),
    visibility 0s;
}

/* 指向来源图标的小箭头。
   用 clip-path 切出真三角而不是 rotate(45deg) 的方块：旋转方块的上半截会压在面板内部，
   两层半透明相加会显出一块更亮的菱形。三角形整体贴在面板下沿之外，只有底边与面板相接。
   自带 backdrop-filter，所以它和面板是同一块玻璃，不是一块平涂色片。 */
.fanout::after {
  content: '';
  position: absolute;
  top: 100%;
  left: var(--origin-x, 50%);
  margin-left: -9px;
  width: 18px;
  height: 9px;
  background: var(--glass-strong, var(--glass));
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  clip-path: polygon(0 0, 100% 0, 50% 100%);
  transition: left 0.26s var(--ease);
}

.fanout-label {
  font-size: 11px;
  font-weight: 650;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  color: var(--text-3);
  padding: 4px 12px 8px;
  user-select: none;
}

.fan-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 12px;
  border-radius: 13px;
  color: inherit;
  text-decoration: none;
  transition: background var(--t-fast), transform 0.18s var(--ease);
}
.fan-item:hover { background: var(--bg-hover); transform: translateX(2px); }
.fan-item.on { background: rgba(0, 122, 255, 0.12); }
.fan-item.on .fan-title { color: var(--blue); }
.fan-item > svg { width: 18px; height: 18px; color: var(--text-2); flex-shrink: 0; }
.fan-item.on > svg { color: var(--blue); }
.fan-body { flex: 1; min-width: 0; }
.fan-title { font-size: 13.5px; font-weight: 600; color: var(--text-1); }
.fan-desc {
  font-size: 11.5px;
  color: var(--text-3);
  margin-top: 1px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* 窄屏：Dock 贴合视口宽度并允许横向滚动，图标缩小，标签收起（放不下） */
@media (max-width: 720px) {
  .dock { left: 12px; right: 12px; transform: none; overflow-x: auto; }
  .dock-group { width: 46px; height: 46px; flex-shrink: 0; }
  .dock-label { display: none; }
  .dock-group:hover { transform: none; }
  .fanout { min-width: 0; right: 12px; }
}

/* ============ 品牌标识 ============ */

.brand { display: flex; align-items: center; gap: 11px; padding: 4px 9px 18px; }
.brand-logo {
  width: 38px; height: 38px;
  border-radius: 11px;
  background: var(--grad-blue);
  display: grid; place-items: center;
  color: #fff; font-weight: 800; font-size: 17px;
  box-shadow: 0 4px 12px rgba(10, 132, 255, 0.35);
  user-select: none;
  flex-shrink: 0;
}
.brand-logo.custom {
  background: transparent;
  border-radius: 0;
  box-shadow: none;
}
.brand-logo img {
  width: 100%; height: 100%;
  display: block;
  object-fit: contain;
}
.brand-logo-letter { position: relative; z-index: 1; }
.brand-name { font-size: 16px; font-weight: 700; letter-spacing: -0.2px; }
.brand-sub { font-size: 11px; color: var(--text-2); font-weight: 500; }

/* ============ 顶栏 ============ */

.topbar {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 18px;
  border-radius: var(--radius-lg);
  background: var(--glass);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--hairline);
  box-shadow: var(--shadow-card);
  position: sticky;
  top: 14px;
  z-index: 15;
}

.page-title { font-size: 20px; font-weight: 700; letter-spacing: -0.3px; }
.page-sub { font-size: 12.5px; color: var(--text-2); margin-top: 1px; }
.topbar-spacer { flex: 1; }

.health-badge {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 6px 12px;
  border-radius: 9999px;
  font-size: 12.5px;
  font-weight: 600;
  background: var(--bg-inset);
  border: 1px solid var(--hairline);
  white-space: nowrap;
}
.health-dot { width: 8px; height: 8px; border-radius: 50%; }
.health-dot.ok { background: var(--green); box-shadow: 0 0 0 3px var(--green-soft); }
.health-dot.warn { background: var(--orange); box-shadow: 0 0 0 3px var(--orange-soft); }
.health-dot.bad { background: var(--red); box-shadow: 0 0 0 3px var(--red-soft); }

/* 不需要 `position: relative` 了：弹层已经 Portal 到 body，用 fixed 定位，
   不再以这里为定位父级。留着反而会让人以为两者还有包含关系。 */
.account-menu { display: flex; }
.account-menu-trigger {
  min-height: 34px;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 3px 8px 3px 4px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-sm);
  background: var(--surface-solid);
  cursor: pointer;
  user-select: none;
  font: inherit;
  color: inherit;
}
.account-menu-trigger:hover { background: var(--bg-hover); }
.account-menu-trigger:focus-visible { outline: 3px solid var(--blue-soft); border-color: var(--blue); }
.account-menu-trigger > svg {
  width: 14px; height: 14px;
  color: var(--text-3);
  transform: rotate(90deg);
  transition: transform var(--t-fast);
}
/* 箭头翻转跟着 aria-expanded —— 状态只有一个来源，不会和 class 说法不一致 */
.account-menu-trigger[aria-expanded="true"] > svg { transform: rotate(-90deg); }
.account-name { max-width: 130px; font-size: 12.5px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.account-menu-popover {
  /* 弹层在 body 下用 fixed 定位（坐标由 shell.rs 按锚点实测算出），
     不再从 `.account-menu` 派生。z-index 只跟页面里其他浮层比。 */
  position: fixed;
  width: 220px;
  padding: 7px;
  /* 小型浮层不用实时 backdrop 采样。
     Chromium/WebKit 会把 fixed + Portal + backdrop-filter 提到独立合成层；它和页面上
     另一层 backdrop-filter（顶栏、面板）及壁纸 filter 同时存在时，纹理缓存偶尔会按
     错误的 tile/裁剪区复用，表现为弹层透明、矩形色块或旧背景残影。这个问题不在 DOM
     层叠规则内，继续加 z-index/isolation 也修不好。

     系统菜单的工程优先级是内容稳定和可读性：使用主题对应的实体 surface，玻璃感由
     发丝边、顶部内高光和浮层阴影表达。大面积顶栏/面板仍保留毛玻璃。 */
  background: var(--surface-solid);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-md);
  box-shadow:
    var(--shadow-pop),
    inset 0 1px 0 var(--glass-sheen);
  z-index: 100;
}
/* 菜单显隐不做 opacity / transform 动画。
   `transform: translateY(0)` 即使视觉位移为零，也会强制创建独立 GPU 合成层；它曾让
   Chromium 在壁纸和多面板场景中复用错误纹理。这个 220px 工具菜单的 6px 动效不值得
   牺牲渲染确定性，直接切换 visibility，交互反馈由箭头旋转提供。 */
.account-menu-popover {
  visibility: hidden;
  pointer-events: none;
}
.account-menu-popover.open {
  visibility: visible;
  pointer-events: auto;
}
.account-menu-identity {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 7px 8px 11px;
  margin-bottom: 5px;
  border-bottom: 1px solid var(--hairline);
}
.account-menu-name { max-width: 145px; font-size: 13px; font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.account-menu-role { margin-top: 1px; color: var(--text-3); font-size: 11px; }
.account-menu-item {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 8px 9px;
  border: none;
  border-radius: var(--radius-xs);
  background: transparent;
  color: var(--text-1);
  font-size: 12.5px;
  font-weight: 500;
  cursor: pointer;
  text-align: left;
}
.account-menu-item:hover { background: var(--bg-hover); }
.account-menu-item.danger { color: var(--red); }
.account-menu-item.danger:hover { background: var(--red-soft); }
.account-menu-item svg { width: 16px; height: 16px; }

.profile-avatar {
  display: grid;
  place-items: center;
  overflow: hidden;
  border-radius: 50%;
  flex-shrink: 0;
  background: var(--grad-blue);
  color: #fff;
  font-weight: 700;
  user-select: none;
}
.profile-avatar img { width: 100%; height: 100%; display: block; object-fit: cover; }
.profile-avatar-topbar { width: 26px; height: 26px; font-size: 11px; }
.profile-avatar-settings { width: 88px; height: 88px; font-size: 32px; box-shadow: 0 8px 24px rgba(10, 132, 255, 0.22); }

/* ============ 面板（玻璃卡片） ============ */

.panel {
  background: var(--glass);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
  padding: 20px 22px;
}

.panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  margin-bottom: 16px;
  flex-wrap: wrap;
}
.panel-title { font-size: 15.5px; font-weight: 700; letter-spacing: -0.2px; }
.panel-sub { font-size: 12px; color: var(--text-2); margin-top: 2px; }
.panel-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

/* ============ 统计卡 ============ */

.metric-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 14px;
}

.metric-card {
  background: var(--glass);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
  padding: 16px 18px;
  display: flex;
  align-items: center;
  gap: 14px;
  transition: transform var(--t-std), box-shadow var(--t-std);
}
.metric-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-pop); }

/* 图标底座：彩色实底渐变改为「同色淡底 + 同色描边」，
   与面板内容区的 soft 色块（.inline-error、健康点等）共用一套语言，
   颜色由 MetricCard 按 grad 注入 --tile-fg / --tile-tint。 */
.metric-icon {
  width: 42px; height: 42px;
  border-radius: 12px;
  display: grid; place-items: center;
  color: var(--tile-fg, var(--text-1));
  background: var(--tile-tint, var(--bg-inset));
  flex-shrink: 0;
  transition: transform 0.35s cubic-bezier(0.22, 1, 0.36, 1);
}
.metric-icon svg { width: 21px; height: 21px; }
.metric-label { font-size: 12px; color: var(--text-2); font-weight: 500; }
.metric-value { font-size: 22px; font-weight: 700; letter-spacing: -0.5px; line-height: 1.2; font-variant-numeric: tabular-nums; }
.metric-hint { font-size: 11.5px; color: var(--text-3); margin-top: 1px; }

/* ============ 按钮 ============ */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 7px 14px;
  font-size: 13px;
  font-weight: 600;
  border-radius: var(--radius-sm);
  border: 1px solid var(--hairline-strong);
  background: var(--surface-solid);
  color: var(--text-1);
  cursor: pointer;
  transition: background var(--t-fast), transform var(--t-fast), box-shadow var(--t-fast), opacity var(--t-fast);
  white-space: nowrap;
  user-select: none;
}
.btn:hover { background: var(--bg-hover); }
.btn:active { transform: scale(0.97); }
.btn:disabled, .btn.disabled { opacity: 0.45; cursor: not-allowed; transform: none; pointer-events: none; }
.btn svg { width: 15px; height: 15px; }

.btn-primary {
  background: var(--blue);
  border-color: transparent;
  color: #fff;
  box-shadow: var(--shadow-btn);
}
.btn-primary:hover { background: var(--blue-hover); }
.btn-primary:active { background: var(--blue-pressed); }

.btn-danger { background: var(--red); border-color: transparent; color: #fff; }
.btn-danger:hover { background: var(--red); filter: brightness(1.08); }

.btn-ghost { background: transparent; border-color: transparent; }
.btn-ghost:hover { background: var(--bg-hover); }

.btn-sm { padding: 4px 10px; font-size: 12px; border-radius: var(--radius-xs); }

.btn-text {
  background: none; border: none; padding: 3px 6px;
  color: var(--blue); font-size: 12.5px; font-weight: 600;
  cursor: pointer; border-radius: 6px;
  transition: background var(--t-fast);
}
.btn-text:hover { background: var(--blue-soft); }
.btn-text.danger { color: var(--red); }
.btn-text.danger:hover { background: var(--red-soft); }
.btn-text:disabled { opacity: 0.4; cursor: not-allowed; }

.icon-btn {
  width: 34px; height: 34px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--hairline);
  background: var(--surface-solid);
  display: grid; place-items: center;
  cursor: pointer;
  color: var(--text-1);
  transition: background var(--t-fast);
  position: relative;
  flex-shrink: 0;
}
.icon-btn:hover { background: var(--bg-hover); }
.icon-btn svg { width: 17px; height: 17px; }

/* ============ 表单控件 ============ */

.input, .select, .textarea {
  font-family: inherit;
  font-size: 13px;
  color: var(--text-1);
  background: var(--surface-solid);
  border: 1px solid var(--hairline-strong);
  border-radius: var(--radius-sm);
  padding: 7px 11px;
  outline: none;
  transition: border-color var(--t-fast), box-shadow var(--t-fast);
  width: 100%;
}
.input:focus, .select:focus, .textarea:focus {
  border-color: var(--blue);
  box-shadow: 0 0 0 3px var(--blue-soft);
}
.input::placeholder, .textarea::placeholder { color: var(--text-3); }
.input:disabled, .select:disabled, .textarea:disabled { opacity: 0.55; cursor: not-allowed; }
.textarea { resize: vertical; min-height: 72px; line-height: 1.55; }
.textarea.mono { font-family: var(--mono); font-size: 12.5px; }
.select { appearance: none; -webkit-appearance: none; padding-right: 28px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%236e6e73' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
}

.field { display: flex; flex-direction: column; gap: 6px; margin-bottom: 14px; }
.field-label { font-size: 12.5px; font-weight: 600; color: var(--text-1); }
.field-help { font-size: 11.5px; color: var(--text-3); }
.field-row { display: flex; gap: 12px; }
.field-row > .field { flex: 1; }

/* 开关 */
.switch {
  position: relative;
  width: 40px; height: 24px;
  border-radius: 9999px;
  background: rgba(120, 120, 128, 0.32);
  border: none;
  cursor: pointer;
  transition: background 0.2s ease;
  flex-shrink: 0;
  padding: 0;
}
.switch::after {
  content: "";
  position: absolute;
  top: 2px; left: 2px;
  width: 20px; height: 20px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.25);
  transition: transform 0.2s var(--ease);
}
.switch.on { background: var(--green); }
.switch.on::after { transform: translateX(16px); }
.switch:disabled { opacity: 0.5; cursor: not-allowed; }
.switch-sm { width: 34px; height: 20px; }
.switch-sm::after { width: 16px; height: 16px; }
.switch-sm.on::after { transform: translateX(14px); }

/* 搜索框 */
.search-box {
  display: flex; align-items: center; gap: 8px;
  background: var(--bg-inset);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-sm);
  padding: 6px 11px;
  transition: border-color var(--t-fast), box-shadow var(--t-fast);
}
.search-box:focus-within { border-color: var(--blue); box-shadow: 0 0 0 3px var(--blue-soft); background: var(--surface-solid); }
.search-box input {
  border: none; outline: none; background: transparent;
  font-family: inherit; font-size: 13px; color: var(--text-1);
  width: 170px;
}
.search-box input::placeholder { color: var(--text-3); }
.search-box svg { width: 14px; height: 14px; color: var(--text-3); flex-shrink: 0; }

/* ============ 标签 / 徽章 ============ */

.tag {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2.5px 9px;
  border-radius: 9999px;
  font-size: 11.5px;
  font-weight: 600;
  white-space: nowrap;
  line-height: 1.5;
}
.tag-blue { background: var(--blue-soft); color: var(--blue); }
.tag-green { background: var(--green-soft); color: var(--green); }
.tag-orange { background: var(--orange-soft); color: var(--orange); }
.tag-red { background: var(--red-soft); color: var(--red); }
.tag-purple { background: var(--purple-soft); color: var(--purple); }
.tag-indigo { background: var(--indigo-soft); color: var(--indigo); }
.tag-gray { background: var(--bg-inset); color: var(--text-2); }
.tag .dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; }

/* ============ 数据表格 ============ */

.table-wrap { overflow-x: auto; }
.table { width: 100%; border-collapse: collapse; font-size: 13px; }
.table th {
  text-align: left;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--text-3);
  text-transform: uppercase;
  letter-spacing: 0.4px;
  padding: 8px 12px;
  border-bottom: 1px solid var(--hairline);
  white-space: nowrap;
  user-select: none;
}
.table th.sortable { cursor: pointer; }
.table th.sortable:hover { color: var(--text-1); }
.table td {
  padding: 10px 12px;
  border-bottom: 1px solid var(--hairline);
  vertical-align: middle;
}
.table tbody tr { transition: background var(--t-fast); }
.table tbody tr:hover { background: var(--bg-hover); }
.table tbody tr:last-child td { border-bottom: none; }
.table .mono { font-family: var(--mono); font-size: 12px; }
.table .num { font-variant-numeric: tabular-nums; }
.table .actions { display: flex; gap: 2px; align-items: center; }
.table .muted { color: var(--text-3); }

/* ============ 空态 / 加载态 ============ */

.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  padding: 44px 20px;
  color: var(--text-3);
  font-size: 13px;
  text-align: center;
}
.empty-state svg { width: 36px; height: 36px; opacity: 0.4; }

.loading-state {
  display: flex; align-items: center; justify-content: center;
  gap: 10px; padding: 40px; color: var(--text-3); font-size: 13px;
}

/* 加载失败态。位置和尺寸与 .empty-state 一致但配色是红的 —— 空列表与加载失败
   长成一个样是原来最误导人的地方。自带重试按钮，因为 toast 3 秒就消失了。 */
.load-error {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 9px;
  padding: 40px 20px;
  text-align: center;
}
.load-error-icon { color: var(--red); opacity: 0.75; }
.load-error-icon svg { width: 30px; height: 30px; }
.load-error-title { font-size: 13.5px; font-weight: 600; color: var(--text-1); }
.load-error-msg {
  max-width: 420px;
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--text-2);
}
.load-error .btn { margin-top: 4px; }

/* 局部失败提示条：整页还能用、只有某一块坏了时用它，不占满一屏。 */
.inline-error {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 9px 12px;
  border-radius: 10px;
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--red);
  background: var(--red-soft);
}
.inline-error svg { width: 15px; height: 15px; flex: none; margin-top: 1px; }

/* 「未订阅所以没有这个值」。刻意不做成 tag：标签在表格里代表一个有效取值，
   拿它承载「没有值」会让空信息看起来像信息。 */
.na {
  font-size: 12px;
  color: var(--text-3);
  font-style: italic;
  cursor: help;
  border-bottom: 1px dashed var(--hairline-strong);
}

.spinner {
  width: 18px; height: 18px;
  border: 2px solid var(--hairline-strong);
  border-top-color: var(--blue);
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
  flex-shrink: 0;
}
.spinner-sm { width: 13px; height: 13px; border-width: 2px; }
@keyframes spin { to { transform: rotate(360deg); } }

/* ============ Tabs ============ */

.tabs {
  display: inline-flex;
  gap: 2px;
  background: var(--bg-inset);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-sm);
  padding: 3px;
}
.tab {
  padding: 5px 14px;
  border-radius: var(--radius-xs);
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text-2);
  background: none;
  border: none;
  cursor: pointer;
  transition: all var(--t-fast);
  white-space: nowrap;
  user-select: none;
}
.tab:hover { color: var(--text-1); }
.tab.active {
  background: var(--surface-solid);
  color: var(--text-1);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
}

/* ============ 模态框 ============ */

.modal-mask {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: rgba(0, 0, 0, 0.32);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  display: grid;
  place-items: center;
  padding: 24px;
  animation: fade-in 0.18s ease;
}

.modal {
  background: var(--glass-strong);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-pop);
  width: 100%;
  max-width: 480px;
  max-height: calc(100vh - 64px);
  display: flex;
  flex-direction: column;
  animation: modal-in 0.24s var(--ease);
}
.modal.wide { max-width: 720px; }
.modal.xwide { max-width: 960px; }

.modal-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 17px 22px 0;
}
.modal-title { font-size: 16px; font-weight: 700; letter-spacing: -0.2px; }
.modal-close {
  width: 28px; height: 28px;
  border-radius: 50%;
  border: none;
  background: var(--bg-inset);
  color: var(--text-2);
  display: grid; place-items: center;
  cursor: pointer;
  transition: background var(--t-fast);
  font-size: 14px;
}
.modal-close:hover { background: var(--bg-hover); color: var(--text-1); }
.modal-body { padding: 16px 22px; overflow-y: auto; }
.modal-foot {
  display: flex; justify-content: flex-end; gap: 9px;
  padding: 0 22px 18px;
}

@keyframes fade-in { from { opacity: 0; } }
@keyframes modal-in {
  from { opacity: 0; transform: scale(0.96) translateY(8px); }
}

/* ============ Toast ============ */

.toast-region {
  position: fixed;
  top: 18px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 200;
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: center;
  pointer-events: none;
}
.toast {
  display: flex; align-items: center; gap: 9px;
  background: var(--glass-strong);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--hairline);
  border-radius: 9999px;
  box-shadow: var(--shadow-pop);
  padding: 9px 18px;
  font-size: 13px;
  font-weight: 600;
  animation: toast-in 0.28s var(--ease);
  pointer-events: auto;
  max-width: 480px;
}
.toast .t-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
.toast-close {
  display: flex; align-items: center; justify-content: center;
  background: none; border: none; padding: 2px; margin: -2px -6px -2px 2px;
  color: var(--text-3); cursor: pointer; opacity: 0.75; flex-shrink: 0;
}
.toast-close:hover { opacity: 1; color: var(--text-1); }
.toast.success .t-dot { background: var(--green); }
.toast.error .t-dot { background: var(--red); }
.toast.warning .t-dot { background: var(--orange); }
.toast.info .t-dot { background: var(--blue); }
@keyframes toast-in {
  from { opacity: 0; transform: translateY(-12px) scale(0.95); }
}

/* ============ 布局工具 ============ */

.grid-2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
.grid-2-1 { display: grid; grid-template-columns: 2fr 1fr; gap: 16px; }
.grid-1-2 { display: grid; grid-template-columns: 1fr 2fr; gap: 16px; }
.row { display: flex; align-items: center; gap: 10px; }
.row-wrap { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.col { display: flex; flex-direction: column; gap: 10px; }
.gap-lg { gap: 16px; }
.flex-1 { flex: 1; min-width: 0; }
.mt { margin-top: 14px; }
.muted { color: var(--text-2); }
.faint { color: var(--text-3); }
.mono { font-family: var(--mono); }
.small { font-size: 12px; }
.nowrap { white-space: nowrap; }
.ellipsis { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* 进入动画 */
/* 面板入场。与 `.main > *` 同一套时长和缓动 —— 顶层面板同时命中这两条规则，
   时长不一致会看到同一个元素上下动两趟。
   用 `backwards` 而不是 `both`：`both` 会在动画结束后继续锁定 `to` 那一帧，
   给后续的 hover/transform 过渡添乱；起始态由 keyframes 提供就够了。 */
/* 面板入场，复用 `glass-in`（纯 opacity）。
   原来这里是自己一份带 `translateY` 的 `rise`，和 `.main > *` 的 `fadeInUp` 撞在同一个
   元素上 —— 顶层面板同时命中两条规则，后定义的赢，另一条白写。合并成一个关键帧后
   就不存在「哪条生效」的问题了。 */
.rise { animation: glass-in 0.18s var(--ease) backwards; }
/* 同一屏内多块面板的错峰。压到 40ms 一档：这是「依次落位」还能被感知的下限，
   再大就变成明显的等待。 */
.rise-2 { animation-delay: 0.04s; }
.rise-3 { animation-delay: 0.08s; }
.rise-4 { animation-delay: 0.12s; }

/* ============ 登录页 ============ */

@keyframes card-enter {
  from { opacity: 0; transform: translateY(32px) scale(0.95); }
}
@keyframes logo-drop {
  /* logo 从上方落入，translate(-50%, -50%) 是运行时基准，这里叠加偏移 */
  from { opacity: 0; transform: translate(-50%, calc(-50% - 18px)) scale(0.82); }
  to   {             transform: translate(-50%, -50%)                          ; }
}
@keyframes orb-a {
  0%, 100% { transform: translate(0, 0)   scale(1);   }
  50%       { transform: translate(40px, -50px) scale(1.1); }
}
@keyframes orb-b {
  0%, 100% { transform: translate(0, 0)    scale(1);  }
  50%       { transform: translate(-30px, 40px) scale(0.9); }
}

/* ============ 认证页（登录 / 注册 / 首启向导）============ */
/* 分屏液态玻璃：左栏品牌叙事，右栏表单。
   材质要点：backdrop-filter 只能模糊、不能位移像素，所以「通透」不是靠调低
   白色填充实现的，而是靠背后有高频细节可供采样。背景必须是真实照片；模糊一层
   本就平滑的渐变得到的还是那个渐变，卡片就会退化成半透明白塑料。 */

.auth-page {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 40px 24px;
  position: relative;
  overflow: hidden;
  /* --auth-wallpaper 由 login.rs / setup.rs 拉取 /api/public/auth-wallpaper 后写入。
     渐变留作图片加载失败或尚未返回时的兜底，不作为最终观感的依据。 */
  /* 默认值指向内置图而不是 none：玻璃质感依赖背后有高频细节可供 backdrop-filter
     采样，模糊一层纯渐变得到的还是那层渐变，会退化成半透明白塑料。 */
  background:
    var(--auth-wallpaper, url("/images/sidebar-bg.jpg")) center / cover no-repeat,
    linear-gradient(150deg, #dbe6ff 0%, #f3dcee 38%, #dfeaff 68%, #dcf5ef 100%);
}
/* 壁纸上压一层极轻的暗化，保证玻璃边缘高光和白色文字在亮色照片上仍可辨认。
   这是可读性下限，不是装饰层，因此不随主题变化。 */
.auth-page::before {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(160deg, rgba(8, 12, 28, 0.20) 0%, rgba(8, 12, 28, 0.32) 100%);
  pointer-events: none;
}

/* ── 分屏外壳：液态玻璃本体 ── */
/* 与 frosted glass 的三处区别，各由一层负责：
   1. 更薄 —— 填充降到 0.14 并把模糊压到 20px。大模糊会抹掉背后所有形状，
      看着像磨砂；小模糊保留可辨认的轮廓，才像有厚度的透明介质。
   2. 边缘折射 —— ::before 用 backdrop-filter 局部重采样，做出光线穿过
      玻璃厚度时的挤压感。纯 CSS 无法真正位移像素（需要 SVG feDisplacementMap），
      这里用「边缘加倍模糊 + 亮度抬升」逼近，代价小且不依赖滤镜链。
   3. 镜面高光 —— ::after 一道掠射角白色渐变，模拟环境光在弧面上的反射。 */
.auth-split {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: 1fr 1fr;
  width: 100%;
  max-width: 1000px;
  min-height: 620px;
  border-radius: 32px;
  background: rgba(255, 255, 255, 0.14);
  backdrop-filter: blur(20px) saturate(180%) brightness(1.08);
  -webkit-backdrop-filter: blur(20px) saturate(180%) brightness(1.08);
  box-shadow:
    /* 投影分两档：近距离接触阴影 + 远距离扩散，玻璃才像悬空而非贴死 */
    0 2px 8px rgba(0, 0, 0, 0.10),
    0 24px 70px rgba(0, 0, 0, 0.22),
    /* 发丝边：上缘受光强、下缘弱，制造弧面错觉 */
    inset 0 1px 0 rgba(255, 255, 255, 0.55),
    inset 0 0 0 0.5px rgba(255, 255, 255, 0.28),
    inset 0 -1px 0 rgba(255, 255, 255, 0.10);
  animation: card-enter 0.5s var(--ease) 0.06s both;
}

/* 边缘折射带。inset 负值让它比卡片略大，只有 padding 环形区域可见。 */
.auth-split::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1.5px;
  backdrop-filter: blur(6px) brightness(1.35) saturate(140%);
  -webkit-backdrop-filter: blur(6px) brightness(1.35) saturate(140%);
  /* 只保留描边区域：填充色遮住中心，mask 挖掉内部 */
  background: linear-gradient(
    150deg,
    rgba(255, 255, 255, 0.6) 0%,
    rgba(255, 255, 255, 0.05) 40%,
    rgba(255, 255, 255, 0.05) 60%,
    rgba(255, 255, 255, 0.45) 100%
  );
  mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
  -webkit-mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
  pointer-events: none;
  z-index: 2;
}

/* 镜面高光：掠射角扫过左上，随 hover 缓慢移动，暗示材质在反射环境而非自发光 */
.auth-split::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: linear-gradient(
    118deg,
    rgba(255, 255, 255, 0.28) 0%,
    rgba(255, 255, 255, 0.10) 18%,
    transparent 42%,
    transparent 100%
  );
  pointer-events: none;
  z-index: 1;
}

/* 两栏要盖在折射层之下、但在玻璃填充之上 */
.auth-brand,
.auth-form-pane {
  position: relative;
  z-index: 3;
}

/* ── 左栏：品牌叙事 ── */
.auth-brand {
  padding: 52px 44px;
  display: flex;
  flex-direction: column;
  gap: 26px;
  border-radius: 32px 0 0 32px;
  /* 品牌色只做壁纸上的晕染，不接管整块背景 */
  background:
    radial-gradient(ellipse 520px 420px at 12% 92%, rgba(0, 122, 255, 0.16) 0%, transparent 62%),
    radial-gradient(ellipse 460px 380px at 88%  6%, rgba(88, 86, 214, 0.13) 0%, transparent 62%);
}
.auth-brand-logo {
  width: 56px; height: 56px;
  border-radius: 17px;
  display: grid;
  place-items: center;
  color: #fff;
  background: linear-gradient(160deg, #6cb4ff 0%, #4a8ef5 42%, #3468d8 82%, #2b58c4 100%);
  box-shadow:
    0 8px 22px rgba(59, 100, 230, 0.34),
    inset 0 1px 0 rgba(255, 255, 255, 0.45);
  overflow: hidden;
  flex: none;
}
.auth-brand-logo svg { width: 30px; height: 30px; }
.auth-brand-logo img { width: 100%; height: 100%; object-fit: cover; }

/* 玻璃变薄后文字直接压在壁纸上，深色字会随壁纸失效。
   左栏改用亮色文字 + 文字阴影，可读性不再依赖背景明度。 */
.auth-brand-title {
  font-size: 28px;
  font-weight: 700;
  letter-spacing: -0.03em;
  line-height: 1.28;
  color: rgba(255, 255, 255, 0.98);
  text-shadow: 0 1px 12px rgba(0, 0, 0, 0.35);
  margin: 0;
}
.auth-brand-sub {
  font-size: 14px;
  line-height: 1.7;
  color: rgba(255, 255, 255, 0.82);
  text-shadow: 0 1px 8px rgba(0, 0, 0, 0.3);
  margin: -12px 0 0;
}

.auth-features { display: flex; flex-direction: column; gap: 12px; margin-top: auto; }
/* 内层卡片比外壳更薄，避免玻璃叠玻璃变成一团白 */
.auth-feature {
  display: flex;
  align-items: flex-start;
  gap: 13px;
  padding: 14px 16px;
  border-radius: 16px;
  background: rgba(255, 255, 255, 0.10);
  backdrop-filter: blur(8px) saturate(150%);
  -webkit-backdrop-filter: blur(8px) saturate(150%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.35),
    inset 0 0 0 0.5px rgba(255, 255, 255, 0.16);
  transition: transform 0.28s var(--ease), background 0.28s var(--ease);
}
.auth-feature:hover {
  transform: translateY(-2px);
  background: rgba(255, 255, 255, 0.18);
}
.auth-feature-icon {
  width: 34px; height: 34px;
  border-radius: 11px;
  display: grid;
  place-items: center;
  flex: none;
  color: #0a6cff;
  background: rgba(0, 122, 255, 0.13);
}
.auth-feature-icon svg { width: 18px; height: 18px; }
.auth-feature-icon svg { width: 18px; height: 18px; }
.auth-feature-title {
  font-size: 13.5px;
  font-weight: 600;
  color: rgba(255, 255, 255, 0.96);
  text-shadow: 0 1px 6px rgba(0, 0, 0, 0.28);
  margin: 0 0 2px;
}
.auth-feature-desc {
  font-size: 12.5px;
  line-height: 1.55;
  color: rgba(255, 255, 255, 0.75);
  text-shadow: 0 1px 6px rgba(0, 0, 0, 0.25);
  margin: 0;
}

/* ── 右栏：表单 ── */
/* 这里刻意不追求通透。输入框要能读、能聚焦、能看清报错，
   所以右栏压一层较厚的白把表单从玻璃里托出来，通透感交给左栏和外壳边缘承担。 */
.auth-form-col,
.auth-form-pane {
  padding: 52px 44px;
  display: flex;
  flex-direction: column;
  border-radius: 0 32px 32px 0;
  background: rgba(255, 255, 255, 0.62);
  backdrop-filter: blur(30px) saturate(190%);
  -webkit-backdrop-filter: blur(30px) saturate(190%);
  box-shadow: inset 0.5px 0 0 rgba(255, 255, 255, 0.45);
}

/* ── 登录/注册标签 ── */
.auth-tabs {
  display: grid;
  grid-auto-flow: column;
  gap: 4px;
  padding: 4px;
  border-radius: 14px;
  background: rgba(255, 255, 255, 0.34);
  box-shadow: inset 0 0 0 0.5px rgba(255, 255, 255, 0.6);
  margin-bottom: 28px;
}
.auth-tab {
  border: none;
  background: transparent;
  padding: 9px 14px;
  border-radius: 11px;
  font: inherit;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--text-secondary);
  cursor: pointer;
  transition: background 0.22s var(--ease), color 0.22s var(--ease), box-shadow 0.22s var(--ease);
}
.auth-tab:hover { color: var(--text); }
.auth-tab.active {
  color: #0a6cff;
  background: rgba(255, 255, 255, 0.85);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.07);
}

.auth-heading {
  font-size: 21px;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--text);
  margin: 0 0 6px;
}
.auth-heading-sub {
  font-size: 13.5px;
  line-height: 1.6;
  color: var(--text-secondary);
  margin: 0 0 24px;
}

/* ── 带前置图标的输入框 ── */
.auth-field { margin-bottom: 16px; }
.auth-label {
  display: block;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text-secondary);
  margin-bottom: 7px;
}
.auth-input-wrap { position: relative; display: flex; align-items: center; }
.auth-input-icon {
  position: absolute;
  left: 14px;
  width: 17px; height: 17px;
  color: var(--text-secondary);
  pointer-events: none;
  opacity: 0.72;
}
.auth-input-icon svg { width: 100%; height: 100%; }
.auth-split .auth-input {
  width: 100%;
  padding: 12px 14px 12px 41px;
  border-radius: 13px;
  border: none;
  font: inherit;
  font-size: 14.5px;
  color: var(--text);
  background: rgba(255, 255, 255, 0.62);
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.07);
  transition: background 0.22s var(--ease), box-shadow 0.22s var(--ease);
}
.auth-split .auth-input::placeholder { color: var(--text-secondary); opacity: 0.7; }
.auth-split .auth-input:focus {
  outline: none;
  background: rgba(255, 255, 255, 0.9);
  box-shadow:
    inset 0 0 0 1px rgba(0, 122, 255, 0.45),
    0 0 0 4px rgba(0, 122, 255, 0.13);
}
/* 密码可见性切换：44px 命中区，键盘可达 */
.auth-reveal {
  position: absolute;
  right: 5px;
  width: 34px; height: 34px;
  border: none;
  background: transparent;
  border-radius: 10px;
  display: grid;
  place-items: center;
  color: var(--text-secondary);
  cursor: pointer;
  transition: background 0.2s var(--ease), color 0.2s var(--ease);
}
.auth-reveal:hover { background: rgba(0, 0, 0, 0.06); color: var(--text); }
.auth-reveal svg { width: 17px; height: 17px; }
.auth-split .auth-input.has-reveal { padding-right: 44px; }

.auth-hint {
  font-size: 11.5px;
  line-height: 1.55;
  color: var(--text-secondary);
  margin: 6px 0 0;
  opacity: 0.85;
}

.auth-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin: 4px 0 22px;
}
.auth-check {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 13px;
  color: var(--text-secondary);
  cursor: pointer;
}
.auth-check input { width: 15px; height: 15px; accent-color: #0a6cff; cursor: pointer; }
.auth-link {
  border: none;
  background: none;
  padding: 0;
  font: inherit;
  font-size: 13px;
  font-weight: 500;
  color: #0a6cff;
  cursor: pointer;
}
.auth-link:hover { text-decoration: underline; }
.auth-link:disabled { opacity: 0.45; cursor: not-allowed; text-decoration: none; }

.auth-submit {
  width: 100%;
  padding: 13px;
  border: none;
  border-radius: 13px;
  font: inherit;
  font-size: 15px;
  font-weight: 600;
  color: #fff;
  background: #007aff;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  box-shadow: 0 6px 18px rgba(0, 122, 255, 0.28);
  transition: background 0.22s var(--ease), transform 0.18s var(--ease), box-shadow 0.22s var(--ease);
}
.auth-submit:hover:not(:disabled) {
  background: #0051d5;
  transform: translateY(-1px);
  box-shadow: 0 10px 26px rgba(0, 122, 255, 0.34);
}
.auth-submit:active:not(:disabled) { transform: translateY(0); }
.auth-submit:disabled { opacity: 0.6; cursor: not-allowed; box-shadow: none; }

.auth-secondary {
  width: 100%;
  margin-top: 10px;
  padding: 12px;
  border: none;
  border-radius: 13px;
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
  background: rgba(255, 255, 255, 0.55);
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.07);
  cursor: pointer;
  transition: background 0.22s var(--ease);
}
.auth-secondary:hover { background: rgba(255, 255, 255, 0.8); }

.auth-divider {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 18px 0 0;
  font-size: 12px;
  color: var(--text-secondary);
}
.auth-divider::before,
.auth-divider::after {
  content: '';
  flex: 1;
  height: 1px;
  background: rgba(0, 0, 0, 0.09);
}

.auth-error {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  padding: 11px 13px;
  border-radius: 12px;
  font-size: 13px;
  line-height: 1.55;
  color: #c2261c;
  background: rgba(255, 59, 48, 0.11);
  box-shadow: inset 0 0 0 1px rgba(255, 59, 48, 0.2);
  margin-bottom: 18px;
}
.auth-error svg { width: 16px; height: 16px; flex: none; margin-top: 1px; }

.auth-note {
  display: flex;
  align-items: flex-start;
  gap: 9px;
  padding: 11px 13px;
  border-radius: 12px;
  font-size: 12px;
  line-height: 1.6;
  color: var(--text-secondary);
  background: rgba(255, 255, 255, 0.34);
  box-shadow: inset 0 0 0 0.5px rgba(255, 255, 255, 0.6);
  margin-top: auto;
}
.auth-note svg { width: 15px; height: 15px; flex: none; margin-top: 1px; opacity: 0.7; }
.auth-note.info {
  color: #0a5bd0;
  background: rgba(0, 122, 255, 0.10);
  box-shadow: inset 0 0 0 0.5px rgba(0, 122, 255, 0.22);
  margin-top: 0;
  margin-bottom: 22px;
}

/* ── 暗色 ── */
[data-theme="dark"] .auth-page {
  background:
    radial-gradient(ellipse 900px 700px at  8% 88%, rgba(120, 60, 200, 0.22) 0%, transparent 60%),
    radial-gradient(ellipse 800px 600px at 92%  8%, rgba( 30, 90, 190, 0.22) 0%, transparent 60%),
    linear-gradient(150deg, #0d1220 0%, #171526 40%, #101a2c 100%);
}
[data-theme="dark"] .auth-split {
  background: rgba(28, 32, 46, 0.52);
  box-shadow:
    0 32px 90px rgba(0, 0, 0, 0.45),
    inset 0 0 0 0.5px rgba(255, 255, 255, 0.13),
    inset 0 1px 0 rgba(255, 255, 255, 0.16);
}
[data-theme="dark"] .auth-form-col {
  background: rgba(255, 255, 255, 0.04);
  box-shadow: inset 0.5px 0 0 rgba(255, 255, 255, 0.10);
}
[data-theme="dark"] .auth-feature,
[data-theme="dark"] .auth-tabs,
[data-theme="dark"] .auth-note {
  background: rgba(255, 255, 255, 0.06);
  box-shadow: inset 0 0 0 0.5px rgba(255, 255, 255, 0.10);
}
[data-theme="dark"] .auth-feature:hover { background: rgba(255, 255, 255, 0.11); }
[data-theme="dark"] .auth-tab.active {
  color: #5aa9ff;
  background: rgba(255, 255, 255, 0.14);
}
[data-theme="dark"] .auth-split .auth-input {
  background: rgba(255, 255, 255, 0.07);
  color: #f4f4f6;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.12);
}
[data-theme="dark"] .auth-split .auth-input:focus {
  background: rgba(255, 255, 255, 0.12);
  box-shadow:
    inset 0 0 0 1px rgba(10, 132, 255, 0.55),
    0 0 0 4px rgba(10, 132, 255, 0.16);
}
[data-theme="dark"] .auth-secondary {
  color: #f4f4f6;
  background: rgba(255, 255, 255, 0.08);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.12);
}
[data-theme="dark"] .auth-secondary:hover { background: rgba(255, 255, 255, 0.14); }
[data-theme="dark"] .auth-reveal:hover { background: rgba(255, 255, 255, 0.1); }
[data-theme="dark"] .auth-divider::before,
[data-theme="dark"] .auth-divider::after { background: rgba(255, 255, 255, 0.12); }
[data-theme="dark"] .auth-error { color: #ff8a80; }
[data-theme="dark"] .auth-note.info { color: #7db8ff; }
[data-theme="dark"] .auth-link { color: #5aa9ff; }

/* ── 窄屏：单栏，左栏特性卡片收起 ── */
@media (max-width: 900px) {
  .auth-split {
    grid-template-columns: 1fr;
    max-width: 460px;
    min-height: 0;
  }
  .auth-brand { padding: 38px 32px 28px; gap: 18px; }
  .auth-features { display: none; }
  /* 圆角必须跟着堆叠方向转 90°。并排时的「左圆右方 / 右圆左方」在单列下会把方角
     摞到外壳的圆角上，而 .auth-split 不裁剪溢出，方角就戳出壳外形成台阶。 */
  .auth-brand { border-radius: 32px 32px 0 0; }
  .auth-form-col,
  .auth-form-pane {
    border-radius: 0 0 32px 32px;
  }
  .auth-form-col {
    padding: 28px 32px 38px;
    box-shadow: inset 0 0.5px 0 rgba(255, 255, 255, 0.55);
  }
  [data-theme="dark"] .auth-form-col { box-shadow: inset 0 0.5px 0 rgba(255, 255, 255, 0.10); }
  .auth-brand-title { font-size: 23px; }
  .auth-note { margin-top: 18px; }
}

/* ============ 日志控制台 ============ */

.log-console-page { display: flex; flex-direction: column; gap: 14px; min-height: 0; }
.log-control-panel { padding: 16px 18px; }
.log-control-head,
.log-connection,
.log-primary-actions,
.log-collection-row,
.log-display-levels,
.log-filter-actions,
.log-inspector-head,
.log-detail-title { display: flex; align-items: center; gap: 10px; }
.log-control-head { justify-content: space-between; gap: 16px; }
.log-connection { flex-wrap: wrap; gap: 8px; }
.log-connection .health-dot { width: 8px; height: 8px; border-radius: 50%; }
.log-connection strong { font-size: 14px; letter-spacing: -0.1px; }
.log-primary-actions { flex-wrap: wrap; justify-content: flex-end; }
.log-collection-row { margin-top: 16px; flex-wrap: wrap; padding-top: 14px; border-top: 1px solid var(--hairline); }
.log-control-label { display: flex; flex-direction: column; gap: 1px; margin-right: 4px; }

/* 采集等级是单选，用 iOS 分段控件而不是一排药丸 */
.log-level-segments { display: inline-flex; padding: 2px; gap: 2px; border-radius: var(--radius-sm); background: var(--bg-inset); box-shadow: inset 0 0 0 1px var(--hairline); }
.log-level-segment { appearance: none; border: 0; background: transparent; padding: 5px 13px; border-radius: 8px; font: inherit; font-size: 12px; font-weight: 600; letter-spacing: 0.02em; color: var(--text-2); cursor: pointer; transition: background var(--t-fast), color var(--t-fast), box-shadow var(--t-fast); }
.log-level-segment:hover:not(:disabled):not(.active) { color: var(--text-1); }
.log-level-segment.active { background: var(--surface-solid); color: var(--text-1); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); }
.log-level-segment.active.seg-error { color: var(--red); }
.log-level-segment.active.seg-warn { color: var(--orange); }
.log-level-segment.active.seg-info { color: var(--blue); }
.log-level-segment.active.seg-debug { color: var(--indigo); }
.log-level-segment.active.seg-trace { color: var(--text-2); }
.log-level-segment.active.seg-custom { color: var(--purple); }
.log-level-segment:disabled { cursor: default; opacity: 0.5; }

/* 显示筛选是多选，用可切换 chip，选中时反白填充 */
.log-filter-chip { appearance: none; padding: 3px 11px; border: 1px solid var(--hairline); border-radius: 9999px; background: transparent; font: inherit; font-size: 11.5px; font-weight: 600; color: var(--text-3); cursor: pointer; transition: background var(--t-fast), color var(--t-fast), border-color var(--t-fast); }
.log-filter-chip:hover { color: var(--text-1); border-color: var(--hairline-strong); }
.log-filter-chip.active { color: var(--text-invert); border-color: transparent; }
.log-filter-chip.active.chip-error { background: var(--red); }
.log-filter-chip.active.chip-warn { background: var(--orange); }
.log-filter-chip.active.chip-info { background: var(--blue); }
.log-filter-chip.active.chip-debug { background: var(--indigo); }
.log-filter-chip.active.chip-trace { background: var(--text-2); }
.log-custom-filter { display: grid; grid-template-columns: minmax(240px, 1fr) auto; gap: 8px; flex: 1 1 420px; }
.log-filter-grid { display: grid; grid-template-columns: minmax(300px, 1.5fr) minmax(240px, 1.3fr) minmax(130px, .7fr) minmax(170px, 1fr); gap: 12px; align-items: end; margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--hairline); }
.input-icon-wrap { position: relative; display: block; }
.input-icon-wrap > svg { position: absolute; left: 10px; top: 50%; width: 15px; height: 15px; transform: translateY(-50%); color: var(--text-3); pointer-events: none; }
.input-icon-wrap > .input { padding-left: 34px; }
.log-display-levels { flex-wrap: wrap; align-self: center; }
.log-display-levels .field-label { width: 100%; }
.log-search-field { min-width: 0; }
.log-filter-grid label { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.log-filter-grid .select { width: 100%; }
.log-filter-actions { grid-column: 1 / -1; flex-wrap: wrap; justify-content: flex-end; }
.log-workspace { display: grid; grid-template-columns: minmax(0, 1fr) minmax(330px, 400px); gap: 14px; min-height: 520px; height: calc(100dvh - 400px); }

/* 圆角/边框/背景归外壳，滚动归内层：滚动条槽是独立绘制层，不受自身 border-radius
   裁剪，只有外层 overflow:hidden 能把它的方角裁掉，两块面板才是同一个圆角。 */
.log-pane { min-width: 0; min-height: 0; overflow: hidden; border-radius: var(--radius-lg); border: 1px solid var(--hairline); box-shadow: var(--shadow-card); }
.log-pane-stream { background: var(--glass); }
.log-pane-inspector { background: var(--glass-strong); }
.log-scroll { height: 100%; overflow-y: auto; overflow-x: hidden; overscroll-behavior: contain; scrollbar-gutter: stable; }
.log-stream { padding: 6px; }

/* 密排数据配细的悬浮滚动条，默认透明，指针进入面板才淡入 */
.log-scroll { scrollbar-width: thin; scrollbar-color: transparent transparent; }
.log-pane:hover .log-scroll,
.log-scroll:focus-within { scrollbar-color: var(--hairline-strong) transparent; }
.log-scroll::-webkit-scrollbar { width: 10px; }
.log-scroll::-webkit-scrollbar-track { background: transparent; }
.log-scroll::-webkit-scrollbar-thumb { background: transparent; border: 3px solid transparent; border-radius: 9999px; background-clip: padding-box; transition: background var(--t-fast); }
.log-pane:hover .log-scroll::-webkit-scrollbar-thumb,
.log-scroll:focus-within::-webkit-scrollbar-thumb { background: var(--hairline-strong); background-clip: padding-box; }
.log-scroll::-webkit-scrollbar-thumb:hover { background: var(--text-3); background-clip: padding-box; }

/* 日志是密排数据行，不是卡片列表：靠等宽列、级别底色和行内强调条扫描 */
.log-event { position: relative; width: 100%; display: grid; grid-template-columns: 74px 62px 84px minmax(140px, 210px) minmax(180px, 1fr) auto 16px; align-items: baseline; gap: 10px; padding: 7px 10px 7px 16px; border: 0; border-radius: 7px; background: transparent; color: var(--text-1); font-size: 12.5px; text-align: left; cursor: pointer; transition: background var(--t-fast); }
/* 强调条画在行内部并留出上下内缩，避免贴到面板圆角被裁成碎片 */
.log-event::before { content: ""; position: absolute; left: 6px; top: 5px; bottom: 5px; width: 3px; border-radius: 9999px; background: transparent; }
.log-event + .log-event { margin-top: 1px; }
.log-event:hover { background: var(--bg-hover); }
.log-event.selected { background: var(--bg-active); }
.log-event.level-error { background: color-mix(in srgb, var(--red) 5%, transparent); }
.log-event.level-error:hover { background: color-mix(in srgb, var(--red) 10%, transparent); }
.log-event.level-warn { background: color-mix(in srgb, var(--orange) 5%, transparent); }
.log-event.level-warn:hover { background: color-mix(in srgb, var(--orange) 10%, transparent); }
.log-event.level-error::before { background: var(--red); }
.log-event.level-warn::before { background: var(--orange); }
.log-event.selected::before { background: var(--blue); }
.log-event-time { color: var(--text-3); font-size: 11.5px; font-variant-numeric: tabular-nums; }

/* 级别标签在密排行里用短横条式实体色，比药丸更省高度也更好对齐 */
.log-level-tag { justify-content: center; padding: 1px 0; border-radius: 4px; font-size: 10px; font-weight: 700; letter-spacing: 0.04em; text-align: center; }
.log-level-tag.tag-red { background: var(--red); color: var(--text-invert); }
.log-level-tag.tag-orange { background: var(--orange); color: var(--text-invert); }
.log-level-tag.tag-blue { background: var(--blue); color: var(--text-invert); }
.log-level-tag.tag-indigo { background: var(--indigo); color: var(--text-invert); }
.log-level-tag.tag-gray { background: var(--bg-inset); color: var(--text-2); box-shadow: inset 0 0 0 1px var(--hairline); }
.log-event-bot { color: var(--text-3); font-size: 11.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.log-event-target { color: var(--text-2); font-size: 11.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.log-event-summary { min-width: 0; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; overflow-wrap: anywhere; line-height: 1.45; }
.log-event > svg { width: 14px; height: 14px; color: var(--text-3); opacity: 0; transition: opacity var(--t-fast); align-self: center; }
.log-event:hover > svg,
.log-event.selected > svg { opacity: 1; }
.log-inspector { min-height: 100%; padding: 16px 18px; }
.log-inspector-empty { min-height: 260px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px; text-align: center; }
.log-inspector-empty > svg { width: 28px; height: 28px; color: var(--text-3); }
.log-inspector-head { justify-content: space-between; margin-bottom: 14px; padding-bottom: 12px; border-bottom: 1px solid var(--hairline); }
.log-inspector-head > div { display: flex; align-items: center; gap: 8px; }
.log-meta-grid,
.log-fields { display: grid; grid-template-columns: 76px minmax(0, 1fr); gap: 8px 12px; margin: 0; font-size: 12.5px; }
.log-meta-grid { padding: 12px; border-radius: var(--radius-sm); background: var(--bg-inset); }
.log-meta-grid dt,
.log-fields dt { color: var(--text-3); font-size: 11.5px; }
.log-meta-grid dd,
.log-fields dd { margin: 0; min-width: 0; overflow-wrap: anywhere; }
.log-code-scroll { overflow-x: auto; white-space: nowrap; }
.log-truncated-note { margin: 12px 0; padding: 8px 10px; border: 1px solid color-mix(in srgb, var(--orange) 34%, var(--hairline-strong)); border-radius: 6px; background: var(--orange-soft); color: var(--orange); }
.log-detail-section { margin-top: 18px; }
.log-detail-title { justify-content: space-between; margin-bottom: 8px; font-size: 12px; font-weight: 600; color: var(--text-2); text-transform: uppercase; letter-spacing: 0.04em; }
.log-detail-title .btn { text-transform: none; letter-spacing: normal; }
.log-detail-section pre,
.log-raw-details pre { margin: 0; padding: 10px; border-radius: 6px; background: var(--bg-inset); color: var(--text-1); font-family: var(--mono); font-size: 12px; line-height: 1.55; white-space: pre-wrap; overflow-wrap: anywhere; overflow-x: auto; }
.log-fields code { display: block; padding: 5px 7px; border-radius: 4px; background: var(--bg-inset); white-space: pre-wrap; overflow-wrap: anywhere; }
.log-raw-details { margin-top: 16px; }
.log-raw-details summary { cursor: pointer; font-weight: 600; }
.log-raw-details[open] summary { margin-bottom: 8px; }

@media (max-width: 980px) {
  .log-filter-grid { grid-template-columns: 1fr 1fr; }
  .log-display-levels,
  .log-search-field { grid-column: 1 / -1; }
  .log-workspace { grid-template-columns: minmax(0, 1fr); height: calc(100dvh - 430px); }
  .log-pane-inspector { display: none; }
  .log-workspace.detail-open .log-pane-inspector { display: block; position: fixed; inset: 0; z-index: 1200; border: 0; border-radius: 0; }
}
@media (max-width: 640px) {
  .log-control-panel { padding: 12px; }
  .log-control-head { align-items: flex-start; }
  .log-control-head,
  .log-collection-row { flex-direction: column; align-items: stretch; }
  .log-primary-actions { justify-content: flex-start; }
  .log-custom-filter,
  .log-filter-grid { grid-template-columns: 1fr; }
  .log-display-levels,
  .log-search-field,
  .log-filter-actions { grid-column: 1; }
  .log-filter-actions { justify-content: flex-start; }
  .log-workspace { height: calc(100dvh - 540px); min-height: 360px; }
  .log-level-segments { width: 100%; justify-content: space-between; }
  .log-level-segment { flex: 1; padding: 6px 4px; text-align: center; }
  .log-event { grid-template-columns: 66px 54px minmax(0, 1fr) 14px; gap: 4px 8px; padding: 8px 9px 8px 15px; }
  .log-event-time { grid-column: 1; grid-row: 1; }
  .log-level-tag { grid-column: 2; grid-row: 1; }
  .log-event-bot { grid-column: 3; grid-row: 1; }
  .log-event-target { grid-column: 1 / 4; grid-row: 2; font-size: 11px; }
  .log-event-summary { grid-column: 1 / 4; grid-row: 3; }
  .log-event > .tag:not(.log-level-tag) { grid-column: 1 / 4; grid-row: 4; justify-self: start; }
  .log-event > svg { grid-column: 4; grid-row: 1; opacity: 1; }
}

/* ============ AI 聊天 ============ */

.chat-layout {
  display: flex;
  flex-direction: column;
  height: calc(100vh - 170px);
  min-height: 420px;
}
.chat-scroll {
  flex: 1;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 6px 4px 18px;
}
.chat-msg { display: flex; gap: 10px; max-width: 78%; }
.chat-msg.user { align-self: flex-end; flex-direction: row-reverse; }
.chat-avatar {
  width: 32px; height: 32px;
  border-radius: 50%;
  display: grid; place-items: center;
  color: #fff; font-size: 13px; font-weight: 700;
  flex-shrink: 0;
}
.chat-msg.user .chat-avatar { background: var(--grad-blue); }
.chat-msg.assistant .chat-avatar { background: var(--grad-purple); }
.chat-bubble {
  padding: 10px 14px;
  border-radius: 16px;
  font-size: 13.5px;
  line-height: 1.6;
  white-space: pre-wrap;
  word-break: break-word;
}
.chat-msg.user .chat-bubble {
  background: var(--blue);
  color: #fff;
  border-bottom-right-radius: 5px;
}
.chat-msg.assistant .chat-bubble {
  background: var(--surface-solid);
  border: 1px solid var(--hairline);
  border-bottom-left-radius: 5px;
  box-shadow: var(--shadow-card);
}
.chat-input-bar {
  display: flex;
  gap: 10px;
  align-items: flex-end;
  padding-top: 12px;
  border-top: 1px solid var(--hairline);
}
.chat-input-bar .textarea { min-height: 44px; max-height: 140px; border-radius: 14px; }

/* ============ 插件列表 master-detail ============ */

.split-layout { display: grid; grid-template-columns: 300px 1fr; gap: 16px; align-items: start; }
.list-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background var(--t-fast);
  border: 1px solid transparent;
}
.list-item:hover { background: var(--bg-hover); }
.list-item.active { background: var(--blue-soft); border-color: rgba(0, 122, 255, 0.25); }
.list-item-title { font-size: 13.5px; font-weight: 600; }
.list-item-sub { font-size: 11.5px; color: var(--text-3); }

/* ============ 群插件设置（按群自助页） ============ */

/* 一个插件一张卡。沿用 .panel 的材质而不是新做一种容器，保持与其他页面同一层次感；
   这里只补卡片之间的间距和内部几处排布。 */
.gp-list { display: flex; flex-direction: column; gap: 14px; }
.gp-card-title { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.gp-intro { margin-top: 10px; line-height: 1.6; }
.gp-notice { margin-bottom: 12px; }

/* 字段标题与来源标记同一行：来源要紧贴它解释的那个字段，
   放到控件下面会读成对输入内容的说明。 */
.gp-field-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
}

/* 字段来源标记。刻意比 .tag 更轻：一屏里每个字段都带一个，
   实心底色会让整页都在闪。 */
.gp-origin {
  font-size: 11px;
  color: var(--text-3);
  white-space: nowrap;
  cursor: help;
}
.gp-origin-own { color: var(--blue); cursor: default; }

/* 判不出类型、只能查看的字段。做成与 .input 同高同圆角的静态块，
   一眼看出「这里本该有个控件但它不可编辑」，而不是像渲染漏了。 */
.gp-readonly {
  padding: 8px 11px;
  font-size: 12.5px;
  color: var(--text-2);
  background: var(--bg-inset);
  border: 1px dashed var(--hairline-strong);
  border-radius: var(--radius-sm);
  word-break: break-all;
}
.gp-readonly-hint { color: var(--orange); }

.gp-card-foot {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 10px;
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--hairline);
}

.gp-no-fields { font-size: 12.5px; color: var(--text-3); padding: 4px 0; }

/* ============ 描述列表 ============ */

.desc-list { display: grid; grid-template-columns: auto 1fr; gap: 8px 18px; font-size: 13px; }
.desc-list dt { color: var(--text-2); font-weight: 500; white-space: nowrap; }
.desc-list dd { color: var(--text-1); word-break: break-all; }

/* JSON 预览 */
.json-view {
  font-family: var(--mono);
  font-size: 12px;
  line-height: 1.6;
  background: var(--bg-inset);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-sm);
  padding: 12px 14px;
  overflow: auto;
  max-height: 380px;
  white-space: pre-wrap;
  word-break: break-all;
}

/* ============ 甜甜圈 / 环形进度 ============ */

.ring-wrap { position: relative; display: grid; place-items: center; }
.ring-center { position: absolute; text-align: center; }
.ring-value { font-size: 22px; font-weight: 700; letter-spacing: -0.5px; }
.ring-label { font-size: 11px; color: var(--text-2); }

/* ============ 步骤时间线（SOP） ============ */

.steps { display: flex; flex-direction: column; }
.step { display: flex; gap: 12px; }
.step-rail { display: flex; flex-direction: column; align-items: center; }
.step-dot {
  width: 12px; height: 12px; border-radius: 50%;
  background: var(--bg-inset);
  border: 2.5px solid var(--text-3);
  flex-shrink: 0;
  margin-top: 4px;
}
.step-dot.done { border-color: var(--green); background: var(--green); }
.step-dot.running { border-color: var(--blue); background: var(--blue-soft); }
.step-dot.failed { border-color: var(--red); background: var(--red); }
.step-line { width: 2px; flex: 1; background: var(--hairline-strong); margin: 3px 0; min-height: 18px; }
.step-body { padding-bottom: 18px; flex: 1; min-width: 0; }
.step-title { font-size: 13px; font-weight: 600; }
.step-meta { font-size: 11.5px; color: var(--text-3); margin-top: 2px; }

/* ============ 外观设置（主题选择 + 壁纸库） ============ */

.theme-picker { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.theme-option {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 12px;
  border-radius: var(--radius-md);
  border: 2px solid var(--hairline);
  background: var(--bg-inset);
  cursor: pointer;
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-1);
  transition: border-color var(--t-fast), transform var(--t-fast);
}
.theme-option:hover { transform: translateY(-1px); }
.theme-option.active { border-color: var(--blue); box-shadow: 0 0 0 3px var(--blue-soft); }
.theme-option svg { width: 15px; height: 15px; }

.theme-swatch {
  height: 64px;
  border-radius: var(--radius-sm);
  padding: 10px;
  display: flex;
  flex-direction: column;
  gap: 7px;
}
.light-swatch { background: linear-gradient(135deg, #f5f5f7, #e8ecff); border: 1px solid rgba(0,0,0,0.07); }
.dark-swatch { background: linear-gradient(135deg, #101a33, #0b0b0d); border: 1px solid rgba(255,255,255,0.1); }
.swatch-bar { height: 7px; width: 70%; border-radius: 4px; background: rgba(128, 128, 140, 0.4); }
.light-swatch .swatch-bar { background: rgba(0, 0, 0, 0.18); }
.dark-swatch .swatch-bar { background: rgba(255, 255, 255, 0.28); }
.swatch-dot { width: 16px; height: 16px; border-radius: 50%; background: var(--blue); }

/* 滑杆 */
.slider {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 5px;
  border-radius: 3px;
  background: var(--hairline-strong);
  outline: none;
  cursor: pointer;
}
.slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 19px;
  height: 19px;
  border-radius: 50%;
  background: #fff;
  border: 1px solid rgba(0, 0, 0, 0.08);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.25);
  transition: transform var(--t-fast);
}
.slider::-webkit-slider-thumb:hover { transform: scale(1.12); }
.slider::-moz-range-thumb {
  width: 19px; height: 19px; border-radius: 50%;
  background: #fff; border: 1px solid rgba(0, 0, 0, 0.08);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.25);
}

/* 效果预览 */
.wallpaper-preview {
  position: relative;
  height: 190px;
  border-radius: var(--radius-md);
  border: 1px solid var(--hairline);
  overflow: hidden;
  background: var(--bg-desktop);
  display: grid;
  place-items: center;
}
.wallpaper-preview-img {
  position: absolute;
  inset: -8px;
  background-size: cover;
  background-position: center;
  transition: filter 0.2s ease, opacity 0.2s ease;
}
.wallpaper-preview-card {
  position: relative;
  width: 55%;
  padding: 18px 16px;
  border-radius: var(--radius-md);
  background: var(--glass);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--hairline);
  box-shadow: var(--shadow-card);
  display: flex;
  flex-direction: column;
  gap: 9px;
}
.wallpaper-preview-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  color: var(--text-3);
}
.wallpaper-preview-empty svg { width: 30px; height: 30px; opacity: 0.5; }

/* 壁纸库网格 */
.wallpaper-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
  gap: 11px;
}
.wallpaper-cell {
  position: relative;
  aspect-ratio: 16 / 10;
  border-radius: var(--radius-sm);
  border: 2px solid var(--hairline);
  background-size: cover;
  background-position: center;
  background-color: var(--bg-inset);
  cursor: pointer;
  overflow: hidden;
  transition: border-color var(--t-fast), transform var(--t-fast), box-shadow var(--t-fast);
}
.wallpaper-cell:hover { transform: translateY(-2px); box-shadow: var(--shadow-card); }
.wallpaper-cell.active { border-color: var(--blue); box-shadow: 0 0 0 3px var(--blue-soft); }
.wallpaper-cell.active::after {
  content: "✓";
  position: absolute;
  right: 7px;
  bottom: 6px;
  width: 21px;
  height: 21px;
  border-radius: 50%;
  background: var(--blue);
  color: #fff;
  font-size: 12px;
  font-weight: 700;
  display: grid;
  place-items: center;
  box-shadow: 0 2px 6px rgba(0, 122, 255, 0.45);
}
.wallpaper-cell.none {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 5px;
  color: var(--text-3);
}
.wallpaper-cell.none svg { width: 18px; height: 18px; }
.wallpaper-cell-badge {
  position: absolute;
  left: 7px;
  top: 6px;
  padding: 1.5px 8px;
  border-radius: 9999px;
  font-size: 10.5px;
  font-weight: 600;
  color: #fff;
  background: rgba(0, 0, 0, 0.42);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}
.wallpaper-cell-delete {
  position: absolute;
  right: 6px;
  top: 6px;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  border: none;
  background: rgba(0, 0, 0, 0.45);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  color: #fff;
  display: grid;
  place-items: center;
  cursor: pointer;
  opacity: 0;
  transition: opacity var(--t-fast), background var(--t-fast);
}
.wallpaper-cell:hover .wallpaper-cell-delete { opacity: 1; }
.wallpaper-cell-delete:hover { background: var(--red); }
.wallpaper-cell-delete svg { width: 12px; height: 12px; }

/* ============ 品牌与管理员资料设置 ============ */

.branding-setting, .profile-setting {
  display: flex;
  align-items: center;
  gap: 18px;
}
.branding-logo-preview {
  width: 88px; height: 88px;
  display: grid;
  place-items: center;
  flex-shrink: 0;
  border-radius: var(--radius-md);
  border: 1px solid var(--hairline);
  background: var(--bg-inset);
}
.branding-logo-preview .brand-logo { width: 58px; height: 58px; border-radius: 16px; font-size: 25px; }
.branding-logo-preview .brand-logo.custom { border-radius: 0; }
.branding-setting-body, .profile-setting-body { display: flex; min-width: 0; flex: 1; flex-direction: column; gap: 9px; }
.profile-username-field { max-width: 360px; margin-bottom: 0; }
.profile-username-field .input[readonly] { color: var(--text-2); background: var(--bg-inset); cursor: default; }

/* ============ Bot 实例管理 ============ */

.bot-tabs-head { margin-bottom: 0; }
.bot-instance-name { font-weight: 650; white-space: nowrap; }
.bot-instance-table { min-width: 1220px; }
.bot-instance-actions { flex-wrap: wrap; min-width: 210px; }
.bot-mode-cell, .bot-ingress-cell { display: flex; align-items: flex-start; flex-direction: column; gap: 5px; }
.bot-webhook-guide {
  margin-bottom: 16px;
  padding: 12px 14px;
  color: var(--text-2);
  font-size: 12.5px;
  line-height: 1.65;
  background: color-mix(in srgb, var(--blue) 8%, var(--bg-inset));
  border: 1px solid color-mix(in srgb, var(--blue) 24%, var(--hairline));
  border-radius: var(--radius-md);
}
.bot-webhook-guide-title { margin-bottom: 5px; color: var(--text-1); font-weight: 700; }
.bot-webhook-guide ul { margin: 0; padding-left: 19px; }
.bot-webhook-success-head { display: flex; align-items: flex-start; gap: 12px; margin-bottom: 18px; }
.bot-webhook-success-head > svg { width: 30px; height: 30px; padding: 6px; color: var(--green); background: color-mix(in srgb, var(--green) 12%, transparent); border-radius: 50%; }
.bot-webhook-success-head strong { display: block; margin-bottom: 3px; font-size: 16px; }
.bot-webhook-success-head p { margin: 0; color: var(--text-2); font-size: 13px; line-height: 1.55; }
.callback-field { margin-bottom: 14px; }
.copy-field { display: flex; align-items: stretch; gap: 8px; }
.copy-field .input { min-width: 0; }
.copy-button { flex: 0 0 auto; }
.copy-button svg { width: 15px; height: 15px; }
.bot-last-error {
  display: block;
  max-width: 220px;
  overflow: hidden;
  color: var(--red);
  font-size: 12px;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.bot-enabled-field {
  width: fit-content;
  margin-bottom: 16px;
  cursor: pointer;
}
.bot-enabled-field > span { display: flex; flex-direction: column; gap: 1px; }
.bot-enabled-help { display: block; }
.bot-advanced {
  border: 1px solid var(--hairline);
  border-radius: var(--radius-md);
  background: var(--bg-inset);
  overflow: hidden;
}
.bot-advanced summary {
  padding: 11px 14px;
  color: var(--text-2);
  font-size: 12.5px;
  font-weight: 650;
  cursor: pointer;
  user-select: none;
}
.bot-advanced[open] summary { border-bottom: 1px solid var(--hairline); }
.bot-advanced-body { padding: 14px; }
.bot-advanced-body .field:last-child { margin-bottom: 0; }

/* ============ 订阅计费 ============ */

/* 客户视角每个群一张卡：状态、到期、以及那段解释「现在该做什么」的话。
   表格塞不下这段说明，而说明恰恰是未开通/已过期客户最需要读到的东西。 */
.billing-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
  gap: 14px;
}
.billing-card {
  border: 1px solid var(--hairline);
  border-radius: 12px;
  padding: 16px;
  background: var(--bg-inset);
}
.billing-card-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 12px;
}
.billing-card-gid { font-size: 15px; font-weight: 600; color: var(--text-1); }
.billing-card-plan { margin-top: 2px; }
.billing-card-headline {
  font-size: 13.5px;
  font-weight: 600;
  color: var(--text-1);
  margin-bottom: 6px;
}
.billing-card-body {
  font-size: 12.5px;
  line-height: 1.7;
  color: var(--text-2);
  margin: 0 0 12px;
}
/* 「还有 N 天」跟在日期后面：日期用于对账，天数用于判断急不急。 */
.billing-remain {
  margin-left: 8px;
  font-size: 12px;
  color: var(--text-2);
}

/* 一条都没有时的说明块。用中性底色而不是红色告警：没开通不是故障。 */
.billing-notice {
  border: 1px solid var(--hairline);
  border-radius: 12px;
  padding: 20px;
  background: var(--bg-inset);
}
.billing-notice-title {
  font-size: 14px;
  font-weight: 600;
  color: var(--text-1);
  margin-bottom: 8px;
}
.billing-notice-body {
  font-size: 12.5px;
  line-height: 1.75;
  color: var(--text-2);
  margin: 0 0 8px;
}

/* 提交前的对账窗口：把「元」换算成的整数分和推算出的新到期时间摆出来。 */
.billing-preview {
  margin-top: 14px;
  padding: 12px 14px;
  border: 1px solid var(--hairline);
  border-radius: 10px;
  background: var(--bg-inset);
}
.billing-preview-title {
  font-size: 12px;
  font-weight: 600;
  color: var(--text-2);
  margin-bottom: 8px;
}
.billing-preview-bad { color: var(--red); font-size: 12.5px; }
.billing-preview-note {
  margin: 10px 0 0;
  font-size: 11.5px;
  line-height: 1.65;
  color: var(--text-2);
}

/* 追加式账本的说明条，和冲正行的视觉标记。
   冲正行必须一眼可辨：一列正负数混排时，只靠减号读账太容易漏。 */
.ledger-note {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  margin-bottom: 12px;
  padding: 10px 12px;
  border-radius: 8px;
  background: var(--bg-inset);
  font-size: 12px;
  line-height: 1.6;
  color: var(--text-2);
}
.ledger-note svg { width: 15px; height: 15px; flex: none; margin-top: 1px; }
.table tr.ledger-correction { background: var(--orange-soft); }
.table tr.ledger-correction .num { color: var(--orange); font-weight: 600; }

/* ============ 404 ============ */

.not-found-page {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 24px;
}
.not-found-panel {
  width: min(100%, 380px);
  padding: 34px;
  text-align: center;
  background: var(--glass-strong);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-pop);
}
.not-found-mark { color: var(--blue); font-size: 42px; font-weight: 800; line-height: 1; }
.not-found-title { margin-top: 15px; font-size: 20px; font-weight: 700; }
.not-found-sub { margin: 6px 0 22px; color: var(--text-2); font-size: 13px; }

/* ============ 响应式 ============ */

@media (max-width: 960px) {
  .sidebar { display: none; }
  .main { padding: 14px 14px calc(112px + env(safe-area-inset-bottom, 0px)); }
  .grid-2, .grid-2-1, .grid-1-2 { grid-template-columns: 1fr; }
  .split-layout { grid-template-columns: 1fr; }
  .topbar { flex-wrap: wrap; }
}

@media (max-width: 600px) {
  .topbar { gap: 9px; padding: 12px; }
  .page-sub, .health-badge, .account-name { display: none; }
  .account-menu-trigger { padding-right: 4px; }
  .account-menu-trigger > svg { display: none; }
  .account-menu-popover { position: fixed; top: 72px; right: 14px; }
  .branding-setting, .profile-setting { align-items: flex-start; flex-direction: column; }
  .auth-brand { padding-left: 24px; padding-right: 24px; }
  .auth-form-col { padding-left: 24px; padding-right: 24px; }
}

/* ============================================================
   增强动画系统
   ============================================================ */

/* 页面内容入场动画：只动 opacity，不动 transform。
   这是「切页不丝滑」的真正原因。`.topbar` / `.panel` 都带 `backdrop-filter`，而
   `backdrop-filter` 的取样源是元素身后的那块画面。元素一旦位移，浏览器就必须在动画的
   每一帧重新取样、重新做一次高斯模糊 —— 而背后是 `html` 上三层 radial-gradient
   加一张 `background-attachment: fixed` 的壁纸，全都得跟着重绘。位移只有 6px，
   代价却是整块面板面积 × 动画帧数的模糊重算。
   纯 opacity 淡入可以整层交给合成器，不触发重绘。观感上 180ms 的 6px 位移本来就
   接近看不见，去掉它几乎不损失「落位感」，换来的是切页不再卡。 */
@keyframes glass-in {
  from { opacity: 0; }
}

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

@keyframes scaleIn {
  from {
    opacity: 0;
    transform: scale(0.96);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

@keyframes slideInRight {
  from {
    opacity: 0;
    transform: translateX(30px);
  }
  to {
    opacity: 1;
    transform: translateX(0);
  }
}

@keyframes pulse {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(1.05); }
}

@keyframes shimmer {
  0% { background-position: -200% 0; }
  100% { background-position: 200% 0; }
}

/* 页面内容入场动画。
   刻意不写 `opacity: 0` 起始态：那样一旦动画被推迟（delay）、被别的 animation 覆盖，
   或者元素在动画结束前被路由替换掉，元素就停在完全透明上 —— 那正是切页面时看到的
   「白屏一下」。这里改成 `backwards` 由 keyframes 自己定义起点，元素的静止态始终是
   可见的，动画只是把它推上来。
   交错延迟也一并去掉：0.05s~0.3s 的阶梯让最后一块内容比第一块晚 300ms 才开始显形，
   页面越复杂空白越久。整页一起 180ms 淡入，感觉比阶梯式更快。 */
.main > * {
  animation: glass-in 0.18s var(--ease) backwards;
}

/* 面板卡片增强动画。
   刻意只过渡 box-shadow，不过渡 transform：`.panel` 是整屏宽的毛玻璃面，
   位移会让它每帧重新采样一遍身后的背景（见 `glass-in` 的说明）。
   悬停靠阴影加深来表达「浮起来」，代价是一次合成而不是一次重绘。 */
.panel, .metric-card {
  transition: box-shadow 0.3s cubic-bezier(0.25, 0.1, 0.25, 1);
}

.panel:hover, .metric-card:hover {
  box-shadow:
    0 8px 32px rgba(0, 0, 0, 0.12),
    0 2px 8px rgba(0, 0, 0, 0.08),
    inset 0 0 0 0.5px var(--glass-edge);
}

/* `.metric-card` 是小卡片，位移的重绘面积可以接受，保留那点抬起，但用更柔的弹性缓动。 */
.metric-card {
  position: relative;
  overflow: hidden;
  transition:
    transform 0.35s cubic-bezier(0.22, 1, 0.36, 1),
    box-shadow 0.35s cubic-bezier(0.22, 1, 0.36, 1);
}

.metric-card:hover {
  transform: translateY(-3px);
  box-shadow:
    0 14px 32px rgba(0, 0, 0, 0.12),
    0 3px 10px rgba(0, 0, 0, 0.06),
    inset 0 0 0 0.5px var(--glass-edge);
}

/* 玻璃扫光：悬停时一道斜向高光从左滑过。背景位置的起止都在可视区之外，
   动画结束后落回静止态的 off-screen 位置，视觉上就是扫完即隐。
   只动 background-position + opacity，不引入位移，避免大面积重绘。 */
.metric-card::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: linear-gradient(120deg, transparent 40%, rgba(255, 255, 255, 0.17) 50%, transparent 60%);
  background-size: 300% 100%;
  background-position: 220% 0;
  opacity: 0;
  transition: opacity 0.35s ease;
  pointer-events: none;
}
.metric-card:hover::before {
  opacity: 1;
  animation: sheenSweep 0.85s cubic-bezier(0.25, 0.1, 0.25, 1);
}

@keyframes sheenSweep {
  0% { background-position: 220% 0; }
  100% { background-position: -80% 0; }
}

/* Dock 图标增强动画 */
.dock-group {
  transition:
    transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1),
    background 0.2s ease,
    box-shadow 0.2s ease,
    color 0.2s ease;
}

/* 悬停时图标背后的径向柔光，随图标一起长大。柔光虚化进玻璃里，比纯底色高亮更有温度。 */
.dock-group::before {
  content: '';
  position: absolute;
  inset: -5px;
  border-radius: 22px;
  background: radial-gradient(circle at 50% 42%, rgba(0, 122, 255, 0.22), transparent 68%);
  opacity: 0;
  transform: scale(0.7);
  transition: opacity 0.3s ease, transform 0.35s cubic-bezier(0.22, 1, 0.36, 1);
  pointer-events: none;
}
.dock-group:hover::before { opacity: 1; transform: scale(1); }

/* 悬停上浮 + 轻微放大，向来源图标借一点「弹性」；:active 按后续规则压回。 */
.dock-group:hover {
  transform: translateY(-5px) scale(1.12);
  background: var(--bg-hover);
}

.dock-group:active {
  transform: translateY(-2px) scale(0.92);
}

.dock-group.active {
  animation: pulse 0.4s cubic-bezier(0.25, 0.1, 0.25, 1);
}

/* 触屏没有 hover，禁掉图标上浮与柔光，避免轻触后图标卡在抬起态。
   这条必须放在增强动画区块之后——否则会被上面 `.dock-group:hover` 的 transform 覆盖。 */
@media (max-width: 720px) {
  .dock-group:hover { transform: none; }
  .dock-group:hover::before { opacity: 0; }
}

/* 按钮增强动画 */
button, .btn, .auth-submit, .btn-primary {
  transition:
    transform 0.15s ease,
    background 0.2s ease,
    box-shadow 0.2s ease;
}

button:active:not(:disabled), .btn:active:not(:disabled),
.auth-submit:active:not(:disabled), .btn-primary:active:not(:disabled) {
  transform: scale(0.96);
}

/* 输入框焦点动画 */
.auth-input, input[type="text"], input[type="password"], input[type="email"] {
  transition:
    border-color 0.2s ease,
    box-shadow 0.2s ease,
    background 0.2s ease;
}

/* Toast 增强动画 */
.toast-region {
  perspective: 1000px;
}

.toast {
  animation: toastSlideIn 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
  transform-origin: top center;
}

@keyframes toastSlideIn {
  from {
    opacity: 0;
    transform: translateY(-30px) scale(0.9) rotateX(-10deg);
  }
  to {
    opacity: 1;
    transform: translateY(0) scale(1) rotateX(0);
  }
}

.toast.removing {
  animation: toastSlideOut 0.3s cubic-bezier(0.25, 0.1, 0.25, 1) forwards;
}

@keyframes toastSlideOut {
  to {
    opacity: 0;
    transform: translateY(-20px) scale(0.95);
  }
}

/* 标签页切换动画 */
.auth-tabs {
  position: relative;
}

.auth-tab {
  position: relative;
  overflow: hidden;
}

.auth-tab::after {
  content: '';
  position: absolute;
  bottom: 0;
  left: 50%;
  width: 0;
  height: 2px;
  background: var(--blue);
  transition: width 0.3s ease, left 0.3s ease;
}

.auth-tab.active::after {
  width: 60%;
  left: 20%;
}

/* 扇出面板项目交错动画。菜单是点开就要立刻能点的东西，所以每档 25ms、总时长 0.2s：
   原来第 5 项要等 0.25s 才开始显形，手已经移过去了目标还是透明的。 */
.fanout.open .fan-item {
  animation: fanItemIn 0.2s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
  opacity: 0;
  transform: translateY(10px);
}

.fanout.open .fan-item:nth-child(1) { animation-delay: 0.02s; }
.fanout.open .fan-item:nth-child(2) { animation-delay: 0.045s; }
.fanout.open .fan-item:nth-child(3) { animation-delay: 0.07s; }
.fanout.open .fan-item:nth-child(4) { animation-delay: 0.095s; }
.fanout.open .fan-item:nth-child(5) { animation-delay: 0.12s; }

@keyframes fanItemIn {
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* 加载骨架屏动画 */
.skeleton {
  background: linear-gradient(
    90deg,
    var(--bg-inset) 25%,
    var(--glass) 50%,
    var(--bg-inset) 75%
  );
  background-size: 200% 100%;
  animation: shimmer 1.5s infinite;
  border-radius: var(--radius-sm);
}

/* 数字变化动画 */
.ring-value {
  transition: color 0.3s ease;
}

/* 健康状态指示器脉冲 */
.health-dot.ok {
  animation: healthPulse 2s ease-in-out infinite;
}

@keyframes healthPulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(52, 199, 89, 0.4); }
  50% { box-shadow: 0 0 0 4px rgba(52, 199, 89, 0); }
}

/* 顶部栏入场交给 `.rise`。
   这里原来还有一套 `slideInDown ... forwards`，会把终态的 `transform: translateY(0)`
   永久留在顶栏上，额外创建没有必要的合成层。后台 surface 已经刻意减少 GPU 合成层，
   不要再为 20px 的重复入场动画把它加回来。 */

/* 链接悬停下划线动画。只给 `.auth-link`（登录页的纯文字链接）—— 二级菜单项不用：
   它已经有底色高亮 + 选中变蓝两层反馈，再加一条线就是第三种说法。
   原来这里还错把 `.fan-item:hover .fan-title::after` 同时写进基础规则和 hover 规则，
   两条同特异性、一条 width:0 一条 width:100%，后者胜出 ⇒ 没有过渡、直接弹出一条黑杠。 */
.auth-link {
  position: relative;
}

.auth-link::after {
  content: '';
  position: absolute;
  bottom: -2px;
  left: 0;
  width: 0;
  height: 1px;
  background: currentColor;
  transition: width 0.25s ease;
}

.auth-link:hover::after {
  width: 100%;
}

/* ---------- 加载骨架 ---------- */

/* 高度撑到 ~200px，接近一屏真实内容。原来的 `.loading-state` 只有 40px，切页时面板先塌
   成一条缝再撑开，那个「塌陷 → 回弹」就是看起来在白闪的东西。 */
.skeleton-block {
  padding: 4px 0;
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-height: 200px;
}

.skeleton-row {
  height: 16px;
  border-radius: 8px;
  /* 扫光用 background-position 动画，不动 transform/width —— 后两者会触发布局或合成层
     重算，一屏十几条同时跑就开始掉帧。 */
  background: linear-gradient(
    90deg,
    rgba(120, 130, 150, 0.07) 25%,
    rgba(120, 130, 150, 0.14) 37%,
    rgba(120, 130, 150, 0.07) 63%
  );
  background-size: 400% 100%;
  animation: skeletonSweep 1.4s ease-in-out infinite;
}

@keyframes skeletonSweep {
  from { background-position: 100% 50%; }
  to { background-position: 0 50%; }
}

/* 前庭功能敏感/减少动态偏好下停掉扫光，保留静态占位块。 */
@media (prefers-reduced-motion: reduce) {
  .skeleton-row { animation: none; }
}

/* 只给读屏软件的文字。骨架屏对视觉用户自解释，但屏幕阅读器需要一句「加载中」，
   否则它只会念出一堆空 div。 */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* 统计卡片图标动画：淡底描边样式下悬停只保留轻微放大，
   不再补投影——tint 底没有实体感，硬加影子反而像脏了一块。 */
.metric-card:hover .metric-icon {
  transform: scale(1.07);
}

/* 空状态图标动画 */
.empty-icon {
  animation: emptyFloat 3s ease-in-out infinite;
}

@keyframes emptyFloat {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(-8px); }
}

/* 图表线条绘制动画 */
.chart-line {
  stroke-dasharray: 1000;
  stroke-dashoffset: 1000;
  animation: drawLine 1.5s ease-out forwards;
}

@keyframes drawLine {
  to { stroke-dashoffset: 0; }
}

/* 趋势图悬停：竖向十字线、命中数据点与数值浮层。
   浮层位置由组件按 viewBox 百分比换算注入，靠近边缘时翻转 translate 对齐。 */
.trend-wrap { position: relative; }
.chart-cursor {
  stroke: var(--hairline-strong);
  stroke-width: 1;
  stroke-dasharray: 4 4;
  pointer-events: none;
}
.chart-dot {
  fill: var(--surface-solid);
  stroke: var(--blue);
  stroke-width: 2.5;
  pointer-events: none;
}
.chart-tip {
  position: absolute;
  z-index: 5;
  pointer-events: none;
  background: var(--surface-solid);
  border: 1px solid var(--hairline);
  border-radius: 10px;
  box-shadow: var(--shadow-pop);
  padding: 7px 11px;
  white-space: nowrap;
}
.chart-tip-time { font-size: 11.5px; color: var(--text-3); }
.chart-tip-value { font-size: 14px; font-weight: 650; color: var(--text-1); font-variant-numeric: tabular-nums; }

/* 暗色模式切换过渡。
   只挂在 `html, body` 上。原来这里把 `.panel, .metric-card, .dock, .fanout` 也列进来，
   而 `transition` 是整属性覆盖 —— 这条写在文件末尾，直接把前面 `.panel` 的
   `transition: box-shadow` 和 `.metric-card` 的 `transform` 全冲掉了，悬停动效实际没生效。
   底色本来就继承自 `--glass` 等变量，换主题时整棵树一起变，不需要逐个元素声明。 */
html, body {
  transition: background 0.3s ease, color 0.3s ease;
}

/* 减少动效：整份文件只保留这一处（前面重复过一次，两块内容一样）。
   把 `transition-duration` 一并压掉，是因为悬停阴影这类过渡对前庭敏感用户同样是干扰。 */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    scroll-behavior: auto !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* ---------- 顶栏通知铃铛 ---------- */
.notif-bell {
  position: relative;
  display: flex;
  align-items: center;
}
.notif-bell-trigger {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border: 1px solid var(--hairline);
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-2);
  cursor: pointer;
}
.notif-bell-trigger:hover { background: var(--bg-hover); color: var(--text-1); }
.notif-badge {
  position: absolute;
  top: -5px;
  right: -5px;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: 8px;
  background: var(--red);
  color: #fff;
  font-size: 10px;
  font-weight: 700;
  line-height: 16px;
  text-align: center;
}
/* 与账户菜单同一套 fixed + Portal 方案，避坑理由见 .account-menu-popover 注释。 */
.notif-popover {
  position: fixed;
  width: 340px;
  max-height: 70vh;
  display: flex;
  flex-direction: column;
  background: var(--surface-solid);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-md);
  box-shadow:
    var(--shadow-pop),
    inset 0 1px 0 var(--glass-sheen);
  z-index: 100;
  visibility: hidden;
  pointer-events: none;
}
.notif-popover.open { visibility: visible; pointer-events: auto; }
.notif-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 12px 8px;
  border-bottom: 1px solid var(--hairline);
  font-size: 13px;
  font-weight: 700;
}
.notif-list { overflow-y: auto; padding: 6px; }
.notif-item {
  display: flex;
  gap: 9px;
  padding: 9px 8px;
  border: none;
  border-radius: var(--radius-xs);
  background: transparent;
  color: var(--text-1);
  text-align: left;
  cursor: pointer;
  width: 100%;
}
.notif-item:hover { background: var(--bg-hover); }
.notif-item.unread { background: var(--bg-inset); }
.notif-item-icon { flex: none; color: var(--text-3); margin-top: 1px; }
.notif-item-main { min-width: 0; flex: 1; }
.notif-item-title { font-size: 12.5px; font-weight: 600; }
.notif-item-body {
  margin-top: 2px;
  color: var(--text-3);
  font-size: 12px;
  line-height: 1.45;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}
.notif-item-time { margin-top: 3px; color: var(--text-4); font-size: 11px; }

/* ---------- 数据分析页 ---------- */
.funnel-label { width: 110px; flex: none; font-size: 12.5px; color: var(--text-2); }
.funnel-track {
  flex: 1;
  height: 10px;
  border-radius: 5px;
  background: var(--bg-inset);
  overflow: hidden;
}
.funnel-bar {
  height: 100%;
  border-radius: 5px;
  background: linear-gradient(90deg, var(--blue), var(--teal));
}
.funnel-count { width: 52px; flex: none; text-align: right; font-weight: 700; font-size: 12.5px; }
.funnel-step { width: 44px; flex: none; text-align: right; color: var(--text-3); font-size: 11.5px; }
.retention-bars {
  display: flex;
  align-items: flex-end;
  gap: 12px;
  padding: 10px 4px 4px;
  overflow-x: auto;
}
.retention-col {
  flex: 1;
  min-width: 42px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
}
.retention-value { font-size: 11.5px; font-weight: 700; color: var(--text-2); }
.retention-track {
  width: 100%;
  height: 140px;
  display: flex;
  align-items: flex-end;
  border-radius: 6px;
  background: var(--bg-inset);
}
.retention-bar {
  width: 100%;
  border-radius: 6px;
  background: linear-gradient(180deg, var(--green), var(--teal));
}
.retention-week { font-size: 10.5px; color: var(--text-4); }

/* ---------- 消息流工作台 ---------- */
.mf-page { display: flex; flex-direction: column; gap: 14px; }
.mf-grid {
  display: grid;
  grid-template-columns: minmax(380px, 46fr) minmax(360px, 54fr);
  gap: 14px;
  align-items: start;
}
@media (max-width: 1120px) {
  .mf-grid { grid-template-columns: 1fr; }
}
.mf-col { display: flex; flex-direction: column; gap: 14px; min-width: 0; }

/* ---- 管线画布：节点绝对定位，坐标是行号的纯函数 ---- */
.mf-canvas { overflow-x: auto; padding: 6px 0 2px; }
.mf-canvas-inner { position: relative; width: 380px; margin: 0 auto; }
.mf-links { position: absolute; top: 0; left: 0; color: var(--text-4); pointer-events: none; }
.mf-link { stroke: currentColor; stroke-width: 2; }
.mf-nodes { position: relative; }

.mf-node {
  position: absolute;
  left: 0;
  width: 100%;
  height: 92px;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  background: var(--surface-solid);
  border: 1px solid var(--hairline);
  border-radius: 12px;
  transition: box-shadow 0.15s, border-color 0.15s, opacity 0.15s;
}
.mf-node.editable { cursor: grab; border-color: var(--hairline); }
.mf-node.editable:hover { border-color: var(--blue); }
.mf-node.editable:active { cursor: grabbing; }
.mf-node.dragging { opacity: 0.45; border-style: dashed; }
.mf-node.drag-over { border-color: var(--blue); box-shadow: 0 0 0 3px var(--blue-soft); }
.mf-node.replay-active { border-color: var(--orange); box-shadow: 0 0 0 3px var(--orange-soft); }

.mf-node-index {
  width: 26px;
  height: 26px;
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 8px;
  background: var(--bg-inset);
  color: var(--text-2);
  font-size: 12px;
  font-weight: 700;
  font-family: var(--mono);
}
.mf-node-main { flex: 1; min-width: 0; }
.mf-node-title { display: flex; align-items: center; gap: 6px; font-weight: 700; font-size: 13.5px; }
.mf-node-desc { font-size: 11.5px; color: var(--text-3); margin-top: 3px; line-height: 1.4; }
.mf-lock { display: inline-flex; color: var(--text-4); }
.mf-lock svg { width: 12px; height: 12px; }
.mf-node-side { display: flex; flex-direction: column; align-items: flex-end; gap: 6px; flex: none; }
.mf-result { font-size: 10.5px; }
.mf-result-none { width: 10px; height: 10px; }
.mf-node-actions { display: flex; flex-direction: column; gap: 2px; }
.mf-step-btn {
  width: 22px;
  height: 20px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-inset);
  border: 1px solid transparent;
  border-radius: 6px;
  color: var(--text-2);
  cursor: pointer;
}
.mf-step-btn:hover { color: var(--text-1); border-color: var(--hairline); background: var(--bg-hover); }
.mf-step-btn svg { width: 12px; height: 12px; }

/* ---- 草稿工具栏 ---- */
.mf-draft-row { display: flex; gap: 8px; flex-wrap: wrap; }
.mf-draft-meta { margin-top: 10px; font-size: 12px; }
.mf-report { margin-top: 12px; padding: 12px; border-radius: 10px; font-size: 12.5px; }
.mf-report.ok { background: var(--green-soft); }
.mf-report.bad { background: var(--red-soft); }
.mf-report-head { margin-bottom: 6px; }
.mf-report-list { margin: 0; padding-left: 18px; display: flex; flex-direction: column; gap: 3px; }
.mf-report-error { color: var(--red); }
.mf-report-warn { color: var(--orange); }

/* ---- 实时 trace 流 ---- */
.mf-trace-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-height: 320px;
  overflow-y: auto;
}
.mf-trace-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 9px 10px;
  text-align: left;
  border-radius: 10px;
  background: var(--bg-inset);
  border: 1px solid transparent;
  cursor: pointer;
  color: var(--text-1);
  font-size: 12.5px;
}
.mf-trace-item:hover { background: var(--bg-hover); }
.mf-trace-item.on { border-color: var(--blue); background: var(--blue-soft); }
.mf-trace-dot { width: 8px; height: 8px; flex: none; border-radius: 50%; background: var(--text-4); }
.mf-trace-dot.live {
  background: var(--green);
  box-shadow: 0 0 0 3px var(--green-soft);
  animation: mf-pulse 1.6s ease-in-out infinite;
}
@keyframes mf-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.45; }
}
.mf-trace-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.mf-trace-summary { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mf-trace-meta { font-size: 11px; }
.mf-trace-side { display: flex; align-items: center; gap: 6px; flex: none; }
.mf-trace-detail-summary {
  margin: 0 0 12px;
  padding: 10px 12px;
  background: var(--bg-inset);
  border-radius: 10px;
  font-size: 12.5px;
  line-height: 1.5;
  word-break: break-all;
}
.mf-stage-table td:first-child, .mf-stage-table th:first-child { width: 34%; }
.mf-stage-table td, .mf-stage-table th { vertical-align: top; }

/* ---- 版本历史 ---- */
.mf-version-row {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  padding: 10px 4px;
  border-bottom: 1px solid var(--hairline);
}
.mf-version-row:last-child { border-bottom: none; }
.mf-version-badge { min-width: 44px; font-weight: 700; font-size: 13px; }
.mf-version-order {
  display: flex;
  align-items: center;
  gap: 4px;
  flex-wrap: wrap;
  flex: 1;
  min-width: 200px;
}
.mf-order-chip {
  padding: 2px 8px;
  border-radius: 6px;
  background: var(--bg-inset);
  color: var(--text-2);
  font-size: 11px;
}
.mf-version-meta { font-size: 11.5px; }
.mf-version-actions { display: flex; align-items: center; gap: 8px; }
.mf-versions-loading { padding: 8px 0; }

/* ============================================================
   设置页 · 运维式运行配置
   ============================================================ */

/* 设置页把「运行配置」拆成一组运维卡片，并降低毛玻璃漂浮感：表面从通用面板的
   --glass(0.84) 提到接近实色的 --glass-strong(0.96)，去掉悬停抬升与扫光，
   圆角与内边距收紧，字段密排 —— 读起来更像控制台而不是展示卡。
   backdrop-filter 本页沿用全局的 none，不引入实时模糊。 */
.ops-card {
  background: var(--glass-strong);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-md);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
  padding: 16px 18px;
}
[data-theme="dark"] .ops-card { box-shadow: 0 1px 2px rgba(0, 0, 0, 0.28); }

/* 运维卡片保持静止清朗：不走玻璃浮动 / hover 抬升 / 扫光 */
.ops-card,
.ops-card:hover { transition: none; }
.ops-card:hover { box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); }

/* 卡片头：图标 + 标题 */
.ops-card-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 14px;
}
.ops-card-title {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 14px;
  font-weight: 700;
  letter-spacing: -0.1px;
  color: var(--text-1);
}
.ops-card-title svg { width: 16px; height: 16px; color: var(--text-2); flex-shrink: 0; }
.ops-card-desc {
  margin: 0 0 12px;
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--text-2);
}
.ops-card-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

/* 运行配置顶部保存条 */
.ops-control { padding: 14px 18px; }

/* 组网：桌面两列、移动单列；跨列卡片用 .ops-full */
.ops-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
  align-items: start;
}
.ops-full { grid-column: 1 / -1; }

/* public_url 强调块：比普通字段更醒目 */
.ops-emph {
  margin-bottom: 14px;
  padding: 14px 16px;
  border: 1px solid color-mix(in srgb, var(--blue) 22%, var(--hairline));
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--blue) 6%, transparent);
}
.ops-emph .field-label { color: var(--blue); font-weight: 700; }
.ops-emph .input {
  font-family: var(--mono);
  font-size: 15px;
  padding: 10px 12px;
}
[data-theme="dark"] .ops-emph {
  background: color-mix(in srgb, var(--blue) 10%, transparent);
}

@media (max-width: 960px) {
  .ops-grid { grid-template-columns: 1fr; }
}
@media (max-width: 640px) {
  .ops-card { padding: 14px 14px; }
  .ops-control { padding: 12px 14px; }
  .ops-emph { padding: 12px 12px; }
}
