/* ══════════════════════════════════════════════════════════════════════════
   EM 音源服务 · 前端样式（2026-09-27：外壳与视觉语言改成 design/em-rebuild 的 C 方案）

   落地的是 C（`c-dashboard-workbench.html`）的**外壳与视觉语言**，不是它的页面集合：
   7 个 `.pane[data-page]`、侧栏 `[data-section]`、全部 id 与 `data-*` 契约一字未动，
   app.js 不需要任何改动。

   C 的定稿五条规则（README 第八节），这里是逐条落的：
     ① **外壳一律无彩**：中性色用 slate（页面 #f8fafc、卡片白、边 #e2e8f0、
        字 #111827 / #5b6472 / #6b7280），另加四级灰做分隔。
     ② **整页只有一个强调色**（`--accent: #2563eb`），且只出现在四处：
        链接、选中项、焦点环、数据里的极值。第二条彩色不要。
     ③ **主操作是深墨实心**（`--ink-1: #111827`），不是彩色按钮。
     ④ **零渐变、零彩色光晕**：所有进度条都是深墨实心，骨架屏用平灰 + 透明度过呼吸，
        没有任何 linear-gradient（上一版有 3 处：两条进度轨 + 一条骨架屏）。
     ⑤ **状态色只做小标记**：绿 / 琥珀 / 红只出现在圆点、文字与 1px 细边里，
        不做大面积填充；标签一律 **4px 直角**（`--radius-sm`），不用胶囊。

   布局：标准 app shell，**全页只有一个滚动容器**（`.main`），不再有
   `calc(100vh - 某个数字)` 这类猜出来的高度（README 第十一节：同一类错误犯过四次）。
   窄屏（≤679px）把滚动权交回文档，导航换成 C 的两件套：底部标签栏（图标 + **文字**）
   + 「更多」底部面板，并在 `.side` 里就地补一个 `#sheet-more` 触发布局。

   约定：无构建、无外部字体（沿用系统栈）、零第三方依赖、无外部请求。
   约定：可点元素高度走 min-height 且不小于 36px（触控下限，契约会查）；
        纯展示的小标签用固定 height，不走 min-height。
   ══════════════════════════════════════════════════════════════════════════ */

:root {
  color-scheme: light;

  /* ── 中性色：Tailwind slate（C 的外壳色）──────────────────────────────── */
  --bg: #f8fafc;          /* slate-50 */
  --panel: #ffffff;
  --panel-2: #f8fafc;     /* 次级底（表头 / 只读输入）*/
  --panel-3: #f1f5f9;     /* slate-100，进度轨、骨架 */
  --ink-1: #111827;       /* 深墨：正文、主操作 */
  --ink-2: #5b6472;
  --ink-3: #6b7280;
  --ink-4: #94a3b8;       /* slate-400，最弱一级 */
  --line: #e8eaee;        /* 分隔线（比边界线浅一档）*/
  --line-2: #cfd4dc;      /* 边界线（输入框、次要按钮）*/

  /* ── 唯一的强调色。只给：链接 / 选中 / 焦点 / 数据极值 ─────────────────── */
  --accent: #2563eb;
  --accent-dark: #1d4ed8;
  --accent-light: #60a5fa;
  --accent-tint: #eff6ff;
  --accent-tint-2: #e7f0fe;

  /* ── 状态色：只做小标记（圆点 / 文字 / 细边），不做大面积填充 ─────────── */
  --ok: #047857;
  --ok-tint: #ecfdf5;
  --warn: #b45309;
  --warn-tint: #fffbeb;
  --danger: #c81e1e;
  --danger-tint: #fef2f2;
  --ok-line: #b7e4ce;
  --warn-line: #f0dca6;
  --danger-line: #f0d2d0;

  /* ── 灰阶外壳（C 的做法：左栏与顶栏都在「纸面」上，重量靠边线与深墨块）── */
  --nav-rail: #ffffff;
  --nav-line: #e8eaee;
  --nav-ink: #475569;
  --nav-ink-dim: #94a3b8;

  /* 小件 6px、面板 10px、标签 4px 直角（胶囊标签一多就像消费级产品）*/
  --radius: 10px;
  --radius-sm: 4px;
  --radius-md: 6px;
  --shadow-1: 0 1px 2px rgba(15, 23, 42, .05);
  --shadow-2: 0 12px 40px -12px rgba(15, 23, 42, .28);

  --appbar-h: 56px;
  /* 左栏常驻展开（2026-09-27 起没有收起态），所以只需要一个宽度。
     原来还有一个 `--side-rail: 56px`（收起态宽度），没有引用处了，删掉。 */
  --side-w: 216px;
  --tabbar-h: 58px;
  --font: "Noto Sans SC", "PingFang SC", "HarmonyOS Sans SC", "Microsoft YaHei UI",
          "Microsoft YaHei", system-ui, -apple-system, sans-serif;
  --mono: "JetBrains Mono", "Cascadia Mono", "SF Mono", Consolas, "Liberation Mono",
          ui-monospace, monospace;
}

* { box-sizing: border-box; }

/* ══ 单一滚动容器（桌面）════════════════════════════════════════════════════
   body 自己撑满视口且**永不滚动**；顶栏 flex:none 按内容定高；
   主区 flex:1 + min-height:0 吃掉剩余空间，并成为全页唯一的滚动容器。
   `min-height: 0` 不能省 —— flex/grid 子项的最小内容高度会把 shell 顶出视口，
   于是外层冒出一条、主区再来一条，屏幕上同时挂两条滚动条。
   ══════════════════════════════════════════════════════════════════════════ */
html, body { margin: 0; padding: 0; height: 100%; }

body {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--bg);
  color: var(--ink-1);
  font-family: var(--font);
  font-size: 14px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

/* 背景滚动锁：弹窗与窄屏登录浮层 / 「更多」面板打开时用。
   注意**不能只写 `overflow: hidden`**：窄屏的滚动发生在文档视口
   （`document.scrollingElement === HTML`），锁 body 的 overflow 锁不住它 ——
   实测「更多」面板开着还能照常滚（内容 top 从 99 变 -301）。
   真正管用的是把 body 变成 fixed（配合 JS 写 `top: -scrollY` 保持画面不跳，
   见 sheet.js 的 setOpen）。`is-locked` 这个类名沿用 app.js 的约定。 */
body.is-locked {
  position: fixed;
  left: 0;
  right: 0;
  width: 100%;
  overflow: hidden;
}

.mono, .num { font-family: var(--mono); font-variant-numeric: tabular-nums; }

/* 链接是强调色四个去处之一（其余是选中 / 焦点 / 数据极值） */
a { color: var(--accent); text-decoration: none; }
a:hover { color: var(--accent-dark); }

h1, h2, h3, p, dl, dd, ol, ul { margin: 0; }
ol, ul { padding: 0; list-style: none; }

.sprite { position: absolute; width: 0; height: 0; overflow: hidden; }
svg { fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }

.dim { color: var(--ink-3); }
.muted { color: var(--ink-3); }
.ok { color: var(--ok); }
.warn { color: var(--warn); }
.is-muted { opacity: 0.55; }

/* 微标签：大写 + 字距（C 的 .micro 写法）*/
.micro {
  font-size: 11px; letter-spacing: .05em; text-transform: uppercase;
  color: var(--ink-3); font-weight: 600;
}
.footnote { font-size: 12px; color: var(--ink-3); line-height: 1.8; margin-top: 12px; }

/* ── 首屏占位 ─────────────────────────────────────────────────────────── */
.boot {
  min-height: 240px;
  display: flex; align-items: center; justify-content: center;
  color: var(--ink-3); font-size: 13px;
}

/* ══════════════════════════════════════════════════════════════════════════
   骨架：顶栏 / 视图容器 / 主区
   ══════════════════════════════════════════════════════════════════════════ */
#app { flex: 1; min-height: 0; display: flex; flex-direction: column; }

.appbar {
  flex: none;
  height: var(--appbar-h);
  background: var(--panel);
  border-bottom: 1px solid var(--line);
  display: flex; align-items: center; gap: 16px;
  padding: 0 16px;
  z-index: 40;
  flex-wrap: nowrap;
  overflow: hidden;
}
/* 滚动之后顶栏与正文之间要有一条边界（app.js 按 window.scrollY 切这个类）*/
.appbar.is-scrolled { box-shadow: var(--shadow-1); }

.brand {
  display: flex; align-items: center; gap: 9px;
  flex: none; white-space: nowrap;
  font: inherit; font-size: 14px; font-weight: 700; letter-spacing: -.01em;
  background: none; border: 0; padding: 0; cursor: pointer;
}
/* 品牌标：**两块实色方块**（深墨 + 蓝），没有渐变、没有光晕。
   渐变方块 + 辉光是"AI 生成感"最典型的两个零件，这里全部不用。 */
.brand .logo {
  width: 24px; height: 24px; border-radius: var(--radius-sm);
  background: var(--ink-1); color: #fff;
  display: flex; align-items: center; justify-content: center; flex: none;
}
.brand .logo svg { width: 15px; height: 15px; }
.brand-text { display: flex; flex-direction: column; line-height: 1.25; text-align: left; }
.brand-text strong { font-weight: 700; }
.brand-text small { font-size: 10px; letter-spacing: .16em; color: var(--ink-4); font-weight: 600; }

.appbar-links { display: flex; gap: 18px; font-size: 13px; }
.appbar-links a { color: var(--ink-2); }
.appbar-links a:hover { color: var(--ink-1); }

.appbar-context { margin-left: auto; display: flex; align-items: center; gap: 10px; }
.appbar-actions { margin-left: auto; display: flex; align-items: center; gap: 6px; }

.iconbtn {
  width: 34px; height: 34px; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid transparent; border-radius: var(--radius-md);
  background: none; color: var(--ink-3); cursor: pointer;
}
.iconbtn:hover { background: var(--panel-3); color: var(--ink-1); }
.iconbtn svg { width: 17px; height: 17px; }

.user-chip {
  display: flex; align-items: center; gap: 8px;
  min-height: 36px; padding: 0 10px 0 4px;
  border: 1px solid var(--line-2); border-radius: var(--radius-md);
  background: var(--panel); color: var(--ink-1); font: inherit; font-size: 12.5px;
  cursor: pointer; max-width: 190px;
}
.user-chip:hover { border-color: var(--ink-4); }
.user-chip #user-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.user-chip .chev { width: 14px; height: 14px; color: var(--ink-4); flex: none; }
.avatar {
  width: 26px; height: 26px; flex: none; border-radius: 50%;
  background: var(--ink-1); color: #fff;
  display: flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: 700;
}
.avatar svg { width: 15px; height: 15px; }

/* 主区：唯一的滚动容器。scrollbar-gutter: stable 保证滚动条出现/消失时
   内容宽度不跳；滚动条收细，默认 15px 的系统滚动条会压住卡片边框。 */
/* 外层默认**照旧整页滚**（落地页 `#view-auth` 也住在这一层，它是一张很长的官网，
   必须能滚）。控制台那一支的"外壳不滚、只有正文滚"在下面按 `[data-view="app"]` 单独收窄。 */
.page {
  flex: 1; min-height: 0;
  overflow: auto;
  overscroll-behavior: contain;
  scrollbar-gutter: stable;
  position: relative;
}
/* ══ 控制台（data-view=app）：外壳铺满视口，只有正文区滚 ═══════════════════
   2026-09-27 用户反馈「桌面端左侧栏的高度/背景不该随页面变化」——
   根因是滚动发生在外层 `.page` 上，而左栏（连同它的白底）是它的后代：
   滚一下整块左栏被带走、白底也就断了；内容短的页面则反过来，左栏高度跟着内容缩
   （实测最短那页只有 632px、离视口底差 212px，下面露出灰底）。

   高度链必须**每一层都给确定高度**，`height: 100%` 才解析得出来：
   `.page`（flex 项，已定高）→ `#view-app` → `.shell`（→ `.side` 铺满 / `.main` 滚）。
   只写 `.shell { height: 100% }` 是不够的 —— 它的父级 `#view-app` 高度是 auto 时，
   百分比会退化成 auto，整条链白写（实测踩到）。 */
.app[data-view="app"] .page { overflow: hidden; }
.app[data-view="app"] #view-app { height: 100%; min-height: 0; }
.app[data-view="app"] .shell { height: 100%; min-height: 0; }
/* 滚动条样式跟着滚动容器走（原来是 .page 上的，现在 `.main` 才是滚动容器） */
.main::-webkit-scrollbar { width: 10px; height: 10px; }
.main::-webkit-scrollbar-thumb {
  background: var(--ink-4); border: 3px solid transparent;
  background-clip: content-box; border-radius: 8px;
}
.main::-webkit-scrollbar-thumb:hover { background: var(--ink-3); background-clip: content-box; }
.main::-webkit-scrollbar-track { background: transparent; }
.page::-webkit-scrollbar { width: 10px; height: 10px; }
.page::-webkit-scrollbar-thumb {
  background: var(--ink-4); border: 3px solid transparent;
  background-clip: content-box; border-radius: 8px;
}
.page::-webkit-scrollbar-thumb:hover { background: var(--ink-3); background-clip: content-box; }
.page::-webkit-scrollbar-track { background: transparent; }

.view { display: block; }
[hidden] { display: none !important; }

/* ══════════════════════════════════════════════════════════════════════════
   控制台骨架：侧栏 + 主区
   ══════════════════════════════════════════════════════════════════════════ */
/* 外壳铺满 `.page` 的高度（**不是 min-height**）：`.main` 要高到能成为滚动容器、
   左栏要铺满视口高度，两者都需要父级有确定高度。真正生效的选择器在下面
   `.app[data-view="app"] .shell`（这里保留基础值，供非控制台场景留底）。 */
.shell {
  display: flex;
  align-items: stretch;
  height: 100%;
  min-height: 0;
  min-width: 0;
}

/* 左栏：白底 + 1px 边（C 的 rail 是半色调外壳，重量靠边线与深墨当前项）。
   **常驻展开**（2026-09-27 用户要求：桌面端菜单一直展开，不要"收起/悬停展开"这个交互）——
   所以宽度写死 `--side-w`、不写 hover 变宽、也**不加宽度过渡**（没有收起态就没有可过渡的东西）。
   窄屏整条撤掉，交给底部标签栏 +「更多」面板。 */
.side {
  width: var(--side-w); flex: none;
  background: var(--nav-rail); color: var(--nav-ink);
  border-right: 1px solid var(--line);
  padding: 8px 8px 14px;
  display: flex; flex-direction: column;
  /* 常驻展开后左栏自己要能滚：菜单项多起来时（或窗口很矮）不许被裁掉。
     横向仍然裁掉，避免长标签撑破 216px。 */
  overflow-y: auto;
  overflow-x: hidden;
}

/* 分组标题（总览 / 我的服务 / 账单与账户 / 快捷）：常驻显示。
   收起态那套「56px 里塞不下四个字、所以整条藏起来」的逻辑随收起态一起删了。 */
.side-group {
  display: block;
  padding: 0 8px; margin: 14px 0 6px;
  font-size: 10px; letter-spacing: .12em; text-transform: uppercase;
  color: var(--nav-ink-dim); font-weight: 700; white-space: nowrap;
  overflow: hidden;
}
.side-group:first-child { margin-top: 4px; }

.side-item {
  display: flex; align-items: center; gap: 12px; width: 100%;
  min-height: 40px; padding: 0 10px; margin-bottom: 2px;
  border: 0; border-radius: var(--radius-md); background: none;
  color: var(--nav-ink); font: inherit; font-size: 13px; font-weight: 500;
  cursor: pointer; text-align: left; white-space: nowrap;
}
.side-item svg { width: 18px; height: 18px; flex: none; color: var(--ink-3); }
.side-item span { flex: 1; min-width: 0; }
.side-label, .side-label-short { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* 常驻展开：显示长标签（「用量与额度」），短标签（「用量」）只在窄屏底栏用。
   以前这两条是「收起态藏起来、悬停再放出来」，随收起态一起改掉了。 */
.side-label { display: block; }
.side-label-short { display: none; }
.side-item em {
  font-style: normal; font-size: 11px; font-family: var(--mono);
  color: var(--nav-ink-dim); white-space: nowrap;
  max-width: 92px; overflow: hidden; text-overflow: ellipsis; flex: none;
  display: block;
}
/* 展开态：图标左对齐，与文字 12px 间距（`gap` 见 `.side-item`）。
   原来这里还有一组「收起态居中 + 悬停改 flex-start/padding」的规则 ——
   它同时是**窄屏底栏"点一下内容上移 9px"** 的元凶（那条 `padding: 0 10px` 盖掉了
   底栏自己的 `padding: 6px 2px 5px`），随收起态一起删。 */
.side-item:hover { background: var(--panel-3); color: var(--ink-1); }
.side-item:hover svg { color: var(--ink-1); }
/* 当前项：深墨实心（C 的第 ③ 条），不是彩色高亮 */
.side-item.is-active { background: var(--ink-1); color: #fff; font-weight: 600; }
.side-item.is-active svg { color: #fff; }
.side-item.is-active em { color: #cbd5e1; }

.side-foot {
  margin-top: auto; padding: 12px 8px 0; border-top: 1px solid var(--nav-line);
  display: flex; flex-direction: column; gap: 8px;
  font-size: 11px; color: var(--nav-ink-dim); white-space: nowrap;
}
.side-status { display: flex; align-items: center; gap: 6px; }
.side-status .dot { background: var(--ok); }

/* 「更多」触发位：只在窄屏出现（宽屏导航全在左栏里，不需要它）。
   C 里这一格是底部标签栏的第 5 格，这里把同一格放进 `.side` —— app.js 的
   标签栏机制（`NAV_FOR_SECTION` / `TABBAR_LAYOUT`）把 `.side-item.is-active`
   打在**某个** `.side-item` 上，用“没有主标签选中”反推“当前页在面板里”，
   于是「更多」的高亮不需要动 app.js 一行。 */
.sheet-more { display: none; }
.sheet-more svg { color: var(--ink-4); }
.sheet-more.is-current { color: var(--ink-1); font-weight: 600; }
.sheet-more.is-current svg { color: var(--ink-1); }

/* 正文区 = **桌面端唯一的滚动容器**（`.page` 已改成 overflow:hidden）。
   父级 `.shell` 有确定高度，所以这里的 `overflow-y:auto` 能真正生效；
   左栏是它的兄弟节点，因此滚正文不会动左栏。 */
.main {
  flex: 1; min-width: 0; padding: 18px 22px 32px;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-gutter: stable;
}
.crumb { display: flex; align-items: center; gap: 7px; font-size: 12px; color: var(--ink-3); }
.crumb svg { width: 13px; height: 13px; color: var(--ink-4); }
.crumb b { color: var(--ink-2); font-weight: 500; }

/* ══ 顶栏第二行：区块跳转（C 的 dash）══════════════════════════════════════
   每个 dash 都带 `data-section`，所以它就是 app.js 认得的普通入口（委托监听
   直接生效，JS 一行不用改）。当前项由 sheet.js 按 `#/xxx` 加 `.is-active`。
   窄屏整排收起：那里导航交给底部标签栏 +「更多」面板。 */
.dashes { display: flex; align-items: center; gap: 2px; min-width: 0; flex: 0 1 auto; }
.dash {
  background: transparent; border: 0; border-radius: var(--radius-md);
  padding: 6px 10px; min-height: 36px; cursor: pointer;
  font: inherit; font-size: 12.5px; font-weight: 500; color: var(--ink-2);
  white-space: nowrap;
}
.dash:hover { background: var(--panel-3); color: var(--ink-1); }
/* 选中态 = 深墨实心（C 的 dashes button.on），不是彩色 */
.dash.is-active { background: var(--ink-1); color: #fff; font-weight: 600; }

/* ══ 内容区 = 12 列 widget 网格（C 的 .grid / .w）═════════════════════════
   每个 `.pane[data-page]` 自己就是一个网格，卡片按重要程度跨列（见下面的
   `.span-*` 与各页的规则）。窄屏一律单列 —— 网格只是桌面这一层的结构。 */
.pane {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: 14px;
  align-items: start;
}
/* 网格的直接子项默认整行铺满；`.span-*` / `.grp-*` 再按需收窄。
   用 `:where()` 把权重压到 0：这样后面那几条 `.ov-*` 布局规则（老的那套
   「两栏 + 页边注」）**不用改也不用删**就能被这里的 display 覆盖 ——
   顺序怎么变都不会翻车。 */
:where(.pane > *) { grid-column: 1 / -1; min-width: 0; }
.span-3 { grid-column: span 3; }
.span-4 { grid-column: span 4; }
.span-6 { grid-column: span 6; }
.span-8 { grid-column: span 8; }
.span-12 { grid-column: span 12; }
/* `.ov-grid` / `.ov-main` / `.ov-side` 这三层不再自己排版，子项直接落在
   外层这 12 列上（页边注的三块因此并进网格，不再单独占一列）。 */
:where(.ov-grid, .ov-main, .ov-side) { display: contents; }

/* 网格里的卡片：同一行的卡片靠 gap 对齐，所以取消卡片自带的上边距 */
.pane .card { margin-top: 0; }
.pane > .kpis, .pane > .usage, .pane > .cols, .pane > .ent-summary { margin-top: 0; }

/* 总览的跨列安排（`.ov-main` / `.ov-side` 已用 display: contents 摊平，
   所以下面这些子项就是外层的 grid item）：
     .kpis         整行（内部再分四格）
     .cols-2       权益时长 / 额度消耗        并排 span-6
     .cols-2b      平台成功率 / 失败构成      并排 span-6
     直接 .card    导入链接                   整行
     .ov-side 三块 今日调用 / 最近一次调用 / 需要帮助，各 span-3
   HTML 里已经给每个分组写好了 span-* 类，这里只处理「页边注那三块」。
   两条兜底都写成**零权重**（`:where()`）：`.span-*` 是类选择器，权重更高，
   能正常把它们收窄 —— 否则会出现「写了 span-6 却还是整行」这种查半天的怪事。 */
:where(.ov-side > .card) { grid-column: span 3; }
/* 中等屏与窄屏：页边注三块铺满整行。写成 `.ov-side > .card` 提高权重是**故意**的：
   上面那条兜底是零权重，不会再跟 `.span-*` 抢。 */
@media (max-width: 1079px) { .ov-side > .card { grid-column: 1 / -1; } }

/* 「导入链接」那张卡深度是 2（.pane > .ov-grid > .ov-main > .card），
   `.kpis` 也是 —— 它们拿不到 `.pane > *` 的默认跨列，漏了就会只占 1 列
   （实测：导入链接 98px、长 URL 撑破 138px；.kpis 只占 1 列时四个 KPI 会竖着堆）。
   所以给这一层的每个子项一个**零权重**的整行兜底：`.span-*` 照样能把它收窄。 */
:where(.ov-main > *) { grid-column: 1 / -1; }

.pagehead {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 20px; margin: 10px 0 18px;
}
.pagehead h1 { font-size: 20px; font-weight: 700; letter-spacing: -.015em; }
.pagehead p { font-size: 12.5px; color: var(--ink-3); margin-top: 5px; line-height: 1.7; }
.acts { display: flex; align-items: center; gap: 8px; flex: none; }

/* ══════════════════════════════════════════════════════════════════════════
   通用零件
   ══════════════════════════════════════════════════════════════════════════ */
/* 标签：**4px 直角**，不是胶囊。状态色只进细边与文字。 */
.chip {
  display: inline-flex; align-items: center; gap: 6px;
  height: 24px; padding: 0 9px; border-radius: var(--radius-sm);
  font-size: 11.5px; font-weight: 500; white-space: nowrap;
  background: var(--panel); color: var(--ink-2); border: 1px solid var(--line-2);
}
.chip-ok { background: var(--ok-tint); border-color: var(--ok-line); color: var(--ok); font-weight: 600; }
.chip-warn { background: var(--warn-tint); border-color: var(--warn-line); color: var(--warn); font-weight: 600; }
.chip-info { background: var(--panel-3); border-color: var(--line-2); color: var(--ink-2); }
.dot { width: 6px; height: 6px; border-radius: 50%; background: var(--ok); display: inline-block; flex: none; }
.dot.warn { background: #d9a343; }
.dot.bad { background: var(--danger); }

.badge {
  display: inline-flex; align-items: center; height: 22px;
  padding: 0 8px; border-radius: var(--radius-sm);
  font-size: 11.5px; background: var(--panel-3); color: var(--ink-2);
  border: 1px solid var(--line-2);
}
.badge-ok { background: var(--ok-tint); border-color: var(--ok-line); color: var(--ok); }
.badge-primary { background: var(--panel-3); border-color: var(--line-2); color: var(--ink-1); font-weight: 600; }
.badge-danger { background: var(--danger-tint); border-color: var(--danger-line); color: var(--danger); }

/* 按钮：主操作 = 深墨实心（C 的第 ③ 条），次操作 = 白底描边。
   彩色按钮在这套语言里没有位置 —— 一个视图只有一个主操作。 */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  min-height: 40px; padding: 0 14px;
  border: 1px solid var(--line-2); border-radius: var(--radius-md);
  background: var(--panel); color: var(--ink-1);
  font: inherit; font-size: 13px; font-weight: 500; cursor: pointer; white-space: nowrap;
}
.btn:hover { border-color: var(--ink-4); background: var(--panel-3); }
.btn svg { width: 15px; height: 15px; flex: none; }
.btn-sm { min-height: 36px; padding: 0 11px; font-size: 12.5px; }
.btn-lg { min-height: 46px; padding: 0 20px; font-size: 14px; }
.btn-block { width: 100%; }
.btn-primary { background: var(--ink-1); border-color: var(--ink-1); color: #fff; font-weight: 600; }
.btn-primary:hover { background: #1f2937; border-color: #1f2937; color: #fff; }
.btn-ghost { background: none; border-color: var(--line-2); color: var(--ink-2); }
.btn-ghost:hover { color: var(--ink-1); }
.btn-danger { background: var(--panel); border-color: var(--danger-line); color: var(--danger); }
.btn-danger:hover { background: var(--danger-tint); border-color: #dca9a5; }
.btn:disabled, .btn[disabled] { opacity: 0.5; cursor: not-allowed; }
.link-btn {
  display: inline-flex; align-items: center; min-height: 36px;
  background: none; border: 0; padding: 0;
  color: var(--accent); font: inherit; font-size: 12.5px; cursor: pointer;
}
.btn.is-busy { opacity: 0.7; pointer-events: none; }
.btn.is-copied { color: var(--ok); border-color: var(--ok-line); }

.eyebrow {
  display: inline-block; font-size: 11px; letter-spacing: .16em;
  color: var(--ink-3); text-transform: uppercase; font-weight: 600;
}
.hint { font-size: 12px; color: var(--ink-3); line-height: 1.7; }
.hint-inline { display: flex; gap: 7px; align-items: flex-start; }
.hint-inline svg { width: 14px; height: 14px; flex: none; margin-top: 3px; color: var(--ink-4); }
.card-body-flush { padding: 0; }

/* ── 表单 ─────────────────────────────────────────────────────────────── */
.field { display: block; margin-bottom: 12px; }
.field-label { display: block; font-size: 12px; color: var(--ink-2); margin-bottom: 6px; font-weight: 500; }
.field input,
.activate-form input,
.inline-form input {
  width: 100%; min-height: 42px; padding: 0 11px;
  /* 16px：iOS 聚焦时不会放大整页（硬指标，别改小） */
  font-size: 16px; font-family: var(--font);
  color: var(--ink-1); background: var(--panel);
  border: 1px solid var(--line-2); border-radius: var(--radius-md);
}
/* 焦点环是强调色四个去处之一 */
.field input:focus, .activate-form input:focus, .inline-form input:focus {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-tint);
}
.field input[readonly] { background: var(--panel-2); color: var(--ink-2); }
.inline-form { display: grid; gap: 12px; }
/* 表单里的按钮不该被栅格拉满整行（看起来像一条横幅，不像按钮） */
.inline-form .btn { justify-self: start; min-width: 148px; }
.activate-form { display: flex; gap: 10px; align-items: flex-end; flex-wrap: wrap; }
.activate-form .field { flex: 1 1 240px; margin-bottom: 0; }
/* 提示条：左侧一道深墨细线 + 极浅底（C 的 .notice），浅底只在警告时上色 */
.auth-error {
  margin-top: 10px; padding: 9px 11px; border-radius: var(--radius-md);
  background: var(--danger-tint); border: 1px solid var(--danger-line);
  border-left: 2px solid var(--danger); color: var(--danger);
  font-size: 12.5px;
}
.alert { padding: 12px; border-radius: var(--radius-md); font-size: 12.5px; line-height: 1.7; }
.alert-warn {
  background: var(--warn-tint); border: 1px solid var(--warn-line);
  border-left: 2px solid var(--warn); color: #6b4a00;
}
.status-note { color: var(--ink-2); }

/* ══════════════════════════════════════════════════════════════════════════
   落地页（未登录）—— 结构未动，只把视觉变量换到与外壳同一套 token
   ══════════════════════════════════════════════════════════════════════════ */
.landing { max-width: 1180px; margin: 0 auto; padding: 34px 24px 0; }

.mast { display: grid; grid-template-columns: minmax(0, 1fr) 380px; gap: 44px; align-items: start; }
.mast-main h1 {
  font-size: 36px; line-height: 1.2; font-weight: 700;
  letter-spacing: -.025em; margin: 10px 0 14px;
}
.mast-main .lead { font-size: 14px; line-height: 1.9; color: var(--ink-2); max-width: 34em; }
.hero-facts { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 18px; }
.fact {
  display: inline-flex; align-items: center; gap: 6px;
  height: 28px; padding: 0 10px; border-radius: var(--radius-sm);
  background: var(--panel); border: 1px solid var(--line-2);
  font-size: 12px; color: var(--ink-2); font-weight: 500;
}
.fact svg { width: 13px; height: 13px; color: var(--ink-3); }
.trust {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  margin-top: 20px; border: 1px solid var(--line);
  border-radius: var(--radius); background: var(--panel); overflow: hidden;
}
.trust > div { padding: 13px 15px; border-right: 1px solid var(--line); }
.trust > div:last-child { border-right: 0; }
.trust .k { display: block; font-size: 11.5px; color: var(--ink-3); }
.trust strong { display: block; font-size: 17px; font-weight: 700; margin-top: 5px; letter-spacing: -.01em; }
.trust .s { display: block; font-size: 11px; color: var(--ink-4); margin-top: 4px; }

/* 登录卡 */
.auth-card {
  background: var(--panel); border: 1px solid var(--line-2);
  border-radius: var(--radius); padding: 20px; box-shadow: var(--shadow-1);
}
.auth-card-title { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.auth-card-title h2 { font-size: 16px; font-weight: 700; letter-spacing: -.01em; }
.auth-card-title p { font-size: 12px; color: var(--ink-3); margin-top: 4px; line-height: 1.7; }
.mobile-auth-close { display: none; }
.tabs { display: flex; gap: 18px; border-bottom: 1px solid var(--line); margin: 14px 0 14px; }
.tab {
  background: none; border: 0; padding: 0 0 9px; cursor: pointer;
  min-height: 40px;
  font: inherit; font-size: 12.5px; color: var(--ink-3);
  border-bottom: 2px solid transparent;
}
/* 选中项：深墨下划线（灰阶语言里"重"就是强调）*/
.tab.is-active { color: var(--ink-1); font-weight: 600; border-bottom-color: var(--ink-1); }
.auth-form { display: block; }
/* 「其余的 Key」（2026-09-29 起每笔购买各一把）：每一项是一块带虚线上边线的分割区，
   与上面那张首把 Key 的卡片同属一张卡。 */
.key-item { margin-top: 16px; padding-top: 14px; border-top: 1px dashed var(--line-2); }
.key-item-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.key-item-head b { font-size: 13.5px; font-weight: 600; }

/* 密码表单与「QQ扫码登录」之间的分隔：中间一个「或」，两侧细线。
   （这条规则此前只写在 styles/auth.css 里 —— 而 index.html 只加载本文件，
   于是线上那个「或」是按浏览器默认样式渲染的：没有分隔线、字号也大一号。
   `[hidden]` 那条 `display: none !important` 仍然盖得住它，QQ 登录关掉时不显示。） */
.auth-sep {
  display: flex; align-items: center; gap: 10px;
  margin: 15px 0 12px; color: var(--ink-3); font-size: 12px;
}
.auth-sep::before,
.auth-sep::after {
  content: ""; flex: 1; height: 1px; background: var(--line);
}

/* 落地页区块 */
.section { padding: 42px 0 0; }
.section-heading {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 24px; padding-bottom: 14px; border-bottom: 1px solid var(--line-2); margin-bottom: 18px;
}
.section-heading h2 { font-size: 22px; font-weight: 700; letter-spacing: -.015em; margin-top: 6px; }
.section-heading p { font-size: 12.5px; color: var(--ink-3); max-width: 42em; line-height: 1.8; }

/* 方案卡（2026-09-27：落地页的表格行 → 卡片网格，与控制台「购买与激活」共用同一套规则）
 *
 * 为什么不再用表格行：
 *   1. 表头写的是「时长 / 每日额度」，而卡片里的字段实际是「有效期 / 调用额度」——
 *      本来就不一致（见 index.html 里那段注释）。
 *   2. 表头是**固定列宽**、卡片是流式内容，两边永远差几像素，并排看就是「错位」。
 *   3. 档位数量不是固定的（线上 4 档、演示数据 5 档），固定 5 列一旦对不上就空一格。
 * 所以这里只保留「容器」的语义，真正的卡片规则复用 `.buy-item` 那一套
 * （下面每条选择器都已经带上 `#plan-grid .plan`，一份实现两处用）。
 */
.plan-table { border: 0; background: transparent; overflow: visible; }
.plan-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(186px, 1fr)); gap: 14px; }
/* 方案表下面那句「额度按自然日 00:00 重置；到期前随时可以延长…」。
   口径与 .footnote 一致（12px / --ink-3）：不写的话它按浏览器默认的 16px 黑字
   渲染，比周围所有说明文字重一大截。（这条同样是从没被加载的 styles/workspace.css 搬来的。） */
.plan-footnote { font-size: 12px; color: var(--ink-3); line-height: 1.8; margin-top: 12px; }
#plan-grid .plan {
  display: flex; flex-direction: column; padding: 16px;
  border: 1px solid var(--line); border-radius: var(--radius); background: var(--panel);
}
/* 窄屏兜底：186px 的两列在 390 上放不下时自动降成一列（auto-fit 会处理，
   但显式写一条避免字体放大时挤成两列很窄的卡） */

.plan-copy { min-width: 0; }
.plan-title-line { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.plan-name { font-size: 14.5px; font-weight: 600; }
.plan-flags { display: flex; gap: 6px; }
/* 角标也是 4px 直角的小标签；「最划算」用深墨描边而不是彩色底 */
.plan-flag {
  font-size: 10.5px; letter-spacing: .04em; padding: 1px 6px; line-height: 1.6;
  border-radius: var(--radius-sm); border: 1px solid var(--line-2); color: var(--ink-2);
}
.plan-flag.is-featured { border-color: var(--ink-1); color: var(--ink-1); font-weight: 600; }
.plan-flag.is-limited { background: var(--warn-tint); border-color: var(--warn-line); color: var(--warn); }
.plan-desc { font-size: 12px; color: var(--ink-3); margin-top: 5px; line-height: 1.7; }
/* 字段与价格的**表格行默认样式已删**：现在两处都是卡片，规则统一在 `.buy-item` 那一段
   （选择器里带上了 `#plan-grid .plan`）。这里只留卡片态仍然需要的东西。 */
.btn-select-plan {
  min-height: 38px; width: 100%; padding: 0 12px;
  border: 1px solid var(--line-2); border-radius: var(--radius-md);
  background: var(--panel); color: var(--ink-1); font: inherit; font-size: 13px;
  font-weight: 500; cursor: pointer;
}
.btn-select-plan:hover { border-color: var(--ink-1); background: var(--panel-3); }
/* 选中项用**深墨边框 + 极浅底**，不是彩色底（强调色只给四处）*/
.plan.is-selected { background: var(--panel-2); box-shadow: inset 3px 0 0 var(--ink-1); }
.plan.is-selected .btn-select-plan {
  background: var(--ink-1); border-color: var(--ink-1); color: #fff; font-weight: 600;
}
/* .plan-foot 是**方案操作按钮的容器**（app.js 的 planCardInner 把 CTA 放在这里）——
   不能藏起来：藏了整个方案表就点不动了（旧版的 .plan-foot 是脚注，这版不是）。 */


/* 音质支持矩阵 */
.matrix {
  display: grid; grid-template-columns: 150px repeat(5, minmax(0, 1fr));
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--panel); overflow: hidden;
}
.matrix > div {
  padding: 11px 14px; font-size: 12.5px; color: var(--ink-2);
  border-bottom: 1px solid var(--line); border-right: 1px solid var(--line);
}
.matrix > div:nth-child(6n) { border-right: 0; }
.matrix > div:nth-last-child(-n + 6) { border-bottom: 0; }
.matrix-hd {
  background: var(--panel-2); font-size: 11px; letter-spacing: .08em;
  text-transform: uppercase; color: var(--ink-3); font-weight: 700;
}
.matrix-rn { font-weight: 500; color: var(--ink-1); }
/* 勾/横是**小标记**：绿色只进文字，不做底色 */
.matrix-yes { color: var(--ok); }
.matrix-no { color: var(--ink-4); }

/* 流程 */
.steps { display: grid; gap: 0; }
.steps li {
  display: grid; grid-template-columns: 40px minmax(0, 1fr); gap: 14px;
  padding: 16px 0; border-bottom: 1px solid var(--line);
}
.steps li:last-child { border-bottom: 0; }
.step-no { font-family: var(--mono); font-size: 13px; color: var(--ink-4); }
.steps h3 { font-size: 14px; font-weight: 600; }
.steps p { font-size: 12.5px; color: var(--ink-3); line-height: 1.8; margin-top: 5px; }
.steps-tight li { padding: 12px 0; }
.steps-tight h3 { font-size: 13px; }

/* FAQ */
.faq { border: 1px solid var(--line); border-radius: var(--radius); background: var(--panel); overflow: hidden; }
.faq details { border-bottom: 1px solid var(--line); }
.faq details:last-child { border-bottom: 0; }
.faq summary {
  min-height: 48px; padding: 14px 16px; font-size: 13.5px; font-weight: 600;
  cursor: pointer; list-style: none; display: flex; align-items: center; gap: 10px;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after { content: "+"; margin-left: auto; color: var(--ink-4); font-size: 15px; }
.faq details[open] summary::after { content: "−"; }
.faq p { padding: 0 16px 15px; font-size: 12.5px; color: var(--ink-3); line-height: 1.85; }

/* 页脚 */
.store-footer {
  display: grid; grid-template-columns: 2fr 1fr 1fr 1fr; gap: 28px;
  margin-top: 46px; padding: 26px 0 34px;
  border-top: 1px solid var(--line); font-size: 12.5px; color: var(--ink-3);
}
.store-footer b { display: block; color: var(--ink-1); font-weight: 700; margin-bottom: 8px; }
.store-footer a { display: block; margin-bottom: 6px; color: var(--ink-3); }
.store-footer a:hover { color: var(--accent); }
.store-footer p { line-height: 1.9; margin-top: 10px; }
.store-footer .chip { display: inline-flex; }
.brand-line { display: flex; align-items: center; gap: 8px; font-weight: 700; color: var(--ink-1); }
.brand-line svg { width: 16px; height: 16px; color: var(--ink-1); }

/* ══════════════════════════════════════════════════════════════════════════
   卡片 / KPI / 栅格（控制台）
   ══════════════════════════════════════════════════════════════════════════ */
/* KPI 行：auto-fit，不写死列数 —— 卡片数是接口/页面决定的，写死就迟早对不上
   （总览是四格：剩余时长 / 今日调用 / 本月·累计 / 平台健康）。 */
.kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 14px; }
/* 总览里用量三格直接并进 KPI 行；「用量与额度」页面里它们是竖排卡片，
   两处共用 .usage 这个容器 —— 所以 .usage 自己要有间距，总览再用 display:contents 摊平。 */
.usage { display: grid; gap: 14px; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
.kpis-usage { display: contents; }
.kpi, .usage-cell {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 14px 16px;
}
.kpi-label, .usage-cell .k { font-size: 12px; color: var(--ink-3); }
.kpi-value { display: flex; align-items: baseline; gap: 4px; margin-top: 8px; }
.kpi-value .num, .usage-cell .v {
  font-family: var(--mono); font-size: 26px; font-weight: 600;
  letter-spacing: -.03em; line-height: 1;
}
.usage-cell .v { margin-top: 8px; }
.kpi-value .unit { font-size: 12px; color: var(--ink-3); }
.kpi-sub, .usage-cell .lim { font-size: 11.5px; color: var(--ink-3); margin-top: 9px; }
/* 两个数值合并在一格时（本月 / 累计），第二个用小一号字挂在同一行上 */
.usage-cell .v .sub { font-size: 15px; font-weight: 500; color: var(--ink-3); margin-left: 2px; }
/* 长数值（时间戳）降一号字号，保证一行放得下 */
.usage-cell.is-wide .v { font-size: 17px; letter-spacing: 0; }

.cols { display: grid; gap: 14px; margin-top: 14px; align-items: start; }
.cols-2 { grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr); }
/* 两块并列的分析卡（平台成功率 / 失败构成）：稿子里就是并排的，
   在宽屏上这样排才用得满宽度，也便于左右对照。
   **按容器宽度自动折行，而不是按视口宽度写死两列**：左栏 2026-09-27 改成常驻展开
   （56px → 216px）之后，1440 视口下内容区其实只剩 ~1200px，两列各 ~280px ——
   「失败构成」的表被压到最窄单元格 55px、平台名竖排（实拍）。
   用 `auto-fit` + 下限 300px：放得下就两列，放不下就自动叠成一列、每张卡拿到整行宽度。
   视口断点那两条仍保留（更窄时提前收），这条负责"内容区比视口窄"的情形。 */
.cols-2b { grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
/* 总览：网格契约见上面的 `.pane`/`span-*` 段 —— 这三层用 display: contents
   摊平。以前的「主列 + 页边注两栏」不在窄屏段之外单独占位（那一条保留给窄屏）。 */
.ov-grid { display: contents; }
.ov-main { min-width: 0; }
.ov-main > .kpis { margin-bottom: 0; }
/* 页边注的三块也落进网格：卡片自己 span 3（一块）或 span 12（一列里堆叠时
   最后一块单独占位）。具体跨列见下面 `.pane .ov-side` 那几条。 */
.ov-side { display: contents; }
.ov-side .card { margin-top: 0; }
.side-figure { display: flex; align-items: baseline; gap: 6px; }
.side-figure .num { font-family: var(--mono); font-size: 26px; font-weight: 600; letter-spacing: -.03em; line-height: 1; }
.side-figure .cap { font-family: var(--mono); font-size: 12px; color: var(--ink-3); }
.bar { height: 6px; border-radius: 3px; background: var(--panel-3); overflow: hidden; margin-top: 10px; }
.bar i { display: block; height: 100%; background: var(--ink-1); }
.scale { display: flex; justify-content: space-between; font-family: var(--mono); font-size: 10.5px;
         color: var(--ink-4); margin-top: 6px; }
.side-line { display: flex; justify-content: space-between; gap: 10px; font-size: 12.5px;
             color: var(--ink-3); margin-top: 9px; }
.side-line b { font-family: var(--mono); color: var(--ink-1); font-weight: 500; }

/* 卡片：白底 + 1px 边 + 10px 圆角；**无投影**（厚度靠边线，不靠光晕）*/
.card {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius); margin-top: 14px; overflow: hidden;
  min-width: 0;
}
/* 栅格里的卡片，分两种情形，别混：
   1) **同一行的卡片**（.cols 的直接子级）：上下间距一律交给栅格的 gap ——
      卡片自己再带 14px 上边距的话，同一行第二张就会低 14px，看起来就是
      「两张卡没对齐」。
   2) **一列里堆叠的卡片**（包在一层 div 里）：第一张不要上边距，后面的保留 14px。
      ↑ 这一条**曾经不成立**：`.pane .card { margin-top: 0 }`（上面那条）把卡片自带的
      上边距全清零了，于是列里第二张卡直接贴住第一张。实测（2026-09-27）「导入步骤」+
      「换一把新 Key」、「相关入口」+「会话」两处的竖向间距都是 **0px**，边框叠边框。
      修法不靠 margin 而靠**给列本身加 gap** —— 稳、不用逐页点名，也不会漏掉新加的列。
      限定 `:not(.card)`：卡片自己不能变成 flex 容器（它内部是 block 布局）。 */
.cols > .card { margin-top: 0; }
/* `.span-*` 是给 `.pane` 那张 12 列网格用的。`.cols`（2 列）里再写 `span 6`
   会生成**隐式列**：卡片被撑出容器、右边缘越界（Key 页实拍就是这个）。
   这里兜底成 auto —— 以后谁在 `.cols` 里多写一个 span 类也不会再把版面算歪。 */
.cols > * { grid-column: auto; }
.cols > div:not(.card) { display: flex; flex-direction: column; gap: 14px; }
.cols > div > .card:first-child { margin-top: 0; }
.card-head {
  display: flex; align-items: center; gap: 12px;
  min-height: 48px; padding: 10px 16px; border-bottom: 1px solid var(--line);
}
/* 卡片标题：小号大写 + 字距（C 的 `.w > header h2`）。
   它是这一版"专业感"的主要来源之一：标题退成标签、内容才是主角，
   层次靠字重 / 字距 / 一条 1px 分隔线建立，不靠颜色和阴影。 */
.card-head h2 {
  font-size: 11px; font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--ink-3);
}
/* 卡片头那条分隔线：C 用 --gray-100（比卡片边框更浅），这里同理 */
.card-head { border-bottom: 1px solid var(--line); }
.card-head:empty { display: none; }
.card-head > .micro, .card-head > .badge, .card-head > .chip { margin-left: auto; }
.card-head > .btn:last-child { margin-left: auto; }
.card-body { padding: 16px; }
/* `.card-body-flush` 是「内容自己铺满卡片内边距」的那一类容器，里面常常是**宽表格**
   （总览的「失败构成」就是）。这类容器必须自己横滚：它有 `padding: 0` 却不是滚动
   容器时，表格会把内容撑到卡片外面，被卡片那层 `overflow: hidden` 从边界处裁掉
   （实测：卡片 320px、表格 392px，右侧两列看不见，且"能不能滑"没有任何提示）。
   滚动发生在**卡片内**，不把页面撑宽 —— 这是 README 第十一节那条 493px 的教训。 */
.card-body-flush { overflow-x: auto; overscroll-behavior-x: contain; }
/* 危险卡：红色只进标题、细边与文字，不做大面积色块 */
.card-danger { border-color: var(--danger-line); }
.card-danger .card-head { border-bottom-color: var(--danger-line); }
.card-danger .card-head h2 { color: var(--danger); }
.danger-text { font-size: 12.5px; color: #7a3330; line-height: 1.8; margin-bottom: 12px; }

/* 键值表 */
.kv-row {
  display: grid; grid-template-columns: 104px minmax(0, 1fr); gap: 14px;
  padding: 10px 0; border-bottom: 1px solid var(--line); font-size: 12.5px;
}
.kv-row:last-child { border-bottom: 0; }
.kv-key { color: var(--ink-3); }
.kv-val { color: var(--ink-1); overflow-wrap: anywhere; }

/* 输入 + 按钮同排 */
.coderow { display: flex; gap: 8px; align-items: center; min-width: 0; }
.coderow input {
  flex: 1; min-width: 0; min-height: 42px; padding: 0 11px;
  font-size: 16px; font-family: var(--mono);
  background: var(--panel-2); color: var(--ink-1);
  border: 1px solid var(--line-2); border-radius: var(--radius-md);
}
.coderow input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-tint); }

/* 权益时长轨道 */
.rail { margin-bottom: 16px; }
.rail-track {
  position: relative; height: 8px; border-radius: 4px; background: var(--panel-3); overflow: hidden;
}
/* 深墨实心（原来是 accent-light → accent 的渐变）*/
.rail-fill {
  display: block; height: 100%; width: var(--past, 0);
  background: var(--ink-1);
  transition: width .25s ease;
}
.rail-fill.is-empty { width: 0; }
/* 轨道给一道极小的起始宽度：刚开通（--past 接近 0）时整条会空成一片灰，
   看不出"已经开始了"，也看不出左端就是今天。 */
.rail-track::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 2px;
  background: var(--ink-1);
}
.rail-now {
  position: absolute; top: -4px; bottom: -4px; left: var(--past, 0);
  width: 1px; background: var(--ink-1);
}
.rail-labels {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; margin-top: 9px; font-size: 11.5px; color: var(--ink-3);
}
.rail-today { color: var(--ink-1); font-weight: 600; }

/* 表格：表头小号大写 + 字距，数字列等宽对齐。
   `overflow: hidden` 这一层是给**超出卡片**那一类事故兜底的：`.table-wrap`
   自己横滚，但表格的**最小内容宽度**超过卡片时，它会撑破卡片边界（卡片虽然
   也有 overflow:hidden，但那是从后面裁，边界线已经画错了）。
   实测过：`.cols-2b` 那一行里「失败构成」表的表头数字列全是 nowrap，
   最小宽度约 490px，而卡片只有 380px 时，「酷我音乐」就被压成一字一行。 */
.table-wrap { overflow-x: auto; overscroll-behavior-x: contain; max-width: 100%; }
.table { width: 100%; border-collapse: collapse; }
.table th {
  font-size: 11px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink-3); font-weight: 700;
  text-align: left; background: var(--panel-2);
  padding: 10px 16px; border-bottom: 1px solid var(--line); white-space: nowrap;
}
.table th.r, .table td.r { text-align: right; }
.table td {
  padding: 12px 16px; border-bottom: 1px solid var(--line);
  font-size: 12.5px; color: var(--ink-2); vertical-align: middle;
}
.table tbody tr:last-child td { border-bottom: 0; }
.table tbody tr:hover { background: var(--panel-2); }
.table td.mono, .table td .mono { font-family: var(--mono); font-variant-numeric: tabular-nums; }
.empty-inline { padding: 18px 16px; font-size: 12.5px; color: var(--ink-3); }

/* 空状态 */
.empty {
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  padding: 26px 16px; text-align: center;
}
.empty svg { width: 26px; height: 26px; color: var(--ink-4); }
.empty p { font-size: 12.5px; color: var(--ink-3); }

/* 行式入口 */
.row-link {
  display: flex; align-items: center; gap: 12px; width: 100%;
  min-height: 60px; padding: 12px 16px; border: 0; border-bottom: 1px solid var(--line);
  background: none; font: inherit; text-align: left; cursor: pointer; color: inherit;
}
.row-link:last-child { border-bottom: 0; }
.row-link:hover { background: var(--panel-2); }
.row-link span { flex: 1; min-width: 0; }
.row-link b { display: block; font-size: 13.5px; font-weight: 600; color: var(--ink-1); }
.row-link small { display: block; font-size: 11.5px; color: var(--ink-3); margin-top: 3px; }
.row-link svg { width: 16px; height: 16px; color: var(--ink-4); flex: none; }

.main-foot {
  margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--line);
  font-size: 11.5px; color: var(--ink-3);
  display: flex; gap: 8px; align-items: center; flex-wrap: wrap;
}

/* ── 额度进度（JS 生成 .quota-*）─────────────────────────────────────── */
.quotas { display: grid; gap: 14px; margin-top: 16px; }
.quota-row { display: block; }
.quota-line {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  font-size: 12.5px; color: var(--ink-2); margin-bottom: 8px;
}
.quota-line b { font-family: var(--mono); color: var(--ink-1); }
.quota-track { height: 6px; border-radius: 3px; background: var(--panel-3); overflow: hidden; }
/* 进度条一律深墨实心；只有"用尽了"才换成状态色（小面积的语义提示）*/
.quota-track i { display: block; height: 100%; background: var(--ink-1); }
.quota-track.is-warn i { background: #d9a343; }
.quota-track.is-bad i { background: var(--danger); }

/* ── 平台（JS 生成 .pl-*）：总览迷你行 + 平台页行卡片 ────────────────── */
.pl-mini { display: grid; }
.pl-mini-row {
  display: flex; align-items: center; gap: 12px;
  padding: 11px 16px; border-bottom: 1px solid var(--line); font-size: 12.5px;
}
.pl-mini-row:last-child { border-bottom: 0; }
.pl-mini-row .nm { flex: 1; min-width: 0; color: var(--ink-1); }
.pl-mini-row .rate { font-family: var(--mono); color: var(--ink-2); min-width: 52px; text-align: right; }
.pl-bar { height: 6px; border-radius: 3px; background: var(--panel-3); overflow: hidden; }
.pl-bar i { display: block; height: 100%; background: var(--ink-1); }
.pl-bar.is-warn i { background: #d9a343; }
.pl-bar.is-bad i { background: var(--danger); }

/* 平台行：名称 | 成功率条 | 数值；下面是口径文字与音质标签 */
.pl-grid { display: grid; }
.pl-card {
  display: grid; grid-template-columns: 176px minmax(0, 1fr) 66px;
  gap: 6px 16px; align-items: center; padding: 14px 16px;
  border-bottom: 1px solid var(--line);
}
.pl-card:last-child { border-bottom: 0; }
.pl-name { grid-column: 1; grid-row: 1; display: flex; align-items: center; gap: 8px; min-width: 0; }
.pl-name strong { font-weight: 600; color: var(--ink-1); font-size: 13.5px; }
.pl-card > .pl-bar { grid-column: 2; grid-row: 1; }
.pl-rate { grid-column: 3; grid-row: 1; font-family: var(--mono); font-size: 14px; font-weight: 600; text-align: right; }
.pl-rate.is-warn { color: var(--warn); }
.pl-rate.is-bad { color: var(--danger); }
.pl-sub { grid-column: 1 / -1; grid-row: 2; font-size: 11.5px; color: var(--ink-3); line-height: 1.7; }
.pl-qualitys { grid-column: 1 / -1; grid-row: 3; display: flex; flex-wrap: wrap; gap: 6px; margin-top: 4px; }
.qchip {
  font-size: 11px; padding: 1px 7px; border-radius: var(--radius-sm);
  background: var(--panel-3); border: 1px solid var(--line-2); color: var(--ink-2);
}
.pl-error { padding: 18px 16px; }
.pl-grid[data-state="error"] { display: block; }

/* ── 检测结果（JS 生成 .diag-*）──────────────────────────────────────── */
.diag-summary {
  display: flex; align-items: center; gap: 9px;
  margin-top: 14px; padding: 11px 12px; border-radius: var(--radius-md);
  font-size: 13px; font-weight: 500;
  border: 1px solid var(--line-2); background: var(--panel-2); color: var(--ink-2);
}
.diag-summary svg { width: 16px; height: 16px; flex: none; }
.diag-summary.is-ok { background: var(--ok-tint); border-color: var(--ok-line); color: var(--ok); }
.diag-summary.is-bad { background: var(--danger-tint); border-color: var(--danger-line); color: var(--danger); }
.diag-list { margin-top: 10px; }
.diag-item {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 11px 2px; border-bottom: 1px solid var(--line);
}
.diag-item:last-child { border-bottom: 0; }
.diag-item svg { width: 15px; height: 15px; flex: none; margin-top: 2px; }
.diag-item.ok svg { color: var(--ok); }
.diag-item.fail svg { color: var(--danger); }
.diag-item .dt { font-size: 12.5px; color: var(--ink-1); }
.diag-item .dd { font-family: var(--mono); font-size: 11.5px; color: var(--ink-3); margin-top: 3px; }

/* ── 骨架屏（JS 生成 .skel-*）──────────────────────────────────────────
   原来是 90deg 的浅灰渐变 + 横向平移。**零渐变**是这一版定的规则，
   所以改成平灰 + 透明度过呼吸：一样表示"在加载"，不靠渐变。 */
.skel-row { display: flex; gap: 12px; padding: 12px 16px; }
.skel {
  display: block; height: 12px; border-radius: 3px;
  background: var(--panel-3);
  animation: skel 1.4s ease-in-out infinite;
}
.skel-row .skel { flex: 1; }
.skel-card { display: grid; gap: 10px; }
@keyframes skel { 0%, 100% { opacity: 1; } 50% { opacity: .48; } }
@media (prefers-reduced-motion: reduce) { .skel { animation: none; } }

/* ── 购买与激活（JS 生成 .ent-* / .buy-item）────────────────────────── */
.ent-summary { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 14px; }
.ent-cell {
  padding: 12px 14px; border: 1px solid var(--line);
  border-radius: var(--radius-md); background: var(--panel-2);
}
.ent-cell .k { font-size: 11.5px; color: var(--ink-3); }
.ent-cell .v { font-size: 15px; font-weight: 700; margin-top: 7px; }

.buy-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 14px; padding: 16px; }
.buy-item {
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--panel); padding: 15px; display: flex; flex-direction: column;
  text-align: left;   /* 卡片里的额度/时长/价格一律左对齐（与方案表同一套读法） */
}
/* 「最划算」那一档：深墨描边，不是彩色底（一个视图只有一个主操作）*/
.buy-item.is-featured, #plan-grid .plan.is-featured { border-color: var(--ink-1); }
.buy-item .plan-flags, #plan-grid .plan-flags { margin-top: 4px; }
/* 说明文字留两行高：五张卡的说明长短不一，不留高度的话下面的规格行、
   价格块每张都错开一点点，五张并排看就是「没对齐」。 */
/* 12px 字 × 1.6 行高 × 2 行 = 38.4px，取 3.3em（39.6px）刚好盖住两行 ——
   取 3.1em 时一行说明的卡片只有 37.2px，五张并排会差出 4px。 */
.buy-item .plan-desc, #plan-grid .plan .plan-desc { margin: 6px 0 10px; min-height: 3.3em; }

/* 购买卡里**必须显示字段名**：方案表靠列表头说明「这列是什么」，卡片没有表头，
   只剩「不限量」「永久」这种没头没尾的值 —— 看起来就像排版坏了。
   所以这里显式打开 plan-cell-label，并把每一行做成「名称左、值右」的规格行。 */
.buy-item .plan-cell, #plan-grid .plan .plan-cell {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  padding: 8px 0; margin-bottom: 0; font-size: 12.5px;
  border-bottom: 1px solid var(--line);
}
.buy-item .plan-cell:last-of-type, #plan-grid .plan .plan-cell:last-of-type { border-bottom: 0; }
.buy-item .plan-cell-label, #plan-grid .plan .plan-cell-label {
  display: block; font-size: 11.5px; color: var(--ink-3);
  white-space: nowrap; flex: none;   /* 字段名不许折行（「调用额/度」那种断法最显粗糙） */
}
.buy-item .plan-cell strong, #plan-grid .plan .plan-cell strong { font-weight: 500; color: var(--ink-1); flex: 1; text-align: right; }

/* 价格单独成块：上边线与规格行分开，标签在上、数字在下 */
.buy-item .plan-price, #plan-grid .plan .plan-price {
  display: block; margin: 12px 0 0; padding-top: 12px;
  font-size: 26px; text-align: left;
  border-top: 1px solid var(--line-2);
  /* 清掉「落地页方案表」那几条规则：那张表在窄屏把价格排到第二列、右对齐，
     还带一个 align-self: center —— 在卡片这种 flex 列里 align-self 是**水平居中**，
     会把「方案价格 / ¥10.1」挪到卡片中间。 */
  grid-column: auto; grid-row: auto; align-self: stretch;
}
.buy-item .plan-price .plan-cell-label, #plan-grid .plan .plan-price .plan-cell-label { display: block; margin-bottom: 3px; }
.buy-item .plan-price .cur, #plan-grid .plan .plan-price .cur { font-size: 15px; }

/* ── 价钱一行：现价 / 划线原价 /「省 N%」────────────────────────────────
   原价由管理员在后台填（`originalPrice`），**只在高于现价时才画** —— 它是拿来
   做对照的，不是第二个价格：所以压成弱灰 + 细删除线，别跟现价抢。
   真正「醒目」的部分交给折扣标签：状态红是整页唯一会用在价格上的颜色，
   而它只是一枚 4px 直角小标签（规则⑤：状态色只做小标记，不做大面积填充）。 */
.price-line { display: flex; align-items: baseline; flex-wrap: wrap; gap: 3px 8px; }
.price-was {
  font-size: 14px; font-weight: 500; color: var(--ink-3);
  text-decoration: line-through; text-decoration-thickness: 1px;
  font-family: var(--mono); font-variant-numeric: tabular-nums;
}
.price-off {
  font-size: 11px; font-weight: 700; letter-spacing: .02em;
  padding: 1px 6px; line-height: 1.6;
  border-radius: var(--radius-sm);
  background: var(--danger-tint); border: 1px solid var(--danger-line); color: var(--danger);
  /* 基线对齐会让小标签沉到数字底部，看上去像掉下去了；按中线放更稳 */
  align-self: center;
}

/* 按钮吸底：五张卡的说明行数不同，不吸底就会高高低低。
   注意 CTA 是 .btn.btn-primary.btn-block（不是 .btn-select-plan），别再写错选择器。 */
.buy-item .plan-foot, #plan-grid .plan .plan-foot {
  margin-top: auto; padding-top: 14px;
  align-self: stretch; width: 100%;
}
.buy-item .plan-foot .btn, #plan-grid .plan .plan-foot .btn { width: 100%; }

/* ── 弹窗 / 提示 / 二维码 ────────────────────────────────────────────── */
.modal {
  position: fixed; inset: 0; z-index: 90;
  display: flex; align-items: center; justify-content: center; padding: 20px;
  background: rgba(15, 23, 42, .45);
}
.modal-card {
  width: 100%; max-width: 440px; max-height: calc(100dvh - 40px);
  overflow-y: auto; background: var(--panel);
  border: 1px solid var(--line-2);
  border-radius: var(--radius); box-shadow: var(--shadow-2);
}
.modal-card-sm { max-width: 400px; }
.modal-head {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 12px 16px; border-bottom: 1px solid var(--line);
  position: sticky; top: 0; background: var(--panel); z-index: 1;
}
.modal-head h3 { font-size: 15px; font-weight: 700; letter-spacing: -.01em; }
.modal-body { padding: 16px; }
.modal-actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 16px; }
.confirm-text { font-size: 13px; line-height: 1.8; color: var(--ink-2); }
.qr-box {
  display: flex; align-items: center; justify-content: center;
  min-height: 200px; padding: 14px; margin-bottom: 12px;
  background: var(--panel-2); border: 1px solid var(--line); border-radius: var(--radius-md);
}
.qr-box svg, .qr-box img, .qr-box canvas { max-width: 100%; height: auto; }


.toast-stack {
  position: fixed; left: 50%; bottom: calc(24px + env(safe-area-inset-bottom, 0px));
  transform: translateX(-50%); z-index: 120;
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  pointer-events: none; width: calc(100% - 32px); max-width: 420px;
}
/* Toast 也是 4px 直角（原来 999px 胶囊）*/
.toast {
  padding: 10px 14px; border-radius: var(--radius-sm);
  background: rgba(17, 24, 39, .95); color: #fff; font-size: 12.5px; font-weight: 500;
  box-shadow: var(--shadow-2); animation: toast-in .18s ease-out;
}
.toast-error { background: #7f1d1d; }
.toast-ok { background: #064e3b; }
/* 警告（比如「需要登录后才能购买」）：info 的灰太弱、error 的红像是出错了 */
.toast-warn { background: #78350f; }
@keyframes toast-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }

/* 窄屏登录浮层的遮罩（浮层本身见下面的窄屏段） */
.auth-scrim {
  position: fixed; inset: 0; z-index: 60; display: none;
  background: rgba(15, 23, 42, .45);
}
.app.is-auth-open .auth-scrim { display: block; }

/* 首屏占位只在「正在载入」时出现：会话回来之后两个视图之一接管页面。
   漏了这条，页首会一直挂着一行「正在载入…」（载入态那条规则只管视图，不管占位）。 */
.app[data-view="auth"] #view-boot,
.app[data-view="app"] #view-boot { display: none; }

/* 登录入口与落地页导航只属于未登录的落地页；进了控制台让位给账号菜单 ——
   否则顶栏同时出现「登录 / 注册」和账号按钮，还把品牌名挤成竖排。 */
.app[data-view="app"] .mobile-auth-trigger { display: none; }
.app[data-view="app"] .appbar-links { display: none; }

/* 反过来：**控制台的区块导航不许出现在落地页**。
   dash 那一排是控制台的东西（账号与安全 / 订单与流水 / 购买与激活…），
   未登录的访客不该看见，更不该点着它把地址栏写成 `#/account`、
   顺带把某个 `.pane` 预先切走（登录后该落在 `#/buy` 的基线就被它干扰了）。
   `#app` 上的 `data-view` 是 app.js 一直在写的（loading / auth / app），
   所以这条纯 CSS 规则跟着登录态自动生效，不用新增 JS —— 也就不用碰 app.js。 */
.app[data-view="auth"] .dashes { display: none; }
/* 状态胶囊与账号芯片同样只在控制台里出现；落地页那一份状态由页脚承担。 */
.app[data-view="auth"] .appbar-context,
.app[data-view="auth"] #account-chip { display: none; }

/* 组件里的 display: flex / grid 会盖掉浏览器默认的 [hidden] { display: none }
   （同权重时作者样式压过 UA 样式），表现就是**页面一打开所有模态框都显示**。
   2026-09-20 用无头浏览器实测抓到过，别再删这条。 */
[hidden] { display: none !important; }

/* 会话回来之前两个视图都不画：否则刷新时先按「未登录」画出落地页，再切成控制台 ——
   单页应用的经典闪屏。 */
.app[data-view="loading"] #view-auth,
.app[data-view="loading"] #view-app { display: none; }

/* 未登录时没有底部标签栏，Toast 不需要让位 */
.app[data-view="auth"] .toast-stack { bottom: calc(24px + env(safe-area-inset-bottom, 0px)); }

/* 窄屏登录浮层打开时把控制台盖住（否则底栏会浮在浮层之上）*/
.app.is-auth-open .appbar { display: none; }

/* ══════════════════════════════════════════════════════════════════════════
   「更多」底部面板 + 底部标签栏（桌面不参与）
   ══════════════════════════════════════════════════════════════════════════ */
.sheet-scrim, .sheet { display: none; }

/* ══════════════════════════════════════════════════════════════════════════
   响应式
   ══════════════════════════════════════════════════════════════════════════ */

/* ── 窄屏（手机，≤679px 与 app.js 的 TABBAR_LAYOUT 同一断点）──────────────
   手机上地址栏收放、下拉刷新都依赖**文档本身能滚**，把 shell 钉死反而别扭，
   所以这里把滚动权交回文档（反向覆盖桌面那套 app shell），全页仍然只有一条滚动条。
   导航按 C 的两件套重做：底部标签栏（图标 + 文字）+「更多」底部面板；
   左栏整条撤掉 —— 手机上它没有文字、还和顶栏重复，导航由底栏与面板承担。 */
@media (max-width: 679px) {
  html, body { height: auto; }
  body { display: block; overflow: visible; }

  #app { display: block; min-height: 0; }
  .page { overflow: visible; scrollbar-gutter: auto; }

  /* 窄屏：网格退回**单列**，但**必须留着 `display: grid`** ——
     写成 `display: block` 会让上面那条 `gap: 14px` 彻底失效（block 布局不认识 gap），
     于是卡片一张张贴在一起、边框叠边框，看起来就是"没有间距"。
     2026-09-27 用户实拍反馈的正是这个：手机上 7 个页面里有 6 个的最小竖向间距是 0px。

     第二件事同样重要：**把一切跨列收回 `auto`**（不能写成 `1 / -1`）。
     `grid-column: span N` 在单列网格里会**生成隐式列**，而那些隐式列在"显式网格"之外 ——
     `1 / -1` 的 `-1` 只指到显式网格的最后一条线（单列时就是第 2 条），于是只覆盖 1 列。
     实测：`1fr` 那一列被挤到 **10px**，隐式列吃掉 194px，「今日调用 / 最近一次调用 /
     需要帮助」三张卡就变成 10px 宽、内容溢出。收回 `auto` 之后不再有隐式列。
     规则要带上透过 `display: contents` 包裹层（.ov-grid / .ov-main / .ov-side）
     露出来的那些卡片，否则它们仍按 `.span-*` 跨列。 */
  .pane { grid-template-columns: minmax(0, 1fr); }
  .pane > *,
  .pane .ov-grid > *,
  .pane .ov-main > *,
  .pane .ov-side > * { grid-column: auto; min-width: 0; }
  .dashes { display: none; }

  .appbar { padding: 0 12px; gap: 10px; position: sticky; top: 0; }
  .appbar-links { display: none; }
  .brand-text small { display: none; }
  .appbar-actions { margin-left: auto; gap: 2px; }
  /* 窄屏顶栏只留「品牌 + 刷新 + 账号」：服务状态在总览里看得到，
     通知入口本轮还没做，不摆空壳。 */
  .appbar-context, .appbar-actions .iconbtn[title="通知"] { display: none; }
  .user-chip { max-width: 116px; }

  .landing { padding: 20px 16px 0; }
  .mast { grid-template-columns: minmax(0, 1fr); gap: 24px; }
  .mast-main h1 { font-size: 27px; }
  .trust { grid-template-columns: minmax(0, 1fr); }
  .trust > div { border-right: 0; border-bottom: 1px solid var(--line); }
  .trust > div:last-child { border-bottom: 0; }
  .section { padding-top: 28px; }
  .section-heading { flex-direction: column; align-items: flex-start; gap: 8px; }
  .section-heading h2 { font-size: 19px; }

  /* 登录卡在窄屏是浮层：默认藏起来，is-auth-open 时升起 */
  .auth-card {
    position: fixed; left: 12px; right: 12px; bottom: 0;
    max-height: 86dvh; overflow-y: auto; display: none;
    border-radius: var(--radius) var(--radius) 0 0;
    padding-bottom: calc(20px + env(safe-area-inset-bottom, 0px));
    transform: translateY(110%); transition: transform .22s ease-out;
  }
  /* 层级写在这条上：契约会比对「Toast 层 > 登录浮层」，同时保证
     浮层打开时购买引导（Toast）仍然看得见。 */
  .app.is-auth-open .auth-card { display: block; transform: none; z-index: 70; }
  .mobile-auth-close { display: inline-flex; }

  /* 档位卡在窄屏：一列铺满（落地页与控制台「购买与激活」共用同一套卡片规则）。
     「表格行堆叠成卡片」那套 grid-column / grid-row 搬家已经删掉 ——
     卡片本身就是 flex 列，字段名由 .plan-cell-label 提供，不需要再搬。 */
  .plan-grid { grid-template-columns: 1fr; }
  .plan { padding: 14px; }
  .plan-cell { font-size: 12.5px; }
  .plan-price { font-size: 22px; }
  .plan .btn-select-plan { min-height: 44px; }
  .plan-selection { padding: 14px; gap: 12px; }
  .selection-copy { flex: 1 1 100%; }
  .selection-price { margin-left: auto; }
  .plan-selection .btn-primary { width: 100%; }

  .matrix { grid-template-columns: 100px repeat(5, minmax(0, 1fr)); }
  .matrix > div { padding: 9px 6px; font-size: 11.5px; text-align: center; }
  .matrix-hd:first-child, .matrix-rn { text-align: left; }

  .store-footer { grid-template-columns: minmax(0, 1fr); gap: 18px; padding-bottom: 28px; }

  /* ── 控制台：左栏整条撤掉，改底部标签栏 ─────────────────────────────
     app.js 的标签栏机制一行没动：它照样把 `.side-item.is-active` 打在
     对应的 `.side-item` 上（`buy`/`orders` 在底栏布局下映射到 `account`），
     这里只是把其中 4 格 + 「更多」摆到底部，其余藏进面板。 */
  .shell { display: block; min-height: 0; height: auto; }
  /* 窄屏把高度链解开：`.page` 在这边是 `overflow: visible`、文档滚动，
     所以 `#view-app` / `.shell` 都要退回 auto —— 否则内容会被 100% 高度裁掉。 */
  .app[data-view="app"] #view-app { height: auto; }
  .app[data-view="app"] .shell { height: auto; }
  .side {
    position: fixed; left: 0; right: 0; bottom: 0; top: auto;
    width: auto; z-index: 80;
    flex-direction: row; align-items: stretch; gap: 0;
    padding: 3px 2px calc(3px + env(safe-area-inset-bottom, 0px));
    border-right: 0; border-top: 1px solid var(--line);
    background: var(--panel);
    transition: none;
  }
  .side:hover, .side:focus-within { width: auto; }   /* 底栏通栏；桌面那组 hover 变宽已删，这条只是兜底 */
  /* 底栏里分组标题与页脚一律不显示。
     **必须把 `:hover` / `:focus-within` 那两条也写上**：上面给桌面展开态写的
     `.side:focus-within .side-group { display: block }`（特异性 0,3,0）比这里的
     `.side-group`（0,1,0）高，只写一条盖不掉 —— 结果是**点底栏任意一格**（按钮获得焦点）
     就会让「我的服务 / 账单与账户 / 快捷」挤进底栏那一行，把标签格从 77px 压到 41px，
     并排看就是排版坏了。2026-09-27 用户实拍反馈的正是这个。
     上一版已经为 `width` 挡过一次同样的坑（上面那行），这次一并补齐。 */
  .side .side-group, .side .side-foot,
  .side:hover .side-group, .side:focus-within .side-group,
  .side:hover .side-foot, .side:focus-within .side-foot { display: none; }

  /* 底栏只放 4 个最常用的去处（总览 / Key / 用量 / 平台）+「更多」：
     购买与订单在 `account` 那一格/面板里，快捷动作在总览卡片里都有。
     这里按 **data-section 逐个点名**，不用 :nth-child —— `.side` 里还夹着
     `.side-group` 与 `.side-foot`，nth-child 的序号跟"第几格"根本不是一回事
     （第一版就是这么错的：`.side .side-item:nth-child(n+7)` 把「更多」也藏了，
     因为它与 `.side .sheet-more` 同权重、却写在后面）。 */
  .side .side-item[data-section="buy"],
  .side .side-item[data-section="orders"],
  .side .side-item[data-section="account"],
  .side .side-quick {
    display: none;
  }
  .side .side-item[data-section="overview"],
  .side .side-item[data-section="key"],
  .side .side-item[data-section="usage"],
  .side .side-item[data-section="platforms"] {
    display: flex;
  }
  /* 底栏格子：**把 `:hover` / `:focus-within` 两条也写进选择器里**。
     基类曾有一组 `.side:hover .side-item { justify-content: flex-start; padding: 0 10px }`
     （给桌面收起态用的），特异性比这条高 —— 手机一点格子（按钮获得焦点）就命中，
     于是底栏的 `justify-content: center; padding: 6px 2px 5px` 被换掉，
     **图标与文字上移 9px**（实测：图标距格子顶 9px → 0px）。2026-09-27 用户反馈"会上移"。
     那组规则已随"桌面常驻展开"删掉，这里同时显式挡住，防止将来被加回来。 */
  .side .side-item,
  .side:hover .side-item, .side:focus-within .side-item {
    flex: 1; flex-direction: column; gap: 3px; justify-content: center;
    min-height: var(--tabbar-h); margin: 0; padding: 6px 2px 5px;
    border-radius: 0; font-size: 10.5px; font-weight: 600;
    text-align: center; letter-spacing: .01em; position: relative;
    background: none; color: var(--ink-4);
  }
  .side-item svg { width: 21px; height: 21px; color: currentColor; }
  .side-item span { flex: none; }
  .side-item em { display: none !important; }
  /* 标签文字：窄屏只显示短标签（「Key」「用量」「平台」），图标 + **文字**都要在。
     基类现在默认显示长标签（桌面常驻展开），所以这里**必须显式藏掉长标签** ——
     否则一格会同时出现「用量与额度」与「用量」，挤成一团。 */
  .side-label { display: none; }
  .side-label-short { display: block; white-space: nowrap; }
  /* 当前项：深墨 + 顶部指示线（灰阶里"重"就是强调，不需要上色）*/
  .side-item:hover { background: var(--panel-3); }
  .side-item.is-active { background: none; color: var(--ink-1); font-weight: 700; }
  .side-item.is-active svg { color: var(--ink-1); }
  .side-item.is-active::before {
    content: ""; position: absolute; top: -3px; left: 50%; transform: translateX(-50%);
    width: 24px; height: 2px; background: var(--ink-1);
    border-radius: 0 0 2px 2px;
  }
  .side .side-quick { display: none; }

  /* 「更多」：底栏第 5 格。当前页不在上面那 4 格里时，sheet.js 给它加
     `.is-current`（指示线 + 深墨），表示"你正看的那一页在面板里"。
     这里**不用 `:has()`** 反推：同权重同顺序下 Chrome 对 `:not(:has(...))`
     的层叠结果和预期不一致（实测该规则匹配上了却不生效），高亮宁可显式给。 */
  .sheet-more { display: flex; }
  .sheet-more.is-current { color: var(--ink-1); font-weight: 700; }
  .sheet-more.is-current svg { color: var(--ink-1); }
  .sheet-more.is-current::before {
    content: ""; position: absolute; top: -3px; left: 50%; transform: translateX(-50%);
    width: 24px; height: 2px; background: var(--ink-1);
    border-radius: 0 0 2px 2px;
  }

  /* ── 「更多」底部面板 ─────────────────────────────────────────────── */
  .sheet-scrim {
    display: block; position: fixed; inset: 0; z-index: 95;
    background: rgba(15, 23, 42, .45);
  }
  .sheet {
    display: block; position: fixed; left: 0; right: 0; bottom: 0; z-index: 96;
    background: var(--panel); border-top: 1px solid var(--line);
    border-radius: 16px 16px 0 0;
    box-shadow: 0 -14px 44px -14px rgba(15, 23, 42, .34);
    max-height: 82dvh; overflow: auto; overscroll-behavior: contain;
    padding: 6px 0 calc(14px + env(safe-area-inset-bottom, 0px));
    animation: sheet-up .22s cubic-bezier(.16, 1, .3, 1);
  }
  @keyframes sheet-up { from { transform: translateY(24px); opacity: .6; } to { transform: none; opacity: 1; } }
  .sheet .grab { width: 38px; height: 4px; border-radius: 2px; background: var(--line-2); margin: 8px auto 4px; }
  .sheet .hd { padding: 8px 18px 4px; display: flex; align-items: baseline; gap: 8px; }
  .sheet .hd b { font-size: 15px; font-weight: 700; letter-spacing: -.01em; }
  .sheet .hd span { color: var(--ink-3); font-size: 12px; }
  .sheet .micro { padding: 14px 18px 6px; display: block; }
  .sheet .sitem {
    display: flex; align-items: center; gap: 12px; width: 100%; border: 0;
    background: transparent; padding: 13px 18px; font: inherit;
    font-size: 14.5px; font-weight: 500;
    color: var(--ink-2); text-align: left; min-height: 48px;
  }
  .sheet .sitem svg { width: 19px; height: 19px; flex: none; color: var(--ink-3); }
  .sheet .sitem .lbl { flex: 1; }
  /* 当前项：浅灰底 + 3px 深墨左标（不用蓝，也不用大面积色块）*/
  .sheet .sitem.is-current { background: var(--panel-3); color: var(--ink-1); font-weight: 700; box-shadow: inset 3px 0 0 var(--ink-1); }
  .sheet .sitem.is-current svg { color: var(--ink-1); }
  .sheet .sitem:active { background: var(--panel-3); }

  /* 正文给底部标签栏让位（标签栏高 + 安全区），内容不被压住 */
  /* 正文给底部标签栏让位（标签栏高 + 安全区），内容不被压住。
     **窄屏把滚动交回文档**：`.page` 在这边已经 `overflow: visible`，
     `.main` 也必须跟着放开，否则手机上正文区会自己滚、而地址栏收放/下拉刷新依赖文档滚动。 */
  .main { padding: 14px 16px calc(var(--tabbar-h) + 30px + env(safe-area-inset-bottom, 0px));
    overflow: visible; scrollbar-gutter: auto; }
  .pagehead { flex-direction: column; gap: 12px; }
  .acts { width: 100%; }
  .acts .btn { flex: 1; }
  .kpis { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
  .kpi, .usage-cell { padding: 12px 13px; }
  .kpi-value .num, .usage-cell .v { font-size: 22px; }
  .cols-2, .cols-2b { grid-template-columns: minmax(0, 1fr); }
  .ov-grid { grid-template-columns: minmax(0, 1fr); }
  .card-body { padding: 14px; }
  .coderow { flex-wrap: wrap; }
  .coderow input { flex: 1 1 100%; }
  .coderow .btn { flex: 1; }
  .kv-row { grid-template-columns: 88px minmax(0, 1fr); gap: 10px; }
  .pl-card { grid-template-columns: minmax(0, 1fr) 62px; gap: 4px 12px; }
  .pl-name { grid-column: 1; grid-row: 1; }
  .pl-rate { grid-column: 2; grid-row: 1; }
  .pl-card > .pl-bar { grid-column: 1 / -1; grid-row: 2; }
  .pl-sub { grid-column: 1 / -1; grid-row: 3; }
  .pl-qualitys { grid-column: 1 / -1; grid-row: 4; }
  .table th, .table td { padding: 11px 12px; }
  /* 五列以上的表在 390px 里一定会把首列挤成一字一行（「失/败/构/成」那种）。
     宁可横向滚动，也别把字压成竖排 —— 表套在 .table-wrap 里，滚动是现成的。
     滚动发生在**卡片内**，不把文档撑宽（README 第十一节那条 493px 的教训）。 */
  .table-wrap .table:not(.table-stack) { min-width: 560px; }

  /* 账单在窄屏换成「一行一单」的卡片式：四到六列横着塞进 390px 只会互相挤，
     竖着读才是手机上愿意看的样子（字段名从 td 的 data-label 取）。 */
  .table-stack { min-width: 0; }
  .table-stack thead { display: none; }
  .table-stack tbody tr { display: block; padding: 13px 14px; border-bottom: 1px solid var(--line); }
  .table-stack tbody tr:last-child { border-bottom: 0; }
  .table-stack td {
    display: grid; grid-template-columns: 72px minmax(0, 1fr); gap: 10px;
    padding: 3px 0; border: 0; font-size: 12.5px; text-align: left;
  }
  .table-stack td::before { content: attr(data-label); color: var(--ink-3); font-size: 11.5px; }
  /* 空字段（比如运营赠送之外的订单没有备注）整行收起，不留一个空标签 */
  .table-stack td:empty { display: none; }
  .table-stack td.mono { overflow-wrap: anywhere; }
  .modal { align-items: flex-end; padding: 0; }
  .modal-card { max-width: none; border-radius: var(--radius) var(--radius) 0 0; max-height: 88dvh; }
  .modal-body { padding-bottom: calc(16px + env(safe-area-inset-bottom, 0px)); }
  .modal-actions .btn { flex: 1; }
  .toast-stack { bottom: calc(var(--tabbar-h) + 16px + env(safe-area-inset-bottom, 0px)); }
  .activate-form .field { flex: 1 1 100%; }
  .activate-form .btn { width: 100%; }
  .buy-grid { padding: 14px; grid-template-columns: minmax(0, 1fr); }
}

/* ── 中等屏（平板 / 窄桌面）：侧栏收窄，dash 收起，KPI 三列 ─────────────
   顶栏那一行元素太多：品牌 + 4 个商城链接 + 7 个 dash + 状态 + 账号菜单
   在 1099px 以下装不下（appbar 是 nowrap + overflow:hidden，装不下会被裁掉一截，
   看起来就是"顶栏缺了一块"）。所以按宽度分两级收：先收 dash，再收商城链接。 */
@media (min-width: 680px) and (max-width: 1279px) {
  .dashes { display: none; }
}
@media (min-width: 680px) and (max-width: 1079px) {
  /* 桌面常驻展开：中等宽度也不再压成 64px 图标条（用户明确要求不要收起态）。
     这里保留一条显式声明，避免以后有人以为"这里可以收窄"。 */
  .side { width: var(--side-w); }
  .appbar { gap: 12px; }
  .appbar-links { display: none; }
  /* 总览 KPI 行固定四格（剩余时长 / 今日调用 / 本月·累计 / 平台健康）。
     两列会把「本月·累计」那格压得太窄，四列在这段宽度里又挤 —— 取三列。 */
  .kpis { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .cols-2, .cols-2b { grid-template-columns: minmax(0, 1fr); }
  .ov-grid { grid-template-columns: minmax(0, 1fr); }
  .pl-card { grid-template-columns: 150px minmax(0, 1fr) 60px; gap: 4px 12px; }
  .mast { grid-template-columns: minmax(0, 1fr); gap: 28px; }
  .mast-main h1 { font-size: 32px; }
}

/* ── 超宽屏：内容区限宽**并居中** ────────────────────────────────────────
   只限宽不居中的话，在 3440 这类超宽屏上会变成「左边一条、右边一大片空白」，
   看起来像没做完。居中之后两侧留白对称，与落地页（.landing 也是居中限宽）一致。 */
@media (min-width: 1700px) {
  /* 上限取 2400：1920 / 2560 这类常见宽度下基本铺满，3440 这类超宽屏才收一收 ——
     收到 1640 在超宽屏上会空掉一半，完全铺满又会让表格的列之间拉开一屏的距离。 */
  .main { max-width: 2400px; margin-left: auto; margin-right: auto; }
}

/* ══════════════════════════════════════════════════════════════════════════
   方案 B · 专业订阅（2026-09-29 落地，design/em-ui-2proposals/plan-b-pro-subscribe.html）

   改的是「怎么买」，不是视觉语言：色板、圆角、状态色规则沿用上面的 C 外壳五条。
   四块新东西：
     ① 信任条（.shop-trust）—— 只写产品事实，不放需要后端聚合的指标；
     ② 落地页定价的两种摆法 —— 桌面规格化定价表（复用 .buy-item 的规格行规则，
        零新样式）、手机 segmented + 规格卡 + 其他档位 + 吸底结算栏；
     ③ 结账页（#view-checkout）—— 页面式购买流，取代原「付款过程弹窗」；
     ④ 核验态与开通结果 —— 自动轮询为主、手动兜底；成功页第一位是导入链接。
   断点取 900px：与 setAuthOpen 的 (min-width: 901px) 一致 —— 登录卡在哪个
   布局里、定价就用哪种摆法，两边永远同步。
   ══════════════════════════════════════════════════════════════════════════ */

/* ── ① 信任条：四格事实，深墨数字 + 弱灰说明，状态色不上 ─────────────────── */
.shop-trust {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--panel); overflow: hidden; margin-bottom: 16px;
}
.shop-trust > div { padding: 12px 15px; border-right: 1px solid var(--line); }
.shop-trust > div:last-child { border-right: 0; }
.shop-trust b {
  display: block; font-size: 16.5px; font-weight: 700; letter-spacing: -.01em;
  font-family: var(--mono); font-variant-numeric: tabular-nums;
}
.shop-trust span { display: block; font-size: 11px; color: var(--ink-3); margin-top: 3px; line-height: 1.6; }
@media (max-width: 900px) {
  .shop-trust { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .shop-trust > div { border-bottom: 1px solid var(--line); }
  .shop-trust > div:nth-child(2n) { border-right: 0; }
  .shop-trust > div:nth-last-child(-n + 2) { border-bottom: 0; }
}

/* ── ② 手机端的定价摆法：桌面先一律藏起 ──────────────────────────────────── */
.plan-picker, .plan-detail, .plan-others, .plan-buybar { display: none; }

@media (max-width: 900px) {
  /* 桌面定价表整块让位给 segmented 摆法 */
  #plan-grid { display: none; }

  /* 切档 segmented：整排平铺（档位数 ≤6，一行放得下；真超了就换行） */
  .plan-picker {
    display: flex; flex-wrap: wrap; gap: 4px;
    background: var(--panel-3); border-radius: var(--radius);
    padding: 3px; margin-bottom: 12px;
  }
  .seg-btn {
    flex: 1 1 auto; min-height: 38px; padding: 0 10px;
    border: 0; border-radius: var(--radius-md); background: transparent;
    font: inherit; font-size: 13px; color: var(--ink-2); cursor: pointer;
    white-space: nowrap;
  }
  .seg-btn.is-active {
    background: var(--panel); color: var(--ink-1); font-weight: 700;
    box-shadow: var(--shadow-1);
  }

  /* 所选档规格卡：与结账页「订单内容」同一套 kv 行读法 */
  .plan-detail {
    display: block; background: var(--panel); border: 1px solid var(--line);
    border-radius: var(--radius); padding: 15px 16px 6px; margin-bottom: 12px;
  }
  .plan-detail-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
  .plan-detail-head h3 { font-size: 15.5px; font-weight: 700; }
  .plan-detail-price {
    margin: 8px 0 4px; font-size: 27px; font-weight: 700;
    font-family: var(--mono); font-variant-numeric: tabular-nums;
  }
  .plan-detail-price .cur { font-size: 16px; }
  .plan-detail .plan-desc { min-height: 0; margin-bottom: 8px; }

  /* 其他档位对比：每行 名称 + 折合 + 价钱，点整行切档 */
  .plan-others {
    display: block; background: var(--panel); border: 1px solid var(--line);
    border-radius: var(--radius); overflow: hidden; margin-bottom: 4px;
  }
  .pick-row {
    display: flex; align-items: baseline; gap: 10px; width: 100%;
    min-height: 44px; padding: 9px 14px; border: 0; border-bottom: 1px solid var(--line);
    background: var(--panel); font: inherit; text-align: left; cursor: pointer;
  }
  .pick-row:last-child { border-bottom: 0; }
  .pick-row:active { background: var(--panel-2); }
  .pick-row b { font-size: 13px; font-weight: 600; color: var(--ink-1); flex: none; }
  .pick-row > span:not(.pick-price) { flex: 1; font-size: 11.5px; color: var(--ink-3); }
  .pick-price {
    display: flex; align-items: baseline; gap: 4px; font-size: 14px; font-weight: 700;
    color: var(--ink-1); font-family: var(--mono); font-variant-numeric: tabular-nums;
  }
  .pick-price .cur { font-size: 11px; }
  .pick-price .price-was { font-size: 10.5px; }
  .pick-price .price-off { font-size: 10px; }
  .pick-row em { font-style: normal; font-size: 12px; color: var(--accent); font-weight: 600; flex: none; }

  /* 吸底结算栏：选好档位后常驻在拇指热区。放在 #view-auth 里，登录后随之消失；
     层级低于登录浮层的遮罩（.auth-scrim z-90）—— 结账流程里登录卡内嵌，
     这条栏只在落地页上出现。 */
  .plan-buybar {
    display: flex; align-items: center; gap: 12px;
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 50;
    background: var(--panel); border-top: 1px solid var(--line-2);
    padding: 10px 16px calc(10px + env(safe-area-inset-bottom, 0px));
    box-shadow: 0 -10px 30px -18px rgba(15, 23, 42, .4);
  }
  .buybar-info { flex: 1; min-width: 0; }
  .buybar-info small { display: block; font-size: 10.5px; color: var(--ink-3); letter-spacing: .06em; }
  .buybar-info b { font-size: 13.5px; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .buybar-price { font-size: 17px; font-weight: 700; flex: none; }
  .buybar-price .cur { font-size: 12px; }
  .buybar-price .price-was, .buybar-price .price-off { display: none; }  /* 栏里放不下，划线价只留在规格卡 */
  .plan-buybar .btn { flex: none; min-width: 128px; }
  /* 给吸底栏让出高度，页脚不再被盖住 */
  .landing { padding-bottom: 96px; }
}

/* 定价列的规格行容器与价格单位：规格行本身复用 .plan-cell（与 buy-item 同源），
   这里只给「折合/有效期」这一组一个上边距、给单位一个弱化的落款样式。 */
#plan-grid .plan .plan-spec, .plan-detail .plan-spec { margin-top: 8px; }
.plan-unit { display: block; font-size: 11.5px; color: var(--ink-4); margin-top: 4px; }

/* ── ③ 结账页：页面式购买流（确认商品 → 登录 → 支付）───────────────────── */
.view-checkout { display: block; }
.cko { max-width: 980px; margin: 0 auto; padding: 20px 24px 48px; }
.cko-nav { display: flex; align-items: center; gap: 10px; }
.cko-nav b { font-size: 16px; font-weight: 700; letter-spacing: -.01em; }

.cko-steps {
  display: flex; align-items: center; gap: 0;
  list-style: none; margin: 14px 0 18px; padding: 0;
  counter-reset: cko-step;
}
.cko-steps li { display: flex; align-items: center; font-size: 12.5px; color: var(--ink-3); }
.cko-steps li + li::before {
  content: ""; width: clamp(16px, 6vw, 48px); height: 1px;
  background: var(--line-2); margin: 0 10px;
}
.cko-dot { position: relative; width: 22px; height: 22px; margin-right: 7px; border-radius: 50%;
  background: var(--panel-3); color: var(--ink-3);
  font-size: 11px; font-weight: 600; display: grid; place-items: center; flex: none; }
.cko-dot::before { counter-increment: cko-step; content: counter(cko-step); }
.cko-steps li[data-state="now"] { color: var(--ink-1); font-weight: 600; }
.cko-steps li[data-state="now"] .cko-dot { background: var(--ink-1); color: #fff; }
.cko-steps li[data-state="done"] { color: var(--ok); }
.cko-steps li[data-state="done"] .cko-dot { background: var(--ok-tint); color: var(--ok); border: 1px solid var(--ok-line); }
.cko-steps li[data-state="done"] .cko-dot::before { content: "✓"; }

.cko-grid { display: grid; grid-template-columns: minmax(0, 1.55fr) minmax(260px, 1fr); gap: 16px; align-items: start; }
.cko-main { display: grid; gap: 16px; min-width: 0; }
.cko-side { display: grid; gap: 16px; min-width: 0; }
.cko-side .card { position: sticky; top: 16px; }
.cko-amount { font-size: 30px; font-weight: 700; font-family: var(--mono); font-variant-numeric: tabular-nums; }
.cko-amount .cur { font-size: 17px; }
.cko-rules { margin: 0; padding-left: 18px; font-size: 12.5px; color: var(--ink-2); line-height: 1.9; }
/* 支付须知里那条硬规矩（不得代理 / 中转 / 共享）：红细边 + 左侧红条 + 极浅底，
   与 .auth-error 同一套写法（状态色只做小面积标记，不做整块填充）。
   它是付款前必须先看到的一句话，所以单独成块并放在列表最前面。 */
.cko-warning {
  margin: 0 0 12px; padding: 9px 11px;
  border: 1px solid var(--danger-line); border-left: 2px solid var(--danger);
  border-radius: var(--radius-md); background: var(--danger-tint);
  color: var(--danger); font-size: 12.5px; font-weight: 500; line-height: 1.8;
}

/* 核验态：转圈 + 已等待秒数 + 日志（等价于把后台在干什么翻译给人看）*/
.cko-verify { display: flex; align-items: flex-start; gap: 12px; }
.cko-ring {
  width: 26px; height: 26px; border-radius: 50%; flex: none;
  border: 3px solid var(--accent-tint-2); border-top-color: var(--accent);
  animation: cko-spin 1s linear infinite;
}
@keyframes cko-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .cko-ring { animation: none; border-top-color: var(--accent-tint-2); } }
.cko-verify b { font-size: 14px; font-weight: 700; }
.cko-verify p { font-size: 12px; color: var(--ink-3); margin-top: 3px;
  font-family: var(--mono); font-variant-numeric: tabular-nums; }
.cko-log {
  list-style: none; margin: 12px 0 0; padding: 2px 0;
  border-top: 1px solid var(--line);
  font-size: 11.5px; color: var(--ink-2);
}
.cko-log li { display: flex; gap: 10px; padding: 5px 0; border-bottom: 1px dashed var(--line); }
.cko-log li:last-child { border-bottom: 0; }
.cko-log li .t { flex: none; color: var(--ink-4); font-family: var(--mono); font-variant-numeric: tabular-nums; }
.cko-log li.ok { color: var(--ok); }
.cko-log li.warn { color: var(--warn); }

/* 开通结果：成功条只做一道浅底细边（状态色只做小标记），主角是导入链接 */
.cko-okline {
  padding: 10px 12px; border-radius: var(--radius-md);
  background: var(--ok-tint); border: 1px solid var(--ok-line);
  color: var(--ok); font-size: 13px; font-weight: 600; line-height: 1.7;
}
#cko-receipt .kv-row strong { font-family: var(--mono); font-variant-numeric: tabular-nums; }

@media (max-width: 900px) {
  .cko { padding: 14px 16px 32px; }
  .cko-grid { grid-template-columns: minmax(0, 1fr); }
  .cko-side .card { position: static; }
  .cko-steps li + li::before { width: auto; flex: 1; margin: 0 8px; }
  .cko-steps li { white-space: nowrap; }
  #cko-paycard { order: -1; }   /* 手机上「应付金额 + 支付按钮」排到最上面：拇指热区 */
}

/* 手机规格卡里的规格行：.plan-cell 的作用域只写到 .buy-item / #plan-grid，
   规格卡（.plan-detail）是第三个宿主，这里补上同一套读法（名称左、值右）。 */
.plan-detail .plan-cell {
  display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
  padding: 8px 0; font-size: 12.5px;
  border-bottom: 1px solid var(--line);
}
.plan-detail .plan-cell:last-child { border-bottom: 0; }
.plan-detail .plan-cell-label { font-size: 11.5px; color: var(--ink-3); white-space: nowrap; flex: none; }
.plan-detail .plan-cell strong { font-weight: 500; color: var(--ink-1); flex: 1; text-align: right; }
