/* ==========================================================================
   控派 ConPad · 设计系统
   两套主题（深色 / 浅色），靠 <html data-theme="light"> 切换。
   所有颜色都必须走变量 —— 写死颜色的地方在浅色模式下必然出问题。
   ========================================================================== */

:root {
  /* 深色主题必须声明 color-scheme: dark。不写的话浏览器认为这页是浅色的，
     于是所有「系统画的」东西 —— checkbox、radio、滚动条、日期选择器、
     自动填充底色 —— 全按白底黑字渲染：深色卡片上会凭空出现一个纯白方块。
     这条和下面的 select 箭头是同一个病：系统控件不会自己看你的配色。 */
  color-scheme: dark;

  --bg:        #0b0d12;
  --bg-soft:   #10131a;
  --panel:     #161a24;
  --panel-2:   #1c2130;
  --line:      #232a3b;
  --line-soft: #1a2030;

  --text:      #e6e9f2;
  --text-2:    #9aa4bd;
  --text-3:    #626c86;

  --accent:    #7c5cff;
  --accent-2:  #a78bfa;
  --accent-dim:rgba(124, 92, 255, .14);
  --accent-hover: #8b6dff;

  --green:     #37d399;
  --green-dim: rgba(55, 211, 153, .13);
  --amber:     #f5b544;
  --amber-dim: rgba(245, 181, 68, .13);
  --red:       #f2555a;
  --red-dim:   rgba(242, 85, 90, .13);
  --blue:      #4f9cf9;

  /* 以下是为了深浅两套主题都能用而抽出来的 */
  --btn-bg:        #1c2130;
  --btn-bg-hover:  #232a3b;
  --btn-border-hover: #313a52;
  --scroll-thumb:  #2a3245;
  --scroll-thumb-hover: #3a4460;
  --row-hover:     rgba(255, 255, 255, .018);
  --spinner-track: rgba(255, 255, 255, .18);

  /* 顶栏：半透明毛玻璃，浅色下要换成白系 */
  --topbar-bg:     rgba(11, 13, 18, .82);
  /* 弹窗遮罩 */
  --mask-bg:       rgba(5, 7, 11, .72);
  --mask-bg-solid: rgba(0, 0, 0, .55);

  /* 阴影：浅色模式下纯黑阴影会显得脏，要降低浓度 */
  --shadow-card:  0 20px 60px rgba(0, 0, 0, .5);
  --shadow-modal: 0 24px 70px rgba(0, 0, 0, .6);
  --shadow-toast: 0 10px 30px rgba(0, 0, 0, .5);
  --shadow-side:  0 0 60px rgba(0, 0, 0, .7);

  /* 终端区：始终深色，不跟随主题（跟真实终端一个道理） */
  --term-bg:     #0a0c11;
  --term-border: #232a3b;
  --term-text:   #c8d0e0;
  --term-err:    #ff8a8f;

  /* 状态色描边：深色下用低透明度，浅色下要提高一点才看得见 */
  --green-line: rgba(55, 211, 153, .25);
  --green-line-strong: rgba(55, 211, 153, .4);
  --amber-line: rgba(245, 181, 68, .2);
  --amber-line-strong: rgba(245, 181, 68, .3);
  --red-line:   rgba(242, 85, 90, .25);
  --red-line-strong: rgba(242, 85, 90, .4);
  --accent-line: rgba(124, 92, 255, .25);
  --accent-glow: rgba(124, 92, 255, .12);

  --r-sm: 6px;
  --r:    10px;
  --r-lg: 14px;

  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
          "Hiragino Sans GB", "Microsoft YaHei", "Source Han Sans SC", sans-serif;

  /* 下拉框箭头。深色底上用 #7b879e：比 --text-3(#626c86) 亮一档，
     在 #10131a 上刚好能看清，又不会比框里的文字还抢眼。
     data-uri 里的 # 必须写成 %23，否则被当成锚点截断。 */
  --sel-caret: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.6 4.6 6 8l3.4-3.4' fill='none' stroke='%237b879e' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}

/* ---------- 浅色主题 ----------
   不是简单把黑白反过来：浅色背景下的文字对比、边框可见度、
   阴影浓度都要单独调，否则会出现「白底浅灰字看不见」这种问题。 */
:root[data-theme="light"] {
  --bg:        #f6f7fb;
  --bg-soft:   #eef0f6;
  --panel:     #ffffff;
  --panel-2:   #f2f4f9;
  /* 浅色下 --line-soft 要略深一点，否则白卡片和页面背景
     只靠一条几乎看不见的线区分，整页糊成一片 */
  --line:      #d2d6e2;
  --line-soft: #e2e5ee;

  --text:      #1a1f2e;
  --text-2:    #4d566b;
  --text-3:    #7c869c;

  --accent:    #6244e0;
  --accent-2:  #7c5cff;
  --accent-dim:rgba(98, 68, 224, .10);
  --accent-hover: #5133d1;

  /* 状态色在浅色底上要压暗，否则黄绿两色会发飘、看不清 */
  --green:     #0f9d68;
  --green-dim: rgba(15, 157, 104, .10);
  --amber:     #b87400;
  --amber-dim: rgba(184, 116, 0, .10);
  --red:       #d62f36;
  --red-dim:   rgba(214, 47, 54, .09);
  --blue:      #2563eb;

  --btn-bg:        #ffffff;
  --btn-bg-hover:  #f0f2f8;
  --btn-border-hover: #c9cedd;
  --scroll-thumb:  #c9cedd;
  --scroll-thumb-hover: #aab1c4;
  --row-hover:     rgba(0, 0, 0, .022);
  --spinner-track: rgba(0, 0, 0, .12);

  --topbar-bg:     rgba(255, 255, 255, .82);
  --mask-bg:       rgba(24, 28, 40, .36);
  --mask-bg-solid: rgba(24, 28, 40, .36);

  /* 浅色下阴影必须淡：白卡片周围一圈灰会显得脏。
     改用很轻的描边承担"分层"的职责，阴影只做一点点浮起感。 */
  --shadow-card:  0 1px 2px rgba(24, 28, 40, .04);
  --shadow-modal: 0 18px 50px rgba(24, 28, 40, .14);
  --shadow-toast: 0 6px 18px rgba(24, 28, 40, .10);
  --shadow-side:  0 0 32px rgba(24, 28, 40, .12);

  --green-line: rgba(15, 157, 104, .28);
  --green-line-strong: rgba(15, 157, 104, .45);
  --amber-line: rgba(184, 116, 0, .28);
  --amber-line-strong: rgba(184, 116, 0, .45);
  --red-line:   rgba(214, 47, 54, .28);
  --red-line-strong: rgba(214, 47, 54, .45);
  --accent-line: rgba(98, 68, 224, .22);
  --accent-glow: rgba(98, 68, 224, .08);

  /* 终端仍然深色 —— 白底终端根本没法读日志 */
  color-scheme: light;

  /* 浅色底上箭头要沉下去：#6c7690 比 --text-3(#7c869c) 深一档，
     否则 #eef0f6 上会显得发虚、像没画完 */
  --sel-caret: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.6 4.6 6 8l3.4-3.4' fill='none' stroke='%236c7690' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}

* { box-sizing: border-box; }

html, body {
  margin: 0; padding: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  font-size: 14px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

body { min-height: 100vh; }

::selection { background: var(--accent-dim); color: #fff; }

/* 滚动条 */
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--scroll-thumb); border-radius: 6px; border: 2px solid var(--bg); }
::-webkit-scrollbar-thumb:hover { background: var(--scroll-thumb-hover); }

/* ---------- 登录 / 初始化 ---------- */
.auth-wrap {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 24px;
  background:
    radial-gradient(900px 500px at 50% -10%, var(--accent-glow), transparent 70%),
    var(--bg);
}

.auth-card {
  width: 100%;
  max-width: 380px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: 32px 28px;
  box-shadow: var(--shadow-card);
}

/* 品牌标志：SVG 用 currentColor 上色，所以这里只管「什么颜色」和「多大」。
   尺寸由传进 LOGO_ICON 的像素值决定，这里不要写死 width/height ——
   写死会让图形跟画布不同比例，会被拉伸。
   viewBox 是正方形（48×48），图形在中间，所以和文字对齐时不用额外偏移。 */
.logo-mark {
  flex: none;
  display: block;
  color: var(--accent);
  line-height: 0;
}
.logo-mark svg { display: block; }

/* 词标 ConPad。两段不同颜色，所以只能用两个 span —— 上渐变文字是另一回事，
   这里要的是「Con 深、Pad 亮」这种分色，渐变做不到干干净净的分界。 */
.logo-word {
  font-weight: 700;
  letter-spacing: -.022em;   /* 大写 C 后面接小写，默认字距偏松，收紧才成块 */
  line-height: 1;
  white-space: nowrap;
}
.logo-word .lw-a { color: var(--text); }
.logo-word .lw-b { color: var(--accent); }

/* 登录 / 初始化页：图标在左、词标在右，副标压在词标下面。
   translateY 是光学修正 —— 两行文字的行高中心比图标中心略高，
   不推这一下，右半边看着像浮在上面。 */
.auth-brand {
  display: flex; align-items: center; gap: 11px;
  margin-bottom: 24px;
}
.auth-brand .brand-txt { min-width: 0; transform: translateY(1px); }
.auth-brand .ver { font-size: 11px; color: var(--text-3); line-height: 1.25; margin-top: 3px; }

.auth-card h2 { font-size: 20px; margin: 0 0 6px; font-weight: 650; letter-spacing: -.2px; }
.auth-card .sub { font-size: 13px; color: var(--text-2); margin: 0 0 22px; }

/* ---------- 表单 ---------- */
.field { margin-bottom: 16px; }
.field label {
  display: block; font-size: 12px; color: var(--text-2);
  margin-bottom: 6px; font-weight: 500;
}
.field .hint { font-size: 11px; color: var(--text-3); margin-top: 5px; line-height: 1.5; }

/* 选择器里必须写引号：Chromium 解析 `input[type=password]` 这种不带引号的
   写法时，会把 `$=` 当成「以…结尾」的操作符处理，整条规则直接失效。
   表现出来就是「密码框有背景色、文本框是刺眼的白底」——深色下尤其明显。
   不要为了「少两个字符」把引号去掉。 */
input[type="text"], input[type="password"], input[type="number"], select, textarea {
  width: 100%;
  /* 用 background-color 而不是 background 简写：简写会把 background-image
     一起重置，下面 select 的箭头就画不出来了（顺序反过来也一样）。 */
  background-color: var(--bg-soft);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  color: var(--text);
  padding: 9px 11px;
  font-size: 13px;
  font-family: inherit;
  outline: none;
  transition: border-color .15s, box-shadow .15s;
}
input:focus, select:focus, textarea:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-dim);
}
/* 关掉 Edge 的「显示密码」眼睛和旧 IE 的清除叉：它们画在框内右上角，
   和我们自己的布局打架，而且只在某些浏览器出现。 */
input[type="password"]::-ms-reveal,
input[type="password"]::-ms-clear { display: none; }
input::-webkit-search-cancel-button,
input::-webkit-search-decoration { -webkit-appearance: none; appearance: none; }
input::placeholder, textarea::placeholder { color: var(--text-3); }
textarea { resize: vertical; min-height: 72px; font-family: var(--mono); font-size: 12px; }

/* ---------- 下拉框 ----------
   浏览器原生箭头不能用：它是系统画的，Chrome 是黑色粗 chevron、Firefox 是
   细三角、Safari 是蓝底胶囊，三种长相；深色主题下更是直接变成深灰贴在
   #10131a 上，几乎看不见。而且它不吃 padding-right，长选项会顶到箭头底下。

   所以一律 appearance:none，自己画一个 12px 的 chevron —— 和侧栏那套手写
   SVG 同一套语言（圆头圆角、1.6 描边）。颜色走变量，两套主题各给一份
   data-uri：data-uri 里写不了 var()，只能预先把颜色编码进去。 */
select {
  appearance: none;
  -webkit-appearance: none;
  /* 三个数是一套的：箭头宽 12、离边框 10、右边距 30（=12+10+8 的安全间隙）。
     改任何一个都要一起改，否则箭头会压到文字或者贴住边框。 */
  --caret-size: 12px;
  --caret-gap: 10px;
  background-image: var(--sel-caret);
  background-repeat: no-repeat;
  background-position: right var(--caret-gap) center;
  background-size: var(--caret-size) var(--caret-size);
  padding-right: calc(var(--caret-size) + var(--caret-gap) + 8px);
  cursor: pointer;
}
/* 多选和「列出 N 行」的列表框没有箭头，别留 30px 空白 */
select[multiple], select[size]:not([size="1"]) {
  appearance: auto;
  -webkit-appearance: auto;
  background-image: none;
  padding-right: 11px;
}
select:disabled { opacity: .5; cursor: not-allowed; }

/* 勾选框和单选圈交给系统画（自己重画要连键盘焦点、forced-colors、
   高对比模式一起处理，收益不值），但把勾选色统一成品牌色 —— 默认的
   系统蓝在这个紫色体系里格外跳。accent-color 连聚焦环一起跟着走。 */
input[type="checkbox"], input[type="radio"] { accent-color: var(--accent); }
input[type="checkbox"]:disabled, input[type="radio"]:disabled { opacity: .45; cursor: not-allowed; }

/* 数字框的上下小箭头同样是系统控件，浅色下是白块、深色下是黑块，
   和输入框底色永远差一格。端口这种地方没人会用滚轮改。 */
input[type="number"] { -moz-appearance: textfield; appearance: textfield; }
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

/* 浏览器自动填充会把输入框刷成刺眼的浅黄，深色主题下像一块创可贴。
   背景色改不掉（UA 优先级最高），只能盖一层同色 inset 阴影，再把文字
   强制成正常颜色。transition 撑到 10 小时是为了不让 Chrome 检测到
   「背景变了」又刷回去。 */
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus {
  -webkit-text-fill-color: var(--text);
  -webkit-box-shadow: 0 0 0 100px var(--bg-soft) inset;
  box-shadow: 0 0 0 100px var(--bg-soft) inset;
  caret-color: var(--text);
  transition: background-color 36000s ease-in-out 0s;
}

/* ---------- 按钮 ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  border: 1px solid var(--line);
  background: var(--panel-2);
  color: var(--text);
  padding: 8px 14px;
  border-radius: var(--r-sm);
  font-size: 13px;
  font-family: inherit;
  font-weight: 500;
  cursor: pointer;
  white-space: nowrap;
  transition: background .15s, border-color .15s, opacity .15s, transform .06s;
}
.btn:hover:not(:disabled) { background: var(--btn-bg-hover); border-color: var(--btn-border-hover); }
.btn:active:not(:disabled) { transform: translateY(1px); }
.btn:disabled { opacity: .45; cursor: not-allowed; }

.btn-primary {
  background: var(--accent); border-color: var(--accent); color: #fff;
}
.btn-primary:hover:not(:disabled) { background: #8b6dff; border-color: #8b6dff; }

.btn-danger { background: transparent; border-color: var(--red-line-strong); color: var(--red); }
.btn-danger:hover:not(:disabled) { background: var(--red-dim); border-color: var(--red); }

.btn-ghost { background: transparent; border-color: transparent; color: var(--text-2); }
.btn-ghost:hover:not(:disabled) { background: var(--panel-2); color: var(--text); border-color: transparent; }

.btn-block { width: 100%; }
.btn-sm { padding: 5px 10px; font-size: 12px; }
.btn-xs { padding: 3px 8px; font-size: 11px; }

/* ---------- 应用外壳 ---------- */
.shell { display: flex; min-height: 100vh; padding-left: 216px; }

.sidebar {
  width: 216px; flex: none;
  background: var(--bg-soft);
  border-right: 1px solid var(--line-soft);
  display: flex; flex-direction: column;
  /* fixed 而不是 sticky：见下方「滚动的归属」注释 */
  position: fixed; left: 0; top: 0; bottom: 0;
}

/* 侧栏品牌区：图标在上、词标在下，竖排。
   为什么竖排：侧栏只有 216px 宽，图标和文字挤一行的话，
   词标要缩到 13px 才放得下，而 13px 的 ConPad 在侧栏里等于一行小字，
   撑不起「这是谁」的作用。竖排之后图标能到 34px、词标能到 17px，
   两者各有各的位置，一眼就能认出是哪个面板。
   底部加一条分隔线，把品牌和下面的导航分开 —— 之前没有线，
   品牌区和第一组导航挤在一起，看着像一个列表的两项。 */
/* 侧栏品牌区：横排（图标 + 文字块）。
   ⚠ 之前是竖排（图标 / ConPad / 副标），三行占了侧栏顶部一大截，太费地方。
     横排之后整块只有 26px 高，省出的空间全给了导航。
   ⚠ 图标 26px 而不是 44：44 时图形本身只有 34px，但因为细节（尾巴圆头、
     指针内凹）都被放大，看起来"重"和"呆"。缩到 26 之后线条的分量对了，
     反而精致 —— 小尺寸的标志要"收"，不是"放"。 */
.side-brand {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 13px 15px 12px;
  border-bottom: 1px solid var(--line-soft);
}
.side-brand .brand-txt { min-width: 0; }
.side-brand .ver { font-size: 10px; color: var(--text-3); line-height: 1.2; margin-top: 2px; }

.nav { padding: 8px 8px; flex: 1; overflow-y: auto; }
.nav-group { margin-bottom: 16px; }
.nav-label {
  font-size: 10.5px; letter-spacing: .8px;
  color: var(--text-3); padding: 0 9px; margin-bottom: 5px; font-weight: 600;
  line-height: 1.6;
}

/* 导航项对齐 —— 这里是「图标和文字对不齐」的正解。
   flex + align-items:center 管的是盒子，管不了字形：以前图标是一个装着
   ◈ ▤ ⚙ 这类符号的文本 span，那些符号在字体里的视觉重心各不相同，
   盒子居中了，字形还是歪的。换成 SVG 之后字形就是几何图形本身，
   再配合固定行高，三者（图标 / 文字 / 行）的中线才能真正重合。 */
.nav-item {
  display: flex; align-items: center; gap: 10px;
  height: 34px;                       /* 定高比 padding 更稳：图标换成 svg 也不会抖 */
  padding: 0 9px;
  border-radius: 7px;
  color: var(--text-2); font-size: 13px; line-height: 1;
  cursor: pointer; user-select: none;
  transition: background .12s, color .12s;
  margin-bottom: 1px;
  position: relative;
}
.nav-item .ico {
  flex: none;
  width: 18px; height: 18px;
  opacity: .78;
  /* SVG 是替换元素，不会像文本那样带上 line-height 造成的上边距 */
  display: block;
  transition: opacity .12s;
}
.nav-item .lbl {
  flex: 1; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  line-height: 1;
  /* 汉字本身有视觉下沉，往下挪 0.5px 跟图标的光学中心对齐 */
  transform: translateY(.5px);
}
.nav-item:hover { background: var(--panel); color: var(--text); }
.nav-item:hover .ico { opacity: 1; }
.nav-item.active { background: var(--accent-dim); color: var(--accent-2); font-weight: 500; }
.nav-item.active .ico { opacity: 1; }
/* 选中态左侧那道细竖线：比整块换底色更克制。
   放在卡片内侧（left:0），不要往外挑 —— 往外挑会正好压在侧栏的
   滚动容器边缘上，窄屏或出横向滚动条时会被裁掉一半。 */
.nav-item.active::before {
  content: ''; position: absolute; left: 0; top: 9px; bottom: 9px;
  width: 2.5px; border-radius: 2px; background: var(--accent);
}
.nav-item .badge {
  margin-left: auto; font-size: 10.5px; padding: 1px 6px;
  border-radius: 20px; background: var(--panel-2); color: var(--text-2);
}
.nav-item .badge.red { background: var(--red-dim); color: var(--red); }
.nav-item.disabled { opacity: .38; cursor: not-allowed; }
.nav-item.disabled:hover { background: transparent; color: var(--text-2); }

.side-foot { padding: 12px; border-top: 1px solid var(--line-soft); }
.user-chip {
  display: flex; align-items: center; gap: 9px;
  padding: 8px 9px; border-radius: var(--r-sm);
  cursor: pointer;
}
.user-chip:hover { background: var(--panel); }
.avatar {
  width: 26px; height: 26px; flex: none; border-radius: 50%;
  background: linear-gradient(135deg, #7c5cff, #4f9cf9);
  display: grid; place-items: center;
  font-size: 11px; font-weight: 700; color: #fff;
}
.user-chip .u-name { font-size: 12.5px; font-weight: 500; line-height: 1.2; }
.user-chip .u-role { font-size: 10.5px; color: var(--text-3); }

/* ---------- 主区域 ---------- */
.shell > .main {
  flex: 1; min-width: 0;
  /* block 而不是 flex：见下方「滚动容器为什么不能用 flex 纵列」。
     flex 纵列里内容块会被压到容器高度，长页面在块内溢出，自己长出一条
     滚动条，和 .main 那条打架，顶部就露出一块没被顶栏盖住的内容。 */
  display: block;
  height: 100vh;
  /* 两条一起才压得住 Mac 触控板：
     overflow-y: auto   —— 滚动条长在这里，而不是 <body>；
     overflow-x: hidden —— WebKit 有个老毛病：只设 overflow-y 时 overflow-x
       会被算成 auto，于是横向也成了一条「可回弹」的滚动轴。触控板双指一划，
       侧向的分量被吃掉，露出右侧一条窄缝。
     两个都写，才明确告诉浏览器「这里只有纵向能滚」。 */
  overflow-x: hidden;
  overflow-y: auto;
  /* 滚到头继续滑时，别把回弹传给外层 —— 外层本来就不滚，但没有这行时
     WebKit 仍会做一次整页回弹，那一步就是「整个页面往下缩一下」。 */
  overscroll-behavior: none;
  /* 内容不够长时不回弹；否则一划就露背景 */
  position: relative;
}

.shell > .main > .topbar {
  height: 52px; flex: none; flex-shrink: 0;
  display: flex; align-items: center; gap: 12px;
  padding: 0 22px;
  border-bottom: 1px solid var(--line-soft);
  background: var(--topbar-bg);
  backdrop-filter: blur(10px);
  position: sticky; top: 0; z-index: 60;
}
.topbar h2 { font-size: 15px; margin: 0; font-weight: 600; letter-spacing: .1px; }
.topbar .spacer { flex: 1; }
.topbar .mode-chip {
  font-size: 11px; padding: 3px 9px; border-radius: 20px;
  background: var(--panel-2); color: var(--text-2);
  border: 1px solid var(--line);
}
.topbar .mode-chip.cloud { background: var(--amber-dim); color: var(--amber); border-color: rgba(245,181,68,.3); }

.shell > .main > .content { padding: 22px; flex: 1; }
.content-inner { max-width: 1180px; margin: 0 auto; }

/* ---------- 卡片 ---------- */
.card {
  background: var(--panel);
  border: 1px solid var(--line-soft);
  border-radius: var(--r);
  overflow: hidden;
}
.card-head {
  display: flex; align-items: center; gap: 10px;
  padding: 13px 16px;
  border-bottom: 1px solid var(--line-soft);
  flex-wrap: wrap;              /* 按钮多到一行放不下时换行，而不是压成竖排单字 */
}
.card-head h3 { font-size: 13.5px; margin: 0; font-weight: 600; }
.card-head .sub { font-size: 12px; color: var(--text-3); }
.card-head .spacer { flex: 1; }
/* 卡头右侧按钮组：面板里有 5~7 个按钮（重启/停止/启动/拉取镜像/发布记录/接入 Git/发布新版本），
   中文窄屏下如果让它们各占一份 flex 空间、又把按钮内部挤到只剩几像素，
   「重启」会变成「重\n启」竖排——看起来就是整个卡头坏了。所以这里禁止收缩、允许换行。 */
.card-head .row { flex-shrink: 0; flex-wrap: wrap; justify-content: flex-end; }
.card-head .row .btn { flex-shrink: 0; white-space: nowrap; }
.card-body { padding: 16px; }
.card-body.tight { padding: 0; }
/* 表格横向滚动容器：窄屏放不下时横向滑，不挤压按钮 */
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.table-scroll > table { min-width: 520px; }
.card-foot {
  padding: 12px 16px;
  border-top: 1px solid var(--line-soft);
  background: var(--bg-soft);
}

/* ---------- 指标格 ---------- */
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px; margin-bottom: 20px; }
.stat {
  background: var(--panel); border: 1px solid var(--line-soft);
  border-radius: var(--r); padding: 14px 16px;
}
.stat .k { font-size: 11.5px; color: var(--text-3); margin-bottom: 4px; }
.stat .v { font-size: 24px; font-weight: 650; letter-spacing: -.5px; line-height: 1.15; font-variant-numeric: tabular-nums; }
.stat .v.ok { color: var(--green); }
.stat .v.warn { color: var(--amber); }
.stat .v.bad { color: var(--red); }
.stat .m { font-size: 11px; color: var(--text-3); margin-top: 2px; }

/* ---------- 状态点 ---------- */
.dot {
  width: 7px; height: 7px; border-radius: 50%;
  display: inline-block; flex: none;
  background: var(--text-3);
}
.dot.running { background: var(--green); box-shadow: 0 0 0 3px var(--green-dim); }
.dot.stopped { background: var(--text-3); }
.dot.failed  { background: var(--red); box-shadow: 0 0 0 3px var(--red-dim); }
.dot.missing { background: var(--amber); box-shadow: 0 0 0 3px var(--amber-dim); }
.dot.unknown { background: var(--text-3); }
.dot.restarting { background: var(--amber); animation: pulse 1.2s infinite; }
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: .35; } }

.state-text { font-size: 12.5px; color: var(--text-2); }
.state-text.running { color: var(--green); }
.state-text.failed  { color: var(--red); }
.state-text.missing { color: var(--amber); }

/* ---------- 项目列表 ---------- */
.plist { display: flex; flex-direction: column; }
.prow {
  display: grid;
  grid-template-columns: 1fr 130px 150px 120px 108px;
  align-items: center; gap: 12px;
  padding: 13px 16px;
  border-bottom: 1px solid var(--line-soft);
  cursor: pointer;
  transition: background .12s;
}
.prow:last-child { border-bottom: none; }
.prow:hover { background: var(--panel-2); }
.prow .p-name { display: flex; align-items: center; gap: 9px; min-width: 0; }
.prow .p-name .t { font-size: 13.5px; font-weight: 550; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.prow .p-name .s { font-size: 11px; color: var(--text-3); }
.prow .p-type {
  font-size: 11px; color: var(--text-2);
  padding: 2px 8px; border-radius: 20px;
  background: var(--panel-2); border: 1px solid var(--line);
  justify-self: start; white-space: nowrap;
}
.prow .p-meta { font-size: 11.5px; color: var(--text-3); font-variant-numeric: tabular-nums; }
.prow .p-actions { display: flex; gap: 5px; justify-content: flex-end; }

/* 资源条 */
.bar { height: 4px; border-radius: 3px; background: var(--panel-2); overflow: hidden; margin-top: 4px; }
.bar > i { display: block; height: 100%; background: var(--accent); border-radius: 3px; transition: width .4s; }
.bar > i.warn { background: var(--amber); }
.bar > i.bad  { background: var(--red); }

/* ---------- 表格 ---------- */
table { width: 100%; border-collapse: collapse; font-size: 13px; }
th {
  text-align: left; font-weight: 600; font-size: 11.5px;
  color: var(--text-3); text-transform: none; letter-spacing: .3px;
  padding: 9px 12px; border-bottom: 1px solid var(--line);
  background: var(--bg-soft); position: sticky; top: 0;
}
td { padding: 10px 12px; border-bottom: 1px solid var(--line-soft); }
tr:last-child td { border-bottom: none; }
tbody tr:hover { background: var(--panel-2); }
td.mono, th.mono { font-family: var(--mono); font-size: 12px; }
td.nw, th.nw { white-space: nowrap; }
/* 表格内首列若是标签，别让它被挤压换行 */
td > .tag:only-child { margin: 0; }

/* ---------- 终端 ---------- */
.term {
  /* 终端区一律深色，不跟主题走 —— 浅底日志没法读，这是刻意的 */
  background: var(--term-bg);
  border: 1px solid var(--term-border);
  border-radius: var(--r-sm);
  font-family: var(--mono);
  font-size: 12px;
  line-height: 1.6;
  padding: 12px 14px;
  height: 420px;
  overflow-y: auto;
  white-space: pre-wrap;
  word-break: break-all;
  color: var(--term-text);
}
.term .err { color: var(--term-err); }
/* 这三个用终端专用色：如果跟着主题变，浅色模式下深色底配深绿字就看不清了 */
.term .ok  { color: #37d399; }
.term .cmd { color: #a78bfa; }

/* 每个 span 必须独占一行。
   appendTerm 每收到一段输出就建一个 span，并靠 textContent 里的 \n 换行 ——
   而 .term 是 white-space: pre-wrap，span 对 \n 的处理受 inline 盒影响：
   相邻两个行内 span 之间不再换行，前一段末尾那个 \n 只会在行尾留个空位。
   结果是一整屏日志挤成一条长行，红字那段的背景色被拉成横贯全宽的条，
   看着像页面坏了。改块级之后 \n 落在段末只是多一个空行，不影响。
   这一条对「部署日志」和「建站日志」同时生效，两边都会踩到。 */
.term > span { display: block; }

/* ---------- Tabs ---------- */
.tabs { display: flex; gap: 2px; border-bottom: 1px solid var(--line-soft); padding: 0 16px; }
.tab {
  padding: 10px 14px; font-size: 13px; color: var(--text-3);
  cursor: pointer; border-bottom: 2px solid transparent;
  margin-bottom: -1px; transition: color .12s, border-color .12s;
  user-select: none;
}
.tab:hover { color: var(--text-2); }
.tab.active { color: var(--text); border-bottom-color: var(--accent); font-weight: 500; }

/* ---------- 弹层 ---------- */
.modal-mask {
  position: fixed; inset: 0; z-index: 100;
  background: var(--mask-bg);
  backdrop-filter: blur(3px);
  display: grid; place-items: center;
  padding: 24px;
  animation: fade .14s ease-out;
}
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }

.modal {
  width: 100%; max-width: 520px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-modal);
  max-height: 88vh; display: flex; flex-direction: column;
  animation: rise .18s cubic-bezier(.2, .9, .3, 1);
}
.modal.wide { max-width: 760px; }
@keyframes rise { from { transform: translateY(10px) scale(.99); opacity: 0; } to { transform: none; opacity: 1; } }

.modal-head {
  display: flex; align-items: center;
  padding: 16px 20px; border-bottom: 1px solid var(--line-soft);
}
.modal-head h3 { margin: 0; font-size: 15px; font-weight: 600; }
.modal-head .x {
  margin-left: auto; cursor: pointer; color: var(--text-3);
  font-size: 18px; line-height: 1; padding: 2px 6px; border-radius: 4px;
}
.modal-head .x:hover { background: var(--panel-2); color: var(--text); }
.modal-body { padding: 20px; overflow-y: auto; }
.modal-foot {
  display: flex; gap: 8px; justify-content: flex-end;
  padding: 14px 20px; border-top: 1px solid var(--line-soft);
}

/* ---------- 杂项 ---------- */
.empty {
  text-align: center; padding: 48px 24px; color: var(--text-3);
}
.empty .big { font-size: 28px; margin-bottom: 10px; opacity: .5; }
.empty p { margin: 0 0 16px; font-size: 13px; }

.tag {
  display: inline-flex; align-items: center;
  font-size: 11px; padding: 2px 8px; border-radius: 20px;
  background: var(--panel-2); color: var(--text-2); border: 1px solid var(--line);
  white-space: nowrap;
  line-height: 1.5;
}
.tag.ok { background: var(--green-dim); color: var(--green); border-color: var(--green-line); }
.tag.warn { background: var(--amber-dim); color: var(--amber); border-color: var(--amber-line); }
.tag.bad { background: var(--red-dim); color: var(--red); border-color: var(--red-line); }

/* 注意：这套是「两列表格」语义（dl/dt/dd），类名不能叫 .kv。
   .kv 在文件后面还被当成「一行键值」的 flex 布局用着，两者同名会互相覆盖，
   表现是 dt/dd 全挤成一竖条。带 dt/dd 的用 .kv-grid。 */
.kv-grid { display: grid; grid-template-columns: 130px 1fr; gap: 9px 14px; font-size: 13px; }
.kv-grid dt { color: var(--text-3); font-size: 12px; }
.kv-grid dd { margin: 0; color: var(--text); word-break: break-all; }

.toast-wrap {
  position: fixed; bottom: 22px; left: 50%; transform: translateX(-50%);
  z-index: 200; display: flex; flex-direction: column; gap: 8px; align-items: center;
}
.toast {
  padding: 10px 18px; border-radius: 8px; font-size: 13px;
  background: var(--panel-2); border: 1px solid var(--line);
  color: var(--text); box-shadow: var(--shadow-toast);
  animation: rise .18s ease-out;
  max-width: 90vw;
}
.toast.ok { border-color: var(--green-line-strong); color: var(--green); }
.toast.bad { border-color: var(--red-line-strong); color: var(--red); }

.spinner {
  width: 14px; height: 14px; border-radius: 50%;
  border: 2px solid var(--spinner-track);
  border-top-color: var(--accent);
  animation: spin .7s linear infinite;
  display: inline-block;
}
@keyframes spin { to { transform: rotate(360deg); } }

.row { display: flex; gap: 10px; align-items: center; }
.row.wrap { flex-wrap: wrap; }
.gap-sm { gap: 6px; }
.mt { margin-top: 16px; }
.mb { margin-bottom: 16px; }
.muted { color: var(--text-3); font-size: 12px; }
.mono { font-family: var(--mono); }

.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }

@media (max-width: 900px) {
  .shell { padding-left: 0; }
  .shell > .main { height: auto; min-height: 100vh; overflow: visible; overscroll-behavior: auto; }
  .sidebar { z-index: 60; transform: translateX(-100%); transition: transform .2s; }
  .sidebar.open { transform: none; box-shadow: var(--shadow-side); }
  .sidebar-mask { position: fixed; inset: 0; z-index: 55; background: var(--mask-bg-solid); }
  .prow { grid-template-columns: 1fr auto; gap: 8px; padding: 12px 14px; }
  .prow .p-type, .prow .p-meta { display: none; }
  .prow > div:nth-child(3) { display: none; }
  .grid-2 { grid-template-columns: 1fr; }
  .shell > .main > .content { padding: 14px; }
  .stats { grid-template-columns: repeat(2, 1fr); }
  .stat .v { font-size: 20px; }
  .kv-grid { grid-template-columns: 100px 1fr; }
  .modal { max-width: none; }
  .modal-mask { padding: 10px; align-items: flex-end; }
}

/* 汉堡按钮：只在窄屏显示 */
.burger {
  display: none; background: transparent; border: none; color: var(--text-2);
  font-size: 19px; cursor: pointer; padding: 4px 8px; border-radius: 6px; line-height: 1;
}
.burger:hover { background: var(--panel-2); color: var(--text); }
@media (max-width: 900px) { .burger { display: block; } }

/* ==========================================================================
   凭据库
   ========================================================================== */

/* ---- 门禁：设置主密码 / 解锁 ---- */
.vault-gate {
  display: flex; align-items: flex-start; justify-content: center;
  padding: 40px 16px; min-height: 420px;
}
.gate-card {
  width: 100%; max-width: 420px;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--r-lg); padding: 30px 28px 26px;
}
.gate-ico {
  width: 48px; height: 48px; border-radius: 14px;
  display: flex; align-items: center; justify-content: center;
  font-size: 22px; margin-bottom: 18px;
  background: var(--accent-dim); color: var(--accent);
  border: 1px solid var(--accent-line);
}
.gate-ico.locked { background: var(--amber-dim); color: var(--amber); border-color: var(--amber-line); }
.gate-card h2 { font-size: 19px; margin: 0 0 6px; font-weight: 650; letter-spacing: -.2px; }
.gate-sub { font-size: 12.5px; color: var(--text-2); margin: 0 0 22px; line-height: 1.65; }
.gate-warn {
  font-size: 12px; line-height: 1.7; color: var(--amber);
  background: var(--amber-dim); border: 1px solid var(--amber-line);
  border-radius: var(--r-sm); padding: 10px 12px; margin-bottom: 18px;
}
.gate-warn b { color: var(--amber); }
.gate-meter {
  height: 3px; background: var(--line); border-radius: 2px;
  margin-top: 14px; overflow: hidden;
}
.gate-meter .bar { height: 100%; width: 0; transition: width .2s, background .2s; }
.gate-more { margin-top: 20px; border-top: 1px solid var(--line-soft); padding-top: 14px; }
.gate-more summary {
  font-size: 12px; color: var(--text-3); cursor: pointer; list-style: none; user-select: none;
}
.gate-more summary::-webkit-details-marker { display: none; }
.gate-more summary::before { content: '▸ '; }
.gate-more[open] summary::before { content: '▾ '; }
.gate-more summary:hover { color: var(--text-2); }
.gate-more-body { font-size: 12px; color: var(--text-3); line-height: 1.75; margin-top: 10px; }
.gate-more-body b { color: var(--text-2); }

/* ---- 顶部状态条 ---- */
.vault-bar {
  display: flex; align-items: center; gap: 10px;
  margin-bottom: 16px; padding: 9px 14px;
  background: var(--panel); border: 1px solid var(--line-soft);
  border-radius: var(--r);
}
.vault-bar .spacer { flex: 1; }
.vstat { display: flex; align-items: center; gap: 8px; font-size: 12.5px; color: var(--text-2); }
.vstat .mono { color: var(--text); font-variant-numeric: tabular-nums; font-weight: 550; }

/* ---- 两栏：左分组 / 右列表 ---- */
.vault-grid { display: grid; grid-template-columns: 168px 1fr; gap: 16px; align-items: start; }
.vault-side {
  background: var(--panel); border: 1px solid var(--line-soft);
  border-radius: var(--r); padding: 10px 8px;
  position: sticky; top: 0;
}
.vs-title {
  font-size: 10.5px; text-transform: uppercase; letter-spacing: .06em;
  color: var(--text-3); padding: 6px 9px 5px; font-weight: 600;
}
.vs-item {
  display: flex; align-items: center; gap: 6px;
  padding: 7px 9px; border-radius: var(--r-sm);
  font-size: 12.5px; color: var(--text-2); cursor: pointer;
  transition: background .12s, color .12s;
}
.vs-item:hover { background: var(--panel-2); color: var(--text); }
.vs-item.on { background: var(--accent-dim); color: var(--text); font-weight: 550; }
.vs-item .n {
  margin-left: auto; font-size: 10.5px; color: var(--text-3);
  font-variant-numeric: tabular-nums;
}
.vault-main { min-width: 0; }
.vault-search { margin-bottom: 12px; }
.vault-search input { font-size: 13px; }

/* ---- 凭据表格 ---- */
.vtable { width: 100%; border-collapse: collapse; font-size: 12.5px; }
.vtable th {
  text-align: left; padding: 9px 12px; font-size: 11px; font-weight: 600;
  color: var(--text-3); border-bottom: 1px solid var(--line);
  background: var(--bg-soft); white-space: nowrap;
}
.vtable th:first-child { border-top-left-radius: var(--r); }
.vtable th:last-child { border-top-right-radius: var(--r); }
.vtable td { padding: 10px 12px; border-bottom: 1px solid var(--line-soft); vertical-align: top; }
.vtable tr:last-child td { border-bottom: none; }
.vtable tbody tr:hover { background: var(--row-hover); }
.vtable td.nowrap { white-space: nowrap; }

.vname { font-size: 13px; color: var(--text); font-weight: 520; }
.venv { font-size: 10.5px; color: var(--accent); margin-top: 2px; opacity: .85; }
.vurl { font-size: 11px; margin-top: 2px; word-break: break-all; max-width: 240px; }
.vproj { margin-top: 4px; }

.vval { display: flex; align-items: center; gap: 4px; }
.vval .mono { font-size: 12px; word-break: break-all; max-width: 260px; }
.vval .mono.masked {
  color: var(--text-2); letter-spacing: 1.5px; font-size: 13px;
  user-select: none;
}

/* 行内小图标按钮 */
.icon-btn {
  background: transparent; border: none; color: var(--text-3);
  cursor: pointer; padding: 2px 5px; border-radius: 4px;
  font-size: 12px; line-height: 1.4; flex: none;
  transition: background .12s, color .12s;
}
.icon-btn:hover { background: var(--panel-2); color: var(--text); }
.icon-btn.danger:hover { background: var(--red-dim); color: var(--red); }

/* ---- 对话框两列 ---- */
.row2 { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.req { color: var(--red); }

@media (max-width: 760px) {
  .vault-grid { grid-template-columns: 1fr; }
  .vault-side {
    position: static; display: flex; flex-wrap: wrap;
    gap: 4px; align-items: center;
  }
  .vs-title { width: 100%; padding: 4px 6px; }
  .vs-item { padding: 5px 9px; }
  .vs-item .n { margin-left: 4px; }
  .row2 { grid-template-columns: 1fr; gap: 0; }

  /* 顶部状态条：窄屏必须换行，否则「已解锁」会被逐字竖排 */
  .vault-bar { flex-wrap: wrap; gap: 8px; }
  .vault-bar .spacer { display: none; }
  .vstat { flex: 1 0 100%; white-space: nowrap; }
  .vault-bar .btn { flex: 1 1 auto; min-width: 0; }

  /* 表格：窄屏改成卡片式，一行一条，不再横向溢出 */
  .vtable thead { display: none; }
  .vtable, .vtable tbody, .vtable tr, .vtable td { display: block; width: 100%; }
  .vtable tr {
    padding: 12px 14px; border-bottom: 1px solid var(--line-soft);
    position: relative;
  }
  .vtable tr:last-child { border-bottom: none; }
  .vtable td { padding: 0; border: none; }
  /* 名称行单独占一行 */
  .vtable td:nth-child(1) { margin-bottom: 6px; padding-right: 84px; }
  /* 账号 / 值 / 类型 / 分组 竖着排，用标签区分 */
  .vtable td:nth-child(2),
  .vtable td:nth-child(3),
  .vtable td:nth-child(4),
  .vtable td:nth-child(5) {
    display: flex; align-items: baseline; gap: 8px; margin-top: 4px;
    font-size: 12px;
  }
  .vtable td:nth-child(2)::before { content: '账号'; color: var(--text-3); font-size: 11px; flex: none; width: 34px; }
  .vtable td:nth-child(3)::before { content: '值';   color: var(--text-3); font-size: 11px; flex: none; width: 34px; }
  .vtable td:nth-child(4)::before { content: '类型'; color: var(--text-3); font-size: 11px; flex: none; width: 34px; }
  .vtable td:nth-child(5)::before { content: '分组'; color: var(--text-3); font-size: 11px; flex: none; width: 34px; }

  /* 操作按钮固定在卡片右上角。
     必须给足宽度，否则三个图标会被压扁成乱码方块（踩过）。 */
  .vtable td:nth-child(6) {
    position: absolute; top: 8px; right: 8px;
    display: flex; gap: 0; width: auto;
  }
  .vtable td:nth-child(6) .icon-btn {
    width: 26px; height: 26px; padding: 0;
    display: flex; align-items: center; justify-content: center;
    font-size: 13px; border-radius: 5px;
  }
  .vtable td:nth-child(1) { padding-right: 92px; }
  .vval { flex-wrap: wrap; }
  .vval .mono { max-width: none; word-break: break-all; }
  .vurl { max-width: none; }
}

/* ==========================================================================
   主题切换按钮
   ========================================================================== */
.theme-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; flex: none;
  padding: 0; border-radius: var(--r-sm);
  background: transparent; border: 1px solid transparent;
  color: var(--text-2); line-height: 1;
  cursor: pointer; user-select: none;
  transition: background .14s, color .14s, border-color .14s, transform .18s;
}
/* 图标是 SVG，让它跟着文字色走，别被拉伸 */
.theme-btn svg { display: block; flex: none; }
.theme-btn:hover {
  background: var(--btn-bg-hover);
  border-color: var(--btn-border-hover);
  color: var(--text);
}
/* 点下去转半圈，给一点"切换发生了"的反馈 */
.theme-btn:active { transform: rotate(-25deg) scale(.94); }

/* 登录页上的浮动版本：固定在右上角 */
.theme-btn.theme-float {
  position: fixed; top: 16px; right: 16px; z-index: 30;
  width: 34px; height: 34px;
  border-color: var(--line);
  background: var(--panel);
  box-shadow: var(--shadow-toast);
}

/* 主题切换时给个短暂过渡，别生硬跳变。
   只对背景和文字上色，不对布局属性过渡 —— 否则页面会"抽搐"。 */
html.theme-anim, html.theme-anim body,
html.theme-anim .card, html.theme-anim .sidebar, html.theme-anim .stat,
html.theme-anim .topbar, html.theme-anim .gate-card, html.theme-anim .modal {
  transition: background-color .18s ease, border-color .18s ease, color .18s ease;
}

/* ==========================================================================
   用户数据
   ========================================================================== */

.data-bar {
  display: flex; align-items: center; gap: 12px;
  margin-bottom: 14px; flex-wrap: wrap;
}
.data-pick { display: flex; align-items: center; gap: 8px; }
.data-pick > span { font-size: 12px; color: var(--text-3); white-space: nowrap; }
.data-pick select { width: auto; min-width: 200px; padding: 7px 30px 7px 10px; font-size: 13px; }

/* ---------- 数据源选择 ---------- */
.src-group-title {
  font-size: 11px; color: var(--text-3); letter-spacing: .04em;
  margin: 16px 0 8px; text-transform: uppercase;
}
.src-group-title:first-child { margin-top: 0; }

.src-card {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 14px; margin-bottom: 8px;
  border: 1px solid var(--line-soft); border-radius: var(--r-sm);
  background: var(--bg-soft);
  transition: border-color .15s, background .15s;
}
.src-card:hover { border-color: var(--accent-line); background: var(--panel-2); }
.src-ico {
  width: 34px; height: 34px; flex: 0 0 34px;
  display: grid; place-items: center;
  border-radius: 8px; font-size: 16px;
  background: var(--accent-dim); color: var(--accent-2);
}
.src-ico.mysql { background: var(--amber-dim); color: var(--amber); }
.src-ico.postgres { background: rgba(79,156,249,.13); color: var(--blue); }
.src-main { flex: 1; min-width: 0; }
.src-title {
  font-size: 13.5px; font-weight: 550;
  display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
}
.src-sub {
  font-size: 11px; color: var(--text-3);
  margin-top: 2px; word-break: break-all;
}
.src-meta {
  display: flex; gap: 14px; flex-wrap: wrap;
  font-size: 11px; color: var(--text-3); margin-top: 4px;
}

/* 「认出来的依据」：让人能自己核对，而不是相信一个没理由的标签 */
.src-own { color: var(--green); }

/* ---------- 映射向导 ---------- */
.wiz-src {
  font-size: 12px; color: var(--text-2);
  padding: 9px 12px; margin-bottom: 18px;
  background: var(--bg-soft); border-radius: var(--r-sm);
  border: 1px solid var(--line-soft);
  word-break: break-all;
}
.wiz-sec { margin-bottom: 22px; }
.wiz-sec:last-child { margin-bottom: 0; }
.wiz-sec h4 { font-size: 13px; margin: 0 0 5px; font-weight: 600; }
.wiz-sec .hint { font-size: 11.5px; color: var(--text-3); margin: 0 0 10px; line-height: 1.6; }

.map-tables {
  max-height: 170px; overflow-y: auto;
  border: 1px solid var(--line-soft); border-radius: var(--r-sm);
}
.map-table {
  display: flex; align-items: center; gap: 10px;
  padding: 7px 12px; border-bottom: 1px solid var(--line-soft);
  font-size: 12.5px;
}
.map-table:last-child { border-bottom: none; }
.map-table.is-user { background: var(--accent-dim); }
.map-radio { display: flex; align-items: center; gap: 8px; cursor: pointer; min-width: 210px; }
.map-radio input { accent-color: var(--accent); }

.wiz-fields {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 0 14px;
}
.wiz-fields .field { margin-bottom: 12px; }
.wiz-fields select { font-size: 12px; padding: 7px 28px 7px 9px; background-position: right 9px center; }

.chip-radio {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12px; color: var(--text-2);
  padding: 6px 11px; border-radius: 20px;
  border: 1px solid var(--line); background: var(--bg-soft);
  cursor: pointer;
}
.chip-radio input { accent-color: var(--accent); }

.data-cols { width: 100%; font-size: 12px; border-collapse: collapse; }
.data-cols td { padding: 6px 12px; border-bottom: 1px solid var(--line-soft); }

/* 语义标签：高置信度实心，低置信度空心 —— 一眼能分出「确定」和「猜的」 */
.sem {
  display: inline-block; font-size: 10.5px; padding: 1px 7px;
  border-radius: 10px; white-space: nowrap; line-height: 1.6;
  border: 1px solid var(--accent-line); color: var(--accent-2);
}
.sem.high { background: var(--accent-dim); }
.sem.mid { border-style: dashed; opacity: .8; }

/* ---------- 概览数字 ---------- */
.data-stats {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 10px; margin-bottom: 16px;
}
.data-stat {
  background: var(--panel); border: 1px solid var(--line-soft);
  border-radius: var(--r); padding: 13px 15px;
}
.data-stat .v {
  font-size: 22px; font-weight: 600; letter-spacing: -.5px;
  font-variant-numeric: tabular-nums;
}
.data-stat .l { font-size: 11.5px; color: var(--text-3); margin-top: 2px; }
.data-stat.bad .v { color: var(--red); }

.notice {
  font-size: 12.5px; line-height: 1.7;
  padding: 10px 14px; margin-bottom: 14px;
  border-radius: var(--r-sm);
  background: var(--panel-2); border: 1px solid var(--line);
  color: var(--text-2);
}
.notice.warn { background: var(--amber-dim); border-color: var(--amber-line-strong); color: var(--amber); }
.notice.bad { background: var(--red-dim); border-color: var(--red-line-strong); color: var(--red); }
.notice.ok { background: var(--green-dim); border-color: var(--green-line-strong); color: var(--green); }

/* 报错后的「怎么修」清单：编号 + 左对齐 + 别把颜色也染成红的
   （步骤本身是中性信息，整片红字会让人以为全是错误） */.notice .fix-list {
  margin: 8px 0 0;
  padding-left: 20px;
  color: var(--text-2);
  font-size: 12.5px;
  line-height: 1.75;
}
.notice .fix-list li { margin-bottom: 4px; }
.notice .fix-list li:last-child { margin-bottom: 0; }
/* 清单里的命令片段用等宽字体，方便对照着抄 */
.notice .fix-list code {
  font-family: var(--mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: 11.5px;
  padding: 1px 5px;
  border-radius: 3px;
  background: var(--panel);
  border: 1px solid var(--line);
  word-break: break-all;
}

/* ---------- 二选一模式切换（添加服务器：自动 / 手动）---------- */
.seg {
  display: inline-flex;
  padding: 3px;
  gap: 3px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
}
.seg-btn {
  border: 0;
  background: transparent;
  color: var(--text-3);
  font: inherit;
  font-size: 12.5px;
  padding: 6px 14px;
  border-radius: calc(var(--r-sm) - 2px);
  cursor: pointer;
  white-space: nowrap;
  transition: background .15s, color .15s;
}
.seg-btn:hover { color: var(--text); }
.seg-btn.active {
  background: var(--accent);
  color: #fff;
}

/* ---------- 主布局：左表格右分析 ---------- */
.data-grid { display: grid; grid-template-columns: minmax(0, 1fr) 262px; gap: 16px; align-items: start; }
.data-side { display: flex; flex-direction: column; gap: 12px; }

.data-tools {
  display: flex; align-items: center; gap: 8px;
  padding: 10px 14px; border-bottom: 1px solid var(--line-soft);
  flex-wrap: wrap;
}
.data-tools input[type="text"] { flex: 1; min-width: 160px; padding: 6px 10px; font-size: 12.5px; }

.data-foot {
  display: flex; align-items: center; gap: 4px;
  padding: 9px 14px; border-top: 1px solid var(--line-soft);
  font-size: 11.5px;
  background: var(--bg-soft);
}

/* ---------- 数据表格 ---------- */
.data-table { width: 100%; border-collapse: collapse; font-size: 12.5px; }
.data-table th {
  text-align: left; font-weight: 500; color: var(--text-3);
  font-size: 11px; padding: 0; border-bottom: 1px solid var(--line);
  white-space: nowrap;
}
.data-table th.sel-col, .data-table td.sel-col { width: 34px; padding-left: 14px; }
.data-table th.sel-col input { accent-color: var(--red); }
.data-table .th-btn {
  background: none; border: 0; color: inherit; font: inherit;
  padding: 9px 12px; cursor: pointer; display: flex; align-items: center; gap: 5px;
}
.data-table .th-btn:hover { color: var(--text); }
.data-table th.sorted .th-btn { color: var(--accent-2); }
.data-table th.sorted.asc .th-btn::after { content: '↑'; }
.data-table th.sorted.desc .th-btn::after { content: '↓'; }
.data-table .th-btn i {
  font-style: normal; font-size: 10px; padding: 0 5px;
  border-radius: 8px; background: var(--accent-dim); color: var(--accent-2);
}
.data-table td {
  padding: 9px 12px; border-bottom: 1px solid var(--line-soft);
  max-width: 240px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.data-table tbody tr:hover { background: var(--row-hover); }
.data-table tbody tr.is-banned td { color: var(--text-3); }
.data-table td.row-act { text-align: right; padding-right: 14px; }
.masked { color: var(--text-3); letter-spacing: 1.5px; font-family: var(--mono); }

/* 隐私字段的「点开」：看起来像可以点的东西，但不能比正文更抢眼。
   只在悬停时才给底色和描边，避免满屏虚线框把表格搞乱。 */
.masked.revealable {
  letter-spacing: 0;
  cursor: pointer;
  border-radius: 5px;
  padding: 1px 6px;
  margin-left: -6px;
  transition: background .15s, color .15s, box-shadow .15s;
  user-select: none;
}
.masked.revealable:hover {
  color: var(--accent);
  background: var(--accent-dim);
  box-shadow: inset 0 0 0 1px var(--accent-line);
}
.masked.revealable:active { transform: translateY(.5px); }
/* 已查看：恢复正常字号与颜色，左侧一道细线表示「这是刚解开的」 */
.masked.revealed {
  color: var(--text-1);
  letter-spacing: 0;
  font-family: var(--mono);
  border-left: 2px solid var(--accent);
  padding-left: 6px;
}

/* ---------- 分布条 / 迷你柱状图 ---------- */
.bar-row {
  display: grid; grid-template-columns: 78px 1fr 42px;
  align-items: center; gap: 8px; margin-bottom: 6px; font-size: 11.5px;
}
.bar-label {
  color: var(--text-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.bar-track { height: 6px; border-radius: 3px; background: var(--bg-soft); overflow: hidden; }
.bar-fill { height: 100%; background: var(--accent); border-radius: 3px; opacity: .8; }
.bar-num { text-align: right; color: var(--text-3); font-variant-numeric: tabular-nums; }

.spark { display: flex; align-items: flex-end; gap: 2px; height: 56px; }
.spark-col { flex: 1; height: 100%; display: flex; align-items: flex-end; }
.spark-bar {
  width: 100%; background: var(--accent); opacity: .55;
  border-radius: 2px 2px 0 0; min-height: 2px;
  transition: opacity .15s;
}
.spark-col:hover .spark-bar { opacity: 1; }
.spark-axis {
  display: flex; justify-content: space-between;
  font-size: 10px; color: var(--text-3); margin-top: 6px;
}

/* ---------- 单行详情 ---------- */
.row-detail {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 12px 18px;
}
.rd-item { min-width: 0; }
.rd-label {
  font-size: 11px; color: var(--text-3); margin-bottom: 4px;
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
}
.rd-label .mono, .rd-label { font-family: inherit; }
.rd-value input { font-size: 12px; padding: 6px 9px; }
.rd-item.sem .rd-label { color: var(--text-2); }

/* ---------- 窄屏 ---------- */
@media (max-width: 900px) {
  .data-grid { grid-template-columns: 1fr; }
  .data-side { flex-direction: row; flex-wrap: wrap; }
  .data-side > .card { flex: 1 1 220px; }
}

@media (max-width: 760px) {
  .data-pick { width: 100%; }
  .data-pick select { flex: 1; min-width: 0; }
  .src-card { flex-wrap: wrap; }
  .src-card > button { width: 100%; margin-top: 4px; }
  .map-radio { min-width: 0; flex: 1; }
  .wiz-fields { grid-template-columns: 1fr 1fr; }
  .data-tools input[type="text"] { width: 100%; }
  .data-side { flex-direction: column; }
  .data-side > .card { flex: none; }

  /* 表格换卡片式：每行变成一个块，不用横向拖 */
  .data-table thead { display: none; }
  .data-table, .data-table tbody, .data-table tr, .data-table td { display: block; width: 100%; }
  .data-table tr {
    border: 1px solid var(--line-soft); border-radius: var(--r-sm);
    margin: 8px; padding: 4px 0;
  }
  .data-table td {
    border: none; max-width: none; white-space: normal;
    padding: 4px 12px; display: flex; gap: 10px; align-items: baseline;
  }
  /* 卡片式下每行左边挂字段名，不然「active」「free」这种值看不出是哪一列 */
  .data-table td[data-label]::before {
    content: attr(data-label);
    flex: none; width: 110px;
    font-size: 11px; color: var(--text-2);
    text-align: right; padding-top: 1px;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .data-table td.sel-col { width: auto; }
  .data-table td.row-act { text-align: left; padding: 6px 12px 8px; }
  .data-table td:empty { display: none; }
  .row-detail { grid-template-columns: 1fr; }
}

/* ==========================================================================
   代码仓库 / 发布代码
   两块新页面共用一组组件：布局、步骤条、拖拽上传、账号卡、仓库卡。
   颜色全部走变量，深色和浅色都要正常。
   ========================================================================== */

/* ---------- 代码仓库：左账号右仓库 ---------- */
.git-layout { display: grid; grid-template-columns: 300px minmax(0, 1fr); gap: 16px; align-items: start; }
.git-side { display: flex; flex-direction: column; }
.git-main { min-width: 0; }

.acc-list { display: flex; flex-direction: column; }
.acc-row {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--line-soft);
}
.acc-row:last-child { border-bottom: none; }
.acc-main { flex: 1; min-width: 0; }
.acc-name {
  font-size: 13px; font-weight: 600; color: var(--text);
  display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap;
}
.acc-meta {
  margin-top: 4px; font-size: 11.5px; color: var(--text-3);
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
}
.dot-sep { opacity: .5; }
.ok-dot, .warn-dot {
  display: inline-block; width: 6px; height: 6px; border-radius: 50%;
}
.ok-dot { background: var(--green); }
.warn-dot { background: var(--amber); }

.mini-empty { padding: 22px 16px; text-align: center; }
.mini-empty p { margin: 0 0 6px; font-size: 12.5px; }
.mini-empty p.muted { color: var(--text-3); font-size: 12px; line-height: 1.65; }

.plain-list { margin: 0; padding-left: 18px; font-size: 12.5px; line-height: 1.85; color: var(--text-2); }
.plain-list li { margin-bottom: 6px; }
.plain-list b { color: var(--text); }

/* ---------- 仓库卡片 ---------- */
.repo-group { margin-bottom: 22px; }
.repo-group:last-child { margin-bottom: 0; }
.repo-group-head {
  display: flex; align-items: center; gap: 8px;
  margin-bottom: 10px; padding-bottom: 8px;
  border-bottom: 1px solid var(--line-soft);
}
.repo-owner { font-size: 12.5px; font-weight: 600; color: var(--text); }
.repo-count {
  margin-left: auto;
  font-size: 11px; color: var(--text-3);
  background: var(--panel-2); border: 1px solid var(--line);
  border-radius: 20px; padding: 1px 8px;
}

.repo-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  gap: 10px;
}
.repo-card {
  display: block;
  padding: 12px 14px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  text-decoration: none;
  color: inherit;
  transition: border-color .15s, transform .15s, background .15s;
}
.repo-card:hover {
  border-color: var(--accent);
  background: var(--accent-dim);
  transform: translateY(-1px);
}
.repo-title { display: flex; align-items: center; gap: 6px; font-size: 13px; font-weight: 600; color: var(--text); }
.repo-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lock { font-size: 10px; opacity: .55; }
.repo-desc {
  margin-top: 5px; font-size: 11.5px; color: var(--text-3); line-height: 1.55;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.repo-foot {
  margin-top: 9px; display: flex; align-items: center; gap: 10px;
  font-size: 11px; color: var(--text-3);
}
.repo-lang { display: flex; align-items: center; gap: 4px; }
.repo-lang::before {
  content: ''; width: 7px; height: 7px; border-radius: 50%;
  background: var(--accent-2);
}

/* ---------- 发布代码：主区 + 侧栏 ---------- */
.release-layout { display: grid; grid-template-columns: minmax(0, 1fr) 290px; gap: 16px; align-items: start; }
.release-main { min-width: 0; }
.release-aside { display: flex; flex-direction: column; gap: 12px; }

.release-card { padding: 0; }
.release-body { padding: 26px 28px 24px; }
.release-h { font-size: 16px; margin: 0 0 8px; font-weight: 600; color: var(--text); }
.release-sub { font-size: 12.5px; color: var(--text-2); line-height: 1.75; margin: 0 0 22px; }
.release-sub b { color: var(--text); }

.release-actions {
  display: flex; align-items: center; gap: 10px; justify-content: flex-end;
  margin-top: 22px; padding-top: 18px;
  border-top: 1px solid var(--line-soft);
}

/* ---------- 步骤条 ---------- */
.steps {
  display: flex; align-items: center; gap: 0;
  padding: 16px 28px;
  border-bottom: 1px solid var(--line-soft);
  background: var(--bg-soft);
}
.step { display: flex; align-items: center; gap: 8px; }
.step-no {
  width: 22px; height: 22px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 11.5px; font-weight: 600;
  background: var(--panel-2); border: 1px solid var(--line);
  color: var(--text-3);
  transition: all .2s;
}
.step-label { font-size: 12.5px; color: var(--text-3); white-space: nowrap; }
.step.active .step-no {
  background: var(--accent); border-color: var(--accent); color: #fff;
  box-shadow: 0 0 0 3px var(--accent-dim);
}
.step.active .step-label { color: var(--text); font-weight: 600; }
.step.done .step-no {
  background: var(--green-dim); border-color: var(--green-line-strong); color: var(--green);
}
.step.done .step-label { color: var(--text-2); }
.step-line {
  flex: 1; height: 1px; margin: 0 12px;
  background: var(--line); min-width: 16px;
}

/* ---------- 拖拽上传 ---------- */
.dropzone {
  border: 1.5px dashed var(--line);
  border-radius: var(--r);
  padding: 34px 20px;
  text-align: center;
  cursor: pointer;
  transition: border-color .18s, background .18s;
  background: var(--bg-soft);
}
.dropzone:hover, .dropzone.over {
  border-color: var(--accent);
  background: var(--accent-dim);
}
.dz-ico { font-size: 26px; color: var(--accent-2); margin-bottom: 10px; opacity: .85; }
.dz-title { font-size: 13.5px; color: var(--text); font-weight: 500; }
.dz-hint { font-size: 11.5px; color: var(--text-3); margin-top: 7px; }
.dz-hint.bad { color: var(--red); }
.dz-file { font-size: 12px; color: var(--accent-2); margin-top: 10px; }

/* ---------- 说明条目 ---------- */
.release-tips { margin-top: 24px; display: flex; flex-direction: column; gap: 14px; }
.tip-item { display: flex; gap: 12px; align-items: flex-start; }
.tip-ico {
  flex-shrink: 0;
  width: 20px; height: 20px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 11px; color: var(--accent-2);
  background: var(--accent-dim); border: 1px solid var(--line);
  margin-top: 1px;
}
/* 只让「小标题」那个 b 独占一行。写成 .tip-item b 会把正文里的 <b> 也变成块级，
   于是「必须是 .zip」被拆成三行 —— 用直接子级 + :first-child 收窄 */
.tip-item > div > b:first-child { font-size: 12.5px; color: var(--text); display: block; margin-bottom: 3px; }
.tip-item p { margin: 0; font-size: 12px; color: var(--text-3); line-height: 1.7; }
.tip-item p b { color: var(--text-2); font-weight: 600; }

/* ---------- 键值清单 ---------- */
.kv-list {
  background: var(--bg-soft);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-sm);
  padding: 4px 16px;
  margin-bottom: 18px;
}
.kv {
  display: flex; align-items: baseline; gap: 12px;
  padding: 10px 0;
  border-bottom: 1px solid var(--line-soft);
  font-size: 12.5px;
}
.kv:last-child { border-bottom: none; }
.kv .k { flex-shrink: 0; width: 86px; color: var(--text-3); }
.kv .v { color: var(--text); min-width: 0; word-break: break-all; }

.link { color: var(--accent-2); text-decoration: none; }
.link:hover { text-decoration: underline; }

/* ---------- 发现项清单 ---------- */
.find-list { margin: 8px 0 0; padding-left: 18px; font-size: 12px; line-height: 1.9; }
.find-list li { margin-bottom: 3px; }
.find-list code {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 6px;
  background: rgba(0, 0, 0, .25);
  border-radius: 4px;
  font-size: 11px;
  font-family: var(--mono);
  color: var(--text-2);
}

/* ---------- 文件清单折叠 ---------- */
.files-details {
  margin-top: 6px;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-sm);
  padding: 10px 14px;
  background: var(--bg-soft);
}
.files-details summary {
  cursor: pointer;
  font-size: 12.5px;
  color: var(--text-2);
  user-select: none;
}
.files-details summary:hover { color: var(--text); }
.files-grid {
  margin-top: 10px;
  display: flex; flex-wrap: wrap; gap: 6px;
  max-height: 200px; overflow-y: auto;
}
.files-grid span {
  font-size: 11px;
  padding: 2px 7px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 4px;
  color: var(--text-2);
}

/* ---------- 账号选择 ---------- */
.acc-opts { display: flex; flex-direction: column; gap: 8px; }
.acc-opt {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  cursor: pointer;
  transition: border-color .15s, background .15s;
}
.acc-opt:hover { border-color: var(--btn-border-hover); }
.acc-opt.sel { border-color: var(--accent); background: var(--accent-dim); }
.acc-opt input[type="radio"] { margin-top: 3px; accent-color: var(--accent); }
.acc-opt-body { flex: 1; min-width: 0; }
.acc-opt-name { font-size: 13px; font-weight: 600; color: var(--text); }
.acc-opt-meta { font-size: 11.5px; color: var(--text-3); margin-top: 2px; }
.acc-opt-mail { font-size: 11px; color: var(--text-3); margin-top: 5px; word-break: break-all; }
.warn-text { color: var(--amber); }

.checkbox-row {
  display: flex; align-items: flex-start; gap: 8px;
  margin-top: 14px; font-size: 12.5px; color: var(--text-2);
  cursor: pointer;
}
.checkbox-row input { margin-top: 2px; accent-color: var(--red); }

/* ---------- 上传进度 ---------- */
.uploading { padding: 30px 10px; text-align: center; }
.up-bar {
  height: 4px; border-radius: 4px;
  background: var(--panel-2); overflow: hidden;
  margin-bottom: 14px;
}
.up-fill {
  height: 100%; width: 0;
  background: linear-gradient(90deg, var(--accent), var(--accent-2));
  transition: width .2s;
}
.up-text { font-size: 12.5px; color: var(--text-2); line-height: 1.7; }

/* ---------- 完成态 ---------- */
.done-wrap { padding: 40px 28px 30px; }
.done-ico {
  width: 46px; height: 46px; margin: 0 auto 16px;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 22px; color: var(--green);
  background: var(--green-dim);
  border: 1px solid var(--green-line-strong);
}
.done-wrap h2 { font-size: 17px; text-align: center; margin: 0 0 8px; }

/* ---------- 推送记录 ---------- */
.rel-history { display: flex; flex-direction: column; }
.rel-item {
  padding: 11px 14px;
  border-bottom: 1px solid var(--line-soft);
}
.rel-item:last-child { border-bottom: none; }
.rel-top { display: flex; align-items: center; gap: 8px; margin-bottom: 5px; }
.rel-top .muted { margin-left: auto; }
.rel-repo { font-size: 11.5px; color: var(--text-2); word-break: break-all; }
.rel-meta { font-size: 11px; margin-top: 3px; }
.rel-err { font-size: 11px; color: var(--red); margin-top: 5px; line-height: 1.55; }

/* 删除记录：常驻但压低存在感，鼠标移上去才变红 —— 功能得能被找到，又不该抢注意力 */
.rel-del {
  flex: none;
  border: 0;
  background: transparent;
  color: var(--text-3);
  font-size: 12px;
  line-height: 1;
  padding: 3px 5px;
  border-radius: 5px;
  cursor: pointer;
  opacity: .55;
  transition: opacity .12s, color .12s, background .12s;
}
.rel-del:hover { opacity: 1; color: var(--red); background: var(--red-dim); }
.rel-del:disabled { opacity: .3; cursor: default; background: transparent; }

.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 0 14px; }

/* ---------- 窄屏 ---------- */
@media (max-width: 1080px) {
  .git-layout, .release-layout { grid-template-columns: minmax(0, 1fr); }
  .release-aside { order: 1; }
}
@media (max-width: 560px) {
  .grid-2 { grid-template-columns: 1fr; }
  .release-body { padding: 20px 16px; }
  .steps { padding: 13px 16px; }
  .step-label { display: none; }
  .done-wrap { padding: 28px 16px 22px; }
}

/* ==========================================================================
   发布向导（发布安全闸门）
   四步：预检 → 确认变更 → 执行 → 完成
   核心是「危险项逐条勾选」那一块，视觉上必须让人一眼看出「这里要你负责」。
   ========================================================================== */

/* ---------- 引导文字 ---------- */
.dw-lead { padding: 4px 0 2px; }
.dw-lead p { margin: 0 0 8px; font-size: 13px; color: var(--text-2); line-height: 1.7; }
.dw-list { margin: 6px 0 0; padding-left: 20px; font-size: 13px; color: var(--text-2); line-height: 1.9; }
.dw-list b { color: var(--text); }

/* ---------- 顶部三格概览 ---------- */
.dw-summary {
  /* 四列：版本 / 备份 / 必须确认 / 老数据体检。
     用 auto-fit 而不是写死 4 —— 窄窗口下自己折成两行，不会把数字压成一条线 */
  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 1px; background: var(--line-soft);
  border: 1px solid var(--line-soft); border-radius: var(--r);
  overflow: hidden;
}
.dw-summary .stat { background: var(--panel); padding: 12px 14px; }
.dw-summary .v { font-size: 18px; }

/* ---------- 危险项标题行 ---------- */
.dw-block-head {
  display: flex; align-items: baseline; gap: 10px;
  margin: 18px 0 10px; font-size: 13px;
}
.dw-block-head b { font-weight: 600; }
.dw-block-head .muted { margin-left: auto; font-size: 11.5px; }

/* ---------- 差异条目 ---------- */
.find-list { display: flex; flex-direction: column; gap: 8px; }

.diff-row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: 12px;
  align-items: start;
  padding: 11px 14px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-left: 3px solid var(--red-line-strong);
  border-radius: var(--r-sm);
  cursor: pointer;
  transition: border-color .15s, background .15s;
}
.diff-row:hover { border-color: var(--btn-border-hover); }
/* 勾过之后整条转绿：一眼就能看出「这条我认过了」 */
.diff-row.acked {
  border-left-color: var(--green-line-strong);
  background: var(--green-dim);
}
/* 「要你自己掂量」：黄色左边线 + 不响应鼠标。
   和 info 的区别只有颜色和位置（它在折叠区外面），因为两者的性质确实只差这一点：
   都是「不影响发布」，但这一类会影响「出事时能不能退」。 */
.diff-row.warn {
  border-left-color: var(--amber);
  cursor: default;
  background: var(--amber-dim);
}
.diff-row.warn .diff-title { color: var(--text); }
.diff-row.info {
  border-left-color: var(--line);
  cursor: default;
  background: var(--bg-soft);
}
.diff-row.disabled { opacity: .5; cursor: not-allowed; }

.diff-kind { padding-top: 1px; }
/* 脚本行的徽章不能被压缩：summary 是 grid，auto 列在窄屏/长标题下会被挤，
   徽章里的字就压到后面的标题上（踩过，看起来像「全表改改数据」）。 */
/* 脚本行比结构行多一个徽章，列数不一样，单独声明 */
.dc-row.script > summary { grid-template-columns: 16px auto minmax(0, 1fr) auto; }
/* 徽章是 grid 项（在 auto 列里）。原来给它加了 white-space: nowrap，
   结果 min-content 宽度算成了「一个字」那么宽（2 个字被当可折行），
   grid 就按 ~25px 派位，徽章整个 overflow 出去压到后面的标题上 ——
   截图里看着像「全表改改数据」。
   现在不写 nowrap：让 grid 按完整内容（「全表改」三个字一整块）算宽度。
   里面 .tag 自己已经有 white-space: nowrap，不会真去折行。 */
.dc-row > summary > .diff-kind { display: block; }
.diff-main { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.diff-title { font-size: 13px; font-weight: 550; color: var(--text); line-height: 1.5; }
.diff-impact {
  font-size: 11.5px; color: var(--red);
  font-weight: 600; font-variant-numeric: tabular-nums;
}
.diff-detail { font-size: 11.5px; color: var(--text-3); line-height: 1.65; }
.diff-row input[type="checkbox"],
.diff-row input[type="radio"] {
  width: 16px; height: 16px; margin: 2px 0 0;
  accent-color: var(--red);
  cursor: pointer;
}
.diff-row.acked input[type="checkbox"] { accent-color: var(--green); }

/* ---------- 提示项折叠 ---------- */
.dw-info-head {
  display: flex; align-items: center; gap: 7px;
  font-size: 12.5px; color: var(--text-2);
  cursor: pointer; user-select: none;
}
.dw-info-head:hover { color: var(--text); }

/* ---------- 解析不了的语句原文 ---------- */
.dw-code {
  margin-top: 8px;
  padding: 9px 11px;
  background: var(--term-bg);
  border: 1px solid var(--term-border);
  border-radius: var(--r-sm);
  font-family: var(--mono);
  font-size: 11px;
  line-height: 1.6;
  color: var(--term-text);
  max-height: 180px;
  overflow: auto;
  white-space: pre-wrap;
  word-break: break-all;
}
.dw-code .muted { color: var(--text-3); }

/* ---------- 完成态 ---------- */
.done-wrap h3 { font-size: 17px; text-align: center; margin: 0 0 8px; }
.done-wrap .muted { text-align: center; display: block; }
.done-ico.bad { color: var(--red); background: var(--red-dim); border-color: var(--red-line-strong); }
.done-acts { display: flex; gap: 8px; justify-content: center; margin-top: 18px; }

/* ---------- 发布记录里的操作行 ---------- */
.rel-acts { margin-top: 8px; display: flex; gap: 6px; }

@media (max-width: 700px) {
  .dw-summary { grid-template-columns: 1fr; }
  .diff-row { grid-template-columns: auto minmax(0, 1fr); }
  .diff-row input[type="checkbox"],
  .diff-row input[type="radio"] { grid-column: 1 / -1; margin-left: auto; }
}

/* ---------- 一步发布（部署到公网） ---------- */
.oc-block {
  margin-top: 26px; padding-top: 22px;
  border-top: 1px solid var(--line-soft);
}
.oc-head { display: flex; align-items: baseline; gap: 10px; margin-bottom: 14px; }
.oc-title { font-size: 14px; font-weight: 600; color: var(--text); }
.oc-probe {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  font-size: 12px; color: var(--text-2); margin-top: 8px;
}
.oc-sep { color: var(--text-3); }
.oc-ok { color: var(--green); }
.oc-bad { color: var(--red); }
.oc-os { font-size: 11.5px; margin-top: 5px; }
.oc-probe-loading { font-size: 12px; color: var(--text-3); margin-top: 8px; }
.oc-actions { display: flex; gap: 10px; margin-top: 16px; flex-wrap: wrap; }
.oc-term { margin-top: 14px; max-height: 260px; }
.oc-key {
  margin-top: 14px; padding: 14px;
  background: var(--bg-soft); border: 1px solid var(--line-soft); border-radius: var(--r-sm);
}
.oc-key-h { font-size: 12.5px; font-weight: 600; color: var(--text); margin-bottom: 8px; }
.oc-steps { margin: 0 0 10px 18px; padding: 0; font-size: 12px; color: var(--text-3); line-height: 1.9; }
.oc-pubkey {
  width: 100%; box-sizing: border-box; resize: vertical;
  font-size: 11px; line-height: 1.6;
  background: var(--panel); color: var(--text-2);
  border: 1px solid var(--line); border-radius: var(--r-sm);
  padding: 9px 10px; margin-bottom: 8px;
}

/* ---------- 智能助手：能力边界说明 ----------
   为什么要有它：接大模型的产品最容易让人误解「它什么都能干」。
   把「不做什么」写在明面上，用户才敢放心把项目交给它管。 */
.ai-scope {
  margin-top: 16px;
  padding: 12px 14px;
  border-radius: var(--r-sm);
  background: var(--panel-2);
  border: 1px solid var(--line);
  font-size: 12.5px;
  line-height: 1.7;
  color: var(--text-2);
}
.ai-scope-title {
  font-weight: 600;
  color: var(--text);
  margin-bottom: 2px;
}
.ai-scope-title + ul { margin: 0 0 10px; padding-left: 18px; }
.ai-scope ul:last-child { margin-bottom: 0; }
.ai-scope li { margin-bottom: 2px; }

/* ---------- 版本升级卡片 ---------- */
.up-now {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 12px 14px; border-radius: var(--r-sm);
  background: var(--bg-soft); border: 1px solid var(--line);
}
.up-actions {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
}
.up-actions input[type=text] {
  padding: 7px 10px; font-size: 12.5px;
}
.up-wait {
  display: flex; align-items: center; gap: 14px;
  padding: 18px 16px; border-radius: var(--r-sm);
  background: var(--bg-soft); border: 1px solid var(--line);
}
.up-backup {
  margin-top: 16px; padding-top: 14px;
  border-top: 1px solid var(--line);
}
.up-backup-row {
  display: flex; align-items: center; gap: 12px;
  padding: 6px 0; font-size: 12.5px;
}
.up-backup-row .mono { flex: 0 0 auto; min-width: 130px; }
.up-backup-row .muted { flex: 1; }

/* ---------- 智能助手的解释卡片 ---------- */
/* 刻意不用警告色：它的定位是「帮忙翻译」，不是「出事了」 */
.ai-answer {
  margin-top: 14px;
  border: 1px solid var(--line);
  border-left: 3px solid var(--accent);
  border-radius: var(--r-sm);
  background: var(--bg-soft);
  overflow: hidden;
}
.ai-answer-head {
  display: flex; align-items: center; gap: 8px;
  padding: 9px 12px;
  border-bottom: 1px solid var(--line);
}
.ai-badge {
  font-size: 11px; padding: 2px 8px; border-radius: 20px;
  background: var(--accent-dim, rgba(124,92,255,.12));
  color: var(--accent);
  border: 1px solid var(--accent-line, rgba(124,92,255,.3));
  white-space: nowrap;
}
.ai-answer-x {
  margin-left: auto; padding: 0 7px; line-height: 1.4;
}
.ai-answer-body {
  padding: 12px 14px;
  font-size: 12.8px; line-height: 1.85;
  white-space: pre-wrap;
  word-break: break-word;
  color: var(--text-1);
  max-height: 420px;
  overflow: auto;
}

/* ---------- 体检弹窗里的「怎么装」选择 ---------- */
.base-pick { margin-top: 10px; display: flex; flex-direction: column; gap: 2px; }
.base-pick label {
  display: flex; align-items: center; gap: 7px;
  font-size: 12.8px; padding: 5px 0; cursor: pointer;
}
.base-pick input[type=radio] { width: auto; accent-color: var(--accent); }
.base-pick-hint {
  font-size: 11.8px; color: var(--text-2); line-height: 1.65;
  padding: 0 0 8px 21px;
}

/* ==========================================================================
   coco · 右下角的圆球助手
   --------------------------------------------------------------------------
   球是「一个人」，所以它的动效必须慢、软、有惯性：
   漂浮 4.6s、转头 450ms 缓出、眨眼 130ms。
   任何一处调快都会变成「电子宠物」而不是「坐在角落的同事」。
   ========================================================================== */

.coco-dock {
  position: fixed;
  right: 26px;
  bottom: 22px;
  /* 90 → 50：原来 90 比顶栏(20)高，它的图层铺在整行宽上，往上滚时会盖住顶栏，
     把「嗨，我是 coco」这类灰字画进标题栏。现在只在球自己那块区域内，
     且低于顶栏(60)、低于弹窗(100)与 toast(200)。 */
  z-index: 50;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  pointer-events: none;         /* 容器不吃点击，只让球和面板吃 */
}
.coco-dock.hidden { display: none; }

/* ---------- 球 ---------- */
.coco-bot {
  position: relative;
  width: 56px;
  height: 56px;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  pointer-events: auto;
  -webkit-tap-highlight-color: transparent;
  outline: none;
}
.coco-bot:focus-visible { outline: 2px solid var(--accent); outline-offset: 6px; border-radius: 50%; }

.coco-float {
  position: absolute;
  inset: 0;
  animation: coco-float 4.6s ease-in-out infinite;
  will-change: transform;
}

.coco-ball {
  position: absolute;
  left: 5px;
  top: 3px;
  width: 46px;
  height: 46px;
  border-radius: 50%;
  background:
    radial-gradient(circle at 33% 24%, rgba(255, 255, 255, .95) 0%, rgba(255, 255, 255, .55) 9%, rgba(255, 255, 255, 0) 34%),
    radial-gradient(circle at 70% 82%, rgba(74, 82, 106, .55) 0%, rgba(74, 82, 106, 0) 58%),
    linear-gradient(158deg, #e6e9f2 0%, #ccd1de 38%, #b0b7c8 68%, #939aae 100%);
  box-shadow:
    inset -5px -9px 16px rgba(64, 72, 96, .34),
    inset 5px 7px 14px rgba(255, 255, 255, .62),
    0 7px 16px rgba(18, 22, 36, .2);
  transition: transform .45s cubic-bezier(.22, 1, .36, 1), box-shadow .3s ease;
  will-change: transform;
  overflow: hidden;   /* 说话时那条嘴的余量要裁在球里，不能溢出去 */
}

/* 眼睛：两个竖着的小圆角块。平时没有嘴，留白是刻意的 ——
   只有开口说话的时候才长出那条会动的嘴（见 .coco-dock.talking）。 */
.coco-face {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10.5px;
  transition: transform .4s cubic-bezier(.22, 1, .36, 1);
  will-change: transform;
}
/* 眼睛的「形状」全交给里面的 i。外面这层只做裁切 ——
   之前外圈自己也涂了深色，结果 i 缩没了眼睛还在，永远闭不上。 */
.coco-eye {
  width: 6.5px;
  height: 11px;
  border-radius: 3.5px;
  overflow: hidden;
}
.coco-eye i {
  display: block;
  width: 100%;
  height: 100%;
  background: #262a35;
  border-radius: 3.5px;
  transform-origin: center;
  transition: transform .11s cubic-bezier(.4, 0, .2, 1);
}
/* 眨眼：收成一条缝而不是缩成一个点，否则远看像眼睛消失了 */
.coco-bot.blink .coco-eye i { transform: scaleY(.08); }

.coco-gloss {
  position: absolute;
  left: 9px;
  top: 5px;
  width: 19px;
  height: 12px;
  border-radius: 50%;
  background: radial-gradient(ellipse at 40% 45%, rgba(255, 255, 255, .95), rgba(255, 255, 255, 0) 72%);
  filter: blur(1.2px);
  transition: transform .5s cubic-bezier(.22, 1, .36, 1);
}

.coco-shadow {
  position: absolute;
  left: 8px;
  bottom: 1px;
  width: 40px;
  height: 7px;
  border-radius: 50%;
  background: radial-gradient(ellipse at center, rgba(10, 14, 26, .34), rgba(10, 14, 26, 0) 72%);
  filter: blur(2px);
  animation: coco-shadow 4.6s ease-in-out infinite;
}
:root[data-theme="light"] .coco-shadow {
  background: radial-gradient(ellipse at center, rgba(40, 50, 80, .22), rgba(40, 50, 80, 0) 72%);
}

.coco-ring {
  position: absolute;
  inset: 2px;
  border-radius: 50%;
  border: 0 solid transparent;
  box-shadow: 0 0 0 0 rgba(124, 92, 255, 0);
  transition: box-shadow .35s ease, transform .12s linear, border-color .2s;
  pointer-events: none;
}

.coco-bot:hover .coco-ring { box-shadow: 0 0 0 9px rgba(124, 92, 255, .09); }
.coco-bot:hover .coco-ball { box-shadow:
    inset -5px -9px 16px rgba(64, 72, 96, .3),
    inset 5px 7px 14px rgba(255, 255, 255, .7),
    0 11px 22px rgba(18, 22, 36, .3),
    0 0 0 5px rgba(124, 92, 255, .07); }
.coco-bot:active .coco-ball { transform: scale(.94); }

/* 点一下的果冻感 */
.coco-bot.poke .coco-ball { animation: coco-poke .42s cubic-bezier(.3, 1.4, .5, 1); }
/* 思考时轻轻晃，像在原地想事情 */
.coco-bot.thinking .coco-ball { animation: coco-bob 1.35s ease-in-out infinite; }
.coco-bot.thinking .coco-eye i { transform: scaleY(.55); }

/* 打招呼的小气泡：一个会话只出现一次 */
.coco-hint {
  position: absolute;
  right: 47px;
  top: 5px;
  white-space: nowrap;
  font-size: 12px;
  line-height: 1;
  padding: 8px 12px;
  border-radius: 999px;
  background: var(--panel);
  color: var(--text);
  border: 1px solid var(--line);
  box-shadow: var(--shadow-toast);
  opacity: 0;
  transform: translateY(6px) scale(.9);
  transform-origin: 100% 100%;
  transition: opacity .3s ease, transform .35s cubic-bezier(.22, 1, .36, 1);
  pointer-events: none;
}
.coco-hint::after { display: none; }   /* 胶囊形状配尖角会变成「>」，去掉更干净 */
.coco-bot.hinting .coco-hint { opacity: 1; transform: none; }

@keyframes coco-float {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-3.5px); }
}
@keyframes coco-shadow {
  0%, 100% { transform: scale(1); opacity: .9; }
  50%      { transform: scale(.9); opacity: .6; }
}
@keyframes coco-bob {
  0%, 100% { transform: translateY(0) rotate(-2.5deg); }
  50%      { transform: translateY(-4px) rotate(2.5deg); }
}
@keyframes coco-poke {
  0%   { transform: scale(1); }
  32%  { transform: scale(1.1, .9); }
  62%  { transform: scale(.95, 1.05); }
  100% { transform: scale(1); }
}

/* ---------- 对话框：从球头顶往上弹 ---------- */
.coco-panel {
  width: 348px;
  height: min(528px, calc(100vh - 150px));
  margin-bottom: 16px;
  display: flex;
  flex-direction: column;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 18px;
  box-shadow: var(--shadow-modal);
  overflow: hidden;
  pointer-events: auto;
  transform-origin: calc(100% - 28px) 100%;
  opacity: 0;
  transform: translateY(12px) scale(.92);
  transition: opacity .16s ease, transform .26s cubic-bezier(.22, 1, .36, 1);
  will-change: transform, opacity;
}
.coco-panel.in { opacity: 1; transform: none; }
.coco-panel[hidden] { display: none; }

.coco-head {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 11px 12px 11px 14px;
  border-bottom: 1px solid var(--line-soft);
  background: linear-gradient(180deg, var(--panel-2), var(--panel));
}
.coco-ava,
.coco-mini {
  position: relative;
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: linear-gradient(150deg, #e2e6f0, #b9c0d2 60%, #9aa1b5);
  box-shadow: inset -2px -3px 5px rgba(64, 72, 96, .3), inset 2px 2px 4px rgba(255, 255, 255, .55);
}
.coco-ava { width: 30px; height: 30px; }
.coco-ava i,
.coco-mini i {
  display: block;
  background: #2a2e3a;
  border-radius: 3px;
  transform-origin: center;
  transition: transform .11s cubic-bezier(.4, 0, .2, 1);
}

/* 眼神单独一层：眼睛跟着光标走（translate），眨眼缩的是里面的 i（scaleY）。
   写在同一层会互相覆盖 —— 上一版就是这样，一转头眨眼就失灵。 */
.coco-look {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  transition: transform .34s cubic-bezier(.22, 1, .36, 1);
  will-change: transform;
}
.coco-mini .coco-look { gap: 3px; }

.coco-dock.thinking .coco-ava i,
.coco-dock.thinking .coco-mini i { transform: scaleY(.5); }
/* 眨眼要压过「思考时眯眼」：两件事可以同时发生，该闭就闭。
   所以这里特意加一层 .coco-dock 提权，并写在 thinking 之后。 */
.coco-dock .coco-bot.blink .coco-eye i,
.coco-dock .coco-ava.blink i,
.coco-dock .coco-mini.blink i { transform: scaleY(.12); }
.coco-ava i { width: 4px; height: 8px; }
.coco-mini { width: 22px; height: 22px; }
.coco-mini i { width: 3px; height: 6px; }

.coco-who { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.coco-who b { font-size: 13px; letter-spacing: .2px; }
.coco-who em { font-style: normal; font-size: 10.5px; color: var(--text-3); }

.coco-x {
  margin-left: auto;
  width: 26px; height: 26px;
  border: 0; border-radius: 8px;
  background: transparent;
  color: var(--text-3);
  font-size: 19px; line-height: 1;
  cursor: pointer;
  transition: background .15s, color .15s;
}
.coco-x:hover { background: var(--btn-bg-hover); color: var(--text); }

.coco-msgs {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 14px 13px 6px;
  display: flex;
  flex-direction: column;
  gap: 11px;
  background: var(--bg-soft);
  scrollbar-width: thin;
  /* 这里曾经有一条 14px 的顶部渐隐（mask-image），想让滚动时第一行
     不被「一刀切断」。实际效果是：一滚动，第一行文字就被一道白蒙住，
     看着像有一条白条压在字上 —— 比硬切难看，也更影响读。
     硬切至少有边界，渐隐是「字还在但看不清」，更容易被当成渲染 bug。
     消息区本身上下都有间距，不需要这层遮罩。 */
}
.coco-msgs::-webkit-scrollbar { width: 6px; }
.coco-msgs::-webkit-scrollbar-thumb { background: var(--scroll-thumb); border-radius: 3px; }

.coco-msg { display: flex; gap: 7px; align-items: flex-start; animation: coco-msg-in .26s ease both; }
.coco-msg.user { justify-content: flex-end; }
.coco-msg > .coco-mini { margin-top: 2px; }

.coco-bub {
  max-width: 83%;
  padding: 8px 11px;
  font-size: 12.8px;
  line-height: 1.62;
  color: var(--text);
  background: var(--panel);
  border: 1px solid var(--line-soft);
  border-radius: 13px;
  border-top-left-radius: 5px;
  word-break: break-word;
  white-space: normal;
}
.coco-msg.user .coco-bub {
  background: var(--accent);
  border-color: transparent;
  color: #fff;
  border-radius: 13px;
  border-top-right-radius: 5px;
  box-shadow: 0 4px 12px rgba(124, 92, 255, .28);
}
.coco-bub b { font-weight: 600; }
.coco-bub code {
  font-family: var(--mono);
  font-size: 11.5px;
  padding: 1px 4px;
  border-radius: 4px;
  background: rgba(124, 92, 255, .12);
  color: var(--accent-2);
  word-break: break-all;
}
.coco-msg.user .coco-bub code { background: rgba(255, 255, 255, .2); color: #fff; }

/* 三个点：正在想 */
.coco-dots { display: inline-flex; gap: 4px; align-items: center; padding: 11px 12px; }
.coco-dots i {
  width: 5px; height: 5px; border-radius: 50%;
  background: var(--text-3);
  animation: coco-dot 1.1s ease-in-out infinite;
}
.coco-dots i:nth-child(2) { animation-delay: .15s; }
.coco-dots i:nth-child(3) { animation-delay: .3s; }

.coco-chips {
  flex: 0 0 auto;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 2px 12px 9px;
  background: var(--bg-soft);
  /* 348px 的面板、12px 内边距，一行放得下两个；原来 max-height 74px
     等于允许三行，直接把输入框往上顶。这里也收一档，行数一多就裁掉。 */
  max-height: 50px;
  overflow: hidden;
}
.coco-chips.empty { display: none; }
.coco-chip {
  flex: 0 0 auto;
  max-width: 78%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  /* 比正文小一档，但不是「注释级」—— 这几个是可点的入口，不是说明文字 */
  padding: 6px 11px;
  font-size: 12.5px;
  color: var(--text-2);
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 999px;
  cursor: pointer;
  text-align: left;
  transition: border-color .15s, color .15s, background .15s;
}
.coco-chip:hover { color: var(--accent-2); border-color: var(--accent-line); background: var(--accent-dim); }

.coco-input {
  display: flex;
  align-items: flex-end;
  gap: 8px;
  padding: 9px 10px 9px 12px;
  border-top: 1px solid var(--line-soft);
  background: var(--panel);
}
.coco-input textarea {
  flex: 1;
  min-height: 34px;
  max-height: 96px;
  padding: 8px 2px;
  border: 0;
  outline: none;
  resize: none;
  background: transparent;
  color: var(--text);
  font-family: var(--sans);
  font-size: 12.8px;
  line-height: 1.55;
}
.coco-input textarea::placeholder { color: var(--text-3); }

.coco-send {
  flex: 0 0 auto;
  width: 32px; height: 32px;
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; border-radius: 50%;
  background: var(--accent);
  color: #fff;
  cursor: pointer;
  transition: background .15s, transform .15s, opacity .15s;
}
.coco-send:hover { background: var(--accent-hover); transform: translateY(-1px); }
.coco-send:disabled { opacity: .45; cursor: default; transform: none; }

.coco-foot {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 10px 9px 12px;
  font-size: 10.5px;
  color: var(--text-3);
  background: var(--panel);
}
.coco-foot-tip {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  transition: color .2s;
}
.coco-foot-tip.hot { color: var(--accent-2); }
.coco-toggles { display: inline-flex; gap: 5px; flex: 0 0 auto; }
.coco-tg {
  display: inline-flex; align-items: center; gap: 3px;
  padding: 2px 6px;
  border: 1px solid var(--line-soft);
  border-radius: 999px;
  background: transparent;
  color: var(--text-3);
  font-size: 10.5px;
  font-family: var(--sans);
  cursor: pointer;
  transition: color .15s, border-color .15s, background .15s;
}
.coco-tg b { font-weight: 500; }
.coco-tg:hover { color: var(--text-2); border-color: var(--line); }
.coco-tg.on {
  color: var(--accent-2);
  border-color: var(--accent-line);
  background: var(--accent-dim);
}

/* ---- 麦克风 ---- */
.coco-mic {
  position: relative;
  flex: 0 0 auto;
  width: 32px; height: 32px;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--line-soft);
  border-radius: 50%;
  background: transparent;
  color: var(--text-3);
  cursor: pointer;
  transition: color .15s, border-color .15s, background .15s, transform .12s;
  -webkit-tap-highlight-color: transparent;
  touch-action: none;
}
.coco-mic:hover:not(:disabled) { color: var(--accent-2); border-color: var(--accent-line); background: var(--accent-dim); }
.coco-mic:disabled { opacity: .4; cursor: not-allowed; }
.coco-mic:active { transform: scale(.94); }

/* 录音时的呼吸圈。只在外圈做，不遮住图标。 */
.coco-mic-wawe {
  position: absolute;
  inset: -1px;
  border-radius: 50%;
  pointer-events: none;
  opacity: 0;
}
.coco-mic.rec {
  color: #fff;
  border-color: transparent;
  background: #e0524b;
}
.coco-mic.rec .coco-mic-wawe {
  opacity: 1;
  border: 2px solid #e0524b;
  animation: coco-wawe 1.4s ease-out infinite;
}
@keyframes coco-wawe {
  0%   { transform: scale(1);   opacity: .55; }
  100% { transform: scale(1.75); opacity: 0; }
}

/* 转写中的占位文字，比普通 placeholder 更「活」一点 */
.coco-input textarea.ghost::placeholder { color: var(--accent-2); opacity: .85; }

/* ---- 球在听/在说 ---- */
/* 听话时：外圈跟着音量开合。--coco-level 由 JS 每 90ms 写一次。 */
.coco-dock.listening .coco-ring {
  opacity: 1;
  transform: scale(calc(1 + var(--coco-level, 0) * 0.22));
  border-color: #e0524b;
  border-width: 2px;
}
/* 说话时的嘴。
   为什么不用 .coco-ball::after：球为了裁高光加了 overflow:hidden，
   嘴一放大就被裁掉；而且伪元素在球里，位置只能靠猜。
   改成球外一个独立的小横条，位置用百分比跟着球走，改口径不用动 JS。 */
.coco-mouth {
  position: absolute;
  left: 50%;
  top: 70%;                 /* 眼睛在 50% 上下，嘴压在下面一点 */
  width: 6px;
  height: 1.6px;
  margin-left: -3px;
  border-radius: 2px;
  background: #262a35;
  opacity: 0;
  pointer-events: none;
  transform: scaleY(1);
  transition: opacity .18s;
}
.coco-dock.talking .coco-mouth {
  opacity: .92;
  animation: coco-mouth 430ms ease-in-out infinite;
}
/* 开口别太大：放大到 3 倍以上就成了一颗牙，不是嘴 */
@keyframes coco-mouth {
  0%, 100% { transform: scaleY(1); }
  50%      { transform: scaleY(2.1); }
}

@media (prefers-reduced-motion: reduce) {
  .coco-mic.rec .coco-mic-wawe,
  .coco-dock.talking .coco-ball::after { animation: none; }
}

@keyframes coco-msg-in {
  from { opacity: 0; transform: translateY(5px); }
  to   { opacity: 1; transform: none; }
}
@keyframes coco-dot {
  0%, 100% { opacity: .3; transform: translateY(0); }
  50%      { opacity: 1; transform: translateY(-3px); }
}

/* ---------- 窄屏 ---------- */
@media (max-width: 640px) {
  .coco-dock { right: 12px; bottom: 12px; }
  .coco-bot { width: 52px; height: 52px; }
  .coco-ball { left: 5px; top: 3px; width: 42px; height: 42px; }
  .coco-eye { width: 6px; height: 10px; }
  .coco-face { gap: 9.5px; }
  .coco-gloss { left: 8px; top: 5px; width: 17px; height: 11px; }
  .coco-shadow { left: 7px; width: 37px; }
  .coco-hint { right: 43px; }
  .coco-panel {
    width: calc(100vw - 28px);
    height: min(64vh, 520px);
    margin-bottom: 12px;
  }
}

/* 系统开了「减少动态效果」就收敛：不漂、不摇，只留转头 */
@media (prefers-reduced-motion: reduce) {
  .coco-float, .coco-shadow, .coco-bot.thinking .coco-ball, .coco-bot.poke .coco-ball { animation: none; }
  .coco-msg { animation: none; }
  .coco-dots i { animation: none; opacity: .6; }
}

/* 对话区的空白不再放任何引导文字。
   原来这里有一块「问我三件事 / 这页是干什么的 / …」，和下面的推荐按钮、
   再下面的输入框占位符说的是同一件事，三份文案挤在半屏里，非常吵。
   要提示怎么问，只留输入框的 placeholder —— 它就在光标位置，最省地方。 */
.coco-panel { position: relative; }


/* ==========================================================================
   设置页的分页
   --------------------------------------------------------------------------
   不是「标签页」而是「十一个抽屉」：每个抽屉上写着「这里面是什么」。
   小标（版本·运行状态·重启）不是装饰 —— 不写清楚，用户还是得一个个点开看。

   布局改成「左边一列抽屉、右边内容」。原来十一个抽屉横排挤一行，每格宽度
   不到 90px，小标全被省略号截断成「版本 · 运行状…」「整个搬到另一…」，
   等于把说明白写了又擦掉。竖排之后每格独享一整行，小标能整句显示；
   窄屏才退回横向一条可滑动的胶囊条。
   ========================================================================== */

.set-wrap {
  display: grid;
  grid-template-columns: minmax(172px, 206px) minmax(0, 1fr);
  gap: 18px;
  align-items: start;
}

.set-tabs {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 6px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 12px;
  /* 不用 sticky：设置页现在在一个自己会滚的容器里，sticky 的参照物变了，
     往上滚时导航会脱离底色飘到内容上（真机上截到过）。 */
  position: relative;
  align-self: start;
}

.set-tab {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1px;
  padding: 7px 10px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  font: inherit;
  color: var(--text-3);
  text-align: left;
  cursor: pointer;
  transition: background .16s ease, color .16s ease;
}
.set-tab b {
  font-size: 13px;
  font-weight: 500;
  letter-spacing: .2px;
}
.set-tab em {
  font-style: normal;
  font-size: 10.5px;
  line-height: 1.35;
  opacity: .72;
  white-space: normal;
}
.set-tab:hover { color: var(--text-2); background: var(--btn-bg-hover); }
.set-tab.active {
  color: #fff;
  background: var(--accent);
}
.set-tab.active em { opacity: .85; }
.set-tab:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.set-panel[hidden] { display: none; }
.set-panel > .card:last-child { margin-bottom: 0; }

@media (max-width: 860px) {
  .set-wrap { grid-template-columns: minmax(0, 1fr); gap: 12px; }
  .set-tabs {
    flex-direction: row;
    position: static;
    gap: 4px;
    overflow-x: auto;
    scrollbar-width: none;
  }
  .set-tabs::-webkit-scrollbar { display: none; }
  .set-tab { flex: 0 0 auto; padding: 7px 12px; }
  .set-tab em { display: none; }
}

/* ==========================================================================
   设置 · 告警分页

   渠道行是「左边信息、右边三个按钮」。按钮不能换行（换行后两行高度不一，
   两条渠道看起来像两套不同的东西），所以给了 flex-shrink:0。
   但在手机上这么干会反过来把左边挤死：URL 被压成「https://oapi.dingt /
   alk.com/…send」两截，一个字都读不出来。所以手机改成竖排 ——
   信息占满一行，按钮落到下一行右对齐。
   ========================================================================== */

.alert-ch {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 0;
  border-bottom: 1px solid var(--line);
  align-items: flex-start;
}

.alert-ch-main { min-width: 0; flex: 1 1 auto; }

.alert-ch-acts {
  display: flex;
  gap: 6px;
  flex-shrink: 0;
}

@media (max-width: 640px) {
  .alert-ch {
    flex-direction: column;
    align-items: stretch;
    gap: 10px;
  }
  .alert-ch-acts { justify-content: flex-end; }
  .alert-ch-acts .btn { flex: 1 1 auto; }
}

/* ==========================================================================
   设置 · 语音分页
   ========================================================================== */

.voice-now {
  padding: 14px 16px;
  border: 1px solid var(--accent-line);
  border-radius: 8px;
  background: var(--accent-dim);
}
.voice-now-k { font-size: 11px; color: var(--text-3); letter-spacing: .04em; }
.voice-now-v { margin-top: 3px; font-size: 17px; font-weight: 600; color: var(--accent-2); }
.voice-now-n { margin-top: 5px; font-size: 12.5px; line-height: 1.65; color: var(--text-2); }

/* 「听」那块不抢「说」的主角位置：同款盒子，但压成中性色、高度更矮。
   两块长得一模一样的话，一眼看不出哪个是结论、哪个是补充。 */
.voice-now.asr { padding: 11px 16px; border-color: var(--line); background: var(--panel-2); }
.voice-now.asr .voice-now-v { font-size: 14.5px; color: var(--text); }

@media (max-width: 420px) {
  .voice-now.asr .voice-now-n { font-size: 12px; }
}

/* 火山「听和说要分开开」的提示条。
   用警示色但不刺眼：它是提醒，不是报错 —— 用户此刻可能还没出错。 */
.voice-tip {
  margin-top: 8px;
  padding: 11px 14px;
  border: 1px solid var(--amber-line);
  border-left: 3px solid var(--amber);
  border-radius: 6px;
  background: var(--amber-dim);
  font-size: 12.5px;
  line-height: 1.75;
  color: var(--text-2);
}
.voice-tip b { color: var(--text); font-weight: 600; }
.voice-tip code {
  padding: 1px 5px;
  border-radius: 3px;
  background: var(--panel-2);
  border: 1px solid var(--line);
  font-size: 12px;
}

.voice-pick { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
.voice-opt {
  position: relative;
  display: block;
  /* 右侧多留 14px 给「已选中」的对钩 —— 三张卡都留，切换选中时才不会让文字横向跳一下 */
  padding: 10px 26px 10px 12px;
  border: 1px solid var(--line);
  border-radius: 7px;
  background: var(--panel);
  cursor: pointer;
  transition: border-color .15s, background .15s;
}
.voice-opt input { position: absolute; opacity: 0; pointer-events: none; width: 0; height: 0; }
.voice-opt b { display: block; font-size: 13px; font-weight: 600; }
.voice-opt em { display: block; margin-top: 3px; font-size: 11.5px; font-style: normal; line-height: 1.5; color: var(--text-3); }
.voice-opt:hover { border-color: var(--line-strong, var(--line)); }
.voice-opt.on {
  border-color: var(--accent-line);
  background: var(--accent-dim);
}
.voice-opt.on b { color: var(--accent-2); }
/* 键盘走到这里时也得看得见焦点 —— 输入框是 opacity:0 的，焦点环只能挂在卡片上 */
.voice-opt:focus-visible,
.voice-opt:focus-within { outline: 2px solid var(--accent-line); outline-offset: 2px; }

/* ---------- 语音页的分区 ----------
   这块原来是一根通到底的表单：结论、警告条、通道选择、云服务、
   密钥、语速、播放开关、按钮、浏览器音色……全在同一个流里，
   中间只靠 .field 的间距撑着。用户的原话是「挤在一起、没有层次、没有分区」。
   现在按「结论 / 选通道 / 各通道自己的设置 / 通用」切成 .vsec，
   每个 .vsec 有标题、有边框、有底色 —— 换了通道，只显示那一条的 .vsec。 */
.vsec {
  margin-top: 18px;
  padding: 14px 16px 16px;
  border: 1px solid var(--line);
  border-radius: 9px;
  background: var(--panel);
}
/* 「还差一门密钥」那块要站在通道卡片的下方、结论的上方。
   它在 DOM 里是插进 body 最前面的（这样不用改一长串模板），
   所以这里反过来用 order 把它调到第二位：
   ①「走哪条通道」 ②「你缺的东西」 ③「现在的结论」 ④ 各通道自己的设置 ⑤ 通用。
   顺序是有讲究的：用户刚点完通道，眼睛还在那一块上，
   缺什么就该紧挨着它出现，而不是跑到页面最上面或者最下面去。 */
#voice-body { display: flex; flex-direction: column; }
#voice-body > .vsec[data-sec="mode"] { order: 1; }
#voice-body > .v-cred-block,
#voice-body > .v-read-fail { order: 2; }
#voice-body > .vsec[data-sec="now"] { order: 3; }
#voice-body > .vsec[data-sec="cloud"],
#voice-body > .vsec[data-sec="native"] { order: 4; }
#voice-body > .vsec[data-sec="common"] { order: 5; }
#voice-body > .ai-scope { order: 6; }
/* 诊断折叠块排最后。它没有 .vsec 的 order，默认 0 会跑到最上面去 ——
   折叠状态下只有一行小字，混在「走哪条通道」前面很容易被当成标题点开。 */
#voice-body > .vdiag { order: 7; }
/* 用 order 调过之后，.vsec 自己的 margin-top:18px 会作用在「现在的结论」上，
   而它上面那块可能没有下边距 —— 统一交给 gap，别再让两个间距叠起来 */
#voice-body { gap: 14px; }
#voice-body > .vsec,
#voice-body > .v-cred-block,
#voice-body > .v-read-fail { margin-top: 0; }
.vsec:first-child { margin-top: 0; }
.vsec-head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 12px; }
.vsec-head h4 {
  margin: 0;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: .01em;
  color: var(--text);
}
.vsec-head em {
  font-style: normal;
  font-size: 11.5px;
  color: var(--text-3);
}
/* 选了「只用云服务」但云没配好时，借出来的那一格密钥。
   它替代了被收起来的整个「云服务」块，所以视觉上要比普通提示更实 ——
   用户此刻的任务就是填完这格，不该让它看起来像一句旁白。 */
.v-cred-block {
  margin: 0 0 14px;
  padding: 14px 16px 16px;
  border: 1px solid var(--accent-line);
  border-radius: 9px;
  background: var(--accent-dim);
}
.v-cred-head {
  font-size: 13px;
  font-weight: 650;
  color: var(--accent-2);
  margin-bottom: 6px;
}
.v-cred-block .hint { color: var(--text-2); font-size: 12px; line-height: 1.65; }
.v-cred-block .row { margin-top: 2px; }

/* 「选完会发生什么」那一行。放在通道卡片正下方，因为它们当下用的声音可能一样，
   真正不同的是「出故障时退不退回浏览器」—— 这件事只能说出来。
   左边一道竖线，跟选中的卡片呼应，让人一眼看到它是这张卡的注脚。 */
.vmode-say {
  margin-top: 10px;
  padding: 8px 12px;
  border-left: 2px solid var(--accent-line);
  border-radius: 0 6px 6px 0;
  background: var(--accent-dim);
  font-size: 12.5px;
  line-height: 1.65;
  color: var(--text-2);
}
/* 选了但做不到（云服务没配好）—— 黄底黄字，别让人以为已经生效了 */
.vmode-say.warn {
  border-left-color: var(--amber);
  background: var(--amber-dim);
  color: var(--amber);
}
/* 「刚换成 xx」—— 切通道时出现在那一行的最右边。
   没有它的时候，用户切完只会去看下面那几块，而那几块（自动 / 只用云服务，
   云端已配好时）正文几乎一样，于是判断成「点了没反应」。
   给个明确回执，比让人自己去找哪里变了要诚实。 */
.vmode-badge {
  margin-left: auto;
  flex: 0 0 auto;
  font-size: 11px;
  line-height: 1;
  padding: 4px 7px;
  border-radius: 5px;
  color: var(--accent);
  background: var(--accent-dim);
  border: 1px solid var(--accent-line);
  animation: vmode-badge 1.6s ease-out;
}
@keyframes vmode-badge {
  0%   { opacity: 0; transform: translateY(-3px); }
  18%  { opacity: 1; transform: none; }
  100% { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .vmode-badge { animation: none; }
}
/* 切通道时新冒出来的那块闪一下。它常常长在页面下方、屏幕外，
   不给记号的话「切到只用浏览器」在视野里就是什么都没发生。 */
.vsec-in {
  animation: vsec-in 1.4s ease-out;
}
@keyframes vsec-in {
  0%   { background: var(--accent-dim); border-color: var(--accent-line); }
  100% { background: var(--panel); border-color: var(--line); }
}
@media (prefers-reduced-motion: reduce) {
  .vsec-in { animation: none; }
}
/* 分区里的最后一项不要留一截多余的底 */
.vsec > .field:last-child,
.vsec > .row:last-child,
.vsec > details:last-child,
.vsec > label:last-child { margin-bottom: 0; }

/* 一组「现在是谁在说 / 谁在听」：合在一个盒子里，别做两个互相抢的卡片 */
.vnow { display: grid; gap: 0; }
.vnow-row { padding: 12px 14px; }
.vnow-row + .vnow-row { border-top: 1px solid var(--line); }
.vnow-k { font-size: 11px; color: var(--text-3); letter-spacing: .04em; }
.vnow-v { margin-top: 3px; font-size: 15px; font-weight: 600; color: var(--text); }
.vnow-v.main { font-size: 17px; color: var(--accent-2); }
.vnow-n { margin-top: 5px; font-size: 12.5px; line-height: 1.65; color: var(--text-2); }

/* 通道选项：选中时左侧加一条竖线，比只换底色更容易扫到 */
.voice-opt.on::before {
  content: '';
  position: absolute;
  left: 0; top: 8px; bottom: 8px;
  width: 2px;
  border-radius: 2px;
  background: var(--accent-2);
}

/* 右上角一个对钩：光靠边框换色 + 一条 2px 竖线，在低对比度屏幕 / 缩放 125% 的
   笔记本上几乎看不出来 —— 用户以为没选中，就会反复点，然后说「点不动」。
   对钩是最不需要分辨颜色也能看懂的那个记号。 */
.voice-opt.on::after {
  content: '';
  position: absolute;
  right: 11px; top: 13px;
  width: 8px; height: 4px;
  border-left: 1.8px solid var(--accent-2);
  border-bottom: 1.8px solid var(--accent-2);
  transform: rotate(-45deg);
}

.voice-cap {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 14px;
  font-size: 12.5px;
  color: var(--text-2);
}

@media (max-width: 720px) {
  .voice-pick { grid-template-columns: 1fr; }
  .vsec { padding: 12px 13px 14px; }
}

.voice-cap b { font-weight: 600; }
.voice-cap b.ok { color: #2f9e69; }
.voice-cap b.no { color: #d0554d; }

/* 设置页里「怎么拿密钥」这类说明：默认折叠，别把表单撑成一堵墙 */
.voice-help {
  margin: 0 0 14px;
  border: 1px solid var(--line);
  border-radius: 7px;
  background: var(--panel-2, rgba(127, 127, 127, .04));
}
.voice-help > summary {
  padding: 9px 12px;
  font-size: 12.5px;
  color: var(--text-2);
  cursor: pointer;
  list-style: none;
  user-select: none;
}
.voice-help > summary::-webkit-details-marker { display: none; }
.voice-help > summary::before {
  content: '▸';
  display: inline-block;
  width: 14px;
  color: var(--text-3);
  transition: transform .15s;
}
.voice-help[open] > summary::before { content: '▾'; }
.voice-help > summary:hover { color: var(--text-1); }
.voice-help ol {
  margin: 0;
  padding: 2px 16px 10px 34px;
  font-size: 12.5px;
  line-height: 1.8;
  color: var(--text-2);
}
.voice-help ol li { margin: 2px 0; }
.voice-help a { color: var(--accent-2); }
.voice-help .hint { padding: 0 14px 12px; }
.voice-help > .row, .voice-help > .field { padding: 0 12px; }
.voice-help > .field:last-child, .voice-help > .row:last-child { padding-bottom: 12px; }

/* 语速滑块：浏览器默认只有一百多像素，缩在左边一小截，看着像坏了 */
.field input[type='range'] {
  width: 100%;
  height: 22px;
  margin: 2px 0 0;
  accent-color: var(--accent);
  cursor: pointer;
}
.hint.vhint-ok { color: #2f9e69; }
.hint.vhint-bad { color: #d0554d; }

/* ==========================================================================
   发布前「老数据体检」
   --------------------------------------------------------------------------
   这一块的唯一任务是把一个**数字**送到用户眼前：加了这条限制，
   到底有多少行老数据不合格。所以数字是主角，字号最大、颜色最重。
   三种状态靠左侧粗竖条 + 结论标题区分，不依赖读完整段文字。
   ========================================================================== */

/* 「备份能不能恢复」—— 数据体检卡片的最后一块。
   单独一条框、左边一道色条：它回答的是和数据体检不同的问题
   （「数据答不答应」vs「出事时退得回去吗」），混在正文里会被当成附注。 */
.drill-box {
  margin-top: 14px;
  padding: 11px 13px;
  border: 1px solid var(--line);
  border-left: 3px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--bg-soft);
  font-size: 12.5px;
  line-height: 1.8;
}
.drill-box b { display: block; font-size: 12.5px; margin-bottom: 2px; }
.drill-box span { color: var(--text-2); }
.drill-box.ok { border-left-color: var(--green-line-strong); background: var(--green-dim); }
.drill-box.ok b { color: var(--green); }
.drill-box.warn { border-left-color: var(--amber); background: var(--amber-dim); }
.drill-box.warn b { color: var(--amber); }
.drill-box.bad { border-left-color: var(--red-line-strong); background: var(--red-dim); }
.drill-box.bad b { color: var(--red); }

.dc-card {
  margin-top: 16px;
  border: 1px solid var(--line);
  border-left: 3px solid var(--line);
  border-radius: 8px;
  background: var(--bg-soft);
  padding: 14px 16px 12px;
}
.dc-card.bad { border-left-color: var(--red); background: var(--red-dim); }
.dc-card.ok { border-left-color: var(--green); }
.dc-card.warn { border-left-color: var(--amber); background: var(--amber-dim); }

.dc-head {
  display: flex; align-items: center; gap: 8px;
  font-size: 13.5px; line-height: 1.5; color: var(--text);
}
.dc-head b { font-weight: 600; }
.dc-card.bad .dc-head b { color: var(--red); }
.dc-card.warn .dc-head b { color: var(--amber); }

.dc-dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--green); flex: none;
}
.dc-card.bad .dc-dot { background: var(--red); }
.dc-card.warn .dc-dot { background: var(--amber); }

/* 「只读」这个小标签：让人一眼知道这一整块不会动他的数据 */
.dc-tag {
  margin-left: auto;
  font-size: 10.5px; letter-spacing: .04em;
  padding: 1px 7px; border-radius: 20px;
  color: var(--text-3);
  border: 1px solid var(--line);
  background: var(--panel);
  flex: none;
}

.dc-sum {
  display: flex; align-items: baseline; gap: 6px;
  margin: 12px 0 4px;
  flex-wrap: wrap;
}
.dc-big {
  font-size: 30px; font-weight: 650; line-height: 1;
  font-variant-numeric: tabular-nums;
  color: var(--text); letter-spacing: -.02em;
}
.dc-card.bad .dc-big { color: var(--red); }
.dc-card.ok .dc-big { color: var(--green); }
.dc-sub { font-size: 12.5px; color: var(--text-2); }
.dc-sep { width: 1px; height: 12px; background: var(--line); margin: 0 6px; }
.dc-meta { font-size: 11.5px; color: var(--text-3); }

.dc-say {
  margin: 8px 0 0; font-size: 12.5px; line-height: 1.65; color: var(--text-2);
}
.dc-say.warn { color: var(--amber); }

.dc-sec {
  margin: 16px 0 7px;
  font-size: 11px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--text-3); font-weight: 600;
}

.dc-list { display: flex; flex-direction: column; gap: 6px; }

/* 每条核查一行，点开看 SQL。不用 <details> 默认的三角，自己画 */
.dc-row {
  border: 1px solid var(--line);
  border-radius: 7px;
  background: var(--panel);
  overflow: hidden;
}
.dc-row > summary {
  display: grid;
  /* 第三列（数字 / 文件名 / 徽章）给足空间，别让它为了省地方把前一列挤扁 */
  grid-template-columns: 16px minmax(0, 1fr) auto;
  align-items: center;
  gap: 10px;
  padding: 9px 12px;
  cursor: pointer;
  list-style: none;
  font-size: 12.5px;
  color: var(--text);
}
.dc-row > summary::-webkit-details-marker { display: none; }
.dc-row > summary:hover { background: var(--row-hover); }

.dc-ico { text-align: center; font-size: 12px; font-weight: 700; color: var(--text-3); }
.dc-row.bad .dc-ico { color: var(--red); }
.dc-row.ok .dc-ico { color: var(--green); }
.dc-row.warn .dc-ico { color: var(--amber); }

.dc-what { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dc-val { font-size: 12px; color: var(--text-2); white-space: nowrap; }
.dc-num {
  font-variant-numeric: tabular-nums;
  font-size: 13.5px; font-weight: 650; color: var(--red);
}
.dc-big.q { color: var(--amber); }
.dc-num.ok { color: var(--green); }
.dc-num.warn { color: var(--amber); }
/* ---------- 上线后对账 ---------- */
.rc-card.pending { border-left-color: var(--line-strong); }
.rc-list { display: flex; flex-direction: column; gap: 2px; }
.rc-line {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  padding: 4px 0; font-size: 12px; color: var(--text-2);
  border-bottom: 1px dashed var(--line-soft);
}
.rc-line:last-child { border-bottom: none; }
.rc-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rc-num { font-variant-numeric: tabular-nums; color: var(--text-3); }
/* 「发布前 → 现在」并排放，箭头弱化，差的那个数加粗上色 */
.rc-pair { font-variant-numeric: tabular-nums; color: var(--text-3); }
.rc-now { font-weight: 650; }
.rc-now.ok { color: var(--green); }
.rc-now.warn { color: var(--amber); }
.rc-now.bad { color: var(--red); }
.dc-big.ok { color: var(--green); }

/* 「待手查」不是数字 —— 不要套 .dc-num，否则排版上会冒充成「查到了几行」 */
.dc-todo {
  font-size: 12.5px; font-weight: 600; color: var(--amber);
}
.dc-file {
  font-family: var(--mono);
  font-size: 11px; color: var(--text-3);
}

.dc-body {
  padding: 0 12px 12px 38px;
  border-top: 1px solid var(--line-soft);
  padding-top: 10px;
}
.dc-why { margin: 0 0 8px; font-size: 12px; line-height: 1.7; color: var(--text-2); }
.dc-err { margin: 0 0 8px; font-size: 12px; line-height: 1.7; color: var(--red); }
.dc-sql-label { font-size: 11px; color: var(--text-3); margin: 6px 0 4px; }
.dc-sql {
  margin: 0;
  padding: 9px 11px;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 6px;
  font-family: var(--mono);
  font-size: 11.5px; line-height: 1.6;
  color: var(--text-2);
  white-space: pre-wrap;
  word-break: break-word;
  max-height: 220px;
  overflow: auto;
}

/* 窗口窄的时候，文件路径先让位，别把「数字」挤没 */
@media (max-width: 720px) {
  .dc-val .dc-file { display: none; }
  .dc-big { font-size: 26px; }
  .dc-body { padding-left: 12px; }
}

/* ==========================================================================
   分析页（第 5 期）
   数字、堆叠条、折线、排行，四个板块共用这套骨架。
   所有颜色走变量，浅色主题下自动跟着变。
   ========================================================================== */

.an-head {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 12px; margin-bottom: 16px;
}
.an-title {
  font-size: 17px; font-weight: 650; letter-spacing: -.2px;
  font-family: var(--mono, ui-monospace, monospace);
}
.an-sub { font-size: 12px; color: var(--text-3); margin-top: 3px; }
.an-hint { font-size: 11.5px; color: var(--text-3); font-weight: 400; }

/* ---- KPI 数字 ---- */
.an-kpis {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(118px, 1fr));
  gap: 10px; margin-bottom: 18px;
}
.an-kpis.three { grid-template-columns: repeat(3, 1fr); }
.an-kpi {
  background: var(--bg-soft); border: 1px solid var(--line-soft);
  border-radius: var(--r); padding: 12px 14px;
}
.an-kpi .v {
  font-size: 22px; font-weight: 660; letter-spacing: -.5px;
  line-height: 1.15; font-variant-numeric: tabular-nums; color: var(--text);
}
.an-kpi .v.ok { color: var(--green); }
.an-kpi .v.warn { color: var(--amber); }
.an-kpi .v.bad { color: var(--red); }
.an-kpi .l { font-size: 11.5px; color: var(--text-3); margin-top: 4px; line-height: 1.35; }

/* ---- 活跃分层：一条堆叠条 + 图例 ---- */
.an-stack {
  display: flex; height: 14px; border-radius: 7px; overflow: hidden;
  background: var(--bg-soft); gap: 2px;
}
.an-seg { height: 100%; transition: width .35s ease; min-width: 2px; }
.an-seg.green, .an-dot.green { background: var(--green); }
.an-seg.blue,  .an-dot.blue  { background: var(--blue); }
.an-seg.amber, .an-dot.amber { background: var(--amber); }
.an-seg.red,   .an-dot.red   { background: var(--red); }
.an-seg.gray,  .an-dot.gray  { background: var(--text-3); }
.an-seg.empty { background: var(--line); }

.an-legend {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 8px 16px; margin-top: 14px;
}
.an-leg {
  display: flex; align-items: center; gap: 7px;
  font-size: 12px; color: var(--text-2);
}
.an-dot { width: 8px; height: 8px; border-radius: 3px; flex: none; }
.an-leg-l { flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.an-leg-n { font-variant-numeric: tabular-nums; color: var(--text); font-weight: 600; }
.an-leg-p {
  font-variant-numeric: tabular-nums; color: var(--text-3);
  width: 44px; text-align: right; font-size: 11px;
}

/* ---- 折线 ---- */
.an-chart { width: 100%; height: 120px; display: block; margin-top: 4px; }
.an-area { fill: var(--accent-dim); stroke: none; }
.an-line {
  fill: none; stroke: var(--accent); stroke-width: 1.8;
  stroke-linejoin: round; stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}
.an-dots circle { fill: var(--accent); }
.an-range {
  display: flex; justify-content: space-between;
  font-size: 10.5px; color: var(--text-3); margin-top: 2px;
}

/* ---- 两列布局 ---- */
.an-grid {
  display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 16px;
}
.an-two {
  display: grid; grid-template-columns: 1fr 1fr; gap: 24px; margin-top: 6px;
}
.an-sub-t {
  font-size: 11.5px; color: var(--text-3); margin-bottom: 10px;
  text-transform: none; letter-spacing: .2px;
}

/* ---- 说明文字 ---- */
.an-note {
  font-size: 11.5px; color: var(--text-3); line-height: 1.6;
  margin-top: 12px; padding-top: 10px; border-top: 1px dashed var(--line-soft);
}

/* ---- 业务表 ---- */
.an-table { width: 100%; border-collapse: collapse; font-size: 12.5px; }
.an-table th {
  text-align: left; font-weight: 500; font-size: 11px; color: var(--text-3);
  padding: 9px 16px; border-bottom: 1px solid var(--line);
}
.an-table td { padding: 10px 16px; border-bottom: 1px solid var(--line-soft); }
.an-table tr:last-child td { border-bottom: none; }
.an-table td.num, .an-table th.num {
  text-align: right; font-variant-numeric: tabular-nums;
}
.an-table td.mono { font-family: var(--mono, ui-monospace, monospace); color: var(--text); }
.pill.hot {
  background: var(--green-dim); color: var(--green);
  font-size: 10.5px; padding: 2px 7px; border-radius: 20px;
}

/* ---- 算不出来的板块 ---- */
.an-missing { border-style: dashed; }
.an-empty-chart {
  padding: 22px 12px; text-align: center; color: var(--text-3);
  font-size: 12.5px; line-height: 1.6;
}
.an-empty-ico { font-size: 22px; color: var(--line); margin-bottom: 6px; }

@media (max-width: 900px) {
  .an-grid, .an-two { grid-template-columns: 1fr; }
  .an-two { gap: 18px; }
}

/* 留存这三格：算不出来的时候是「—」加原因，不是 0% */
.an-kpi .v.dim { color: var(--text-3); }
.an-kpi-why {
  font-size: 10.5px; color: var(--text-3); line-height: 1.45;
  margin-top: 6px; padding-top: 6px; border-top: 1px dashed var(--line-soft);
}

/* ---- 控制台板块里的两列排行 ----
   grid 的 1fr 默认 min-width 是 auto，不会小于内容宽度，
   放在半宽的列里就会撑破容器（表现是紫条越过卡片右边线）。
   这里三列全用 minmax(0, …) 压住。 */
.an-two > div { min-width: 0; }
.an-two .bar-row { grid-template-columns: minmax(0, 78px) minmax(0, 1fr) 42px; }
.an-grid .bar-row { grid-template-columns: minmax(0, 68px) minmax(0, 1fr) 42px; }

/* ── 选数据源：把「该选哪个」从一段说明文字里拎出来 ──
   起因：用户在 5 张长得一样的卡片里不知道点哪个 —— 页面上其实写了
   「带本项目的标记的优先选」，但那句话埋在 12.5px 的一整段灰字里，
   一眼扫过去只看见下面一长串「用这个」按钮。
   所以：把本项目的置顶加边、其余折叠、每个非本项目的库直接说清是什么。 */
.src-pick-tip {
  padding: 10px 13px; margin: 0 0 14px;
  font-size: 13px; line-height: 1.7;
  border-radius: var(--r-sm);
  background: var(--accent-dim);
  border-left: 3px solid var(--accent);
  color: var(--text);
}
.src-pick-tip.warn {
  background: var(--amber-dim);
  border-left-color: var(--amber);
}
.src-pick-tip b { font-weight: 600; }

/* 本项目的库：左加一条实线 + 底色抬高，跟下面那堆一眼分开 */
.src-card.src-mine {
  border-color: var(--accent-line);
  background: var(--panel-2);
  box-shadow: inset 3px 0 0 var(--accent);
}
.src-card.src-mine .src-title { font-size: 14px; }

/* 只有一个候选时轻微提一下，不抢戏，也不替用户点 */
.src-card.src-only { border-color: var(--accent); }
@keyframes pulseOnce {
  0%   { box-shadow: 0 0 0 0 var(--accent-glow, rgba(120,110,255,.45)); }
  70%  { box-shadow: 0 0 0 7px rgba(120,110,255,0); }
  100% { box-shadow: 0 0 0 0 rgba(120,110,255,0); }
}
.pulse-once { animation: pulseOnce 1.1s ease-out 2; }

/* 「控派自己的库」这类说明：不是错误，是提醒，别做成刺眼的红 */
.tag.src-notmine {
  background: var(--bg-soft);
  color: var(--text-dim);
  border: 1px solid var(--line-soft);
  font-weight: 400;
}

/* 其余数据库默认折叠 */
.src-others { margin-top: 10px; }
.src-others > summary {
  cursor: pointer;
  padding: 8px 2px;
  font-size: 12.5px;
  color: var(--text-dim);
  user-select: none;
  list-style: none;
}
.src-others > summary::-webkit-details-marker { display: none; }
.src-others > summary::before {
  content: '▸';
  display: inline-block;
  width: 14px;
  transition: transform .15s;
}
.src-others[open] > summary::before { transform: rotate(90deg); }
.src-others > summary:hover { color: var(--text); }
.src-others-body { padding-top: 8px; }

/* ---------- 接口令牌 ---------- */
/* 令牌明文那块：它是页面上唯一一个「关掉就没了」的东西，
   所以视觉上要和普通卡片区分开 —— 描边用警示色，等宽字体，可整段选中。 */
.token-pre {
  margin: 0;
  padding: 11px 13px;
  border-radius: var(--r-sm);
  background: var(--bg-soft);
  border: 1px solid var(--line);
  font-family: var(--mono);
  line-height: 1.6;
  color: var(--text);
  user-select: all;
}

/* ---------- 接入项目：先扫描，再手填 ---------- */
/* 这一步的手填字段（Compose 目录、主容器名）只有运维答得上来，
   而这两样东西服务器上本来就能扫出来。所以扫描必须排在表单前面 ——
   不是加个「高级」入口，是把它变成第一眼看到的东西。 */
.scan-tip {
  display: flex;
  gap: 16px;
  align-items: center;
  justify-content: space-between;
  padding: 13px 15px;
  margin-bottom: 18px;
  border-radius: var(--r-md);
  background: var(--accent-dim);
  border: 1px solid rgba(124, 92, 255, .26);
}
.scan-tip b { display: block; font-size: 13px; margin-bottom: 4px; }
.scan-tip .muted { font-size: 12px; line-height: 1.65; }
.scan-tip .btn { flex: none; }

/* 「或者手动填写」这条分隔线：让两段的关系一眼看得懂，
   不用读文字也知道下面是备选路径。 */
.ap-hand {
  display: flex;
  align-items: center;
  gap: 11px;
  margin: 2px 0 15px;
  font-size: 11.5px;
  color: var(--text-3);
}
.ap-hand::before,
.ap-hand::after { content: ''; height: 1px; background: var(--line); flex: 1; }

@media (max-width: 640px) {
  .scan-tip { flex-direction: column; align-items: stretch; }
  .scan-tip .btn { width: 100%; }
}

/* ---------- 从零建站 ---------- */
/* 同一件事的第二个岔路口：没东西可扫的人，从这里直接建一个。
   和上面那块扫描区故意长得不一样（描边而非填充色）——
   两块都用高亮色的话，用户会以为要二选一地读，其实它们是并列的两条路。 */
.bz-fork {
  display: flex;
  gap: 16px;
  align-items: center;
  justify-content: space-between;
  padding: 13px 15px;
  margin-bottom: 18px;
  border-radius: var(--r-md);
  background: var(--bg-2);
  border: 1px dashed var(--line-2);
}
.bz-fork b { display: block; font-size: 13px; margin-bottom: 4px; }
.bz-fork .muted { font-size: 12px; line-height: 1.65; }
.bz-fork .btn { flex: none; }

/* 模板选择：用按钮而不是下拉框。
   下拉框一次只能看见一个选项，用户得逐个点开才知道有哪几种；
   这些选项本来就不多，全部平铺出来让他一眼扫完更省事。 */
.bz-tpl-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}
.bz-tpl {
  text-align: left;
  padding: 12px 14px;
  border-radius: var(--r-md);
  border: 1px solid var(--line);
  background: var(--bg-1);
  cursor: pointer;
  font: inherit;
  color: inherit;
  transition: border-color .12s, background .12s;
}
.bz-tpl:hover { border-color: var(--line-2); }
.bz-tpl.on {
  border-color: var(--accent);
  background: var(--accent-dim);
  box-shadow: inset 0 0 0 1px var(--accent);
}
.bz-tpl-n { font-size: 13.5px; font-weight: 600; margin-bottom: 3px; }
.bz-tpl-d { font-size: 11.5px; line-height: 1.6; color: var(--text-3); }
.bz-tpl-tag {
  display: inline-block;
  margin-top: 6px;
  padding: 1px 7px;
  font-size: 10.5px;
  border-radius: 999px;
  background: var(--amber-dim);
  color: var(--amber);
}

/* 打不开时的排查清单。第一条（安全组）要明显重一些 ——
   并列排布会让人以为六条都要试一遍，实际上八成是第一条。 */
.bz-hints { display: flex; flex-direction: column; gap: 9px; }
.bz-hint {
  padding: 11px 13px;
  border-radius: var(--r-md);
  background: var(--bg-2);
  border: 1px solid var(--line);
}
.bz-hint.top { border-color: rgba(242, 85, 90, .32); background: rgba(242, 85, 90, .05); }
.bz-hint-t { font-size: 12.5px; font-weight: 600; margin-bottom: 4px; line-height: 1.5; }
.bz-hint-d { font-size: 12px; line-height: 1.7; color: var(--text-3); word-break: break-word; }

@media (max-width: 640px) {
  .bz-tpl-grid { grid-template-columns: 1fr; }
  .bz-fork { flex-direction: column; align-items: stretch; }
  .bz-fork .btn { width: 100%; }
}

/* 部署成功后折叠起来的「打不开怎么办」。
   做成折叠而不是直接展开：部署成功的人里，多数是真的能打开，
   展开会把「完成」的喜悦感冲淡。但它必须**看得见**（summary 一行），
   因为打不开的那部分人，需要在这一屏上看到它的存在。 */
.oc-reach {
  margin-top: 10px;
  border-radius: var(--r-md);
  border: 1px solid var(--line);
  background: var(--bg-2);
  padding: 10px 14px;
}
.oc-reach > summary {
  cursor: pointer;
  font-size: 12.5px;
  color: var(--text-2);
  list-style: none;
  user-select: none;
}
.oc-reach > summary::-webkit-details-marker { display: none; }
.oc-reach > summary::before { content: '▸ '; color: var(--text-3); }
.oc-reach[open] > summary::before { content: '▾ '; }
.oc-reach[open] > summary { margin-bottom: 4px; }

/* ---------- 术语小问号 ---------- */
/* 放在术语旁边的一个小圆点问号，点一下展开一句人话。
   刻意做得很小、很低对比：它要在界面里出现几十次，
   做得显眼就成了噪音；但它必须在每个出现行话的地方都在，
   否则用户碰到的还是「这个词我不认识，但不知道去哪查」。 */
.qt {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 14px;
  height: 14px;
  margin-left: 3px;
  border-radius: 50%;
  border: 1px solid var(--line-2);
  color: var(--text-3);
  font-size: 9.5px;
  line-height: 1;
  cursor: help;
  vertical-align: 1px;
  user-select: none;
  font-family: var(--mono);
  transition: color .12s, border-color .12s;
}
.qt:hover, .qt:focus-visible { color: var(--accent); border-color: var(--accent); outline: none; }
.qt-pop {
  margin: 8px 0 0;
  padding: 11px 14px;
  border-radius: var(--r-md);
  background: var(--bg-2);
  border-left: 2px solid var(--accent);
  font-size: 12.5px;
  line-height: 1.75;
  color: var(--text-2);
}
.qt-pop b { color: var(--text-1); }
.qt-pop .qt-plain { color: var(--accent); font-weight: 600; }

/* 术语表列表（设置页整页看） */
.gloss { display: grid; gap: 14px; }
.gloss-item { padding-bottom: 13px; border-bottom: 1px solid var(--line-soft); }
.gloss-item:last-child { border-bottom: none; padding-bottom: 0; }
.gloss-term { font-size: 13px; font-weight: 600; font-family: var(--mono); }
.gloss-plain { color: var(--accent); font-size: 12.5px; margin-top: 2px; }
.gloss-detail { color: var(--text-3); font-size: 12px; line-height: 1.75; margin-top: 5px; }

/* ==========================================================================
   滚动的归属（11-07）
   --------------------------------------------------------------------------
   症状：「侧边栏明明没撑满整页，往上滚它却跟着跑到上面去了」，双指下划时
   整个页面还会被拉出一条窄缝。

   根因：滚动条长在 <body> 上，而侧栏用的是 position: sticky。
   sticky 元素跟着**文档**一起滚，只是被 top:0「按」在视口顶边，所以它没撑满
   时露出来的那条底色会一路跟着跑；浏览器再把滚动交给 Safari 的橡皮筋，
   就露出背景色。真正该有的结构是：文档**永远不滚**，滚的是右半边。

   现在：侧栏 position: fixed（相对视口，压根不参与滚动），
        .main 自己 height:100vh + overflow-y:auto。
   两台设备都顾到：手机上地址栏会伸缩，.main 退回自然高度、由 body 滚
   （抽屉式侧栏是 fixed，本来就不会跑）。
   ========================================================================== */

/* 文档层：高度锁死、不许滚、不许回弹（滚的是 .main）。
   overscroll-behavior 写在 html 上对 Safari 才生效，写 body 是白写。 */
html, body {
  height: 100%;
  overflow: hidden;
  overscroll-behavior: none;
}

/* ==========================================================================
   滚动容器为什么不能用 flex 纵列（11-07 第二次翻车）
   --------------------------------------------------------------------------
   把 `.main` 设成 `display:flex; flex-direction:column` + `height:100vh` 之后，
   里面的 `.content`（flex:1）会被**压到容器高度**，而不是按内容长高。
   于是长页面不是在 `.main` 里滚，而是在 `.content` 自己的块里溢出，
   长出一条独立的滚动条；`.topbar` 的 sticky 参照物也跟着变，往上滚时
   顶部就露出一块没被顶栏盖住的内容 —— 截图里那条诡异的白色横带。

   结论：**滚动容器必须是普通块级（display:block）**，让内容按自然高度长，
   由它自己滚。`.content` 的 flex:1 在这种结构下无意义，故一并去掉依赖。
   ========================================================================== */

.shell > .main > .content { flex: 1 0 auto; }

/* ==========================================================================
   设置页：左侧导航钉住 + 三处留白统一 22px
   --------------------------------------------------------------------------
   导航「短条」要固定不滚（用户明确要求），所以用 position:fixed，参照视口。
   代价是它脱离布局流，正文必须自己让出位置 —— 让位量必须**恰好**等于
   「导航左边距 + 导航宽 + 中缝」，且都和 22px 对齐：

     · 侧栏右 → 导航左     22   （left: 216 + 22 = 238）
     · 导航右 → 卡片左     22
     · 卡片右 → 窗口右     22   （.content 的 padding 22，上限已去掉）

   历史坑（都在这一个数字上翻过三次车）：
     +18 → 中缝 18，肉眼差 4px；+40 → 中缝 44，差一倍。
   以后只改这一处：留白 = 22 + 206 + 22。
   ========================================================================== */
@media (min-width: 861px) {
  .set-tabs {
    position: fixed;
    /* 垂直方向也要对齐：卡片顶 = 顶栏高(52) + .content 上内边距(22) = 74。
       原来写 66（照旧顶栏高度估的），导航盒比卡片盒高出 8px —— 截图里
       左边灰框的上边缘从红线上方冒出来，右边卡片顶却贴着红线。
       改 74 后：导航盒顶 = 卡片顶 = 74，且首个抽屉的文字(74+6+7=87)
       也正好落在卡片标题(88)的同一行。 */
    top: 74px;
    left: 238px;
    z-index: 10;                 /* 低于顶栏(60)：往上滚时被顶栏盖住，不横穿标题 */
    width: 206px;
    max-height: calc(100vh - 96px);
    padding-right: 10px;
    scrollbar-width: thin;
    overflow-y: auto;
    overscroll-behavior: contain;
  }
  /* 留白 22 + 导航 206 + 中缝 22 = 250 */
  .set-wrap {
    /* grid-column-gap 也减掉：grid 的 padding 和 gap 会叠加，
       只写 padding 的话中缝 = 22(gap 之外的左余量) + 18(gap) = 40，
       实测中缝 44 —— 这就是「还是窄很多」的来源。 */
    padding-left: 228px;
    /* 网格显式改成单列：导航脱离流后，原来的两列第一列仍占 206px，
       正文会被压进第二列（只剩 100px，版本号竖排成一字一行）。 */
    grid-template-columns: minmax(0, 1fr);
  }
  .set-wrap > .set-body { min-width: 0; }
}

/* 顶栏必须压住一切会滚上来的内容。
   `.set-tabs` 是 fixed，在 DOM 里又排在顶栏后面 —— 往上滚时它盖住顶栏，
   「系统 / 版本·运行状态·重启」横穿「设置」标题（真机上截到过）。
   `.coco-dock` 是 `position:fixed; right:26px; bottom:22px`，它的**图层**铺满整行宽
   （容器不吃点击，但会画东西），一旦有子元素定位错了，灰字就会跑到顶栏里。
   两条一起收拾：顶栏抬到 60，导航和 coco 都在它下面。 */
/* 具体数值已经写回上面 .topbar / .coco-dock 的原声明处，这里只留结论：
   顶栏 60 > 设置页导航 10 > coco 50（coco 仍需 < 弹窗 100）。 */

/* ==========================================================================
   为什么所有外壳选择器都要带 .shell 前缀（11-07 第六翻车，最贵的一次）
   --------------------------------------------------------------------------
   `.main` 这个词产品里早就被用掉了 —— 语音页「现在的声音」那一行的值容器
   就叫 `class="vnow-v main"`。我写 `.main { height:100vh; overflow-y:auto }`
   的时候，它把那一行**撑成整整一屏高**，于是在「豆包语音…」下面出现一大片
   空白，页面还能继续滚两屏。用户连着截了三次图，我三次都在改别的地方。

   规矩：外壳这套类名（.main / .topbar / .content / .sidebar）太通用，
   一律写成 `.shell > .main`、`.shell > .main > .topbar` 这种带作用域的形式。
   判断标准：**只要这个类名可能在业务里复用，就必须限定父级**。
   ========================================================================== */

/* 设置页：右侧留白要和左侧一样宽（11-07 第七轮）。
   现象：`.content-inner` 有 `max-width:1180px; margin:0 auto`，窗口一宽
   （MacBook 1440 起）右边就多出一条比左边宽一倍的空白；左边那条
   22px 的缝（侧栏→内容）和右边那条 50px+（内容→窗口边）明显不等。
   做法：设置页这一屏不设上限、居中留白交给 .content 的内边距 ——
   于是「侧栏→内容」和「内容→窗口右边缘」都恰好是 .content 的 22px。
   ⚠ 只对设置页生效，其它页（列表/表格）保持原有 1180 上限，避免大屏行太长。 */
.shell > .main > .content > .content-inner:has(> .set-wrap) {
  max-width: none;
  /* 左右两边都对齐到 22px：外层已经有 padding:22px，这里再给 22px 就变 44px；
     所以右边只补 0，让「内容→窗口」正好等于「侧栏→内容」的 22px。 */
  margin: 0;
  padding: 0;
  width: auto;
}
