:root{
  --page:#e9ebef;
  --bg:#f3f4f7;
  --card:#fff;
  --ink:#1b1d21;
  --ink2:#5f6672;
  --ink3:#97a0ac;
  --line:#eef0f4;
  --line2:#e4e7ec;
  --blue:#2b6bff;
  --blue-d:#1f56e0;
  --blue-soft:#eaf1ff;
  --amber-bg:#fff8e9;
  --amber-ink:#b4740a;
  --amber-line:#f2ddb4;
  --green:#12a150;
  --green-soft:#e8f7ee;
  --red:#e5484d;
  --red-soft:#fdecec;
  --r:16px;
  --shadow:0 6px 20px rgba(24,39,75,.06);
}

*{box-sizing:border-box;-webkit-tap-highlight-color:transparent}
html,body{margin:0;padding:0}
body{
  background:var(--page);color:var(--ink);
  font:400 14px/1.6 -apple-system,BlinkMacSystemFont,"PingFang SC","Hiragino Sans GB","Microsoft YaHei",sans-serif;
}
a{color:inherit;text-decoration:none}
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}
input,select{font:inherit;color:inherit}

/* 作者样式表里的 display 会盖掉 UA 的 [hidden]{display:none}，这里显式兜底。
   凡是靠 hidden 属性收起的元素（弹层/提示条/调试面板）都依赖这条规则。 */
[hidden]{display:none!important}

/* 弹层打开时锁定背景滚动（iOS / 微信 WebView 需要 position:fixed 才稳） */
body.no-scroll{position:fixed;left:0;right:0;width:100%;overflow:hidden}

.ic{width:20px;height:20px;flex:none;display:block}
.ic-14{width:14px;height:14px}
.ic-16{width:16px;height:16px}
.ic-18{width:18px;height:18px}

.app{max-width:520px;margin:0 auto;min-height:100vh;background:var(--bg);padding-bottom:40px}
@media (min-width:560px){
  body{padding:24px 0}
  .app{min-height:auto;border-radius:22px;overflow:hidden;box-shadow:0 18px 50px rgba(24,39,75,.14);padding-bottom:24px}
}

/* ---------------------------------------------------------------- hero */
/* 页头整块的字体由后台「页头字体」决定（--head-font 只在配了非默认值时才有）。
   回退值写 `inherit`：没配过的站点与改造前完全一致，仍然吃 body 的系统字体栈。 */
.hero{background:linear-gradient(160deg,var(--hero-a,#3d7bff) 0%,var(--blue,#2b6bff) 48%,var(--hero-c,#1f56e0) 100%);padding:16px 16px 0;position:relative;font-family:var(--head-font,inherit)}

/* 品牌行：品牌名 + **一组成员**（导航或语言切换），另一组独占第二行。
   谁在行里由后台「页头排列」决定（见下面 data-head 那一段），两种排法共用这段 DOM。

   `flex-wrap:wrap` 不是装饰：320–360px 的机型上「6 字站名 + 3 个入口」加起来确实
   放不下（品牌名不许折行、导航入口也不许折行），没有 wrap 就会把行撑破、整页横向溢出。
   有了它，放不下时导航整组掉到第二行 —— 退化成「品牌名一行、导航一行」，
   正是改造前的样子，而不是一个坏掉的页头。 */
.hero-row{display:flex;flex-wrap:wrap;align-items:center;column-gap:12px;row-gap:4px;padding-bottom:12px}
.brand{display:flex;align-items:center;gap:8px;color:#fff;min-width:0}
.mark{display:flex}
/* 后台上传的 logo：与默认图标同高，白色描边圆角底让它压在品牌色上也不糊。
   ⚠️ 高度定死、宽度必须**自适应**（原来是宽高都锁 20px）：
     横版 logo 锁进正方形后会被 contain 压成一条细线，小尺寸下根本看不出画的是什么
     —— 2026-09-27 经营者反馈「上传的头像看不清」，主因就是这个，放大只是治标。
   `max-width` 兜住超宽图；超过上限时 object-fit 仍按比例缩，不会拉变形（盒子里留白居中）。
   改高度时记得同步 `src/server.ts` 里默认 svg 的尺寸（两处是同一位置的两种形态）。 */
.mark .brand-logo{height:28px;width:auto;max-width:132px;object-fit:contain;border-radius:6px;background:rgba(255,255,255,.16);padding:4px;box-sizing:content-box}
/* 品牌名与副标题**竖排**（原来是横排）。横排时「品牌名 + 副标题 + 导航」三样挤在
   同一个视觉行里：品牌名一折行就变成「跨盘搬运小 / 站」两行，副标题又紧贴导航、
   看着像同一组。竖排后副标题固定在品牌名正下方，主次分明，也不再和导航抢横向空间。 */
.brand-text{display:flex;flex-direction:column;align-items:flex-start;gap:1px;min-width:0}
/*
 * 字号全部走变量 + 回退值。回退值就是改造前写死的那个数 ——
 * 没有后台配置时浏览器吃回退值，渲染结果与改造前逐像素一致。
 * 上下限由服务端 `clampNum` 夹死（14–28 / 10–17 / 16–30 / 11–15），
 * 这里不重复夹：两处都夹，将来改了一处就会出现「界面显示 30、实际画 28」。
 */
.brand-text b{font-size:var(--brand-fs,19px);font-weight:500;letter-spacing:.3px;line-height:1.25;white-space:nowrap}
.brand-text small{font-size:var(--tag-fs,11.5px);line-height:1.3;opacity:.78;white-space:nowrap}
/* 品牌名必须始终可见（备案名称与页面主标识要一致，审核员会比对这一处）。
   副标题竖排后不再和导航抢位置，所以只有在极窄屏才收起它。 */
@media (max-width:300px){.brand-text small{display:none}}

/* 导航容器。两种排列下它的行为不同（见下面 data-head 那一段）：
   · 与品牌同行时：整组跟着品牌名，放不下就在**行内换行**，不做横向滚动；
   · 独占第二行时：整行宽，模块多到放不下时留一手横向滚动兜底。
   `min-width:0` 是让它在 flex 行里可以被压缩/换行，否则内容宽度会顶穿行宽。 */
.hero-links{display:flex;align-items:center;gap:18px;font-size:var(--nav-fs,12.5px);color:rgba(255,255,255,.85);min-width:0}
/* 导航模块为空时（备案模式会剔除视频教程等）整组不占位 */
.hero-links:empty{display:none}
.hero-links a{white-space:nowrap;flex:none}

/*
 * 窄屏（≤389px）把导航**压回品牌行**，不再让它掉到第二行。
 *
 * 起因：2026-09-29 经营者第二次报「安卓页头难看」。上一版（见下面 data-head 那段）
 * 只修了「换行后不悬空」——换行本身还在，而安卓主流正好是 **360dp**。
 *
 * 缺口有多大（都是本机 Chrome 154 + `check-header-layout.mjs` 的实测值，不是估的）：
 *   品牌块固有宽 151px ＋ 行内列间距 12px ＋ 导航整组固有宽 186px ＝ **349px**
 *   360dp 可用 = 360 − 16×2 = **328px** ⇒ 缺 **21px**，导航必然掉到第二行；
 *   390dp（iPhone 主流）可用 358px ⇒ 余 9px ⇒ 苹果那边看着正常。
 * ⚠️ 151 是「在 380px 换行、381px 放得下」之间逐像素夹出来的（1px 精度）。
 * 别用「品牌块在某档量到的宽度」当固有宽 —— 品牌块是 `flex:1 0 auto`，
 * 放得下时它会**撑满剩余空间**（390 档量到 160px 就是这么来的），那个数不是固有宽度，
 * 拿它算缺口会算成 30px。（本行第一版就写错过，注释里的数一样要复核。）
 *
 * 怎么补：判据是「**放不下就缩小**」，不是「放不下就换行」。
 * 导航掉第二行本身没错（320px 上真塞不下），但 360dp 是安卓的绝对主流，不该退化。
 * 缩的量全部来自**间距**（经营者给的方向：把导航间距弄小），字号只封顶、不改比例。
 *
 * 阈值取 389 = 「小于 iPhone 主流（390dp）就收紧」。
 * ⚠️ 这是**有意留的余量**，不是「381–389 真的放不下」——逐像素扫过 360–390：
 * 未收紧时，夹具（6 汉字站名）在 **≤380px 换行、≥382px 放得下**（临界 381）。
 * 不贴着 381 卡，是因为**临界点跟着站名长度走**：站名可在后台改（最多 30 字），
 * 换成更长的名字临界就往上移。贴着实测值卡，等于把「以后改个站名就悄悄塌回去」埋回来。
 * 代价只是 381–389 的机型间距略紧一点（几乎看不出），换来站名变动时的余量。
 *
 * ⚠️ 字号用 `min(var(--nav-fs,12.5px), 12px)` 而**不是**写死 12px：
 * `--nav-fs` 是后台「导航字号」的输出（可配 11–15px，见 src/server.ts 的 brandStyleCss）。
 * 写死会**静默吃掉经营者的设置**（后台显示 15、实际画 12，且不报错）；
 * 用 min() 才是「以配置为准、窄屏封顶」，配置成 11px 时这里也不再往下压。
 *
 * 四个数各自的效果（实测：收紧后 360 档「需要 319px ≤ 可用 332px」）：
 *   · `.hero` 左右 16→14        ⇒ 可用 328→332（+4）
 *   · `.hero-row` 列间距 12→8   ⇒ −4
 *   · `.hero-links` 项间距 18→8 ⇒ −20
 *   · `.hero-links` 字号 12.5→12 ⇒ −6（3 项 × 4 个字）
 * 合计 −30 并把可用 +4，把 21px 的缺口翻成 **13px 余量**。
 *
 * ⚠️ 预算（360dp、收紧后）：可用 332 − 导航 160 − 列间距 8 = **品牌块上限 164px**。
 * 夹具实测 151px、余量 13px。**改站名 / 改 logo / 改副标题前照这个预算估一下**，
 * 超了就会退回「导航另起一行」。
 * ⚠️ 夹具不等于线上：线上是「PanMove + 24 小时智能中转平台」，临界点要按上面这个预算另算
 * （本轮没单独量线上那一组，别把夹具的 381 直接套上去）。
 * 而且 check-header-layout.mjs 用的是夹具站名 ⇒ **线上站名本身变长它抓不到**，
 * 这是本闸门已知的覆盖缺口，已记入待办。
 * 改任何一个数都要重跑那条闸门看实测值，别按这里的算式推断 ——
 * 字体渲染的取整不可预期（本块数字就是照实测回填的）。
 */
@media (max-width:389px){
  .hero{padding-left:14px;padding-right:14px}
  .hero-row{column-gap:8px}
  .hero-links{gap:8px;font-size:min(var(--nav-fs,12.5px),12px)}
}

/* ------------------------------------------------- 页头排列（后台可配） */
/*
 * 由服务端在 `<html>` 上打 `data-head` 驱动（见 src/server.ts 的 renderHtml）。
 *
 * 为什么两套排法共用一段 DOM 而不是服务端挑一套 HTML 渲染：
 *   `#langSwitch` 与导航分散在两个分支里，将来改一处必然漏另一处
 *   （本项目已经吃过一次「只推 public 没推 src」的亏）；
 *   而这两种排法本来就只是 flex 换个顺序。
 *
 * 排法的规则只有两条：
 *   · 在品牌行里的那一组：`flex:0 1 auto`（跟着品牌名，`data-nav` 决定贴左/中/右）；
 *   · 独占第二行的那一组：`flex:1 0 100%`（撑满一行，自己再决定内部怎么对齐）。
 */
/* nav-first（2026-09-27 起的默认）：导航与品牌名同行，语言切换掉到第二行贴右 */
html[data-head="nav-first"] .hero-links{flex:0 0 auto;flex-wrap:wrap;overflow:visible;row-gap:2px;max-width:100%}
html[data-head="nav-first"] .lang-switch{flex:1 0 100%;justify-content:flex-end}

/*
 * ── 导航的垂直位置：与「品牌名那一行」对齐（2026-09-29 第三次页头反馈）──
 *
 * 经营者原话：「导航再往上移动一点，跟品牌名 PanMove 对齐，中英文切换也跟着上移」。
 *
 * 成因：`.hero-row` 的 `align-items:center` 是相对**整个品牌块**居中，而 `.brand` 有两行
 * （品牌名 + 副标题）。导航是单行，被居中后它的中线落在品牌名与副标题**之间** ——
 * 实测读数（`node scripts/check-header-layout.mjs --reverse-valign`，可直接复现）：
 * 旧写法下导航中线 **35.8**、品牌名中线 **27.9** ⇒ 低 **≈8px**。
 * 这就是「看着挂低半行」的来源；窄屏修好同行之后它反而更显眼，
 * 因为此时导航右边紧挨着品牌名，两条基线高下立判。
 *
 * 修法：整行改 `flex-start`（顶对齐），导航再用 margin-top 往下推**半个行高差**，
 * 让两条中线重合。两个行高都能由变量算出来，所以后台改字号（品牌名 14–28 / 导航
 * 11–15）之后对齐关系自动成立，不需要回来改数字：
 *     导航中线   = margin-top + 导航行盒/2
 *     品牌名中线 =                品牌行盒/2
 *     ⇒ margin-top = (品牌行盒 − 导航行盒) / 2
 * 两边行高系数都取 `1.25`（品牌名原本就是 1.25；导航必须**显式**设成同一个值，
 * 否则它吃 `line-height:normal`，行盒高随字体而变，公式就不成立了）。
 * 改完实测：360–520 档中线差 **0.0px**（导航 y 26 → 20）。
 *
 * ⚠️ 第一行的高度仍由品牌块自己决定（两行 ≈39.7px），导航只是在这行里被顶到上方 ——
 *    第二行（语言切换）的位置不动，它要单独提，见下面那个 `min-width:360` 的媒体查询。
 */
html[data-head="nav-first"]:not([data-hero="center"]) .hero-row{align-items:flex-start}
html[data-head="nav-first"]:not([data-hero="center"]) .hero-links{
  align-self:flex-start;
  line-height:1.25;
}

/*
 * 下面两条「偏移」只在**导航一定与品牌名同行**的宽度上生效，共享同一个阈值 `360`。
 *
 * 为什么必须限定：导航一旦换行到第二行，这两条偏移就都成了病 ——
 *   · 导航的 `margin-top` 没有对齐对象，只会把它往下推 ≈4px；
 *   · 语言切换的负 margin 更直接：它上面紧挨着的就是导航，上提会**压住导航**。
 *     这个不是推测，是 `--reverse-narrow` 实测出来的（导航底 80 / 语言顶 78，压 2px）。
 *
 * 阈值取 **360** 而不是实测的换行临界 347（逐像素扫过 330–360：
 * **346 换行、348 同行**，本机 Chrome 154 + 夹具站名）—— 因为那 13px 是留给**站名长度**的：
 * 站名可在后台改（最多 30 字），名字越长临界越高，贴着临界卡等于把「改个站名就压住导航」
 * 埋回来。代价只是 347–359px 那一段（极少数机型）看不到这两条偏移，观感略松，不出错。
 * ⚠️ 真到了「站名长到 360 也换行」那天，闸门的「导航与品牌名同一行」会先红 —— 那时
 * 该处理的是站名长度，而不是把这里的阈值往下调。覆盖缺口的详情见文件头与待办。
 */
@media (min-width:360px){
  html[data-head="nav-first"]:not([data-hero="center"]) .hero-links{
    margin-top:calc((var(--brand-fs,19px) - var(--nav-fs,12.5px)) * 1.25 / 2);
  }
  /* 语言切换跟着上提。它独占第二行，而第二行的位置由**第一行的高度**（品牌块两行
     ≈39.7px）决定 —— 导航被顶上去之后，导航底与语言切换顶之间就空出 ≈24px，
     比改前多 8px，看着更散。经营者说的「也跟着上移」就是指这里。
     ⚠️ 第一行右侧在副标题那一带本来就是空的，提上去不会压到任何东西
     （实测 360 档：导航底 35.7、语言切换顶被提到 53.7，中间留 18px）。 */
  html[data-head="nav-first"]:not([data-hero="center"]) .lang-switch{margin-top:-6px}
}
/* 窄屏导航字号被封顶到 12px（见上面 `@media (max-width:389px)`），公式里的 `--nav-fs`
   也要跟着封顶，否则会多推 0.3px。差 <0.5px，但既然能算准就没必要留误差。 */
@media (min-width:360px) and (max-width:389px){
  html[data-head="nav-first"]:not([data-hero="center"]) .hero-links{
    margin-top:calc((var(--brand-fs,19px) - min(var(--nav-fs,12.5px),12px)) * 1.25 / 2);
  }
}
/*
 * 宽屏上导航贴行右缘 —— 但**由「品牌名吃剩余空间」实现，不再给导航加 `margin-left:auto`**。
 * 这是 2026-09-29 经营者报「安卓页头排得乱、导航跑到品牌名下面」的直接原因，别改回去。
 *
 * 旧写法的注释当初是这么写的：「导航万一因为放不下掉到第二行，`space-between` 会让它
 * 左对齐，而 margin 仍然把它钉在右边」—— 那句话对宽屏成立，在窄屏上却是灾难：
 * 实测线上 360px，「PanMove + 3 个导航项」合计 351px > 可用 328px ⇒ 导航整组掉到第二行，
 * 而 margin 依旧把它钉在行右缘 ⇒ 导航左边缘落在 158px，**左边空出 142px 的空白**，
 * 再下一行还有个贴右的语言切换 —— 三行全右对齐、左边一路空着，整块看着就是歪的。
 * 机型差异也解释得通：iPhone 主流 390/430px 刚好放得下（390px 只剩 7px 余量），
 * 所以只有安卓（主流 360dp）看得见这个塌陷，桌面上更是永远不会出现。
 *
 * 换成「品牌名吃剩余空间」之后，两种落点自动都对：
 *   · 同行：品牌名长到把导航顶到行右缘（与旧观感一致）；
 *   · 换行：品牌名撑满第一行（内容仍左对齐，无视觉影响），导航落到第二行且
 *     **没有 margin** ⇒ 与品牌名左边缘对齐，左边线一致，不再是悬空的一块。
 * 配套的两条：`flex:0 0 auto`（导航不收缩）保证它「要么整组同行、要么整组掉下去」，
 * 不会被压成内部折行；`max-width:100%` 是导航项太多时的兜底 —— 宁可它自己内部折行，
 * 也不横向溢出（这条护栏在 check-header-layout.mjs 里）。
 */
/*
 * ⚠️ 必须排除 `data-hero="center"`：居中模式下 `.brand` 带着
 * `max-width:calc(100% - 100px)`（给绝对定位的语言切换器让位），品牌名一旦能 flex-grow
 * 就会**长到那个上限**，品牌块比内容宽一截、居中的观感跟着偏移。
 * 也就是说这条规则只在「页头对齐 = 左/右」时生效 —— 那正是线上两站的形态（`data-hero="left"`）。
 */
html[data-head="nav-first"]:not([data-hero="center"]) .brand{flex:1 0 auto;min-width:0;max-width:100%}
/* 居中 = 在品牌名右侧的剩余空间里居中（品牌名占着行首，做不到相对整行居中）。
   居中时品牌名**不能**撑开，否则剩余空间被它吃光，导航没有可居中的余地。 */
html[data-head="nav-first"][data-nav="center"] .brand{flex:0 1 auto}
html[data-head="nav-first"][data-nav="center"] .hero-links{flex:1 1 auto}
/* lang-first（改造前的排法，留着当退路）：语言切换留在品牌行右上角，导航独占第二行 */
html[data-head="lang-first"] .lang-switch{margin-left:auto}
html[data-head="lang-first"] .hero-links{flex:1 0 100%;padding-bottom:16px;overflow-x:auto;scrollbar-width:none;-webkit-overflow-scrolling:touch}
html[data-head="lang-first"] .hero-links::-webkit-scrollbar{display:none}

/* ---------------------------------------------------- 页头对齐（后台可配） */
/*
 * 由服务端在 `<html>` 上打 `data-hero` / `data-nav` 属性驱动（见 server.ts 的
 * brandStyleCss）。**没配过就是 `left`，下面这些选择器一条都不命中**，
 * 页头与改造前完全一样。
 *
 * 为什么用属性选择器而不是 CSS 变量：这一组要同时改 `.hero-row` `.brand-text`
 * `.hero-links` `.seo-h1` 四处，属性选择器一条写完；换成变量得往每条规则各塞一个
 * var()，漏一处就出现「品牌名居中了、主标题还杵在左边」这种半截效果 —— 而且不报错。
 *
 * 居中时语言切换器**绝对定位**到右上角：它作为 flex 子项会和居中的品牌名争空间，
 * `justify-content:center` 只能把「品牌名 + 语言」这一对当作整体居中，看着偏左。
 * 抽出去之后品牌名是真居中。
 *
 * ⚠️ 也因此，**居中模式下导航必须回到第二行**（下一条规则）：导航一旦和品牌名
 * 同行，绝对定位的语言切换就会压在它身上 —— 居中的品牌名占着行中心，
 * 右侧只剩一格位置。所以「居中」实际上按 `lang-first` 渲染，
 * 后台「页头排列」在居中模式下不生效（后台界面里也这么写着）。
 * 这几条必须排在 data-head 那一段**之后**：同优先级时后者胜出。
 */
html[data-hero="center"] .hero-row{justify-content:center;position:relative}
html[data-hero="center"] .lang-switch{position:absolute;right:0;top:50%;transform:translateY(-50%)}
html[data-hero="center"] .hero-links{flex:1 0 100%;padding-bottom:16px}
/* 兜底上限（站名最多 30 个字，不留位会一路撑满整行）+ 12px 右侧让位。
   那 12px 是**真机量出来的**，不是估计：390px 视口 + 24px 的 6 字站名时，
   品牌名居中的右边缘落在 282px、语言切换器左边缘 279px —— 压住 3px。
   加右边距让「品牌 + 边距」整体居中，品牌退到 276px，让出 3px 间隙；
   代价是品牌中心比视口中心偏左约 6px（肉眼不可察），
   比「站名压在语言按钮上」好得多。别为了那个精确的中心值把这行删掉。 */
html[data-hero="center"] .brand{max-width:calc(100% - 100px);margin-right:12px}
html[data-hero="center"] .brand-text{align-items:center;text-align:center}
html[data-hero="center"] .hero-links{justify-content:center;justify-content:safe center}
html[data-hero="center"] .seo-h1{text-align:center;margin-left:auto;margin-right:auto}

/* 主标题单独对齐（后台「主标题对齐」）。
   空值不输出 → `data-h1=""` 时这几条不命中，主标题继续跟随「页头对齐」。
   必须排在 data-hero 那两条之后：同优先级、后者胜出，显式选择才能盖掉页头对齐。
   `margin-*` 一起改：`max-width:15em` 让标题块比整行窄，只改 text-align
   会出现「文字靠右、块却还贴在左边」的半截效果。 */
html[data-h1="left"] .seo-h1{text-align:left;margin-left:0;margin-right:auto}
html[data-h1="center"] .seo-h1{text-align:center;margin-left:auto;margin-right:auto}
html[data-h1="right"] .seo-h1{text-align:right;margin-left:auto;margin-right:0}

/*
 * 导航位置。`safe` 是给「模块多到一行放不下」兜底的：普通 center 在内容溢出时
 * 会把左侧那截裁掉且滚不回去，`safe center` 退化成从左边开始排。
 * 认不出 safe 的旧浏览器忽略该条、退回上一条（最坏情况是能横向滚动看全）。
 */
html[data-nav="center"] .hero-links{justify-content:center;justify-content:safe center}
html[data-nav="right"] .hero-links{justify-content:flex-end;justify-content:safe flex-end}

/* 语言切换器：只有一种语言时 app.js 会清空它，:empty 让整个容器不占位 */
.lang-switch{display:flex;align-items:center;gap:5px;color:rgba(255,255,255,.7);flex:none}
.lang-switch:empty{display:none}
.lang-btn{padding:0 7px;border-radius:999px;font-size:11.5px;line-height:18px;letter-spacing:.2px;
  color:rgba(255,255,255,.8);border:1px solid rgba(255,255,255,.3);transition:background .15s,color .15s}
.lang-btn.on{background:#fff;color:var(--blue);border-color:#fff;font-weight:500}

.quota{background:var(--card);border-radius:var(--r);padding:14px 16px 16px;margin-bottom:-28px;box-shadow:0 10px 26px rgba(16,42,96,.16);position:relative;z-index:2}
.quota-head{display:flex;align-items:center;justify-content:space-between;font-size:12.5px;color:var(--ink3);padding-bottom:12px;border-bottom:1px solid var(--line)}
/* 额度状态：**只印一个状态词**（未激活 / 已激活 / 已过期），前面一个小圆点。
   原来是「时钟图标 + 有效期至 + 时间」，标签删掉之后图标也得一起走 ——
   时钟暗示「有一个到期时间」，而这行现在表达的是状态。
   圆点按 `data-state` 上色，且它挂在**父节点**上（`em` 里是文字）。 */
.quota-status{display:flex;align-items:center;gap:6px}
.quota-status em{font-style:normal;color:var(--ink2)}
.qs-dot{width:6px;height:6px;border-radius:50%;background:var(--ink3);flex:none}
/* 未激活：整行淡一档 —— 它不是「可用」，也不需要客户现在处理什么 */
.quota-status[data-state="inactive"] em{color:var(--ink3)}
/* 已激活：点变绿，一眼看出额度是通的 */
.quota-status[data-state="active"] .qs-dot{background:var(--green)}
/* 已过期：点变红。只在有限期配置（QUOTA_VALID_HOURS>0）下才会出现 */
.quota-status[data-state="expired"] .qs-dot{background:var(--red)}
.quota-link{color:var(--blue);font-size:12.5px}
/* 入口容器：备案模式下为空（app.js 不往里面放任何东西），保留右对齐位置 */
.quota-acts{display:flex;align-items:center;gap:8px;min-height:1px}
.quota-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:6px;padding-top:12px;text-align:center}
.quota-grid .q-k{font-size:11px;color:var(--ink3);white-space:nowrap}
.quota-grid .q-v{font-size:16px;font-weight:500;margin-top:2px;letter-spacing:-.2px}
.quota-grid .q-v small{font-size:11px;font-weight:400;color:var(--ink2);margin-left:1px}

/* --------------------------------------------------------------- body */
.body{padding:44px 12px 0}
.card{background:var(--card);border-radius:var(--r);padding:16px;box-shadow:var(--shadow)}
.card-title{margin:0 0 14px;font-size:16px;font-weight:500}

.field{display:flex;align-items:center;gap:10px;background:#f6f7f9;border:1px solid transparent;border-radius:12px;padding:0 14px;height:48px;margin-bottom:10px;color:var(--ink3);transition:border-color .16s,background .16s}
.field:focus-within{background:#fff;border-color:var(--blue);box-shadow:0 0 0 3px rgba(43,107,255,.1)}
.field input{flex:1;min-width:0;border:0;background:none;outline:none;height:100%;font-size:14px;color:var(--ink)}
.field input::placeholder{color:var(--ink3)}
/* 自动填进来的提取码闪一下，让客户看见「它替我填好了」，而不是以为漏填 */
.field input.just-filled{animation:justFilled 1.2s ease}
@keyframes justFilled{0%,60%{background:rgba(46,204,113,.2)}100%{background:transparent}}

.route-row{display:flex;align-items:center;gap:10px;margin:2px 0 12px;font-size:12.5px;color:var(--ink3)}
.route-src{display:flex;align-items:center;gap:6px}
.route-src b{font-weight:500;color:var(--blue);background:var(--blue-soft);padding:3px 8px;border-radius:6px;font-size:12px}
.route-arrow{color:var(--ink3)}
.route-tgt{margin-left:auto;display:flex;align-items:center;gap:6px}
.route-tgt em{font-style:normal;font-weight:500;color:var(--blue);background:var(--blue-soft);padding:3px 8px;border-radius:6px;font-size:12px}

/*
 * 目标网盘点选：两列大按钮。
 *
 * 这块是整个流程里客户唯一要做的选择，所以按钮做大（46px 高、14.5px 字），
 * 并且**只有这一处入口** —— 上面那条「快捷组合」横滑条已删除（见 index.html
 * 里的说明：来源由链接自动识别，两两配对是重复信息，20 条还必然溢出）。
 *
 * 用 grid 而不是 flex-wrap：五个网盘时 flex 会排成 4+1（最后一行一个孤零零
 * 撑满半宽），grid 两列看起来是整齐的 2/2/1。
 */
.tgt-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;margin:0 0 10px}
.pill{position:relative;height:46px;border:1px solid var(--line2);border-radius:11px;background:#fbfcfd;font-size:14.5px;color:var(--ink2);transition:background .16s,border-color .16s,color .16s}
.pill.on{border-color:var(--blue);background:var(--blue-soft);color:var(--blue);font-weight:500}
/* 选中打勾：光靠「变蓝」对中老年客户不够明确，加个勾一眼就知道选的是它 */
.pill.on::after{content:"";position:absolute;right:11px;top:50%;width:5px;height:10px;margin-top:-7px;border:2px solid var(--blue);border-top:0;border-left:0;transform:rotate(45deg)}
/*
 * 与链接来源相同的那一家：直接禁掉。
 * 不禁的话客户得点一下才知道不行，而下面还要再用一行红字解释一遍
 * 「源网盘与目标网盘不能相同」—— 一个本来可以不让它发生的事故。
 */
.pill[disabled]{background:#f2f4f7;color:var(--ink3);border-style:dashed;cursor:not-allowed}
.mtx{margin:0 0 14px;font-size:11.5px;color:var(--ink3);line-height:1.6}

.hint{margin:0 0 10px;font-size:12.5px;color:var(--red);background:var(--red-soft);padding:9px 12px;border-radius:10px}

.btn{display:flex;align-items:center;justify-content:center;gap:8px;width:100%;height:48px;border-radius:12px;font-size:15.5px;font-weight:500;transition:filter .16s,opacity .16s}
.btn.primary{background:var(--blue);color:#fff;box-shadow:0 6px 16px rgba(43,107,255,.28)}
.btn.primary:active{filter:brightness(.94)}
.btn[disabled]{opacity:.5;pointer-events:none;box-shadow:none}

.notice{display:flex;align-items:center;gap:7px;background:var(--amber-bg);color:var(--amber-ink);border-radius:12px;padding:12px 14px;font-size:12.5px;margin:14px 0 18px}
/* 额度未激活 / 已过期：同一条通知形态，但描边加重一档 —— 它是「现在做不了事」，
   和下面那条「速度慢怎么办」的提示不是一个量级，不能长得一模一样。 */
.notice-alert{border:1px solid var(--amber-line);font-weight:500}

.list-head{display:flex;align-items:baseline;justify-content:space-between;padding:0 4px 12px}
.list-head h2{margin:0;font-size:17px;font-weight:500}
.list-head .count{font-size:12.5px;color:var(--ink3)}

.field.search{border-radius:24px;height:44px}

.tabs{display:flex;gap:20px;overflow-x:auto;scrollbar-width:none;padding:4px 4px 0;margin-bottom:12px;border-bottom:1px solid var(--line2)}
.tabs::-webkit-scrollbar{display:none}
.tabs button{white-space:nowrap;padding:8px 0 12px;font-size:14px;color:var(--ink2);position:relative}
.tabs button.on{color:var(--blue);font-weight:500}
.tabs button.on::after{content:"";position:absolute;left:50%;bottom:-1px;transform:translateX(-50%);width:22px;height:3px;border-radius:2px;background:var(--blue)}

/* --------------------------------------------------------------- task */
.task-list{display:flex;flex-direction:column;gap:12px}
.task{background:var(--card);border-radius:14px;padding:14px;box-shadow:var(--shadow)}
.tk-top{display:flex;align-items:center;gap:8px;font-size:12.5px;color:var(--ink3)}
.tk-id{color:var(--ink2);font-variant-numeric:tabular-nums}
.tk-keep{display:flex;align-items:center;gap:3px;color:var(--ink3);font-size:12px;margin-left:auto}
.badge{font-size:12px;padding:3px 10px;border-radius:999px;font-weight:500;white-space:nowrap}
.badge.s-running{background:var(--blue);color:#fff}
.badge.s-pending{background:#fff1e2;color:#c96a09}
.badge.s-done{background:var(--green);color:#fff}
.badge.s-failed{background:var(--red);color:#fff}
.badge.s-canceled{background:#eef0f4;color:var(--ink2)}
.badge.s-cleaned{background:#f1f3f7;color:var(--ink3)}
.badge.s-parsing{background:var(--blue-soft);color:var(--blue)}

.tk-mid{display:flex;align-items:center;gap:8px;margin-top:10px}
.chip{background:var(--blue-soft);color:var(--blue);font-size:12.5px;padding:3px 9px;border-radius:6px;font-weight:500}
.tk-remark{margin-left:auto;display:flex;align-items:center;gap:4px;font-size:12.5px;color:var(--blue)}
.tk-remark.has{color:var(--ink2)}
.tk-remark em{font-style:normal;max-width:130px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

.tk-url{margin-top:9px;font-size:12.5px;color:var(--ink2);word-break:break-all;line-height:1.5;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.tk-url.mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px}

.tk-meta{display:flex;background:#f7f8fa;border-radius:10px;padding:9px 12px;margin-top:11px;font-size:12.5px;color:var(--ink2)}
.tk-meta span{display:flex;align-items:center;gap:6px}
.tk-meta span:last-child{margin-left:auto}
.tk-meta .ic{color:var(--ink3)}

.tk-bar{margin-top:12px}
.tk-bar-head{display:flex;align-items:baseline;font-size:12.5px;margin-bottom:7px}
.tk-bar-head .k{color:var(--ink2)}
.tk-bar-head .sp{color:var(--blue);margin-left:auto;font-weight:500;font-variant-numeric:tabular-nums}
.tk-bar-head .pc{margin-left:12px;color:var(--ink);font-weight:500;font-variant-numeric:tabular-nums;min-width:34px;text-align:right}
.tk-track{height:7px;border-radius:4px;background:#eef1f6;overflow:hidden}
.tk-track i{display:block;height:100%;border-radius:4px;background:linear-gradient(90deg,#5b8cff,#2b6bff);transition:width .3s ease}
.task[data-status="done"] .tk-track i{background:var(--green)}
.task[data-status="failed"] .tk-track i{background:var(--red)}

.tk-error{display:flex;align-items:flex-start;gap:6px;margin-top:11px;font-size:12.5px;color:var(--red);background:var(--red-soft);padding:9px 11px;border-radius:10px}
.tk-scan{display:flex;align-items:center;gap:6px;margin-top:11px;font-size:12.5px;color:var(--ink3);background:#f7f8fa;padding:9px 11px;border-radius:10px}
.tk-share{margin-top:11px;display:flex;align-items:center;gap:8px;background:var(--green-soft);color:#0b7a3c;padding:10px 12px;border-radius:10px;font-size:12.5px}
.tk-share code{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:ui-monospace,Menlo,Consolas,monospace}
.tk-share button{color:var(--green);font-weight:500;white-space:nowrap}

.acts{display:flex;gap:8px;margin-top:13px}
.acts button{flex:1;height:38px;border-radius:10px;font-size:13.5px;border:1px solid;display:flex;align-items:center;justify-content:center;gap:4px;transition:background .16s}
.acts .a-green{color:var(--green);border-color:#b9e6cd}
.acts .a-green:active{background:var(--green-soft)}
.acts .a-red{color:var(--red);border-color:#f6c6c7}
.acts .a-red:active{background:var(--red-soft)}
.acts .a-blue{color:var(--blue);border-color:#c3d7ff}
.acts .a-blue:active{background:var(--blue-soft)}
.acts button[disabled]{opacity:.4;pointer-events:none}
/* 窄屏：任务卡最多三个按钮并排，360px 宽时每个只剩 ~104px，字挤成一团、触控区
   也只有 38px 高。所以放不下就换行，高度抬到 44px（iOS/微信里 44 是可点的下限），
   主操作（生成链接 / 复制链接）独占一行 —— 它才是客户真正要点的那个。 */
@media (max-width:480px){
  .acts{flex-wrap:wrap}
  .acts button{flex:1 1 calc(50% - 4px);min-width:100px;height:44px}
  .acts .a-green{flex-basis:100%}
  .tk-share{padding:11px 12px}
  .tk-share button{font-size:13.5px;padding:3px 5px}
}

.empty{text-align:center;color:var(--ink3);font-size:13px;padding:44px 0 34px}
/* 页面主标题（<h1>）：跟品牌行同在蓝色头图里，白色。
   它首先是给搜索引擎判断页面主题用的，其次才是给访客看的一句话说明。

   这一处是「中英混排的一整句话」（现约 24 字），默认排版会很难看，两条措施缺一不可：

   ① `max-width:15em` —— 不给上限时浏览器会一路往右塞，实测第一行塞到 413px、
      第二行只剩「夸克网盘」三个字（两行宽度比 5.66）。限制到 15em 后不再有孤行。
   ② `word-break:keep-all` —— 光有 ① 会在汉字之间乱断，实测断在「文 / 件转存到…」，
      把一个词拆开了。keep-all 禁止在汉字之间断行，于是断点只能落在空格与「、」处。

   实测（320/360/390/430/461/520 六种视口，19px）：
     两行 [238, 269]，断在「Google Drive、OneDrive / 文件转存到百度网盘、夸克网盘」，
     不拆词、不溢出。备案模式的短标题（9 字）本来就在一行内，不会平白多折一行。

   `overflow-wrap:break-word` 是 keep-all 的安全网：万一将来文案变成一长串没有空格
   也没有标点的汉字（keep-all 下整段都不可断），它能兜住不让它横向溢出 ——
   已用 30 字无标点的极端文案验证过（320px 视口下仍无溢出）。

   `text-wrap:balance` 是**锦上添花，不是防线**：Chrome 114+ 才认这条属性。
   本机两个 Chrome 的实测差异就是活教材（见 `scripts/check-header-layout.mjs`）——
   用户级 109.0.5414.120 上 `getComputedStyle(h1).textWrap` 是 undefined（等于没写），
   机器级 154.0.8037.58 上它生效，会把行宽自动抹匀。
   ⚠️ 危险之处在于「生效」比「不生效」更难查：154 上即使把 `max-width` 删掉，
   行仍被抹成 [263,250]（均衡比 0.95），看不出问题 —— 排查时很容易误判成
   「上限没用，删了算了」；而老浏览器上删掉它立刻塌成 [474,38]（0.08）。
   **所以 `max-width` 必须留着，别只留 balance。**

   字重走 `--h1-w`：回退值 600 就是原来写死的那个数，后台「主标题字重」只在
   改成别的值时才输出这个变量（见 server.ts 的 brandStyleCss）。 */
.seo-h1{margin:0 0 15px;font-size:var(--h1-fs,19px);font-weight:var(--h1-w,600);line-height:1.5;color:#fff;letter-spacing:.2px;
  max-width:15em;word-break:keep-all;overflow-wrap:break-word;text-wrap:balance}

/* 首页底部「这是什么 + 常见问题」。
   必须是 HTML 里的静态文字（不是 JS 渲染的）：蜘蛛读不到 JS 补出来的内容。
   默认折叠，只有点开的那条才展开 —— 访客是被搜索引擎带来找答案的，
   但来的第一眼要看到的还是那张输入框。 */
.seo{margin:16px 12px 0;background:var(--card);border-radius:var(--r);padding:16px;box-shadow:var(--shadow)}
.seo h2{margin:0 0 10px;font-size:16px;font-weight:500;line-height:1.5}
.seo>p{margin:0 0 4px;font-size:13.5px;line-height:1.75;color:var(--ink2)}
.seo-item{border-top:1px solid var(--line);padding:11px 0 2px}
.seo-item>summary{display:flex;align-items:center;gap:6px;font-size:13.5px;color:var(--ink);cursor:pointer;list-style:none;user-select:none}
.seo-item>summary::-webkit-details-marker{display:none}
.seo-item>summary::before{content:"+";width:14px;flex:none;color:var(--blue);font-weight:600}
.seo-item[open]>summary::before{content:"\2212"}
.seo-item[open]>summary{margin-bottom:6px}
.seo-item>p{margin:0;font-size:13px;line-height:1.75;color:var(--ink2)}

.foot{text-align:center;font-size:12.5px;color:var(--ink3);padding:26px 0 10px;margin:0}

/* 页脚：ICP 备案号与公安联网备案号都是硬性要求，通过后必须展示在这里。
   没拿到号时服务端渲染成空行，靠 :empty 整行隐藏（占位符两侧不能有空白，
   否则 :empty 不生效）。两行各自独立 —— 只批下来一个时只显示那一行。 */
.site-foot{padding:8px 16px 26px;text-align:center;color:var(--ink3)}
.site-foot p{margin:0}
.foot-brand{font-size:12.5px;color:var(--ink2);letter-spacing:.2px}
.foot-note{margin-top:7px;font-size:11.5px;line-height:1.7}
/* 法务页入口（隐私政策 / 服务条款）。它必须**始终可见**：OAuth 应用审核会
   去首页抓「指向隐私政策的链接」，藏进 <details> 或前端渲染都会被判为没有。 */
.foot-links{margin-top:9px;font-size:11.5px}
.foot-links a{color:var(--ink3);text-decoration:underline;text-underline-offset:2px}
/* 友情链接（姊妹站）：与上面法务入口同档的次要链接，所以样式也复用同一套。
   没配 SISTER_SITES 时服务端渲染成空串，靠 :empty 整行消失 —— 因此
   那对占位符两侧一个空白都不能有（留了空白，这条 :empty 就永远不命中）。 */
.foot-friend{margin-top:7px;font-size:11.5px}
.foot-friend a{color:var(--ink3);text-decoration:underline;text-underline-offset:2px}
/* 姊妹站的说明（「对面那个站做什么」）。它在链接**外面** —— 是注释，不是入口，
   所以比起链接略淡一点、也不加下划线：一眼就能看出「哪个是能点的」。
   紧随链接、不加间距，读起来是「云捷链（国内网盘互转）」一整串，而不是两段。
   `.foot-friend:empty` 只认「没有任何子节点」，所以有备注时这一行照常显示。 */
.foot-friend-note{color:var(--ink3);opacity:.85}
.foot-friend:empty{display:none}
.foot-icp{margin-top:9px;font-size:11.5px}
.foot-icp a{color:var(--ink3);text-decoration:underline}
.foot-icp:empty{display:none}
/* 公安备案号紧跟在 ICP 号下面，间距小一点（它俩是一组，不该被拉成两段） */
.foot-police{margin-top:5px;font-size:11.5px}
.foot-police a{color:var(--ink3);text-decoration:underline}
.foot-police:empty{display:none}
/* ICP 那行被 :empty 藏掉时，公安备案号就成了页脚最后一行 —— 补回上边距，
   否则它会贴着免责声明，看着像漏了一行。 */
.foot-icp:empty + .foot-police{margin-top:9px}

/* 完整免责声明。默认收起 —— 页脚是「已读不回」的区域，一次摊开九条没人看；
   但它**必须**在源码字节里（备案核查抓的就是源码），所以用 <details> 折叠，
   而不是由前端隐藏：折叠只是视觉，抓取方读得到全文。 */
.foot-legal{margin-top:9px;font-size:11.5px}
.foot-legal>summary{display:inline-block;cursor:pointer;color:var(--ink3);text-decoration:underline;text-underline-offset:2px;list-style:none}
/* Safari 与 Chrome 各自多画一个三角，两边都得关，否则箭头会出现两个 */
.foot-legal>summary::-webkit-details-marker{display:none}
.foot-legal>summary::after{content:"\25be";margin-left:4px;font-size:9px;vertical-align:1px}
.foot-legal[open]>summary::after{content:"\25b4"}
.foot-legal .legal-body{margin-top:10px;padding:14px 16px;border:1px solid var(--line2);border-radius:12px;background:var(--card);text-align:left;color:var(--ink2);line-height:1.75}
/* 段距要显式写：`.site-foot p{margin:0}` 会把 `.legal-body` 里的段落拉平成一坨 */
.foot-legal .legal-body p{margin:0 0 6px}
.foot-legal .legal-body p:last-child{margin-bottom:0}
.foot-legal .legal-updated{margin:0 0 9px;color:var(--ink3);font-size:11px}
.foot-legal .legal-h{margin:11px 0 3px;font-weight:600;color:var(--ink);font-size:11.5px}
/* 首条标题紧跟在「最近更新」下面，不该再吃一次上边距 */
.foot-legal .legal-updated + .legal-h{margin-top:0}

/* -------------------------------------------------------------- sheet */
.sheet{position:fixed;inset:0;z-index:60;display:flex;align-items:flex-end;justify-content:center}
.sheet-backdrop{position:absolute;inset:0;background:rgba(12,18,32,.42)}
.sheet-panel{position:relative;width:100%;max-width:520px;max-height:82vh;max-height:min(82vh,calc(100dvh - 64px));background:#fff;border-radius:20px 20px 0 0;display:flex;flex-direction:column;animation:up .22s ease}
@media (min-width:560px){.sheet{align-items:center}.sheet-panel{border-radius:20px;max-height:78vh}}
@keyframes up{from{transform:translateY(18px);opacity:.4}to{transform:none;opacity:1}}
.sheet-head{display:flex;align-items:center;justify-content:space-between;padding:16px 18px;border-bottom:1px solid var(--line)}
.sheet-head h3{margin:0;font-size:16px;font-weight:500}
.sheet-x{display:flex;align-items:center;justify-content:center;width:44px;height:44px;margin:-10px -12px -10px 0;font-size:24px;line-height:1;color:var(--ink3);border-radius:12px}
.sheet-x:active{background:#f1f3f7;color:var(--ink)}
.sheet-body{padding:16px 18px;overflow:auto;font-size:13.5px}
.sheet-foot{padding:12px 18px calc(14px + env(safe-area-inset-bottom));border-top:1px solid var(--line);display:flex;gap:10px}
.sheet-foot:empty{display:none}
.sheet-foot .btn{flex:1 1 0;min-width:0;width:auto;height:44px;border-radius:11px;font-size:14.5px;border:1px solid var(--line2);color:var(--ink2)}
.sheet-foot .btn.primary{background:var(--blue);color:#fff;border-color:transparent}
/* 次要出口（关闭）。.btn 自带 width:100%，在 flex 行里会让两个按钮各占一整行、
   把主按钮挤到看不见 —— 所以主按钮 flex:1 撑开、次要按钮按内容宽。 */
.sheet-foot .btn.ghost{flex:0 0 auto;background:transparent;padding:0 18px}

.kv{display:flex;gap:12px;padding:9px 0;border-bottom:1px dashed var(--line2);font-size:13.5px}
.kv:last-child{border-bottom:0}
.kv dt{flex:0 0 76px;color:var(--ink3);margin:0}
.kv dd{flex:1;margin:0;word-break:break-all}
.kv dd.mono{font-family:ui-monospace,Menlo,Consolas,monospace;font-size:12.5px}
.linkbox{display:flex;align-items:center;gap:8px;background:#f6f7f9;border-radius:10px;padding:10px 12px;margin-top:8px}
.linkbox code{flex:1;font-family:ui-monospace,Menlo,Consolas,monospace;font-size:12.5px;word-break:break-all}
.linkbox button{color:var(--blue);font-weight:500;white-space:nowrap;font-size:13px}

/* ------------------------------------------------------- 发货链接到账条 */
/* 客户点开发货链接后立刻看到「到账了什么」，是这条变现链路的信任基础 */
.entry{display:flex;gap:10px;background:#fff;border-radius:14px;padding:13px 14px;margin-bottom:12px;box-shadow:0 8px 20px rgba(16,42,96,.16)}
.entry .entry-ic{display:flex;align-items:center;justify-content:center;width:32px;height:32px;border-radius:10px;background:var(--green-soft);color:var(--green);flex:none}
.entry .entry-txt{display:flex;flex-direction:column;gap:2px;min-width:0}
.entry .entry-txt b{font-size:14px;font-weight:500}
.entry .entry-txt span{font-size:12.5px;color:var(--ink2)}
.entry .entry-txt em{font-style:normal;font-size:11.5px;color:var(--green);line-height:1.55}
.entry.bad .entry-ic{background:var(--red-soft);color:var(--red)}
.entry.bad .entry-txt em{color:var(--red)}

/* ------------------------------------------------------- 兑换额度包弹层 */
.redeem-reason{margin:0 0 12px;padding:9px 12px;border-radius:10px;background:var(--amber-bg);color:var(--amber-ink);font-size:12.5px;line-height:1.6}
.redeem-lead{margin:0 0 14px;font-size:13px;color:var(--ink2);line-height:1.75}
.redeem-field{display:flex;align-items:center;gap:10px;background:#f6f7f9;border-radius:12px;padding:0 14px;height:48px}
.redeem-field span{flex:0 0 auto;font-size:13.5px;color:var(--ink2)}
.redeem-field input{flex:1;height:100%;background:none;border:0;outline:none;font-size:14px;letter-spacing:.4px}
.redeem-note{display:flex;gap:9px;margin-top:14px;padding:12px 14px;border-radius:12px;background:var(--blue-soft);color:#1f4fb8;font-size:12.5px;line-height:1.85}
.redeem-note .ic{flex:0 0 auto;margin-top:3px}
.redeem-note p{margin:0}
/* 分享弹层里的「交付说明」（如 PikPak ≥100 MB 要先保存到自己的网盘）。
   与 .redeem-note 同一套观感，但用中性色 —— 它是说明，不是需要客户马上处理的提醒 */
.share-note{display:flex;gap:9px;margin-top:14px;padding:11px 13px;border-radius:12px;background:#f5f6f8;color:#4a5058;font-size:12.5px;line-height:1.85}
.share-note .ic{flex:0 0 auto;margin-top:3px}
.redeem-buy{display:block;margin:16px 0 2px;text-align:center;color:var(--red);font-size:14px;font-weight:500}
/* 多平台下单入口：一行并排、**不换行** —— 换行会把下面的兑换码输入框顶到折叠线以下
   （本项目实测过一次：折叠线以下的输入框客户根本看不见）。
   名字太长时宁可省略号，也不占第二行。 */
.redeem-buys{display:flex;gap:8px;margin:16px 0 2px}
.redeem-buys .redeem-buy{flex:1 1 0;min-width:0;margin:0;padding:0 4px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.redeem-tip{margin:10px 0 0;font-size:11.5px;color:var(--ink3);line-height:1.65}
.redeem-code{margin-top:16px;border-top:1px dashed var(--line2);padding-top:14px}
.redeem-code>summary{margin-bottom:10px;font-size:12.5px;color:var(--blue);cursor:pointer;list-style:none;user-select:none}
.redeem-code>summary::-webkit-details-marker{display:none}

.pkg{border:1px solid var(--line2);border-radius:12px;overflow:hidden}
.pkg-h{display:flex;justify-content:space-between;padding:9px 13px;background:#f7f8fa;font-size:11.5px;color:var(--ink3)}
.pkg-r{display:flex;justify-content:space-between;align-items:center;padding:10px 13px;border-top:1px solid var(--line);font-size:13.5px}
.pkg-r b{font-weight:400}
.pkg-r span{font-weight:500;color:var(--red)}

/* ---------------------------------------------------- 站内收款（海外站） */

/* 可选中的档位行。`.pkg-r` 原本只被当静态表格行用，这里把 <button> 的 UA 样式
   压平，让两种形态长得一模一样 —— 「能不能点」要靠选中底色说，不靠外观差异。
   width:100% + text-align:left 是必须的：<button> 默认居中且按内容收缩，
   不改的话这一行会比上面的表头窄一截，整块表格看起来是歪的。 */
.pkg-r.pickable{width:100%;text-align:left;cursor:pointer;transition:background .14s}
.pkg-r.pickable.on{background:var(--blue-soft)}
.pkg-r.pickable.on b{color:var(--blue);font-weight:500}

/* 开通支付后底栏被「立即支付」占了，兑换码的提交按钮落到折叠块里 */
.redeem-code-go{height:42px;margin-top:10px;font-size:14px}

/* 付款区。外框用虚线，视觉上把它与「价目表 / 说明文字」分开 ——
   它是这一屏里唯一需要客户动手的地方。 */
.pay-box{margin:14px 0 2px;padding:14px;border:1px dashed var(--line2);border-radius:12px;background:#fbfcfd;text-align:center}
.pay-amt{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:12px}
.pay-amt span{font-size:12.5px;color:var(--ink2)}
.pay-amt b{font-size:22px;font-weight:600;color:var(--red)}
.pay-qr{display:flex;align-items:center;justify-content:center;width:216px;height:216px;margin:0 auto;background:#fff;border:1px solid var(--line2);border-radius:10px}
/*
 * 有图时容器**跟着图片走**，而不是把图片塞进 216 的框里。
 *
 * 上游给的付款码是 279×279（每模块 9px，正好整数）。写死 216 会让浏览器
 * 把它**重采样**到 216 —— 模块边缘被插值成灰色，微信的相册识别率明显下降，
 * 而这正是「截图后扫不动」的一半原因。
 *
 * `image-rendering:pixelated` 让缩放走最近邻：二维码是二值图，硬边缘比
 * 双线性插值出来的灰边更容易被识别算法二值化回去。
 */
.pay-qr.has-img{width:auto;height:auto;padding:10px;max-width:100%;box-sizing:border-box}
.pay-qr-img{display:block;width:279px;max-width:100%;height:auto;border-radius:6px;image-rendering:pixelated}
.pay-empty{padding:0 18px;font-size:12.5px;color:var(--ink3);line-height:1.7}
.pay-scan{margin:12px 0 0;font-size:12.5px;color:var(--ink2);line-height:1.7;text-align:left}
.pay-meta{display:flex;flex-wrap:wrap;gap:6px 12px;justify-content:space-between;margin-top:10px;font-size:11.5px;color:var(--ink3)}
.pay-meta code{font-size:11.5px;letter-spacing:.3px;word-break:break-all}
.pay-state{margin:10px 0 0;font-size:12.5px;color:var(--blue)}
.pay-keep{margin:6px 0 0;font-size:11.5px;color:var(--ink3)}
.pay-done{margin:6px 0 0;font-size:15px;font-weight:500;color:var(--green)}
.pay-grant{margin:6px 0 0;font-size:12.5px;color:var(--ink2)}
.pay-dead{margin:6px 0 0;font-size:13px;color:var(--ink2)}
.pay-copy{height:40px;margin-top:10px;font-size:13.5px;border:1px solid var(--line2)}
/* 手机端那颗「去收银台支付」：主按钮，上面就是金额、下面跟着引导与
   「复制客服微信号」，视觉上成一组。 */
/* 它是 <a> 而不是 <button>（原生导航不受 JS 时序影响，见 app.js），
   所以要把 .btn 的按钮外观补齐：去掉下划线、并保持与 button 一致的可点区域。 */
.pay-go{height:44px;margin-top:12px;font-size:14px;text-decoration:none;cursor:pointer;box-sizing:border-box;width:100%}

/* -------------------------------------------------------------- toast */
.toast{position:fixed;left:50%;top:46%;transform:translate(-50%,-50%) scale(.96);background:rgba(18,24,38,.86);color:#fff;padding:12px 18px;border-radius:12px;font-size:13.5px;max-width:74vw;text-align:center;opacity:0;pointer-events:none;transition:opacity .18s,transform .18s;z-index:80}
.toast.on{opacity:1;transform:translate(-50%,-50%) scale(1)}

.sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)}

/* --------------------------------------------------------- 测试链接 */
.demo{margin-top:14px;border-top:1px solid var(--line);padding-top:12px}
.demo>summary{display:flex;align-items:center;gap:7px;font-size:13.5px;color:var(--blue);cursor:pointer;list-style:none;user-select:none}
.demo>summary::-webkit-details-marker{display:none}
.demo>summary em{margin-left:auto;font-style:normal;font-size:11px;color:var(--ink3);background:#f1f3f7;padding:3px 8px;border-radius:20px}
.demo[open]>summary{margin-bottom:10px}
.demo-lead{margin:0 0 10px;font-size:12.5px;color:var(--ink3);line-height:1.65}

.demo-list{display:flex;flex-direction:column;gap:8px}
.demo-item{display:flex;flex-direction:column;gap:4px;text-align:left;padding:11px 13px;border:1px solid var(--line2);border-radius:12px;background:#fbfcfd;transition:border-color .16s,background .16s}
.demo-item:active{background:#f2f5fb;border-color:var(--blue)}
.demo-item .di-top{display:flex;align-items:center;gap:8px}
.demo-item .di-top b{font-size:13.5px;font-weight:500}
.demo-item .di-route{margin-left:auto;font-size:11.5px;color:var(--ink3);white-space:nowrap}
.demo-item .di-meta{font-size:12px;color:var(--ink2)}
.demo-item .di-purpose{font-size:11.5px;color:var(--ink3);line-height:1.55}
.demo-item.tone-warn{background:var(--amber-bg);border-color:#f3dfb6}
.demo-item.tone-info{background:#f7fbff;border-color:#dbe8ff}

/* --------------------------------------------------------- 本地调试 */
.dev{margin-top:18px;background:var(--card);border-radius:var(--r);box-shadow:var(--shadow);overflow:hidden}
.dev>summary{display:flex;align-items:center;gap:8px;padding:14px 16px;font-size:13.5px;color:var(--ink2);cursor:pointer;list-style:none;user-select:none}
.dev>summary::-webkit-details-marker{display:none}
.dev>summary::before{content:'';width:6px;height:6px;border-radius:50%;background:var(--green);flex:none}
.dev-body{padding:0 16px 16px}
.dev-kv div{display:flex;justify-content:space-between;gap:12px;padding:7px 0;border-bottom:1px dashed var(--line2);font-size:12.5px}
.dev-kv div:last-child{border-bottom:0}
.dev-kv div span{flex:none;color:var(--ink3)}
.dev-kv div b{font-weight:500;text-align:right;word-break:break-all}
.dev-lan{margin-top:12px;font-size:12.5px;color:var(--ink3)}
.dev-lan span{display:block;margin-bottom:6px}
.dev-lan code{display:block;margin-bottom:6px;padding:9px 11px;border-radius:9px;background:var(--blue-soft);color:var(--blue);font-family:ui-monospace,Menlo,Consolas,monospace;font-size:12px;word-break:break-all;cursor:pointer}
.dev-acts{display:flex;gap:8px;margin-top:14px}
.dev-acts .btn{height:42px;font-size:13.5px;border:1px solid var(--line2);background:#f7f8fa;color:var(--ink2)}
.dev-acts .btn.primary{background:var(--blue);color:#fff;border-color:transparent}
.dev-tip{margin:11px 0 0;font-size:11.5px;color:var(--ink3);line-height:1.6}

/* --------------------------------------------------------- 帮助面板 */
/* 视频教程 / 常见问题 / 使用说明：内容由后台维护，这里只管排版 */
.help-empty{margin:26px 0;text-align:center;color:var(--ink3);font-size:13px;line-height:1.9}

.tut{padding:14px 0;border-bottom:1px solid var(--line)}
.tut:first-child{padding-top:0}
.tut:last-child{border-bottom:0;padding-bottom:0}
.tut h4{margin:0 0 9px;font-size:14.5px;font-weight:500}
.tut-video{width:100%;max-height:300px;border-radius:12px;background:#0e1116;display:block}
.tut-audio{width:100%;display:block}
.tut-img{max-width:100%;border-radius:12px;display:block}
.tut-open{display:flex;align-items:center;justify-content:center;height:44px;border-radius:11px;background:var(--blue-soft);color:var(--blue);font-size:14px;font-weight:500}
.tut-desc{margin:9px 0 0;font-size:12.5px;color:var(--ink2);line-height:1.75}

.faq{border-bottom:1px solid var(--line)}
.faq:last-child{border-bottom:0}
.faq>summary{display:flex;align-items:center;gap:8px;padding:13px 0;font-size:14px;cursor:pointer;list-style:none;user-select:none}
.faq>summary::-webkit-details-marker{display:none}
.faq>summary::after{content:"+";margin-left:auto;color:var(--ink3);font-size:17px;line-height:1;flex:none}
.faq[open]>summary::after{content:"−"}
.faq[open]>summary{color:var(--blue);font-weight:500}
.faq>p{margin:0 0 14px;padding:12px 14px;border-radius:11px;background:#f7f8fa;font-size:13px;color:var(--ink2);line-height:1.85}

.guide-sec{margin-bottom:20px}
.guide-sec:last-child{margin-bottom:0}
.guide-sec h4{margin:0 0 8px;font-size:14.5px;font-weight:500;color:var(--blue)}
.guide-sec p{margin:0 0 11px;font-size:13.5px;line-height:1.9;color:var(--ink2)}
.guide-sec p:last-child{margin-bottom:0}
