/* rosetta-claude 内置面板。无构建链、无依赖，改完刷新即生效。
 *
 * 设计对齐 zivv 后台（repos/zivv/frontend/src/index.css）：玄墨 + 品牌蓝点睛。
 * token 名与那边逐字一致 —— zivv 调色时能直接 diff 过来，别改名。
 *   primary = 近黑，承担按钮/选中态等「填充」角色；
 *   accent  = 品牌蓝，只做链接/强调词/数据高亮等「文字与点睛」角色；
 *   secondary = 墨青，承担 success/active 语义。
 * 只 port 面板实际用到的那些，不搬 zivv 全部 50 个 token。
 */

:root {
  --color-primary: #1a1c20;
  --color-on-primary: #ffffff;
  --color-primary-hover: #33373f;
  --color-accent: #2557e0;
  --color-on-accent: #ffffff;
  --color-secondary: #0d7d6b;
  --color-tertiary: #b45309;
  --color-tertiary-container: #b45309;
  --color-error: #ba1a1a;
  --color-error-hover: #9f1414;
  --color-on-error: #ffffff;
  --color-outline: #5b6068;
  --color-outline-variant: #cdd2da;
  --color-on-surface: #16181c;
  --color-on-surface-variant: #454a52;
  --color-surface: #f7f8fa;
  --color-surface-bright: #ffffff;
  --color-surface-dim: #d6dbe2;
  --color-surface-container-lowest: #ffffff;
  --color-surface-container-low: #f2f4f7;
  --color-surface-container: #eceff3;
  --color-surface-container-high: #e5e9ef;
  --color-surface-container-highest: #dfe4ea;

  /* 侧边栏在 zivv 是用 tailwind slate 调色板直接写死的，这里收成四个专用变量 */
  --sidebar-bg: #f2f4f6;
  --sidebar-fg: #334155;
  --sidebar-hover: #e0e3e5;
  --sidebar-active-bg: rgba(255, 255, 255, .5);

  /* 中文字体栈必须显式声明：Inter/Manrope 只含拉丁字符，缺省时部分 Windows
     浏览器会把中文回退到宋体（SimSun），小字号点阵渲染就是「糊」。 */
  --font-cjk: -apple-system, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei",
    "Noto Sans SC", sans-serif;
  --font-headline: "Manrope", var(--font-cjk);
  --font-body: "Inter", var(--font-cjk);
  --font-label: "Inter", var(--font-cjk);
  --font-mono: ui-monospace, "SFMono-Regular", Consolas, "Liberation Mono", monospace;

  --radius: 8px;
  --radius-lg: 12px;
  --radius-xl: 16px;

  /* ---- 字号阶梯 ----
   * 此前每处自己写 px，同一层级的东西在不同页签是 15/16/20 三种大小。
   * 收成八档，越界就说明那处该归到已有的某一档里，而不是再造一档。
   *
   * base(14) 比 body(13) 大不是笔误：base 是 <body> 的默认字号，给散文段落和
   * 侧栏这类"一行字不多、要读得舒服"的地方；body 是表格和表单里的密排正文，
   * 一屏要塞四十行，小一档换来的行高收益比那 1px 的可读性重要。
   *
   * display 只给英雄区一个数用；metric 是卡片里的数；label 是全部 uppercase 小标。
   * 图标（.ms）的尺寸不走这套 —— 字形的视觉重心和字母不是一回事，
   * 16px 的图标配 13px 的字才是齐的。 */
  --fs-display: 34px;
  --fs-metric: 22px;
  --fs-title: 16px;
  --fs-lead: 15px;
  --fs-base: 14px;
  --fs-body: 13px;
  --fs-sm: 12px;
  --fs-label: 11px;
  --fs-micro: 10px;

  /* 散文的最大行宽。一行超过 ~80 个字符，眼睛回到下一行时容易串行；
     面板的卡片宽度在 1512px 下能到 1160px，不封顶的话说明文字就是那么长一条。 */
  --measure: 76ch;

  /* ---- 间距阶梯 ---- 4 的倍数，卡片内外统一用它，别再写 18px/22px 这种独一份的值 */
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 20px;
  --sp-6: 24px;

  /* ---- 状态色（静音版）----
   * 语义色直接铺大面积会把页面变成红绿灯：账号页一屏 40 行，每行两三个状态徽标，
   * 全用 --color-error 这类饱和色，眼睛没法在里面找出真正要处理的那一条。
   * 这里给每个语义配一对：-fg 用于文字/描边（仍是原色，保证对比度），
   * -bg 是同色极低透明度的底（统一 10%，此前散落着 6/7/8/10/12% 五种写法）。 */
  --tone-ok-fg: var(--color-secondary);
  --tone-ok-bg: color-mix(in srgb, var(--color-secondary) 10%, transparent);
  --tone-warn-fg: var(--color-tertiary);
  --tone-warn-bg: color-mix(in srgb, var(--color-tertiary) 10%, transparent);
  --tone-err-fg: var(--color-error);
  --tone-err-bg: color-mix(in srgb, var(--color-error) 10%, transparent);
  --tone-info-fg: var(--color-accent);
  --tone-info-bg: color-mix(in srgb, var(--color-accent) 10%, transparent);
  --tone-mute-fg: var(--color-outline);
  --tone-mute-bg: color-mix(in srgb, var(--color-outline) 10%, transparent);

  /* ---- 英雄区渐变 ----
   * 深色带做主指标的底。必须走 token：写死一个深蓝（#263246 之类）在深色主题下
   * 会比页面底色还浅，整块浮起来，而且浅/深两套主题的文字对比度只能顾一头。 */
  --hero-a: #1c1f26;
  --hero-b: #2b3446;
  --hero-fg: #ffffff;
  --hero-fg-dim: rgba(255, 255, 255, .62);
  --hero-line: rgba(255, 255, 255, .14);
}

:root.dark {
  --color-primary: #e7eaf0;
  --color-on-primary: #16181c;
  --color-primary-hover: #ffffff;
  --color-accent: #6f9dff;
  --color-on-accent: #0c1430;
  --color-secondary: #6fd6c2;
  --color-tertiary: #ffb866;
  --color-tertiary-container: #ffb866;
  --color-error: #ffb4ab;
  --color-error-hover: #ffc9c2;
  --color-on-error: #690005;
  --color-outline: #8b909a;
  --color-outline-variant: #3a3f48;
  --color-on-surface: #dfe4ea;
  --color-on-surface-variant: #c2c7d0;
  --color-surface: #15181c;
  --color-surface-bright: #363a42;
  --color-surface-dim: #131519;
  --color-surface-container-lowest: #101216;
  --color-surface-container-low: #181b1e;
  --color-surface-container: #1b1e22;
  --color-surface-container-high: #262a31;
  --color-surface-container-highest: #2b2f36;

  --sidebar-bg: #0f172a;
  --sidebar-fg: #cbd5e1;
  --sidebar-hover: #1e293b;
  --sidebar-active-bg: rgba(30, 41, 59, .5);

  /* 深色下 tertiary 是亮橙，10% 的底在近黑背景上几乎看不见 —— 语义底统一提到 16%。
     -fg 不动：深色调色板里的语义色本来就是为深底挑的高亮版本。 */
  --tone-ok-bg: color-mix(in srgb, var(--color-secondary) 16%, transparent);
  --tone-warn-bg: color-mix(in srgb, var(--color-tertiary) 16%, transparent);
  --tone-err-bg: color-mix(in srgb, var(--color-error) 16%, transparent);
  --tone-info-bg: color-mix(in srgb, var(--color-accent) 16%, transparent);
  --tone-mute-bg: color-mix(in srgb, var(--color-outline) 16%, transparent);

  /* 深色下英雄区要比页面底色**更深**才不会浮起来（页面底 #15181c）。 */
  --hero-a: #0c0e12;
  --hero-b: #171d28;

  /* 让浏览器把滚动条、原生下拉、input 内的日历图标也切成深色。
     不声明的话，一屏深色里会嵌着几个亮白的原生控件。 */
  color-scheme: dark;
}

/* ---- 字体 ---- */

@font-face {
  font-family: 'Material Symbols Outlined';
  font-style: normal;
  font-weight: 100 700;
  /* block：字体加载期间图标位保持空白，而非闪现原始连字文本（dashboard→"da"） */
  font-display: block;
  src: url('./fonts/material-symbols-outlined.woff2') format('woff2');
}

@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('./fonts/inter-latin-400.woff2') format('woff2');
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('./fonts/inter-latin-500.woff2') format('woff2');
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('./fonts/inter-latin-600.woff2') format('woff2');
}
@font-face {
  font-family: 'Manrope';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('./fonts/manrope-latin-700.woff2') format('woff2');
}
@font-face {
  font-family: 'Manrope';
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url('./fonts/manrope-latin-800.woff2') format('woff2');
}

.ms {
  font-family: 'Material Symbols Outlined';
  font-weight: normal;
  font-style: normal;
  font-size: 20px;
  display: inline-block;
  line-height: 1;
  text-transform: none;
  letter-spacing: normal;
  word-wrap: normal;
  white-space: nowrap;
  direction: ltr;
  width: 1em;
  height: 1em;
  overflow: hidden;
  vertical-align: middle;
  flex: none;
  user-select: none;
}

.ms.fill { font-variation-settings: 'FILL' 1; }
.ms.sm { font-size: 18px; }

/* ---- 动画 ---- */

@keyframes toastIn {
  from { opacity: 0; transform: translateX(80px); }
  to { opacity: 1; transform: translateX(0); }
}
@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}
@keyframes dialogIn {
  from { opacity: 0; transform: scale(.95); }
  to { opacity: 1; transform: scale(1); }
}

/* ---- 基础 ---- */

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: var(--font-body);
  font-size: var(--fs-base);
  line-height: 1.5;
  background: var(--color-surface);
  color: var(--color-on-surface);
  -webkit-font-smoothing: antialiased;
}

.hidden { display: none !important; }

a { color: var(--color-accent); }

/* ---- 布局：侧边栏 + 顶栏 + 内容 ---- */

#app {
  min-height: 100vh;
}

aside {
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  width: 240px;
  z-index: 40;
  display: flex;
  flex-direction: column;
  background: var(--sidebar-bg);
  border-right: 1px solid var(--color-outline-variant);
  transition: width .2s ease;
  overflow: hidden;
}

#app.collapsed aside { width: 56px; }

.brand {
  display: flex;
  align-items: center;
  gap: 10px;
  height: 64px;
  padding: 0 12px;
  flex: none;
}

.brand .logo {
  width: 32px;
  height: 32px;
  flex: none;
  display: grid;
  place-items: center;
  border-radius: var(--radius);
  background: var(--color-primary);
  color: var(--color-on-primary);
}

.brand .name {
  font-family: var(--font-headline);
  font-weight: 800;
  font-size: var(--fs-lead);
  letter-spacing: -.01em;
  white-space: nowrap;
}

.brand .spacer { flex: 1; }

#app.collapsed .brand .name,
#app.collapsed .brand .spacer { display: none; }

/* 折叠后 #collapseBtn 必须留着 —— 它是唯一的开关，隐掉就再也展不开，
   只能去清 localStorage。折叠态里把 logo 和它上下排，56px 宽正好放得下。 */
#app.collapsed .brand {
  flex-direction: column;
  gap: 6px;
  height: auto;
  padding: 12px 0 8px;
}

nav {
  flex: 1;
  overflow-y: auto;
  padding: 4px 8px 12px;
}

.nav-group { margin-bottom: 14px; }

.nav-group > .glabel {
  padding: 0 12px 6px;
  font-family: var(--font-label);
  font-size: var(--fs-label);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .08em;
  color: var(--color-on-surface-variant);
  user-select: none;
}

/* 折叠态没有横向空间放组标题，换成一条分隔线（同 zivv） */
#app.collapsed .nav-group > .glabel {
  padding: 0;
  height: 1px;
  margin: 0 4px 8px;
  overflow: hidden;
  background: var(--color-outline-variant);
  color: transparent;
}

nav button {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 9px 12px;
  margin-bottom: 2px;
  border: none;
  border-right: 4px solid transparent;
  border-radius: var(--radius);
  background: none;
  cursor: pointer;
  font: inherit;
  font-size: var(--fs-body);
  font-weight: 500;
  letter-spacing: -.01em;
  color: var(--sidebar-fg);
  white-space: nowrap;
  text-align: left;
  transition: background-color .2s, color .2s;
}

nav button:hover {
  background: var(--sidebar-hover);
  color: var(--color-on-surface);
}

nav button.active {
  color: var(--color-accent);
  font-weight: 600;
  background: var(--sidebar-active-bg);
  border-right-color: var(--color-primary);
}

nav button.active .ms { font-variation-settings: 'FILL' 1; }

#app.collapsed nav button { justify-content: center; padding: 9px 0; }
#app.collapsed nav button > span:not(.ms) { display: none; }

.aside-foot {
  flex: none;
  padding: 10px 12px;
  border-top: 1px solid var(--color-outline-variant);
}

#app.collapsed .aside-foot { padding: 10px 4px; }
#app.collapsed .aside-foot .badge { width: 100%; justify-content: center; padding: 4px 0; }
#app.collapsed .aside-foot .badge span:not(.dot) { display: none; }

header {
  position: fixed;
  top: 0;
  left: 240px;
  right: 0;
  height: 64px;
  z-index: 30;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 24px;
  background: color-mix(in srgb, var(--color-surface) 80%, transparent);
  backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--color-outline-variant);
  transition: left .2s ease;
}

#app.collapsed header { left: 56px; }

header h1 {
  margin: 0;
  font-family: var(--font-headline);
  font-size: 18px;
  font-weight: 700;
  letter-spacing: -.02em;
}

header .spacer { flex: 1; }

/* 不设 max-width：配合 margin-left 又没有自动居中的话，多出来的宽度会全部
   堆在右侧变成一大块留白。zivv 的 <main> 同样没有宽度上限。 */
main {
  margin-left: 240px;
  padding: 88px 24px 48px;
  transition: margin-left .2s ease;
}

#app.collapsed main { margin-left: 56px; }

@media (max-width: 720px) {
  aside { width: 56px; }
  aside .brand .name, aside .brand .spacer, aside #collapseBtn { display: none; }
  aside .nav-group > .glabel {
    padding: 0; height: 1px; margin: 0 4px 8px; overflow: hidden;
    background: var(--color-outline-variant); color: transparent;
  }
  aside nav button { justify-content: center; padding: 9px 0; }
  aside nav button > span:not(.ms) { display: none; }
  header { left: 56px; padding: 0 12px; }
  main { margin-left: 56px; padding: 80px 12px 40px; }
  header h1 { font-size: var(--fs-title); }
}

/* ---- 卡片 ---- */

.card {
  background: var(--color-surface-container-lowest);
  border: 1px solid var(--color-outline-variant);
  border-radius: var(--radius-lg);
  margin-bottom: var(--sp-4);
  overflow: hidden;
}

/* 裸卡：只做分组容器，不画边框底色。总览的英雄区自己有底，再套一层白卡
   等于给深色带镶个白边框。 */
.card.bare {
  background: none;
  border: none;
  border-radius: 0;
  overflow: visible;
}

.card-hd {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-5) var(--sp-3);
}

.card-ti {
  margin: 0;
  font-family: var(--font-headline);
  font-size: var(--fs-title);
  font-weight: 700;
  letter-spacing: -.02em;
  line-height: 1.2;
}

/* max-width 是必须的：说明文字铺满 1500px 宽屏时一行 180 个字，读到行尾就找不到
   下一行的行首了。88ch 与策略页的 .sdesc 同口径。 */
.card-desc {
  margin: 3px 0 0;
  max-width: var(--measure);
  font-size: var(--fs-sm);
  line-height: 1.55;
  color: var(--color-outline);
}

.card-hd .spacer { flex: 1; }

/* 卡头右侧的单个读数。标题行右边本来就是空白，把这张卡的一句话结论放这儿，
   比再起一行小标题省一层纵向空间。 */
.hd-fig { flex: none; text-align: right; white-space: nowrap; }
.hd-fig > .label {
  font-family: var(--font-label);
  font-size: var(--fs-label);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .08em;
  color: var(--color-outline);
}
.hd-fig > .n-value { display: block; margin-top: 1px; }

.card-bd { padding: 0 var(--sp-5) var(--sp-5); }

@media (max-width: 720px) {
  .card-hd { padding: var(--sp-3) var(--sp-4) var(--sp-2); }
  .card-bd { padding: 0 var(--sp-4) var(--sp-4); }
}

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

/* 图标此前是一个 36px 的彩色方块，卡片 170px 宽扣掉内边距只剩 106px 给数字，
   长金额直接被省略号截掉。现在图标退成标签行里的一枚 14px 灰字形：
   它本来就只是"这张卡是讲什么的"的提示，不该跟数值抢地位。 */
.stats {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: var(--sp-2);
}

.stat-card {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--color-outline-variant);
  border-radius: var(--radius);
  background: var(--color-surface-container-lowest);
}

.stat-card .label {
  display: flex;
  align-items: center;
  gap: 5px;
  min-width: 0;
  font-family: var(--font-label);
  font-size: var(--fs-label);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .08em;
  color: var(--color-outline);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.stat-card .label .ms { font-size: 14px; flex: none; opacity: .75; }

.stat-card .value {
  font-family: var(--font-headline);
  font-size: var(--fs-metric);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  letter-spacing: -.03em;
  line-height: 1.1;
  /* 数字不该换行成两行（卡片高度会参差不齐），装不下就截断 —— 但 token 类数值
     已在 JS 侧缩写成 1.23M，正常不会走到这一步，这里只是兜底。 */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* 语义只落在数值和左边框上。此前是给图标方块换底色 —— 一屏九张卡里，
   有颜色的是那个装饰方块而不是"失败 46"这个数本身，看的人得先找图标再回读数字。 */
.stat-card.ok, .stat-card.warn, .stat-card.err { border-left-width: 3px; }
.stat-card.ok { border-left-color: var(--tone-ok-fg); }
.stat-card.ok .value { color: var(--tone-ok-fg); }
.stat-card.warn { border-left-color: var(--tone-warn-fg); }
.stat-card.warn .value { color: var(--tone-warn-fg); }
.stat-card.err { border-left-color: var(--tone-err-fg); }
.stat-card.err .value { color: var(--tone-err-fg); }

/* 可点的卡（账号页：点一下即按该状态筛选）。
   悬停只抬底色不动边框 —— 动边框会跟上面那条语义色边冲突。 */
.stat-card.clickable { cursor: pointer; transition: background .12s, box-shadow .12s; }
.stat-card.clickable:hover { background: var(--color-surface-container); }
/* 选中态：内描边 + 主色小标。此前只在标题后加一个 "·"，点完基本看不出变化。 */
.stat-card.on {
  background: var(--color-surface-container);
  box-shadow: inset 0 0 0 1px var(--color-primary);
}
.stat-card.on .label { color: var(--color-primary); }

/* ---- 时间线：label 左、值右、备注跟随 ----
 *
 * 为什么授权页的时间不做成 stat-card：卡片网格列宽 174px，扣掉 16px 内边距 ×2 和
 * 36px 图标 chip 只剩 92px，而 "2026-08-18 04:53" 需要 144px —— 实测降到 15px 字号
 * 仍被截断（need=144 / have=92）。这是宽度问题，改字号治不了。
 *
 * 换成 label 左 / 值右的两栏行，天然容得下；而且五个时间放在一起才看得出先后关系
 * （"最近尝试" 比 "最近成功续约" 新很多 = 续约正在连续失败），拆成五张卡反而看不出来。
 */
.tl {
  border: 1px solid var(--color-outline-variant);
  border-radius: var(--radius-lg);
  overflow: hidden;
}
.tl-row {
  display: flex;
  align-items: baseline;
  gap: 12px;
  padding: 11px 16px;
  background: var(--color-surface-container-lowest);
}
.tl-row + .tl-row { border-top: 1px solid var(--color-outline-variant); }
.tl-row.warn { background: color-mix(in srgb, var(--color-tertiary-container) 7%, transparent); }
.tl-row.err { background: color-mix(in srgb, var(--color-error) 7%, transparent); }
.tl-row.ok .tl-value { color: var(--color-secondary); }
.tl-row.warn .tl-value { color: var(--color-tertiary); }
.tl-row.err .tl-value { color: var(--color-error); }

.tl-label {
  flex: none;
  width: 110px;
  font-size: var(--fs-sm);
  color: var(--color-on-surface-variant);
}
.tl-value {
  font-family: var(--font-body);
  font-size: var(--fs-base);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--color-on-surface);
  white-space: nowrap;
}
.tl-note {
  font-size: var(--fs-sm);
  color: var(--color-outline);
  margin-left: auto;
  text-align: right;
}

@media (max-width: 560px) {
  .tl-row { flex-wrap: wrap; }
  .tl-label { width: 100%; }
  .tl-note { margin-left: 0; width: 100%; text-align: left; }
}

/* ---- 数字阶梯 ----
 *
 * 数字一律 tabular-nums：比例字距下 "1" 比 "8" 窄，一列金额上下对不齐，
 * 而且每 15 秒刷新一次的容量读数会在数值变化时左右抖。
 *
 * 字重刻意压到 500/600 而不是 800：大字号 + 特粗在 34px 上是「广告牌」观感，
 * 而且 Manrope 800 的中文回退字体没有对应字重，会被合成伪粗体，糊。
 * 大小本身已经足够表达层级，粗细留给语义（比如变化量）。 */
.n-display, .n-metric, .n-value {
  font-family: var(--font-headline);
  font-variant-numeric: tabular-nums;
  letter-spacing: -.03em;
  line-height: 1.05;
  white-space: nowrap;
}
.n-display { font-size: var(--fs-display); font-weight: 600; }
.n-metric { font-size: var(--fs-metric); font-weight: 600; }
.n-value { font-size: var(--fs-title); font-weight: 600; letter-spacing: -.02em; }

/* 数字后面跟的单位/分母，永远比数字小一档且灰 —— "480 / 800" 里的 "/ 800"
   跟主数字一样大的话，读者得先判断哪个才是答案。 */
.n-unit {
  font-family: var(--font-body);
  font-size: var(--fs-sm);
  font-weight: 500;
  letter-spacing: 0;
  color: var(--color-outline);
}

/* ---- 英雄区 ----
 *
 * 为什么整块深色：总览顶部要一眼分出「今天的运行状况」和「下面的明细表」。
 * 此前两者都是白底卡片、只有字号差别，扫视时是一片同质的灰白。
 *
 * 为什么环形图不用单段进度环：可用率一个数会把「冷却中（几分钟自愈）」和
 * 「已停用（要人去点）」抹成同一个"不可用"。处置动作完全不同的东西不能同色。
 */

.hero {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--sp-6);
  align-items: center;
  padding: var(--sp-6);
  border-radius: var(--radius-xl);
  background: linear-gradient(135deg, var(--hero-a) 0%, var(--hero-b) 100%);
  color: var(--hero-fg);
}

/* 环 + 中心读数。SVG 只画弧，中心数字用绝对定位的普通元素 —— <text> 没法用
   tabular-nums 之外的排版能力，也没法让"可用/总数"两行自然堆叠。 */
.ring { position: relative; flex: none; width: 132px; height: 132px; }
/* rotate(-90deg)：dasharray 从 3 点钟方向起画，转过来让 0 点落在 12 点钟方向。 */
.ring svg { display: block; width: 132px; height: 132px; transform: rotate(-90deg); }
.ring circle { fill: none; }
/* 底圈。号池全空时只剩它，此时它就是"一个号都没有"的视觉表达。 */
.ring .track { stroke: rgba(255, 255, 255, .09); }
/* butt 而非 round：round 会让每段两端各鼓出半个线宽，段间缝被吃掉，
   相邻两段看着是连在一起的一整条。 */
.ring .seg { stroke-linecap: butt; }
.ring > .mid {
  position: absolute;
  inset: 0;
  display: grid;
  place-content: center;
  text-align: center;
  line-height: 1.1;
}
.ring .mid-n {
  font-family: var(--font-headline);
  font-size: 30px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  letter-spacing: -.03em;
}
.ring .mid-l {
  margin-top: 2px;
  font-size: var(--fs-label);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--hero-fg-dim);
}

.hero-main { min-width: 0; }

/* 两个主数字并排。auto-fit + 220px：窄屏自动落成一列，不必写断点。 */
.hero-figs {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: var(--sp-5);
}

.fig { min-width: 0; }
.fig > .label {
  font-family: var(--font-label);
  font-size: var(--fs-label);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .1em;
  color: var(--hero-fg-dim);
  margin-bottom: var(--sp-2);
}
.fig > .n-display { display: flex; align-items: baseline; gap: var(--sp-2); }
.fig > .sub {
  margin-top: 6px;
  font-size: var(--fs-sm);
  color: var(--hero-fg-dim);
}
.fig > .sub .err { color: #ff9a92; font-weight: 600; }

/* 状态筹码。数量为 0 的**不渲染**（在 JS 侧过滤）：给"封禁 0"留一个槽位，
   等于每天都在提醒一件没发生的事，还占掉真正有值的那几项的横向空间。 */
.chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin-top: var(--sp-5);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--hero-line);
}
.chip-stat {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px 4px 8px;
  border-radius: 999px;
  background: rgba(255, 255, 255, .08);
  font-size: var(--fs-sm);
  color: var(--hero-fg-dim);
  white-space: nowrap;
}
.chip-stat > b {
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--hero-fg);
}
.chip-stat > i {
  width: 7px; height: 7px; border-radius: 50%; flex: none;
  background: currentColor;
}
.chip-stat.ok > i { background: #46d6b8; }
.chip-stat.warn > i { background: #ffbf6b; }
.chip-stat.err > i { background: #ff8a80; }
.chip-stat.mute > i { background: rgba(255, 255, 255, .45); }

@media (max-width: 720px) {
  .hero { grid-template-columns: 1fr; justify-items: start; padding: var(--sp-4); }
}

/* 环比徽标。up/down 是**语义**不是方向：调用方按「涨了是好是坏」传，
   所以费用涨了会拿到 down（红），请求涨了拿到 up（绿）。
   英雄区里在深底上，不能用浅色主题的那套前景色，故单独覆盖一份。 */
.delta {
  font-family: var(--font-label);
  font-size: var(--fs-sm);
  font-weight: 600;
  padding: 2px 7px;
  border-radius: 999px;
  letter-spacing: 0;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.delta.up { color: var(--tone-ok-fg); background: var(--tone-ok-bg); }
.delta.down { color: var(--tone-err-fg); background: var(--tone-err-bg); }
.delta.flat { color: var(--tone-mute-fg); background: var(--tone-mute-bg); }

.hero .delta.up { color: #46d6b8; background: rgba(70, 214, 184, .16); }
.hero .delta.down { color: #ff8a80; background: rgba(255, 138, 128, .16); }
.hero .delta.flat { color: var(--hero-fg-dim); background: rgba(255, 255, 255, .1); }

.sec-block { margin-top: var(--sp-5); }

.sec-hd {
  display: flex;
  align-items: baseline;
  gap: var(--sp-3);
  margin-bottom: var(--sp-3);
}
.sec-ti {
  font-family: var(--font-label);
  font-size: var(--fs-label);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .1em;
  color: var(--color-outline);
}
.sec-sum { font-size: var(--fs-body); color: var(--color-on-surface-variant); }
.sec-sum b {
  color: var(--color-on-surface);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* ---- 渐进披露 ----
 *
 * 总览此前把 16 个数字一次全铺出来，每一个都同等显眼 = 一个都不显眼。
 * 现在只有「还能接多少量」的四道闸常驻，其余（性能、累计用量、逐项释义）收进折叠。
 * 用原生 <details>：不写一行 JS，键盘可达，浏览器自带展开动画与 Ctrl+F 命中展开。
 */
.fold { margin-top: var(--sp-4); }
.fold > summary {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 2px;
  cursor: pointer;
  list-style: none;
  user-select: none;
  font-size: var(--fs-sm);
  color: var(--color-outline);
  transition: color .15s;
}
.fold > summary::-webkit-details-marker { display: none; }
.fold > summary:hover { color: var(--color-on-surface); }
.fold > summary .ms { font-size: 16px; transition: transform .2s; }
.fold[open] > summary .ms { transform: rotate(180deg); }
.fold > .fold-bd { padding-top: var(--sp-3); }

.mini-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: var(--sp-2);
}

/* 标签在上、数值在下（此前是左右对齐的一行）：左右布局下标签长度不一，
   数值的左边缘就参差不齐，四道闸的余量没法竖着扫。 */
.mini-stat {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--color-outline-variant);
  border-radius: var(--radius);
  background: var(--color-surface-container-lowest);
}
.mini-label {
  font-size: var(--fs-sm);
  color: var(--color-on-surface-variant);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.mini-value {
  font-family: var(--font-headline);
  font-size: var(--fs-metric);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  letter-spacing: -.03em;
  line-height: 1.1;
  color: var(--color-on-surface);
  white-space: nowrap;
}
.mini-value .n-unit { margin-left: 1px; }

/* 高水位配色。只染数值和左边框，不整块变色：一行里同时有标签和数值，
   整块染会盖掉标签的可读性。 */
.mini-stat.warn, .mini-stat.err { border-left-width: 3px; }
.mini-stat.warn {
  border-left-color: var(--tone-warn-fg);
  background: color-mix(in srgb, var(--color-tertiary) 6%, var(--color-surface-container-lowest));
}
.mini-stat.warn .mini-value { color: var(--tone-warn-fg); }
.mini-stat.err {
  border-left-color: var(--tone-err-fg);
  background: color-mix(in srgb, var(--color-error) 6%, var(--color-surface-container-lowest));
}
.mini-stat.err .mini-value { color: var(--tone-err-fg); }

/* ---- 表格 ---- */

/* 横向滚动 + 两侧滚动阴影。
   账号表 16 列在 1512px 下仍要横滚，而截断处没有任何提示时，看的人以为表就到这儿了。
   四层背景是无 JS 的标准做法：两层 local 渐变跟着内容滚（充当遮挡片），
   两层 scroll 阴影钉在容器边上 —— 滚到头时遮挡片正好盖住阴影，阴影自动消失。 */
.scroll {
  overflow-x: auto;
  /* 遮罩色必须是**卡片**底色而不是页面底色：.scroll 长在 .card 里，
     卡是 container-lowest（白），页面是 surface（#f7f8fa）。取错的后果在有数据时
     被表格盖住看不出来，一到空状态就露馅 —— 左右各一道 20px 的浅灰竖条。 */
  /* 遮罩是**实心**色块不是渐变：渐变到第 9 像素只剩五成不透明，压不住底下那道
     9px 的阴影，不滚动时左右各留一条淡灰竖条 —— 表格挡着看不见，空状态一眼就看见。
     实心块与卡片同色，盖在白底上没有边界；一旦真能横滚，它随内容左移，
     露出来的就是一道干净的 9px 阴影。 */
  background:
    linear-gradient(var(--color-surface-container-lowest), var(--color-surface-container-lowest)) left / 10px 100% no-repeat local,
    linear-gradient(var(--color-surface-container-lowest), var(--color-surface-container-lowest)) right / 10px 100% no-repeat local,
    radial-gradient(farthest-side at 0 50%, rgba(0, 0, 0, .13), transparent) left / 9px 100% no-repeat scroll,
    radial-gradient(farthest-side at 100% 50%, rgba(0, 0, 0, .13), transparent) right / 9px 100% no-repeat scroll;
}

/* 状态列：不可用原因是一句完整的话（"额度受限：5h 达 97.2%（门限 970‰）"），
   不封顶的话 auto 布局会为它留出 274px —— 一列吃掉四分之一屏，把右边的列全顶出去。
   截断后全文仍在 title 和展开行的「可用性」里。 */
td.cell-status { max-width: 180px; overflow: hidden; }

table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-body);
  text-align: left;
}

thead tr { border-bottom: 1px solid var(--color-outline-variant); }

/* 表头**不能**做 sticky：外层 .scroll 是 overflow-x:auto，按 CSS 规范 overflow-y
   随之计算成 auto，于是 sticky 会锚在这个不滚动的容器上而不是视口 —— 写了也不生效，
   还看不出哪里错。要 sticky 得先把横向滚动换成别的方案。 */
th {
  padding: var(--sp-2) var(--sp-3);
  background: var(--color-surface-container-low);
  font-family: var(--font-label);
  font-size: var(--fs-label);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .08em;
  color: var(--color-on-surface-variant);
  white-space: nowrap;
  vertical-align: middle;
}

th:first-child { border-top-left-radius: var(--radius); }
th:last-child { border-top-right-radius: var(--radius); }

/* 行高从 11px 内边距压到 8px。账号页的名称列会换行，把行撑到 81px，
   一屏只放得下 9 行 —— 密度问题的大头在换行不在内边距，见 .cell-name。 */
td {
  padding: var(--sp-2) var(--sp-3);
  border-bottom: 1px solid color-mix(in srgb, var(--color-outline-variant) 55%, transparent);
  vertical-align: middle;
}

tbody tr { transition: background-color .15s; }
tbody tr:hover { background: var(--color-surface-container-low); }
tbody tr:last-child td { border-bottom: none; }

td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }

/* 表格里的数字输入框（定价费率表）右对齐 + 等宽数字。
   左对齐时 "3" "15" "0.3" "18.75" 的小数点各在各的位置，
   一列价钱要挨个读才能比大小 —— 而这张表存在的意义就是横竖比大小。 */
td input[type="number"] { text-align: right; font-variant-numeric: tabular-nums; }

/* 名称列：邮箱这类长串一律单行截断 + title 兜全文。
   换行的代价是全表所有行都变高（行高由最高的那个单元格决定），
   为了极少数超长邮箱让整张表松一倍，不划算。 */
td.cell-name {
  max-width: 220px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* 两行式单元格（主值 + 灰色副行），副行不该继承 13px。
   nowrap 不能省：只写 overflow+ellipsis 而允许换行的话，文字会先折行再溢出，
   省略号永远不出现，副行照样把整行撑高 —— 等于这套截断白写。 */
td .sub-line {
  display: block;
  font-size: var(--fs-label);
  line-height: 1.35;
  color: var(--color-outline);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

td.acts { white-space: nowrap; text-align: right; }

/* 额度条那一列：条本身是固定 64px，但底下的「$449.04 / $500.00」副行按
   auto 布局能要到 160px+。封顶后副行自己省略，条不受影响。 */
td.cell-quota { width: 150px; max-width: 150px; }

/* 「默认值」文字：永久、不限、未设置这类。它们是配置的缺省态，
   既不是数据也不是警告 —— 用正文色会被当成真实取值去读，用语义色更糟。 */
.dim { color: var(--color-outline); }
/* 单个数值上语义色，不带底、不成徽标。给"还有 3 天到期"这种
   还没出事、但该提前看见的值用。 */
.tone-warn { color: var(--tone-warn-fg); }

/* 勾选列：固定窄宽，别让复选框把 ID 列挤到换行 */
td.chk, th.chk { width: 34px; padding-right: 0; }
td.chk input, th.chk input { cursor: pointer; margin: 0; vertical-align: middle; }

/* 可排序表头。整格可点，箭头常驻占位 —— 只在 hover 时才出现的话，
   列宽会在鼠标移过去的瞬间跳一下。 */
th.sortable { cursor: pointer; user-select: none; }
th.sortable:hover { color: var(--color-on-surface); }
th.sortable > .ms {
  font-size: var(--fs-base);
  vertical-align: -3px;
  margin-left: 2px;
  opacity: .35;
}
th.sortable.on > .ms { opacity: 1; color: var(--color-primary); }

/* 展开行：整行一格，内部自己排版 */
tr.detail > td { background: var(--color-surface-container-low); padding: 14px 18px; }
tr.detail dl {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 10px 20px;
  margin: 0;
}
tr.detail dt {
  font-family: var(--font-label);
  font-size: var(--fs-micro);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .08em;
  color: var(--color-outline);
  margin-bottom: 2px;
}
tr.detail dd { margin: 0 0 8px; font-size: var(--fs-sm); line-height: 1.5; word-break: break-word; }

/* ---- 工具条 / 批量条 / 分页 ---- */

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

.bulkbar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  margin-bottom: 12px;
  border: 1px solid var(--color-primary);
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--color-primary) 8%, transparent);
  font-size: var(--fs-body);
}
.bulkbar .spacer { flex: 1; }
.bulkbar > .ms { color: var(--color-primary); }

.pager {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 12px;
}
.pager .spacer { flex: 1; }

/* ---- 行菜单 ----
   必须 portal 到 body：表格外层是 .scroll { overflow-x: auto }，而 CSS 规范规定
   overflow-x 为非 visible 时 overflow-y 计算成 auto —— 菜单留在单元格里会被裁掉。
   位置由 JS 在点击回调里量好后写 left/top（fixed 定位，跟随视口）。 */
.row-menu {
  position: fixed;
  z-index: 60;
  min-width: 180px;
  padding: 6px;
  border: 1px solid var(--color-outline-variant);
  border-radius: var(--radius);
  background: var(--color-surface-container-lowest);
  box-shadow: 0 8px 24px rgba(0, 0, 0, .18);
}
.row-menu button {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 8px 10px;
  border: none;
  border-radius: 6px;
  background: none;
  color: var(--color-on-surface);
  font-family: inherit;
  font-size: var(--fs-body);
  text-align: left;
  cursor: pointer;
}
.row-menu button:hover { background: var(--color-surface-container-high); }
.row-menu button.del { color: var(--color-error); }
.row-menu button > .ms { font-size: 18px; color: var(--color-outline); }
.row-menu button.del > .ms { color: var(--color-error); }
.row-menu hr {
  height: 1px;
  margin: 5px 4px;
  border: none;
  background: var(--color-outline-variant);
}

/* 行菜单的外部点击遮罩。透明但铺满，接住菜单外的任何一次点击 */
.menu-veil { position: fixed; inset: 0; z-index: 59; }

/* ---- 额度窗口条 ----
   在利用率条上画一根账号级门限刻度线。没有它就看不出"这号 82% 就停了"是
   因为账号自己设了 850‰ 而不是全局的 970‰。
   刻度线不能放进 .bar —— 那里有 overflow:hidden 会把它裁掉，故外面套一层。 */
.winbar { position: relative; display: inline-block; vertical-align: middle; line-height: 0; }
.winbar > .tick {
  position: absolute;
  top: -3px;
  height: 11px;
  width: 2px;
  background: var(--color-on-surface);
  opacity: .5;
  pointer-events: none;
}

/* 额度条 + 百分比，强制同一行。gap 由 flex 给，不靠标签间的空白 ——
   空白是可断点，列一窄就在那儿折行。 */
.quota-cell {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
}

.empty {
  padding: 40px 16px !important;
  text-align: center;
  color: var(--color-outline);
}
/* 限宽只给 div：空状态里带一整句"为什么空 + 怎么办"的都是 div（图表、图示），
   不限就在宽屏上拉成 940px 一行，而居中文本最怕长行 —— 每次回扫都得重新找行首。
   表格里的 .empty 是 td[colspan]，文案都是「暂无数据」这类短句本就不需要限宽，
   而给 colspan 单元格加 max-width 会被表格布局算进列宽，反过来把表头挤窄。 */
div.empty { max-width: var(--measure); margin-inline: auto; }
.empty .ms { font-size: 32px; display: block; margin: 0 auto 8px; opacity: .5; }

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

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  height: 36px;
  padding: 0 16px;
  border: 1px solid var(--color-outline-variant);
  border-radius: var(--radius);
  background: var(--color-surface-container-lowest);
  color: var(--color-on-surface);
  font: inherit;
  font-size: var(--fs-body);
  font-weight: 700;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color .15s, color .15s, border-color .15s;
}

.btn:hover { background: var(--color-surface-container-high); }
.btn:active { transform: scale(.96); }
.btn:focus-visible {
  outline: 2px solid color-mix(in srgb, var(--color-primary) 50%, transparent);
  outline-offset: 1px;
}
.btn:disabled { opacity: .5; pointer-events: none; }

.btn.primary {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: var(--color-on-primary);
}
.btn.primary:hover { background: var(--color-primary-hover); border-color: var(--color-primary-hover); }

.btn.secondary {
  background: var(--color-surface-container-high);
  border-color: var(--color-outline-variant);
}
.btn.secondary:hover { background: var(--color-surface-dim); }

.btn.ghost {
  background: none;
  border-color: transparent;
  color: var(--color-accent);
}
.btn.ghost:hover { background: color-mix(in srgb, var(--color-primary) 8%, transparent); }

.btn.danger {
  background: var(--color-error);
  border-color: var(--color-error);
  color: var(--color-on-error);
}
.btn.danger:hover { background: var(--color-error-hover); border-color: var(--color-error-hover); }

.btn.sm { height: 30px; padding: 0 10px; font-size: var(--fs-sm); border-radius: 6px; }

.btn.icon { width: 36px; padding: 0; }
.btn.icon.sm { width: 30px; }

/* 表格操作列：图标按钮，无边框，hover 才显形 —— 账号表 13 列，
   文字按钮撑得横向滚不到头。 */
.btn.act {
  width: 28px; height: 28px; padding: 0;
  border-color: transparent; background: none;
  color: var(--color-on-surface-variant);
  border-radius: 6px;
}
.btn.act:hover { background: var(--color-surface-container-high); color: var(--color-on-surface); }
.btn.act.del:hover { color: var(--color-error); }

/* ---- 表单控件 ---- */

input, select, textarea {
  font-family: inherit;
  font-size: var(--fs-body);
  color: var(--color-on-surface);
  background: var(--color-surface-container-lowest);
  border: 1px solid var(--color-outline-variant);
  border-radius: var(--radius);
  padding: 7px 10px;
  transition: border-color .15s;
}

input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--color-accent);
}

input[readonly] { background: var(--color-surface-container-low); color: var(--color-on-surface-variant); }

textarea {
  width: 100%;
  resize: vertical;
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  line-height: 1.6;
}

.row { display: flex; gap: 10px; flex-wrap: wrap; align-items: flex-end; }

.field { display: flex; flex-direction: column; gap: 4px; }
.field.wide { flex: 1; min-width: 220px; }
.field > label {
  font-family: var(--font-label);
  font-size: var(--fs-label);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--color-outline);
}
.field input, .field select { min-width: 140px; }
.field.wide input { width: 100%; min-width: 0; }
.field .fhint { font-size: var(--fs-label); color: var(--color-outline); line-height: 1.4; }

.hint {
  color: var(--color-outline);
  font-size: var(--fs-sm);
  margin: 8px 0 0;
  line-height: 1.55;
}
.hint.warn { color: var(--color-tertiary); }

/* 卡片正文里的整段说明才封行宽。
   只挑 .card-bd 的直接子 <p>：.hint 还兼着分页信息、按钮旁的一句话这些
   行内角色（.locate .hint 甚至是 flex 项），给它们封宽会把布局挤坏。 */
.card-bd > p.hint, .card-bd > .banner > div { max-width: var(--measure); }

/* banner 图表/表格上方的整条提示。.hint 是脚注（小、灰、可忽略），banner 是
   「先读这个再看下面的数」—— 量级不同，不能靠 .hint 凑。 */
.banner {
  display: flex; gap: 10px; align-items: flex-start;
  padding: 10px 12px; border-radius: 8px;
  border-left: 3px solid var(--color-outline);
  background: var(--color-surface-container-lowest);
  font-size: var(--fs-sm); line-height: 1.55;
}
.banner .ms { font-size: 18px; flex: none; }
.banner.warn {
  border-left-color: var(--color-tertiary-container);
  background: color-mix(in srgb, var(--color-tertiary-container) 8%, var(--color-surface-container-lowest));
}
.banner.warn .ms { color: var(--color-tertiary-container); }

/* 一串 ID + 尾随的复制按钮。
   不加这层 flex 的话，36 字符的 UUID 撑满窄格子，复制按钮被挤到下一行单独杵着。
   min-width:0 让 ID 自己按字符断，按钮才留得住位置。 */
.idline { display: flex; align-items: flex-start; gap: 2px; }
.idline > .mono { min-width: 0; word-break: break-all; }

/* ---- 定位块（请求日志）----
   跟折叠的筛选条件视觉上要分开：这里是"把那一条找出来"，下面是"筛一批出来看"。
   浅一档的底 + 细边框，比再加一张卡省一层嵌套。

   两种定位（按 ID / 按时刻）同处一块并用分隔线断开，不做成两个并排的方块 ——
   两个同样大小、同样底色的框摆在一起，第一眼读到的是"这张卡重复了一遍"。 */
.locate {
  padding: var(--sp-2) var(--sp-4);
  margin-bottom: var(--sp-3);
  border: 1px solid var(--color-outline-variant);
  border-radius: var(--radius);
  background: var(--color-surface-container-lowest);
}
.locate > .row { padding: var(--sp-2) 0; }
.locate > .row + .row { border-top: 1px dashed var(--color-outline-variant); }
.locate .field.wide { max-width: 360px; }
/* 说明文字跟按钮同排收尾。flex-end 会把它按基线顶到底，减 4px 让它和按钮的
   文字中线大致齐平；窄屏 .row 换行时它自己落到下一行，不用另写断点。 */
.locate .hint { flex: 1; min-width: 220px; margin: 0 0 4px; }

/* ---- 分组表单（策略页） ---- */

.form-section + .form-section {
  margin-top: 22px;
  padding-top: 20px;
  border-top: 1px solid var(--color-outline-variant);
}

.form-section > h3 {
  margin: 0 0 4px;
  font-family: var(--font-headline);
  font-size: var(--fs-lead);
  font-weight: 700;
  letter-spacing: -.01em;
}

.form-section > .sdesc {
  margin: 0 0 14px;
  max-width: var(--measure);
  color: var(--color-on-surface-variant);
  font-size: var(--fs-sm);
  line-height: 1.6;
}
.form-section > .sdesc b { color: var(--color-on-surface); font-weight: 600; }
/* 前置条件型的警告走 banner，紧跟在说明下面、与说明同宽。 */
.form-section > .banner { margin: 0 0 14px; max-width: var(--measure); }

.form-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 14px 18px;
}

/* 策略页几乎全是「6」「970」「90000」这种三五位的数，
   而输入框此前跟着网格拉到 375px：数字孤零零飘在左端、ms/‰ 后缀贴在四十个像素
   之外的右端，底下那行解释反倒被挤成三行。
   把框收到刚够放下六位数，解释就能用满整列宽 —— 这页真正要读的是解释。
   非数值字段（.field.wide、下拉、文本）不受影响。 */
.form-grid .field > input[type="number"],
.form-grid .field > .input-unit { max-width: 170px; }
.form-grid .field > .fhint { max-width: 100%; }

/* 单位后缀贴在输入框右侧内部，别另占一行把网格撑乱 */
.input-unit { position: relative; display: flex; }
.input-unit > input { width: 100%; min-width: 0; padding-right: 46px; }
.input-unit > .unit {
  position: absolute;
  right: 10px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--color-outline);
  font-size: var(--fs-label);
  pointer-events: none;
}

code, .mono { font-family: var(--font-mono); font-size: var(--fs-sm); }
code {
  padding: 1px 5px;
  border-radius: 4px;
  background: var(--color-surface-container);
  /* 一段带底色的 code 被折成两半，后半截会顶着行首再来一块底色，
     读起来像是两个不同的标识符（claude-sonnet-4- / 5）。 */
  white-space: nowrap;
}

/* ---- 日志筛选折叠 ---- */

.filters {
  border: 1px solid var(--color-outline-variant);
  border-radius: var(--radius-lg);
  background: var(--color-surface-container-low);
}

.filters > summary {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 14px;
  cursor: pointer;
  font-size: var(--fs-sm);
  color: var(--color-on-surface-variant);
  list-style: none;
  user-select: none;
}
.filters > summary::-webkit-details-marker { display: none; }
.filters > summary .ms { transition: transform .2s; }
.filters[open] > summary .ms.chev { transform: rotate(180deg); }
.filters[open] > summary { border-bottom: 1px solid var(--color-outline-variant); }
.filters .fbody { padding: 14px; }

.shortcuts { display: flex; gap: 6px; flex-wrap: wrap; }

/* ---- 徽标 / 状态灯 ---- */

/* 字重从 700 降到 600、去掉 uppercase 之外的加压：账号表一行能挂三四个徽标，
   全部特粗大写时整行都在喊，反而读不出哪个是异常。 */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 8px;
  border-radius: 999px;
  font-family: var(--font-label);
  font-size: var(--fs-label);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .06em;
  white-space: nowrap;
  background: var(--tone-info-bg);
  color: var(--tone-info-fg);
}

.badge > .dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex: none; }

.badge.ok { background: var(--tone-ok-bg); color: var(--tone-ok-fg); }
.badge.warn { background: var(--tone-warn-bg); color: var(--tone-warn-fg); }
.badge.err { background: var(--tone-err-bg); color: var(--tone-err-fg); }
.badge.mute { background: var(--tone-mute-bg); color: var(--tone-mute-fg); }

/* 正常态的徽标（"ok"、"200"）在一屏几十行里是绝大多数，给它们上色等于给背景上色。
   降到纯灰描边，颜色留给真正要看的异常行。 */
.badge.quiet {
  background: none;
  color: var(--color-on-surface-variant);
  box-shadow: inset 0 0 0 1px var(--color-outline-variant);
}
.badge.quiet > .dot { background: var(--tone-ok-fg); }
/* quiet 的点默认是绿（正常态），配 warn/err 时只换点的颜色、不动底色 ——
   「需要注意但不是故障」这一档需要一个比整块染色轻、又比纯灰重的表达。 */
.badge.quiet.warn { background: none; color: var(--color-on-surface-variant); }
.badge.quiet.warn > .dot { background: var(--tone-warn-fg); }
.badge.quiet.err { background: none; color: var(--color-on-surface-variant); }
.badge.quiet.err > .dot { background: var(--tone-err-fg); }

/* 额度条 */
.bar {
  width: 64px; height: 5px;
  background: var(--color-surface-container-high);
  border-radius: 3px; overflow: hidden;
  display: inline-block; vertical-align: middle;
}
.bar > i { display: block; height: 100%; background: var(--color-secondary); }
.bar > i.warn { background: var(--tone-warn-fg); }
.bar > i.err { background: var(--tone-err-fg); }

/* 进度条 */
.progress {
  height: 6px;
  background: var(--color-surface-container-high);
  border-radius: 3px;
  overflow: hidden;
  margin: 10px 0;
}
.progress > i { display: block; height: 100%; background: var(--color-accent); transition: width .3s; }

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

#login {
  max-width: 400px;
  margin: 12vh auto;
  padding: 0 16px;
  animation: fadeIn .2s ease-out;
}

#login .card { margin: 0; }

#login .lock {
  width: 44px; height: 44px;
  display: grid; place-items: center;
  border-radius: var(--radius-lg);
  background: var(--color-primary);
  color: var(--color-on-primary);
  margin-bottom: 14px;
}
#login .lock .ms { font-size: 24px; }

/* ---- 对话框 ---- */

dialog {
  padding: 0;
  border: none;
  border-radius: var(--radius-xl);
  background: var(--color-surface-container-lowest);
  color: var(--color-on-surface);
  box-shadow: 0 12px 32px rgba(25, 28, 30, .18);
  outline: 1px solid color-mix(in srgb, var(--color-outline-variant) 60%, transparent);
  width: min(560px, calc(100vw - 32px));
  max-height: 85vh;
}

dialog[open] { animation: dialogIn .15s ease-out; }
dialog::backdrop { background: rgba(0, 0, 0, .4); backdrop-filter: blur(2px); }

/* 批量导入这类「一屏 textarea + 结果表」的对话框需要更宽，
   560px 下结果表的地址列会被挤到换行，一屏看不下几条。 */
dialog.wide-dlg { width: min(820px, calc(100vw - 32px)); }

dialog h3 {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  padding: 18px 22px;
  font-family: var(--font-headline);
  font-size: var(--fs-title);
  font-weight: 700;
  letter-spacing: -.02em;
  border-bottom: 1px solid color-mix(in srgb, var(--color-outline-variant) 60%, transparent);
}
dialog h3 .spacer { flex: 1; }
dialog h3 .btn.act { color: var(--color-outline); }

.dlg-bd { padding: 18px 22px; overflow-y: auto; max-height: calc(85vh - 130px); }
.dlg-bd > .row + .row { margin-top: 12px; }

/* 对话框表单网格：.row 是 flex-end 底对齐，一列带 .fhint 另一列不带时控件会上下错位。
   网格顶对齐，控件统一 36px 与 .btn 同高。 */
.dlg-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-4) var(--sp-4);
  align-items: start;
}
.dlg-grid .span-2 { grid-column: 1 / -1; }
.dlg-grid .field input:not([type=checkbox]), .dlg-grid .field select { width: 100%; min-width: 0; }
.dlg-grid input:not([type=checkbox]), .dlg-grid select, .dlg-sec input, .input-group .btn { height: 36px; }
.field > label.chk-line {
  align-self: flex-start;
  text-transform: none; letter-spacing: 0; font-weight: 400;
  font-size: var(--fs-sm); color: var(--color-on-surface);
}
.field > label.chk-line > input { min-width: 0; width: auto; margin: 0; }
.field .fhint:empty { display: none; }
@media (max-width: 520px) {
  .dlg-grid { grid-template-columns: minmax(0, 1fr); }
}

/* 对话框里表单下方的独立分区（建号操作区） */
.dlg-sec {
  margin-top: var(--sp-5);
  padding-top: var(--sp-5);
  border-top: 1px dashed var(--color-outline-variant);
}
.dlg-sec .field input { width: 100%; min-width: 0; }
.sec-act { display: flex; justify-content: flex-end; margin-top: var(--sp-3); }

.input-group { display: flex; gap: var(--sp-2); margin-top: var(--sp-2); }
.input-group input { flex: 1; min-width: 0; }
.input-group input.mono { font-size: var(--fs-sm); }

/* 分步引导：编号圆点 + 标题 + 内容，未解锁的步骤整体置灰不可点 */
.steps { display: flex; flex-direction: column; gap: var(--sp-5); }
.step-group { display: flex; flex-direction: column; gap: var(--sp-5); transition: opacity .15s; }
.step-group.off { opacity: .45; pointer-events: none; }
.step { display: grid; grid-template-columns: 24px minmax(0, 1fr); gap: var(--sp-3); }
.step-no {
  display: grid; place-items: center;
  width: 24px; height: 24px; border-radius: 50%;
  background: var(--color-primary); color: var(--color-on-primary);
  font-size: var(--fs-sm); font-weight: 700;
}
.step-group.off .step-no { background: var(--color-surface-container-high); color: var(--color-on-surface-variant); }
.step-title { font-size: var(--fs-body); font-weight: 600; line-height: 24px; }
.step-bd > .hint { margin: 2px 0 var(--sp-2); }
.step-bd > .hint:empty { display: none; }
.step-bd > .btn { margin-top: var(--sp-1); }

.dlg-ft {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  padding: 14px 22px;
  border-top: 1px solid color-mix(in srgb, var(--color-outline-variant) 60%, transparent);
}

/* ---- toast ---- */

#toast {
  position: fixed;
  right: 24px;
  bottom: 24px;
  z-index: 9999;
  display: none;
  align-items: flex-start;
  gap: 10px;
  max-width: 460px;
  min-width: 260px;
  padding: 12px 16px;
  border-radius: var(--radius);
  border-left: 4px solid var(--color-accent);
  background: var(--color-surface-container-lowest);
  color: var(--color-on-surface);
  box-shadow: 0 8px 24px rgba(25, 28, 30, .16);
  outline: 1px solid color-mix(in srgb, var(--color-outline-variant) 60%, transparent);
  font-size: var(--fs-body);
  white-space: pre-wrap;
  pointer-events: none;
}
#toast.show { display: flex; animation: toastIn .25s ease-out; }
#toast > .ms { flex: none; color: var(--color-accent); margin-top: 1px; }
#toast.ok { border-left-color: var(--color-secondary); }
#toast.ok > .ms { color: var(--color-secondary); }
#toast.err { border-left-color: var(--color-error); }
#toast.err > .ms { color: var(--color-error); }

/* ---- 号商 / 用户页 ---- */

/* 权限勾选：一行一个短标签，横向流式排开 */
.chk-line { display: inline-flex; align-items: center; gap: 6px; margin: 0 16px 6px 0;
  font-size: var(--fs-sm); color: var(--color-on-surface); cursor: pointer; }

/* 30 天费用柱：纯 CSS，不引图表库 */
.trend { display: flex; align-items: flex-end; gap: 3px; height: 140px; padding-top: 8px; }
.trend-col { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: flex-end;
  height: 100%; min-width: 0; }
.trend-col > i { display: block; width: 100%; border-radius: 3px 3px 0 0;
  background: color-mix(in srgb, var(--color-accent) 70%, transparent); }
.trend-col:hover > i { background: var(--color-accent); }
.trend-col > span { font-size: 10px; color: var(--color-outline); margin-top: 4px; }

/* 合计行 */
tfoot td { border-top: 2px solid var(--color-outline-variant); font-weight: 600; }

#viewAsBadge a { margin-left: 6px; color: var(--color-accent); }
#whoBadge { white-space: nowrap; }
