/* ============================================================================
   Humble Bundle 合集 · 客户站样式表
   —— 纯 CSS，零框架 / 零构建 / 零外部资源（图标是内联 SVG）

   目录
     ① 设计系统（变量：色板 / 间距 / 圆角 / 阴影 / 字体 / 动效）
     ② 深色模式变量（跟随系统 + 手动覆盖 data-theme）
     ③ 基础元素与可访问性（焦点可见 / 跳转链接 / 选中色）
     ④ 顶栏 · 搜索 · 页脚
     ⑤ 周包卡片网格（响应式：≥1200 四列 / 640–1199 两列 / <640 一列）
     ⑥ 月包主打区（首页置顶的 Hero：卡头 + 左大图 / 右文字 + 「本包还包含」小卡片网格）｜⑥b 月包详情（遗留深链）
     ⑦ 周包详情（封面 / 价格 / 锁区提示 / 档位分组）
     ⑧ 四种状态：骨架屏 loading / 空 empty / 错误 error + 重试 / 正常
     ⑨ 管理页（后台专用：封面缩略图 / 行内价格输入 / 锁区编辑器）
     ⑩ 弹窗（客户页「查看完整说明」+ 后台锁区编辑器共用；ESC / 点遮罩关闭）
   ============================================================================ */

/* ---------------------------- ① 设计系统 ---------------------------- */
:root {
  /* 色板 —— 品牌 */
  --c-brand:        #2f6fe0;
  --c-brand-strong: #1f57bd;
  --c-brand-soft:   #eaf1fe;
  --c-brand-ring:   rgba(47, 111, 224, .35);

  /* 色板 —— 语义
     ⚠ 2026-10-15（用户要求 ⑤ 对比度）：`--c-warn` 从 #a9670a 加深到 **#9c5f08** ——
       它被大量用在"警告 chip / 锁区提示"的**小字**上，原值对 `--c-warn-soft`（#fdf3e2）只有 4.12:1，
       现在 4.71:1（过 WCAG AA 正文）。只动了这一档的明度，**色相与设计系统语义没变**；
       品牌色（--c-brand 系）一个字没动。 */
  --c-ok:      #12805a;
  --c-ok-soft: #e6f5ee;
  --c-warn:      #9c5f08;
  --c-warn-soft: #fdf3e2;
  --c-danger:      #c0392b;
  --c-danger-soft: #fdecea;
  --c-accent:      #b4761a;   /* 价格 */

  /* 中性 5 档灰（50 / 100 / 300 / 600 / 900） */
  --n-50:  #f7f8fa;
  --n-100: #eef0f4;
  --n-300: #d3d8e0;
  --n-600: #5b6472;
  --n-900: #1a1e26;

  /* 文字 / 背景 / 描边 */
  --text:      var(--n-900);
  --text-2:    var(--n-600);
  /* ⚠ 2026-10-15（用户要求 ⑤ 可读性）：`--text-3` 原来是 #7b8494（对白底 3.9:1，偏淡）。
     现在加深到 #646e7e —— 对 `--bg` 白底约 **4.9:1**、对 `--bg-2/#f7f8fa` 约 4.7:1，
     过 WCAG AA 正文标准。（品牌色一律没动。） */
  --text-3:    #646e7e;
  --bg:        #ffffff;
  --bg-2:      var(--n-50);
  --bg-3:      var(--n-100);
  --line:      var(--n-300);
  --line-soft: #e7eaf0;

  /* 间距刻度 4 / 8 / 12 / 16 / 24 / 32 */
  --s-1: 4px;  --s-2: 8px;  --s-3: 12px; --s-4: 16px; --s-5: 24px; --s-6: 32px;

  /* 圆角 */
  --r-xs: 6px; --r-sm: 8px; --r-md: 12px; --r-lg: 16px; --r-pill: 999px;

  /* 两级阴影 */
  --sh-1: 0 1px 2px rgba(16, 20, 28, .06), 0 1px 3px rgba(16, 20, 28, .05);
  --sh-2: 0 8px 24px rgba(16, 20, 28, .12), 0 2px 6px rgba(16, 20, 28, .07);

  /* 字体栈（系统字体优先） */
  --font: "PingFang SC", "HarmonyOS Sans SC", "Microsoft YaHei UI", "Microsoft YaHei",
          "Hiragino Sans GB", "Noto Sans CJK SC", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace;

  /* 正文字号 / 行高（可读性：正文 ≥15px、行高 1.6）
     ⚠ 2026-10-15（用户要求 ⑤）：15px/1.65 → **15.5px/1.72**。
       中文在 15px 下笔画容易糊在一起，15.5px 在 100% 缩放下跨过了 Windows 的字体渲染阈值；
       行高 1.72 让长段落（激活地区说明那种）一眼能分清行。 */
  --fs-body: 15.5px;
  --lh-body: 1.72;
  /* 次要文字（时间 / 说明 / 元信息）：原来是 12–13px，偏小 ⇒ 统一到 13.5px 这一档 */
  --fs-meta: 13.5px;
  /* 小一号的辅助字（chip / 表格里的 sub） */
  --fs-small: 12.5px;

  /* 布局 */
  --wide: 1240px;
  --ease: cubic-bezier(.22, .61, .36, 1);

  /* 图片占位（纯 CSS 渐变，图片挂了也不会出现破图图标） */
  --ph: linear-gradient(135deg, #dfe6f2 0%, #e9edf5 45%, #d7e0f0 100%);
}

/* ------------------------- ② 深色模式变量 ------------------------- */
:root[data-theme="dark"] {
  --c-brand:        #6fa2f5;
  --c-brand-strong: #8fb8ff;
  --c-brand-soft:   #1b2740;
  --c-brand-ring:   rgba(111, 162, 245, .45);

  --c-ok:      #4cc38a;
  --c-ok-soft: #14291f;
  --c-warn:      #e2b04a;
  --c-warn-soft: #2a2415;
  --c-danger:      #f0715f;
  --c-danger-soft: #2c1a18;
  --c-accent:      #ffcf70;

  --text:      #e8ecf3;
  --text-2:    #a9b3c2;
  /* ⚠ 2026-10-15（用户要求 ⑤）：原来是 #7b8798（对 #10131a 约 4.5:1，勉强）⇒ 提到 #8b97a9
     （约 **5.4:1**），次要文字在深色下不再"发灰看不清"。品牌色没动。 */
  --text-3:    #8b97a9;
  --bg:        #10131a;
  --bg-2:      #161b24;
  --bg-3:      #1d232e;
  --line:      #2b3340;
  --line-soft: #232b36;

  --sh-1: 0 1px 2px rgba(0, 0, 0, .5);
  --sh-2: 0 10px 28px rgba(0, 0, 0, .55);

  --ph: linear-gradient(135deg, #222b39 0%, #1b2331 45%, #26313f 100%);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --c-brand:        #6fa2f5;
    --c-brand-strong: #8fb8ff;
    --c-brand-soft:   #1b2740;
    --c-brand-ring:   rgba(111, 162, 245, .45);

    --c-ok:      #4cc38a;
    --c-ok-soft: #14291f;
    --c-warn:      #e2b04a;
    --c-warn-soft: #2a2415;
    --c-danger:      #f0715f;
    --c-danger-soft: #2c1a18;
    --c-accent:      #ffcf70;

    --text:      #e8ecf3;
    --text-2:    #a9b3c2;
    --text-3:    #8b97a9;
    --bg:        #10131a;
    --bg-2:      #161b24;
    --bg-3:      #1d232e;
    --line:      #2b3340;
    --line-soft: #232b36;

    --sh-1: 0 1px 2px rgba(0, 0, 0, .5);
    --sh-2: 0 10px 28px rgba(0, 0, 0, .55);

    --ph: linear-gradient(135deg, #222b39 0%, #1b2331 45%, #26313f 100%);
  }
}

/* -------------------- ③ 基础元素与可访问性 -------------------- */
*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* ---------------- ③b 白昼 / 夜间切换的平滑过渡（用户要求 ④） ----------------
   ⚠ 三条硬规矩：
     ① **绝不对 `*` 全局加 transition** —— 那会和每个元素的 hover / 位移动效打架，还拖慢整页
        （实测：`* { transition: background-color … }` 让一次主题切换多出 2000+ 个动画）；
     ② 只列**真的会因为换主题而变色的那一层**（结构性地块 + 控件），逐个挂；
     ③ `prefers-reduced-motion: reduce` ⇒ **一条都不加**（见下面那个媒体查询）。
   过渡属性只有四个：`background-color` / `color` / `border-color` / `box-shadow`。
   下面这些选择器是清单（改主题时会一起淡入）：body、顶栏与页脚、卡片 / 面板 / 条目 /
   chip / 徽章 / 输入控件 / 按钮 / 表格与表头 / 弹窗与遮罩内层 / 骨架屏块 / 提示与 toast。
   ⚠ 不在这里挂的：`.card` / `.mitem` / `.sk` / `.btn` 等**本来就有自己的 transition**
     （它们既动颜色也动位移，优先级更高的自有规则会把这一条盖住 —— 这是对的，别去合并）。 */
body,
.topbar, .sitefoot, .wrap,
.card-head, .panel, .card, .mblock, .mstage, .mitem, .tier, .tier > .head, .tier .items,
.toolbar, .seg, .seg button, .iconbtn, .chip, .badge, .pricebadge .pb-value,
.tierlist li, .names .name, .lockline, .lockblock, .lockcorner, .commitbar,
.empty, .errorbox, .notice, .toast, .pager .pinfo,
.tablewrap, table.tbl, table.tbl thead th, table.tbl tbody tr,
.modal-mask, .modal, .modal-head, .modal-foot, .lockfull,
.pkgbox, .authbox, .lockedit, .bulkbar, .sk-card, .sk,
input, select, textarea, .btn, .linkbtn, .dragh {
  transition: background-color .28s var(--ease), color .28s var(--ease),
              border-color .28s var(--ease), box-shadow .28s var(--ease);
}
/* 换主题时**不要让整个页面同时滚一遍动画**：开启 reduce 就彻底不加（一条也不加） */
@media (prefers-reduced-motion: reduce) {
  body,
  .topbar, .sitefoot, .wrap,
  .card-head, .panel, .card, .mblock, .mstage, .mitem, .tier, .tier > .head, .tier .items,
  .toolbar, .seg, .seg button, .iconbtn, .chip, .badge, .pricebadge .pb-value,
  .tierlist li, .names .name, .lockline, .lockblock, .lockcorner, .commitbar,
  .empty, .errorbox, .notice, .toast, .pager .pinfo,
  .tablewrap, table.tbl, table.tbl thead th, table.tbl tbody tr,
  .modal-mask, .modal, .modal-head, .modal-foot, .lockfull,
  .pkgbox, .authbox, .lockedit, .bulkbar, .sk-card, .sk,
  input, select, textarea, .btn, .linkbtn, .dragh { transition: none !important; }
}

/* ⚠ 2026-10-15（用户要求 ⑤）：正文提到 15.5px 之后，标题要**重新拉开层级差**，
   否则 h2（16px）和正文几乎一样大 ⇒ 三级标题各提一档（18→19 / 16→17），并给 h2/h3 一个
   `margin-bottom: var(--s-3)` 的呼吸位。 */
h1, h2, h3 { margin: 0 0 var(--s-3); line-height: 1.32; font-weight: 650; letter-spacing: -.01em; }
h1 { font-size: clamp(21px, 3.2vw, 27px); }
h2 { font-size: 19px; }
h3 { font-size: 17px; }
p { margin: 0 0 var(--s-2); }

a { color: var(--c-brand); text-decoration: none; }
a:hover { color: var(--c-brand-strong); text-decoration: underline; text-underline-offset: 2px; }

img { max-width: 100%; }

/* ⚠ 2026-10-15 修（用户：「选择文字的时候这个底色不明显，尤其夜间模式」）：
   原来用 `--c-brand-soft` —— 浅色是 #f5ecff（在白底上几乎看不见）、
   深色是 #24183a（在深底上同样暗）⇒ 选中了看不出来 ❌
   ⇒ 改成**主色做底 + 反色文字**：浅色白字 on #8a12d6 ≈ 7.0:1，
     深色近黑 #14101c on #b366e0 ≈ 9.4:1，两个主题都一眼看得出 ✅ */
::selection { background: var(--c-brand); color: #fff; }
:root[data-theme="dark"] ::selection,
:root:not([data-theme="light"]) ::selection { background: var(--c-brand); color: #14101c; }
::-moz-selection { background: var(--c-brand); color: #fff; }
:root[data-theme="dark"] ::-moz-selection,
:root:not([data-theme="light"]) ::-moz-selection { background: var(--c-brand); color: #14101c; }

:focus-visible {
  outline: 3px solid var(--c-brand-ring);
  outline-offset: 2px;
  border-radius: var(--r-xs);
}
:focus:not(:focus-visible) { outline: none; }

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--bg); color: var(--text); padding: var(--s-2) var(--s-4);
  border-radius: 0 0 var(--r-sm) 0; box-shadow: var(--sh-2);
}
.skip:focus { left: 0; }

.sr {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.wrap { max-width: var(--wide); margin: 0 auto; padding: var(--s-5) var(--s-4) var(--s-6); }

/* 通用图标 */
.i { width: 20px; height: 20px; display: block; }

/* --------------------- ④ 顶栏 · 搜索 · 页脚 --------------------- */
.topbar {
  position: sticky; top: 0; z-index: 40;
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  border-bottom: 1px solid var(--line-soft);
  backdrop-filter: saturate(180%) blur(10px);
  -webkit-backdrop-filter: saturate(180%) blur(10px);
}
.topbar-in {
  max-width: var(--wide); margin: 0 auto;
  padding: var(--s-3) var(--s-4);
  display: flex; align-items: center; gap: var(--s-4); flex-wrap: wrap;
}
.brand { display: inline-flex; align-items: center; color: var(--text); font-weight: 700; letter-spacing: -.01em; }
.brand:hover { color: var(--text); text-decoration: none; }
/* ★ 2026-10-15（用户要求）：左上角换成「咕鸽游戏 / PIGEONGAME」的角标 LOGO。
   源图 `D:\Ai Tools\角标LOGO.png` 带白底 ⇒ 已用 `tools/make-brand-logo.py` 把白底转透明 + 裁白边，
   产物 `public/logo.png`（489x160）。
   ⚠ 顶栏内容高约 26~34px ⇒ LOGO 取 **30px 高**（`height` 定高、宽度自适应）✅ */
.brand .brandlogo {
  display: block;
  height: 30px;
  width: auto;
  flex: none;
  /* 顶栏是 88% 透明的 sticky 条 —— 给 LOGO 一点投影，让它跟背景分层 */
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .10));
}
/* ⚠ 2026-10-15 修（用户：「LOGO 不是 PNG 吗？夜间模式下怎么会有白色背景底图」）：
   那块白底是我加的 —— 当初怕 LOGO 里那半只黑鸽子在深色顶栏上糊掉，就垫了一块白。
   但这**破坏了 PNG 的透明**，用户不接受 ❌
   ⇒ 改成：**完全透明**（不垫任何底色、不加圆角/内边距），
     深色下只给一层**极淡的白色外发光**把黑色轮廓"托"出来 —— 透明仍然保持 ✅ */
:root[data-theme="dark"] .brand .brandlogo,
:root:not([data-theme="light"]) .brand .brandlogo {
  background: none;
  border-radius: 0;
  padding: 0;
  /* 0 偏移 + 1px 模糊的白光 ⇒ 只描出轮廓，不会糊成一团 */
  filter: drop-shadow(0 0 1px rgba(255, 255, 255, .55)) drop-shadow(0 0 3px rgba(255, 255, 255, .28));
}
/* 旧的 `<span class="brand-name">Bundle 合集</span>` 已删（用户要求）——
   这两条留着是为了兼容可能的残留引用，不再被使用 */
.brand .logo { width: 26px; height: 26px; color: var(--c-brand); flex: none; }
.brand-name { font-size: 16px; }

/* ⚠ 2026-10-15：顶栏原来那两个「周包 / 月包」tab 的样式（`.tabs`）已随导航一起删掉 ——
   月包与周包现在**同页**（月包在页面最上面），没有第二个页可跳。
   ⚠ 后台那一排选择按钮用的是另一套类名 `.tabsbar`（见 ⑨ 管理页），**别一起删**。 */

.searchbar {
  position: relative; flex: 1 1 220px; min-width: 160px; max-width: 420px; margin-left: auto;
  display: flex; align-items: center;
}
.searchbar .i { position: absolute; left: 10px; color: var(--text-3); width: 18px; height: 18px; pointer-events: none; }
.searchbar input {
  width: 100%; padding: 8px var(--s-3) 8px 34px;
  font: inherit; font-size: 14px; color: var(--text);
  background: var(--bg-2); border: 1px solid var(--line); border-radius: var(--r-pill);
}
.searchbar input::placeholder { color: var(--text-3); }
.searchbar input:hover { border-color: var(--text-3); }
.searchbar input:focus { border-color: var(--c-brand); background: var(--bg); }

.iconbtn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; padding: 0; flex: none; cursor: pointer;
  color: var(--text-2); background: var(--bg-2);
  border: 1px solid var(--line); border-radius: var(--r-pill);
}
.iconbtn:hover { color: var(--text); border-color: var(--text-3); }
.iconbtn .i-sun { display: none; }
:root[data-theme="dark"] .iconbtn .i-sun { display: block; }
:root[data-theme="dark"] .iconbtn .i-moon { display: none; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .iconbtn .i-sun { display: block; }
  :root:not([data-theme="light"]) .iconbtn .i-moon { display: none; }
}

.sitefoot { border-top: 1px solid var(--line-soft); background: var(--bg-2); margin-top: var(--s-6); }
.sitefoot-in { max-width: var(--wide); margin: 0 auto; padding: var(--s-5) var(--s-4); color: var(--text-2); font-size: 13px; }
.sitefoot-in p { margin: 0 0 var(--s-1); }
.sitefoot .muted { color: var(--text-3); }
.linkbtn {
  background: none; border: 0; padding: 0; font: inherit; font-size: 13px;
  color: var(--text-3); text-decoration: underline; cursor: pointer;
}
.linkbtn:hover { color: var(--c-brand); }

/* 页头（视图标题 + 说明） */
.viewhead { display: flex; align-items: baseline; gap: var(--s-3); flex-wrap: wrap; margin-bottom: var(--s-4); }
.viewhead .sub { color: var(--text-2); font-size: 14.5px; }
.viewhead .spacer { flex: 1 1 auto; }

/* ------------------- ⑤ 周包卡片网格（响应式） ------------------- */
.toolbar {
  display: flex; align-items: center; gap: var(--s-3); flex-wrap: wrap;
  background: var(--bg-2); border: 1px solid var(--line-soft); border-radius: var(--r-md);
  padding: var(--s-3); margin-bottom: var(--s-5);
}
.toolbar .grow { flex: 1 1 220px; }
.toolbar label { display: inline-flex; align-items: center; gap: var(--s-1); color: var(--text-2); font-size: 13px; }

.seg { display: inline-flex; gap: var(--s-1); background: var(--bg-3); border-radius: var(--r-pill); padding: 3px; }
.seg button {
  font: inherit; font-size: 13px; padding: 5px var(--s-3); border: 0; cursor: pointer;
  background: transparent; color: var(--text-2); border-radius: var(--r-pill);
}
.seg button:hover { background: var(--bg); color: var(--text); }
.seg button[aria-pressed="true"] { background: var(--bg); color: var(--text); box-shadow: var(--sh-1); font-weight: 600; }

/* ------------------- ⑤b 表单控件（全站统一，用户要求 ⑥） -------------------
   ⚠ 用户在后台的原话是「特别是文本框和日期选择框」太丑 ⇒ 这里把 **textarea / input / select /
     input[type=date]** 全部收到**同一套**设计系统里：
       · 背景 `--bg`（在 `--bg-2` 的面板上自己"浮"出来）、边框 `--line`、圆角 `--r-sm`；
       · 内边距 8px 12px（原来是 6px 8px，太小、点起来局促）；
       · focus 环用 `--c-brand-ring`（和 `:focus-visible` 那套一致）；
       · placeholder 统一 `--text-3`（**不用浏览器默认那种灰**）。
   ⚠ `input[type=date]` 是**原生控件**：**绝不用 `appearance:none`** —— 那会把 Chrome 的日历按钮
     一起去掉，等于没法选日期。做法是：① 外壳（字体 / 高度 / 边框 / 内边距）跟别的控件一致；
     ② 只给 `::-webkit-calendar-picker-indicator` 调一下图标（透明度 / 手型 / 深色下反色）。
     ③ `color-scheme` 已经在 `<meta name="color-scheme">` 里声明了，深色下原生面板跟着变深。 */
select, input[type="number"], input[type="password"], input[type="text"], input[type="search"],
input[type="date"], textarea {
  font: inherit; font-size: 14.5px; color: var(--text); background: var(--bg);
  border: 1px solid var(--line); border-radius: var(--r-sm);
  padding: 8px var(--s-3); min-height: 38px; line-height: 1.45;
}
select { padding-right: var(--s-1); cursor: pointer; }
input[type="date"] { cursor: text; font-variant-numeric: tabular-nums; }
input[type="date"]::-webkit-datetime-edit { padding: 0; }
input[type="date"]::-webkit-datetime-edit-fields-wrapper { color: var(--text); }
input[type="date"]::-webkit-calendar-picker-indicator {
  cursor: pointer; opacity: .62; padding: 2px; border-radius: var(--r-xs);
}
input[type="date"]::-webkit-calendar-picker-indicator:hover { opacity: 1; background: var(--bg-3); }
:root[data-theme="dark"] input[type="date"]::-webkit-calendar-picker-indicator { filter: invert(1); opacity: .72; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) input[type="date"]::-webkit-calendar-picker-indicator { filter: invert(1); opacity: .72; }
}
select:hover, input:hover, textarea:hover { border-color: var(--text-3); }
select:focus, input:focus, textarea:focus {
  outline: none; border-color: var(--c-brand);
  box-shadow: 0 0 0 3px var(--c-brand-ring);
}
select::placeholder, input::placeholder, textarea::placeholder { color: var(--text-3); }
textarea { width: 100%; resize: vertical; font-size: 14.5px; line-height: 1.6; min-height: 44px; }

.btn {
  font: inherit; font-size: 14px; font-weight: 500; cursor: pointer;
  padding: 8px var(--s-3); border-radius: var(--r-sm);
  background: var(--bg); color: var(--text); border: 1px solid var(--line);
}
.btn:hover { border-color: var(--text-3); }
.btn.primary { background: var(--c-brand); border-color: var(--c-brand); color: #fff; }
.btn.primary:hover { background: var(--c-brand-strong); border-color: var(--c-brand-strong); color: #fff; }
.btn.warn { background: var(--c-warn-soft); border-color: var(--c-warn); color: var(--c-warn); }
.btn[disabled], .btn:disabled { opacity: .5; cursor: default; }
.btn[disabled]:hover { border-color: var(--line); }
/* 行内的小方按钮（↑ ↓ HOT）：一行里并排四个也不挤 */
.btn.iconmini { padding: 5px 9px; font-size: var(--fs-small); font-weight: 650; line-height: 1.2; }

/* 网格：默认一列，逐档加列
   ⚠ 周包（.bundle-grid）的列数是**硬要求**：大屏（≥1200px）**就是 4 个**、中屏两列、小屏一列。
     所以 ≥1200px 那条**必须**写 `repeat(4, …)`，**不能用 auto-fill / auto-fit** ——
     auto-fill 会随容器宽度自己改列数（有时 3 个、有时 5 个），列数就不固定了。
   ⚠ 每列都用 `minmax(0, 1fr)`（**不是裸 `1fr`**）：裸 `1fr` 的最小值是 auto，
     长标题（`-webkit-line-clamp` 也算）会把格子撑破、把这一行挤出去；
     `minmax(0, …)` 把最小宽度钉成 0，格子永远等宽、标题按 2 行截断。
   ⚠ 1200px 是**后写的覆盖档**：1024–1199px 走上面那条三列，≥1200px 才切成四列。
   ⚠ 只有 ≥1200px 那一档把间距收到 16px（上一档是 24px）：4 列时列宽本来就只有约 290px，
     间距再留 24px 会让卡片更瘦。列数与间距都只在这一档改，别动上面的档。 */
.bundle-grid { display: grid; gap: var(--s-5); grid-template-columns: 1fr; }
@media (min-width: 640px)  { .bundle-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1024px) { .bundle-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (min-width: 1200px) { .bundle-grid { gap: var(--s-4); grid-template-columns: repeat(4, minmax(0, 1fr)); } }

.card {
  position: relative; display: flex; flex-direction: column;
  background: var(--bg); border: 1px solid var(--line-soft); border-radius: var(--r-lg);
  overflow: hidden; box-shadow: var(--sh-1);
  transition: transform .18s var(--ease), box-shadow .18s var(--ease), border-color .18s var(--ease);
}
.card:hover, .card:focus-within { transform: translateY(-3px); box-shadow: var(--sh-2); border-color: var(--c-brand-ring); }

.card .thumb {
  position: relative; display: block; aspect-ratio: 16 / 9; overflow: hidden;
  background: var(--ph); border-bottom: 1px solid var(--line-soft);
}
.card .thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* 卡片缩略图右上角那一叠角标（状态 / HOT / 锁区）—— 用户要求 ①⑨ */
.card .thumb .thumb-corner {
  position: absolute; right: var(--s-2); top: var(--s-2);
  display: flex; align-items: center; gap: var(--s-1); flex-wrap: wrap; justify-content: flex-end;
  max-width: calc(100% - var(--s-4));
}
.card .thumb .badge {
  position: static;
  display: inline-flex; align-items: center; gap: 4px;
  font-size: var(--fs-small); font-weight: 600; padding: 3px var(--s-2); border-radius: var(--r-pill);
  background: rgba(255, 255, 255, .92); color: var(--n-900); box-shadow: var(--sh-1);
}
.card .thumb .badge.ended { background: rgba(26, 30, 38, .82); color: #fff; }
.card .thumb .badge.hot { background: var(--c-warn); color: #fff; }
/* HOT（手打的热销标）：走设计系统的强调色，圆角药丸 + 细描边，别土 */
.card .thumb .badge.hot-badge {
  background: color-mix(in srgb, var(--c-accent) 88%, #000 12%);
  color: #1a1204; border: 1px solid color-mix(in srgb, var(--c-accent) 60%, #000 40%);
  letter-spacing: .06em;
}
:root[data-theme="dark"] .card .thumb .badge.hot-badge { color: #201705; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .card .thumb .badge.hot-badge { color: #201705; }
}

/* 「锁区」角标：一个小药丸按钮（图标 + 两个字），点开是完整说明的弹窗 */
.lockcorner {
  display: inline-flex; align-items: center; gap: 4px; cursor: pointer;
  font: inherit; font-size: var(--fs-small); font-weight: 600; line-height: 1;
  padding: 4px var(--s-2); border-radius: var(--r-pill);
  color: #fff; background: color-mix(in srgb, var(--c-warn) 88%, #000 12%);
  border: 1px solid color-mix(in srgb, var(--c-warn) 55%, #000 45%);
  box-shadow: var(--sh-1); white-space: nowrap;
}
.lockcorner:hover { background: var(--c-warn); }
.lockcorner .i { width: 13px; height: 13px; }
.lockcorner.mini { padding: 3px 6px; background: transparent; color: var(--c-warn); border-color: color-mix(in srgb, var(--c-warn) 45%, transparent); box-shadow: none; }
.lockcorner.mini:hover { background: var(--c-warn-soft); }
/* ⚠ 2026-10-15：`.mstage .stage-corner` 已废（锁区按钮挪到右侧文字区右上角 = `.meyebrow-row .leadlock`）
 *   —— 用户截图指明位置后删掉了这条规则，别再往大图上挂东西。 */
.card .thumb .thumb-corner { z-index: 1; }

/* ⚠ 2026-10-15（用户要求 ⑤）：内边距从 16 → 18px，标题与正文之间靠 gap（12→14px）分开，
   卡片里元素多了（价格 chips / 提示条）也不挤。 */
.card .body { display: flex; flex-direction: column; gap: 14px; padding: 18px; flex: 1; }

/* 标题：最多 2 行截断 */
.card .cardtitle {
  margin: 0; font-size: 17px; font-weight: 680; line-height: 1.38;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.card .cardtitle a { color: var(--text); }
.card .cardtitle a:hover { color: var(--c-brand); text-decoration: none; }

.card .foot { display: flex; align-items: flex-end; justify-content: space-between; gap: var(--s-3); margin-top: auto; }

/* 价格徽章 */
.pricebadge { display: inline-flex; flex-direction: column; gap: 2px; }
.pricebadge .pb-label { font-size: var(--fs-small); color: var(--text-3); }
/* ⚠ 价格是客户站最该被一眼看到的东西（用户要求 ⑤：价格数字的强调度）：
   字号 24→26px、字重 750→800、色用 --c-accent、并且**加一层极淡的同色光晕**把数字从底上"托起来"。 */
.pricebadge .pb-value {
  font-size: 26px; font-weight: 800; letter-spacing: -.02em; color: var(--c-accent); line-height: 1.12;
  text-shadow: 0 0 18px color-mix(in srgb, var(--c-accent) 22%, transparent);
}
.pricebadge.big .pb-value { font-size: clamp(30px, 5vw, 38px); }
.pricebadge.na .pb-value { color: var(--text-3); font-weight: 600; text-shadow: none; }

.meta { display: flex; flex-direction: column; gap: 2px; color: var(--text-3); font-size: var(--fs-meta); text-align: right; }
.meta b { color: var(--text-2); font-weight: 600; }
.meta b.soon { color: var(--c-warn); }
.meta b.over { color: var(--text-3); }

.chiprow { display: flex; flex-wrap: wrap; gap: var(--s-1); }
.chip {
  display: inline-block; font-size: var(--fs-small); font-weight: 500; padding: 3px var(--s-2);
  border-radius: var(--r-pill); background: var(--bg-3); color: var(--text-2); white-space: nowrap;
}
.chip.brand { background: var(--c-brand-soft); color: var(--c-brand-strong); }
.chip.ok { background: var(--c-ok-soft); color: var(--c-ok); }
.chip.warn { background: var(--c-warn-soft); color: var(--c-warn); }
.chip.line { background: transparent; border: 1px solid var(--line); }
/* 各档价格的 chip（用户要求 ⑧）：数字用等宽数字 + 比普通 chip 略重的字 */
.chip.tchip { font-variant-numeric: tabular-nums; font-weight: 650; color: var(--text); background: var(--bg-3); }

/* 卡片的「各档价格」一行（chip 行 + 一个小标签） */
.tierline { display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap; }
.tierline .tlab { font-size: var(--fs-small); color: var(--text-3); flex: none; }

/* ------------------- 锁区提示条（**列表卡片用**） -------------------
   ⚠ 2026-10-15（用户澄清第 ② 条）：**周包卡片上不放任何锁区 UI** ⇒ 这一套现在只被
     「暂时没有锁区信息」那一行淡提示用到（万一日后又要往卡片上放东西，样式还在这儿）。
   · 一行紧凑的提示：细边框 + 警示色 + 图标 + 一句话 + 一个细按钮；
   · `.wide` 是详情页那一档（内边距 / 字号各加一级）。 */
.lockline {
  display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap;
  padding: 6px var(--s-2) 6px 6px; border-radius: var(--r-md);
  background: color-mix(in srgb, var(--c-warn-soft) 78%, transparent);
  border: 1px solid color-mix(in srgb, var(--c-warn) 34%, transparent);
  font-size: var(--fs-small); line-height: 1.45;
}
.lockline .lockico { display: inline-flex; color: var(--c-warn); flex: none; }
.lockline .lockico .i { width: 16px; height: 16px; }
.lockline .lockmsg { color: var(--text-2); min-width: 0; }
.lockline .lockextra { color: var(--text-3); font-size: var(--fs-small); }
.lockline .lockmore { margin-left: auto; padding: 3px var(--s-3); font-size: var(--fs-small); border-radius: var(--r-pill); }
.lockline.wide { padding: var(--s-3) var(--s-4); font-size: 14.5px; gap: var(--s-3); }
.lockline.wide .lockico .i { width: 19px; height: 19px; }
.lockline.wide .lockmsg { font-size: 15px; font-weight: 600; color: var(--text); }
.lockline.wide .lockmore { padding: 5px var(--s-4); font-size: 14px; }

/* ------------------- 锁区信息块（**详情页用**，用户澄清第 ② 条） -------------------
   版式：小标题（图标 + 「激活地区提示」）→ 一句摘要 → 「查看完整说明」按钮。
   和同一页的 `.tier` 用**同一套语言**：同圆角、同内边距刻度、同一个警示色系、同按钮尺寸；
   深色模式只靠变量切换，不需要另写覆盖。 */
.lockblock {
  display: flex; flex-direction: column; gap: var(--s-2);
  padding: var(--s-3) var(--s-4);
  border-radius: var(--r-md);
  background: color-mix(in srgb, var(--c-warn-soft) 72%, transparent);
  border: 1px solid color-mix(in srgb, var(--c-warn) 34%, transparent);
}
.lockblock.none { background: var(--bg-2); border-color: var(--line-soft); }
.lockblock .lockhead {
  display: flex; align-items: center; gap: var(--s-2);
  font-weight: 680; font-size: 14.5px; color: var(--c-warn);
}
.lockblock .lockhead .i { width: 18px; height: 18px; flex: none; }
.lockblock .lb-msg { margin: 0; font-size: 14.5px; color: var(--text); line-height: 1.6; }
.lockblock.none .lb-msg { color: var(--text-3); font-size: var(--fs-meta); }
.lockblock .lb-until {
  display: inline-block; margin-left: var(--s-2); color: var(--text-3); font-size: var(--fs-meta);
}
.lockblock .lb-act { display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap; }
.lockblock .lockmore { border-radius: var(--r-pill); }

/* ------------------- ⑥ 月包主打区（首页置顶的 Hero） -------------------
   ⚠ 2026-10-15（用户要求）：月包**不再单独一页**，改成首页最上面一块**「当季主打」Hero**：
     外层一个大卡（`.card.mcard`）+ 卡头（`.card-head`：一个 h2 大标题 + 一行灰色小字说明），
     里面每个月一块（`.mblock`）做成**左大图 / 右文字**，其余商品在下面「本包还包含」的小卡片网格里。
     ⚠ 视觉权重必须**分层**：招牌名（全站最大字）＞ 商品小卡片（13px 名字）＞ 周包卡片（16px 标题）。
   ⚠ 所以给它加了 `.mcard` 修饰类：
     ① 盖掉 `.card` 的 hover 上浮 —— 整块主打区不该像卡片那样跳；
     ② 盖掉 `.card` 的 `overflow: hidden` —— 里面的招牌大图按自己的圆角裁，不靠外层裁。 */
.card.mcard { display: block; overflow: visible; box-shadow: var(--sh-1); }
.card.mcard:hover, .card.mcard:focus-within { transform: none; box-shadow: var(--sh-1); border-color: var(--line-soft); }

/* 卡头：h2 标题在左、灰色小字说明在右（窄屏自动折行）——这是「分区」统一长相
   ⚠ 月包那个分区是**主打区**（Hero）：标题更大、字号/字距单独调，见下面 `.mcard .card-head`；
     周包那一段用默认这套即可。 */
.card-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--s-3); flex-wrap: wrap; margin-bottom: var(--s-4); }
.card-head h2 { margin: 0; font-size: 17px; }
.card-head .muted { color: var(--text-2); }
.card-head .small { font-size: 13px; }

/* 月包主打区：标题更大、下面多留一段呼吸空间（企业官网那种"当季主打"的段落感） */
.mcard .card-head { margin-bottom: var(--s-5); }
.mcard .card-head h2 { font-size: clamp(19px, 2.4vw, 23px); letter-spacing: -.02em; }

/* 周包那一段也是「分区」，但里面的卡片网格要留着（所以不用 .mcard，另写一条） */
.card.msection { display: block; overflow: visible; cursor: default; }
.card.msection:hover, .card.msection:focus-within { transform: none; border-color: var(--line-soft); box-shadow: var(--sh-1); }

/* 每个月一块 —— **企业官网「当季主打」(Hero) 那种版式**：左大图 + 右文字，下面才是其余商品 */
.mb-list { display: flex; flex-direction: column; gap: var(--s-6); }
.mblock {
  display: flex; flex-direction: column; gap: var(--s-5);
  border: 1px solid var(--line-soft); border-radius: var(--r-lg);
  background: var(--bg-2); padding: var(--s-6);
}

/* Hero 两栏（企业官网新品页的常见版式）
   ⚠⚠ 为什么是两栏 + 图上限 660px —— **本地缓存的商品图本身是缩略图**（实测 667×383）：
      若把招牌图拉成全宽 1200px 的 banner，等于**拉伸 1.8 倍 ⇒ 客户一眼看出糊**。
      ⇒ 图只占一栏、且 `max-width: 660px`（≤ 原生宽），**容器再宽也不超过它**；
        左边不够就把右边文字区放宽、**宁可留白，也不发虚**。
      ⚠ 哪天后端换成更大的图，这个上限**不会碍事**（`max-width` 只是上限）。
      ⚠ 不要用 `width:100vw` / 全宽 banner / `background-size:cover` 铺满屏。 */
.mhero {
  /* 两栏加起来封顶 1152px 并居中：左栏 52% ≈ 599px（**离 660px 上限还有余量，永不发虚**），
     右栏吃掉剩下的 ≈ 553px —— 放大到 1240px 的页面也不会出现"站在墙角"的窄文字栏。 */
  width: 100%; max-width: 1152px; margin: 0 auto;
  display: grid; grid-template-columns: 1fr; gap: var(--s-5); align-items: start;
}
@media (min-width: 900px) {
  .mhero { grid-template-columns: minmax(0, 52fr) minmax(0, 48fr); gap: var(--s-6); }
}
.mstagewrap { min-width: 0; }
/* 招牌大图：16:9、按容器缩放但**最宽只到原生 660px**（渲染宽永远 ≤ 原生宽） */
.mstage {
  position: relative; overflow: hidden; display: block;
  width: 100%; max-width: 660px;
  aspect-ratio: 16 / 9;
  border-radius: var(--r-lg);
  border: 1px solid var(--line-soft);
  background: var(--ph);
  box-shadow: var(--sh-2);
}
.mstage img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform .5s var(--ease); }
/* 动效克制：hover 时大图极轻微推近（1.03） */
.mstage:hover img { transform: scale(1.03); }
@media (prefers-reduced-motion: reduce) { .mstage img { transition: none; } .mstage:hover img { transform: none; } }
/* 全都没图时的占位：渐变底 + 一个图标，不空着 */
.mstage.ph { display: grid; place-items: center; color: var(--text-3); }
.mstage.ph svg { width: 46px; height: 46px; opacity: .8; }

.mlead { display: flex; flex-direction: column; gap: var(--s-3); align-items: flex-start; min-width: 0; }
/* 眉标（月份 · 品牌）+ 下方一条细分割线 */
.meyebrow {
  margin: 0; position: relative;
  font-size: 12.5px; font-weight: 650; letter-spacing: .16em; text-transform: uppercase;
  color: var(--c-brand-strong); font-variant-numeric: tabular-nums;
}
.meyebrow::after {
  content: ''; display: block; width: 54px; height: 2px; margin-top: var(--s-3);
  background: linear-gradient(90deg, var(--c-brand), transparent); border-radius: 2px;
}
/* 招牌商品名：全站**最大字**（跟周包卡片的 16px 明显拉开） */
.mlead-kicker { margin: 0 0 var(--s-1); font-size: 13px; font-weight: 600; color: var(--text-3); letter-spacing: .04em; }
.mlead-name {
  margin: 0; min-width: 0; overflow-wrap: anywhere;
  font-size: clamp(26px, 3.6vw, 44px); font-weight: 750; line-height: 1.1; letter-spacing: -.03em;
  color: var(--text);
}
.mrow { display: flex; align-items: flex-end; gap: var(--s-4); flex-wrap: wrap; margin-top: var(--s-2); }
/* 售价旁边那句「本包共 N 件商品」 */
.mcount { font-size: 13.5px; color: var(--text-2); font-variant-numeric: tabular-nums; }
.mlead .chiprow { margin-top: var(--s-1); }
.msub { margin: 0; font-size: 13.5px; color: var(--text-3); }

/* 其余商品网格：「本包还包含」一行小标题 + 小卡片（视觉权重**明显小于**招牌位） */
.mmore-head {
  display: flex; align-items: center; gap: var(--s-4);
  font-size: 13px; color: var(--text-3); letter-spacing: .04em;
}
.mmore-head::before, .mmore-head::after {
  content: ''; height: 1px; flex: 1 1 auto;
  background: var(--line-soft);
}
.mgrid { display: grid; gap: var(--s-3); grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); }
.mitem {
  margin: 0; min-width: 0; display: flex; flex-direction: column; gap: 6px;
  background: var(--bg); border: 1px solid var(--line-soft); border-radius: var(--r-md);
  overflow: hidden; box-shadow: var(--sh-1);
  transition: transform .18s var(--ease), box-shadow .18s var(--ease), border-color .18s var(--ease);
}
.mitem:hover { transform: translateY(-2px); box-shadow: var(--sh-2); border-color: var(--c-brand-ring); }
.mitem img, .mitem .mi-ph { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; display: block; background: var(--ph); }
/* 图挂了（onerror 加了类）就藏起来，露出容器的渐变占位 —— 不留破图图标 */
.mitem img.mi-broken { display: none; }
.mitem .mi-nm { padding: 0 var(--s-3) var(--s-3); font-size: 13px; line-height: 1.45; color: var(--text); overflow-wrap: anywhere; }
/* 没有商品名单时的提示，别让一块里只剩售价 */
.mnone { color: var(--text-3); font-size: 13.5px; margin: 0; }

/* 小屏（<768px）：Hero 自然堆叠（图在上、文字在下），图仍是 100% 宽但受 660px 上限约束 */
@media (max-width: 767px) {
  .mblock { padding: var(--s-4); gap: var(--s-4); }
  .mgrid { gap: var(--s-2); grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 420px) {
  .mgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ------------------- ⑥b 月包详情（遗留深链 #/choice/<月>） -------------------
   ⚠ 首页不再提供入口；这条路由只为老链接兜底。 */
.backlink { display: inline-flex; align-items: center; gap: 6px; font-size: 14px; margin-bottom: var(--s-4); }

/* 商品名单网格 */
.names { display: grid; gap: var(--s-2); grid-template-columns: 1fr; }
@media (min-width: 640px) { .names { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1024px) { .names { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.names .name {
  display: flex; align-items: center; gap: var(--s-2);
  background: var(--bg-2); border: 1px solid var(--line-soft); border-radius: var(--r-sm);
  padding: var(--s-2) var(--s-3); font-size: 14px; word-break: break-word;
}
.names .name .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--c-brand); flex: none; }

/* 详情两栏 */
.detail { display: grid; gap: var(--s-5); grid-template-columns: 1fr; align-items: start; }
@media (min-width: 900px) { .detail { grid-template-columns: minmax(0, 1fr) 340px; } }
.detail .side { display: flex; flex-direction: column; gap: var(--s-4); position: sticky; top: 84px; }
@media (max-width: 899px) { .detail .side { position: static; } }

.panel {
  background: var(--bg); border: 1px solid var(--line-soft); border-radius: var(--r-lg);
  padding: var(--s-4); box-shadow: var(--sh-1);
}
.panel > h2 { font-size: 16px; padding-bottom: var(--s-2); margin-bottom: var(--s-3); border-bottom: 1px solid var(--line-soft); }

.cover { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; border-radius: var(--r-md); background: var(--ph); display: block; }

/* ⚠ 2026-10-15 修（用户截图：「激活有效期」被折成两行）：
   原来是写死的 `84px`，装不下「激活有效期 + 提示标」⇒ 换行错位 ❌
   ⇒ 改成 `max-content`（整列按最宽的那个标签对齐）+ `nowrap`，永不折行 ✅ */
/* ⚠ 2026-10-15 修（用户截图：「时间与信息」排版错位）：
   原来是 `align-items: stretch`（grid 默认）—— 某一行的 dd 因为 chip 掉到第二行而变高时，
   同一行的 **dt 也被拉成同样高** ⇒ 标签文字**垂直居中** ⇒ 看着就是错位 ❌
   ⇒ 改成 `start`：每格都对齐到行顶端，dt 永远是它自己的高度 ✅
   （列宽 `max-content 1fr` + `dt { nowrap }` 是上一轮改的，那条已经让「激活有效期」不折行了 ✅）*/
/* ⚠ 2026-10-15：字号 14 → **13px** —— 「销售结束时间」那行的日期 + chip 在 204px 里放不下，
   收一档之后 ≈197px，**同一行放得下**（同时也更紧凑）✅ */
.kv { display: grid; grid-template-columns: max-content 1fr; gap: 6px var(--s-3); margin: 0; font-size: 13px; align-items: start; }
.kv dt { color: var(--text-3); white-space: nowrap; }
.kv dd { margin: 0; color: var(--text); word-break: break-word; }
/* dd 里的 chip（「还剩 1 天 13 小时」）收紧一点，尽量和日期同一行 */
.kv dd .chip { padding: 0 7px; font-size: 12px; line-height: 1.7; white-space: nowrap; }
/* 万一行不够、chip 掉到第二行：从行首开始，别再缩进 */
.kv dd { text-align: left; }

/* ------------------- ⑦ 周包详情（锁区 / 档位） -------------------
   ⚠ 2026-10-15：老的那块 `.lockbox`（一整块警示色 + 大段纯文本）**已经在 app.js 里不再使用**
     —— 详情页现在用 `.lockblock`（上面 ⑤ 那一段里统一写了）。
     这里删掉旧样式，避免"同一件事有两套长相"（下一页改一处不生效那种坑）。
   ⚠ `.regions` 还在用：弹窗（`lockViewHtml`）里的地区码 chip 那一排。 */
.regions { display: flex; flex-wrap: wrap; gap: var(--s-1); margin-top: var(--s-2); }

/* 详情页侧栏的「各档价格」清单（用户要求 ⑧） */
.tierlist { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.tierlist li {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--s-3);
  padding: 6px var(--s-2); border-radius: var(--r-sm);
}
.tierlist li + li { border-top: 1px solid var(--line-soft); }
.tierlist .tn { color: var(--text-2); font-size: var(--fs-meta); min-width: 0; overflow-wrap: anywhere; }
.tierlist .tp { color: var(--c-accent); font-weight: 700; font-size: 16px; font-variant-numeric: tabular-nums; flex: none; }

.tier { border: 1px solid var(--line-soft); border-radius: var(--r-lg); overflow: hidden; margin-bottom: var(--s-4); box-shadow: var(--sh-1); }
.tier > .head { display: flex; align-items: center; gap: var(--s-3); flex-wrap: wrap; background: var(--bg-2); padding: var(--s-3) var(--s-4); }
.tier > .head .tlabel { font-weight: 680; font-size: 15.5px; }
.tier > .head .spacer { flex: 1 1 auto; }
/* 每个档位**各自标价**（用户要求 ⑧）：字号与颜色强调到和整包售价同一档 */
.tier > .head .tprice { font-size: 19px; font-weight: 780; color: var(--c-accent); font-variant-numeric: tabular-nums; }
.tier > .head .tprice.na { font-size: var(--fs-meta); font-weight: 500; color: var(--text-3); }
.tier .items { display: grid; gap: var(--s-2); grid-template-columns: 1fr; padding: var(--s-3) var(--s-4) var(--s-4); }
@media (min-width: 560px)  { .tier .items { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1100px) { .tier .items { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

.item { display: flex; align-items: center; gap: var(--s-3); min-width: 0; }
.item .ithumb {
  width: 44px; height: 44px; flex: none; border-radius: var(--r-sm); overflow: hidden;
  background: var(--ph); border: 1px solid var(--line-soft); position: relative;
}
.item .ithumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.item .iname { min-width: 0; font-size: 14.5px; line-height: 1.45; word-break: break-word; }
.item .iretail { color: var(--text-3); font-size: var(--fs-small); }
.item .lockcorner.mini { margin-left: auto; flex: none; }

/* ------------------- ⑧ 四种状态 ------------------- */
/* 8.1 加载中：骨架屏 */
.skeleton-grid { display: grid; gap: var(--s-5); grid-template-columns: 1fr; }
@media (min-width: 640px)  { .skeleton-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1024px) { .skeleton-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
.sk-card { border: 1px solid var(--line-soft); border-radius: var(--r-lg); overflow: hidden; background: var(--bg); box-shadow: var(--sh-1); }
.sk { background: linear-gradient(90deg, var(--bg-3) 25%, var(--bg-2) 37%, var(--bg-3) 63%); background-size: 400% 100%; animation: sk 1.4s ease-in-out infinite; }
.sk-img { aspect-ratio: 16 / 9; border-radius: 0; }
.sk-line { height: 12px; border-radius: var(--r-pill); margin: var(--s-4) var(--s-4) 0; }
.sk-line.w80 { width: 80%; }
.sk-line.w45 { width: 45%; margin-bottom: var(--s-4); }
.sk-line + .sk-line { margin-top: var(--s-2); }
@keyframes sk { 0% { background-position: 100% 0; } 100% { background-position: 0 0; } }
@media (prefers-reduced-motion: reduce) { .sk { animation: none; } }

/* 8.2 空状态 */
.empty, .errorbox {
  display: flex; flex-direction: column; align-items: center; gap: var(--s-3);
  text-align: center; padding: var(--s-6) var(--s-4);
  background: var(--bg-2); border: 1px dashed var(--line); border-radius: var(--r-lg);
}
.empty .bigicon, .errorbox .bigicon {
  width: 48px; height: 48px; color: var(--text-3);
  display: flex; align-items: center; justify-content: center;
  background: var(--bg-3); border-radius: 50%;
}
.empty .bigicon svg, .errorbox .bigicon svg { width: 26px; height: 26px; }
.empty h3, .errorbox h3 { margin: 0; font-size: 17px; }
.empty p, .errorbox p { margin: 0; color: var(--text-2); font-size: 14px; max-width: 46ch; }
.errorbox { border-color: color-mix(in srgb, var(--c-danger) 40%, var(--line)); background: var(--c-danger-soft); }
.errorbox .bigicon { background: color-mix(in srgb, var(--c-danger) 18%, transparent); color: var(--c-danger); }
.errorbox p.errmsg { color: var(--text); font-family: var(--font-mono); font-size: 13px; word-break: break-word; }

/* 分页 */
.pager { display: flex; align-items: center; justify-content: center; gap: var(--s-2); flex-wrap: wrap; margin-top: var(--s-5); }
.pager .pinfo { color: var(--text-2); font-size: 13.5px; min-width: 150px; text-align: center; font-variant-numeric: tabular-nums; }

/* 轻提示 */
.toast-host {
  /* ★★ 2026-10-15（用户两次指出"提示气泡还在右下角"）——
     ★ 我一直误会成"保存按钮旁那个小药丸"；**用户说的就是这块提示本身**。
     ⇒ 从**右下角**挪到**顶部居中**（这是他要的位置）。
     ⚠ 用 fixed 相对视口，`left:50% + translateX(-50%)` 居中；多条提示纵向堆叠。 */
  position: fixed; left: 50%; transform: translateX(-50%); top: var(--s-4);
  z-index: 90; display: flex; flex-direction: column; align-items: center; gap: var(--s-2);
  max-width: min(520px, calc(100vw - 32px)); pointer-events: none;
}
.toast {
  box-shadow: var(--sh-2); border-radius: var(--r-md); padding: var(--s-3) var(--s-4);
  background: var(--bg); border: 1px solid var(--line); border-left: 4px solid var(--c-brand);
  font-size: 14px; animation: pop .18s var(--ease);
}
.toast.ok { border-left-color: var(--c-ok); }
.toast.warn { border-left-color: var(--c-warn); }
.toast.err { border-left-color: var(--c-danger); }
@keyframes pop { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }

.notice {
  background: var(--bg-2); border: 1px solid var(--line-soft); border-left: 4px solid var(--c-brand);
  border-radius: var(--r-sm); padding: var(--s-3) var(--s-4); color: var(--text-2); font-size: 14px; margin: var(--s-4) 0;
}
.notice.warn { border-left-color: var(--c-warn); }
.notice.err { border-left-color: var(--c-danger); }
.notice.ok { border-left-color: var(--c-ok); }
.hint { color: var(--text-3); font-size: 13px; }
.hint code { font-family: var(--font-mono); font-size: 12.5px; background: var(--bg-3); border-radius: var(--r-xs); padding: 1px 5px; }

/* ------------------- ⑨ 管理页（后台专用，朴素够用） ------------------- */
.admin .panel { margin-bottom: var(--s-4); }
.admin h2 { font-size: 17px; }
.admin .row { display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap; }
.admin .row .spacer { flex: 1 1 20px; }
.admin .tokenbar input[type="password"] { width: min(320px, 100%); }
.admin .tabsbar { display: inline-flex; gap: var(--s-1); background: var(--bg-3); border-radius: var(--r-pill); padding: 3px; }
.admin .tabsbar button {
  font: inherit; font-size: 13.5px; padding: 5px var(--s-4); border: 0; cursor: pointer;
  background: transparent; color: var(--text-2); border-radius: var(--r-pill);
}
.admin .tabsbar button[aria-pressed="true"] { background: var(--c-brand); color: #fff; font-weight: 600; }

/* 「保存修改」那条 sticky 工具条（用户要求 ⑦：统一保存 + 未保存 N 处）
   ⚠ sticky 贴在**表格上方**：`.admin .panel` 没有 overflow 裁切，所以 sticky 有效；
     表格自己的表头也是 sticky（top:0）⇒ 这里给到 `top: 0` 会和表头抢位置，
     所以只让它在滚动到面板顶部附近时停住（`top: 6px`），视觉上压在表格之上。 */
.commitbar {
  position: sticky; top: 6px; z-index: 3;
  display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap;
  padding: var(--s-2) var(--s-3); margin-bottom: var(--s-3);
  background: color-mix(in srgb, var(--bg) 92%, transparent);
  border: 1px solid var(--line); border-radius: var(--r-md);
  box-shadow: var(--sh-1);
  backdrop-filter: saturate(180%) blur(8px);
  -webkit-backdrop-filter: saturate(180%) blur(8px);
}
.commitbar .cmsg { font-size: 13.5px; color: var(--text-2); font-weight: 600; }
.commitbar .cmsg.on { color: var(--c-warn); }
.commitbar .spacer { flex: 1 1 20px; }
.commitbar .hint { font-size: var(--fs-small); }

.tablewrap { overflow-x: auto; border: 1px solid var(--line-soft); border-radius: var(--r-md); background: var(--bg); }
table.tbl { border-collapse: collapse; width: 100%; min-width: 1080px; font-size: 13.5px; }
table.tbl th, table.tbl td { padding: var(--s-2) var(--s-3); text-align: left; vertical-align: middle; border-bottom: 1px solid var(--line-soft); }
table.tbl thead th { background: var(--bg-2); color: var(--text-2); font-weight: 600; white-space: nowrap; position: sticky; top: 0; z-index: 1; }
table.tbl tbody tr:hover { background: var(--bg-2); }
table.tbl td.k { font-family: var(--font-mono); font-size: 12px; color: var(--text-2); max-width: 190px; word-break: break-all; }
table.tbl td .sub { color: var(--text-3); font-size: var(--fs-small); }
table.tbl td .pre { font-weight: 700; font-size: 15px; color: var(--c-accent); font-variant-numeric: tabular-nums; }
table.tbl input[type="number"] { width: 96px; }
table.tbl input[type="text"] { width: 100%; min-width: 170px; }
table.tbl td.regionscell input { min-width: 110px; width: 120px; font-family: var(--font-mono); text-transform: uppercase; }
table.tbl td.act { white-space: nowrap; }
table.tbl textarea { min-height: 52px; }

/* 行里的操作组：统一一个横排，图标按钮不换行挤成两行 */
table.tbl td.act .actrow { position: relative; display: flex; align-items: center; gap: var(--s-1); flex-wrap: wrap; }
/* 拖动排序（用户要求 ⑨）：行可拖；拖动中的行淡一点，落点用一条 2px 的强调色线标出来 */
table.admin-tbl tbody tr[draggable="true"] { cursor: default; }
table.admin-tbl tbody tr.dragging { opacity: .45; }
table.admin-tbl tbody tr.dropbefore { box-shadow: inset 0 2px 0 0 var(--c-brand); }
table.admin-tbl tbody tr.dropafter { box-shadow: inset 0 -2px 0 0 var(--c-brand); }
.dragh { color: var(--text-3); cursor: grab; margin-right: 6px; font-size: 15px; user-select: none; }
.dragh:active { cursor: grabbing; }
/* HOT 开关（⑥ 2026-10-15）：**开 / 关必须一眼看出来，而且点了就一直那样** ——
   用户原话「后台的 HOT 按钮点了之后不会固定颜色」。
   · 关（默认）：次级外观 —— 透明底 + 描边（走 `.btn` 那套）；
   · 开：**填充强调色底 + 白字 + 加粗**，不再依赖 hover；
   · ⚠ 两个钩子都认：`aria-pressed="true"`（无障碍状态，`toggleHot()` 每次都写）
     和 `.is-on`（同一个函数顺手加的类 ⇒ 样式不靠"属性选择器能不能选中"这一件事）；
   · ⚠ 深浅色都能看：底色用 `--c-accent`（设计系统里深色模式会自动换成更亮的一档）。 */
/* ★★ 2026-10-15 重做：HOT 亮/暗 ——
   ⚠ 以前的写法是 `table.tbl .f-hot.is-on`：**强依赖"表格有 tbl 类 + 按钮是它的后代"**，
     只要 DOM 结构动一点就**静默失效**（用户："启用了却不常亮"就是这么来的）。
   ⇒ 现在**不带上下文前缀**，用 `.btn.f-hot`（特异性 0,2,0）保证盖过 `.btn`（0,1,0）。
   ⇒ 亮 = 强调色填充 + 白字 + 加粗；暗 = 透明底 + 次级字色。**一眼能分**。 */
.btn.f-hot { background: transparent; border-color: var(--line); color: var(--text-2); }
.btn.f-hot[aria-pressed="true"],
.btn.f-hot.is-on {
  background: var(--c-accent);
  border-color: color-mix(in srgb, var(--c-accent) 70%, #000 30%);
  color: #fff;
  font-weight: 700;
  box-shadow: 0 1px 0 0 color-mix(in srgb, var(--c-accent) 45%, #000 55%);
}
/* 已经打开时 hover 不再"变淡 / 回退"，只抬 1px（否则看起来像被点回去了） */
.btn.f-hot[aria-pressed="true"]:hover,
.btn.f-hot.is-on:hover {
  background: color-mix(in srgb, var(--c-accent) 88%, #fff 12%);
  color: #fff;
}
.btn.f-hot:active { transform: translateY(1px); }

.batch { display: grid; gap: var(--s-4); grid-template-columns: 1fr; }
@media (min-width: 900px) { .batch { grid-template-columns: minmax(0, 1fr) 330px; } }
.batch textarea { font-family: var(--font-mono); font-size: 13px; }

/* ------------------- ⑩ 弹窗（成品页 + 后台都用这一套） -------------------
   ⚠ 两条硬要求：
     ① 弹窗**内容长**（激活地区说明实测有几百字）也能关掉/看完 ⇒ `.modal` 自己滚、
        `.modal-body` 里再滚一层（锁区编辑器那张表），**不设 maxlength、不做任何截断**；
     ② ESC / 点遮罩关闭由 `bindModalDismiss()` 统一接线（点弹窗内部不算）。 */
.modal-mask { position: fixed; inset: 0; z-index: 80; background: rgba(8, 11, 17, .6); display: grid; place-items: center; padding: var(--s-4); }
.modal {
  width: min(560px, 96vw); max-height: 88vh; display: flex; flex-direction: column;
  background: var(--bg); border: 1px solid var(--line); border-radius: var(--r-lg);
  box-shadow: var(--sh-2); overflow: hidden;
}
.modal.modal-wide { width: min(1120px, 96vw); }
.modal-head { display: flex; align-items: center; gap: var(--s-3); padding: var(--s-4); border-bottom: 1px solid var(--line-soft); }
.modal-head h3 { margin: 0; font-size: 16px; flex: 1 1 auto; }
.modal-body { padding: var(--s-4); overflow: auto; }
.modal-foot { display: flex; align-items: center; gap: var(--s-3); flex-wrap: wrap; padding: var(--s-3) var(--s-4); border-top: 1px solid var(--line-soft); background: var(--bg-2); }
.modal-foot .spacer { flex: 1 1 auto; }
/* 只读全文块：**必须换行 + 可滚**（几百字也不截） */
.lockfull { white-space: pre-wrap; word-break: break-word; font-size: 14px; line-height: 1.6;
  background: var(--bg-2); border: 1px solid var(--line-soft); border-radius: var(--r-md);
  padding: var(--s-3) var(--s-4); max-height: 46vh; overflow: auto; }
.modal-lead { margin: 0 0 var(--s-2); font-weight: 650; }
/* ★ 2026-10-16（用户：「右上角的有效期样式套用到左边来，然后右边的删掉」）：
   正文这行**套用原来标题右边那个药丸的样式**（橙底 + 圆角 + 加粗数字）✅
   ⚠ 右侧那个 `.modal-badge` 已删（生成处 + CSS 都删了）✅ */
.modal-meta {
  margin: 0 0 var(--s-2);
  display: flex; width: fit-content; align-items: center; gap: 4px;
  padding: 4px 10px; border-radius: var(--r-pill);
  background: var(--c-warn-soft); color: var(--c-warn);
  border: 1px solid color-mix(in srgb, var(--c-warn) 30%, transparent);
  font-size: 12.5px; white-space: nowrap;
}
.modal-meta b { font-weight: 800; font-variant-numeric: tabular-nums; }

/* 客户页的「查看完整说明」按钮（提示条里的那个细按钮已经由 .lockline 管了，
   这里只留弹窗里 / 单独出现时的兜底间距） */
.lockmore { margin-top: 0; }

/* 后台：可选令牌那条（折叠） */
.authbox { border: 1px solid var(--line-soft); border-radius: var(--r-md); padding: var(--s-3) var(--s-4); background: var(--bg-2); }
.authbox > summary { cursor: pointer; font-weight: 600; font-size: 14.5px; }
.authbox[open] > summary { margin-bottom: var(--s-3); }

/* 后台表格：封面缩略图 + 行内输入 */
.admin-tbl th.pickcol, .admin-tbl td.pick { width: 30px; text-align: center; }
.admin-tbl td.th { width: 132px; }
table.tbl .thumb-cell {
  display: block; width: 116px; aspect-ratio: 16 / 9; overflow: hidden;
  border-radius: var(--r-sm); background: var(--ph); border: 1px solid var(--line-soft);
}
table.tbl .thumb-cell img { width: 100%; height: 100%; object-fit: cover; display: block; }
table.tbl td.nm { max-width: 300px; }
table.tbl td.nm .nm-t { font-weight: 600; font-size: 14px; line-height: 1.4; word-break: break-word; }
table.tbl td.pricecell { white-space: nowrap; }

/* 后台：锁区编辑器（用户要求 ⑥：文本框 / 日期框统一到设计系统，别像外来的）
   · 三层结构：标题行 → 批量条（一条独立的横条：标题 + 字段行 + 动作行）→ 产品表；
   · 产品表**列对齐**（`table-layout: fixed` + 每列固定宽度）⇒ 说明 / 地区码 / 有效期上下成列，
     不再是一坨；说明那种长文本用 textarea（**默认 2 行、可拉高、不限长度**）。 */
.lockedit { border: 1px solid var(--line-soft); border-radius: var(--r-md); padding: var(--s-4); margin-bottom: var(--s-4); background: var(--bg-2); }
.lockedit + .lockedit { margin-top: var(--s-5); }
.lockedit-head { display: flex; align-items: center; gap: var(--s-3); margin-bottom: var(--s-3); }
.lockedit-thumb { flex: none; }
.lockedit-title { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.lockedit-title b { font-size: 15.5px; }
.lockedit-title .hint { font-size: var(--fs-small); }
/* 批量条：sticky 在弹窗滚动区顶部，勾了谁 / 填什么 / 作用范围一眼看清 */
.bulkbar {
  position: sticky; top: 0; z-index: 2;
  display: flex; flex-direction: column; gap: var(--s-2);
  padding: var(--s-3); margin-bottom: var(--s-3);
  background: color-mix(in srgb, var(--bg) 94%, transparent);
  border: 1px solid var(--line); border-radius: var(--r-md);
  box-shadow: var(--sh-1);
  backdrop-filter: saturate(160%) blur(6px);
  -webkit-backdrop-filter: saturate(160%) blur(6px);
}
.bulkbar-t { display: flex; align-items: baseline; gap: var(--s-2); flex-wrap: wrap; }
.bulkbar-t b { font-size: 14.5px; }
.bulkbar-fields { display: flex; align-items: flex-start; gap: var(--s-2); flex-wrap: wrap; }
.bulkbar-act { display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap; }
.bulkbar .a-bulk-text { flex: 1 1 340px; min-width: 220px; min-height: 54px; }
.bulkbar .a-bulk-regions { width: 160px; }
.bulkbar .lk-date { display: inline-flex; align-items: center; gap: 6px; }
/* 产品表：固定列宽 ⇒ 产品 / 说明 / 地区码 / 有效期四列上下对齐 */
table.tbl.lock-tbl { min-width: 860px; table-layout: fixed; }
table.tbl.lock-tbl thead th:nth-child(1) { width: 38px; }
table.tbl.lock-tbl thead th:nth-child(2) { width: 200px; }
table.tbl.lock-tbl thead th:nth-child(4) { width: 226px; }   /* ★ 2026-10-15：日期框 148 + 间距 6 + 按钮 34 + 内边距 24 */
table.tbl.lock-tbl td.pick { text-align: center; }
table.tbl.lock-tbl td.pn { font-weight: 600; word-break: break-word; }
table.tbl.lock-tbl td.ctext textarea { min-height: 62px; }
table.tbl.lock-tbl td.cregion input { font-family: var(--font-mono); text-transform: uppercase; letter-spacing: .04em; }
/* ⚠ 2026-10-15 修（用户截图：日期框被拉得超宽）：
   原来写的是 `width: 100%`，而「地区码」列被我隐藏后，最后一列吃掉了腾出来的宽度
   ⇒ 输入框跟着变超宽 ❌  ⇒ 改成**固定宽度** ✅ */
table.tbl.lock-tbl td.cdate input[type="date"] { width: 100%; max-width: 168px; }

/* 列宽重新分配：勾选 / 产品 / 说明(自适应) / 有效期(固定) */
table.tbl.lock-tbl th:nth-child(3), table.tbl.lock-tbl td.ctext { width: auto; }
table.tbl.lock-tbl td.cdate { width: 190px; }

/* ★ 隐藏**原生日历图标** —— 我们要装自己的日历弹层，避免两个入口打架；
   输入框本身仍可键盘直接打字（无障碍/表单逻辑都不受影响）✅ */
table.tbl.lock-tbl input[type="date"]::-webkit-calendar-picker-indicator,
.dtp input[type="date"]::-webkit-calendar-picker-indicator { opacity: 0; pointer-events: none; }

/* ★ 2026-10-15（用户要求）：锁区表**去掉「地区码」那一列** ——
   现在地区码由说明文字**自动识别**得出，不用手填 ✅
   ⚠ `.f-p-regions` 这个 input **必须留在 DOM 里**（提交逻辑靠它拿值）⇒ 只隐藏、不删 ✅

   ⚠⚠ 2026-10-15 二次修（用户截图：有效期列占了大半屏、表头是空的）：
     我上一版拿 **`th:nth-child(4)`** 去指「地区码」那一列表头，
     但那个表头**已经从 HTML 里删掉了** ⇒ 第 4 个 th 变成了「激活有效期」
     ⇒ 把它的表头也隐藏了、列宽跟着错位 ❌
     ⇒ **教训：绝不用位置选择器指某一列**，列一改位置就全错 ✅
     现在：**只隐藏行里的 `td.cregion`**（表头在 HTML 里本来就不生成了）✅ */
table.tbl.lock-tbl td.cregion { display: none !important; }

/* 列宽：table-layout: fixed + 明确的四列（勾选 / 产品 / 说明 / 有效期）✅ */
table.tbl.lock-tbl { table-layout: fixed; }
/* ★ 2026-10-16：勾选列删了 ⇒ 现在是 **3 列**（产品 / 激活地区说明）✅ */
table.tbl.lock-tbl { min-width: 720px; }
table.tbl.lock-tbl th:nth-child(1), table.tbl.lock-tbl td.pn { width: 240px; }
table.tbl.lock-tbl th:nth-child(2), table.tbl.lock-tbl td.ctext { width: auto; }
table.tbl.lock-tbl th:nth-child(2), table.tbl.lock-tbl td.pn { width: 230px; }
table.tbl.lock-tbl th:nth-child(3), table.tbl.lock-tbl td.ctext { width: auto; }
/* ⚠ 2026-10-15 三次修（用户截图：日历按钮被裁掉了）：
   日期框 148 + 间距 6 + 按钮 34 = **188px**，再减掉 td 的左右内边距 ⇒ 列宽至少 **226px**。
   我上一版给 186px ⇒ 按钮被挤掉 ❌（更早那版写 190px 也不够，而且那条规则当时选择器写错被干掉了，
   才导致列宽失控 = 用户说的"占了大半屏"）✅ */
table.tbl.lock-tbl th:nth-child(4), table.tbl.lock-tbl td.cdate { width: 226px; }
/* 有效期格里不许裁内容（日历按钮必须完整可见） */
table.tbl.lock-tbl td.cdate { overflow: visible; width: 226px; }
/* ⚠ 这里必须**同时**给 th 和 td 写宽度 —— 这两套样式块里 th/td 的宽度是分开写的，
   只写一个会被另一套覆盖（我就在这上面绕了三圈）✅ */
table.tbl.lock-tbl td.cdate .dtp { display: inline-flex; }
/* 说明列里那个 textarea 跟着列宽走，不要顶破 */
table.tbl.lock-tbl td.ctext textarea { width: 100%; max-width: 100%; box-sizing: border-box; }
.pkgbox { border: 1px dashed var(--line); border-radius: var(--r-md); padding: var(--s-3) var(--s-4); background: var(--bg); }
.pkgbox > summary { cursor: pointer; font-size: 14px; color: var(--text-2); font-weight: 600; }
.pkgbox-body { display: flex; flex-direction: column; gap: var(--s-3); margin-top: var(--s-3); }
.pkgbox-body label { display: flex; flex-direction: column; gap: 4px; }

@media print {
  .topbar, .sitefoot, .iconbtn, .searchbar { display: none !important; }
  .card, .panel { box-shadow: none; }
}

/* ── 月包「包含的商品」网格已上移到 ⑥（`.mgrid` / `.mitem`）：
      2026-10-15 首页置顶的月包分区与月包详情**共用**同一套网格类，
      所以只留一处定义 —— 别在这里再写一份（两份会互相覆盖，改一处不生效）。 ── */

/* ★ 2026-10-15（用户截图指明）：眉标行 = 左「月份 · 品牌」+ 右「锁区」按钮（锁区在**右上角**）。
   用 flex 排同一行（比绝对定位稳：长月份名/长文案都不会叠）。 */
.meyebrow-row { display: flex; align-items: center; justify-content: space-between; gap: var(--s-3); width: 100%; flex-wrap: wrap; }
.meyebrow-row .meyebrow { margin: 0; }
.meyebrow-row .leadlock { flex: none; }

/* ★★ 2026-10-15（用户要求）：**打了 HOT 的卡片要整体高亮**（原来只有右上角一个小药丸，不够显眼）。
   做法：① 描边换成强调色 ② 一层彩色光晕 ③ 卡片顶部一条 3px 强调条 ④ hover 更亮一点。
   ⚠ 只用设计系统里已有的变量（--c-accent / --line / --sh-2 / --r-lg），深浅色都自动跟着变。 */
.card.hot {
  border-color: color-mix(in srgb, var(--c-accent) 52%, var(--line));
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--c-accent) 26%, transparent),
              0 6px 22px -10px color-mix(in srgb, var(--c-accent) 55%, transparent),
              var(--sh-2);
  position: relative;
}
/* 顶部强调条：贴住圆角上沿（用 border-radius 裁切，不用伪元素超出） */
.card.hot::before {
  content: ""; position: absolute; left: 0; right: 0; top: 0; height: 3px;
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--c-accent) 92%, transparent),
    color-mix(in srgb, var(--c-accent) 45%, transparent));
  border-top-left-radius: var(--r-lg); border-top-right-radius: var(--r-lg);
  pointer-events: none;
}
.card.hot:hover {
  border-color: color-mix(in srgb, var(--c-accent) 70%, var(--line));
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--c-accent) 34%, transparent),
              0 10px 26px -10px color-mix(in srgb, var(--c-accent) 62%, transparent),
              var(--sh-2);
}
/* 深色模式下光晕要更淡一点，否则糊成一团 */
:root[data-theme="dark"] .card.hot:hover {
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--c-accent) 30%, transparent),
              0 10px 24px -12px color-mix(in srgb, var(--c-accent) 45%, transparent),
              var(--sh-2);
}
/* HOT 药丸本身也稍微加强（和卡片高亮呼应） */
.badge.hot-badge { letter-spacing: .06em; }

/* ★ 2026-10-15（用户："月包的锁区按钮没有让人知道这是可以点击的意思"）：
   把它做得**明确像按钮** —— ① 文案带动词「锁区说明」② 末尾 `›` 指示可展开
   ③ hover 抬 1px + 变亮 ④ 按下回落 ⑤ `›` hover 时右移 ⑥ focus 有可见环（键盘可达） */
.lockcorner .lc-c { opacity: .85; margin-left: 1px; transition: transform .18s var(--ease); }
.lockcorner:hover .lc-c { transform: translateX(2px); }
.lockcorner:hover { background: var(--c-warn); transform: translateY(-1px); }
.lockcorner:active { transform: translateY(0); }
.lockcorner:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--c-brand-ring); }
@media (prefers-reduced-motion: reduce) {
  .lockcorner, .lockcorner .lc-c { transition: none; }
  .lockcorner:hover { transform: none; }
  .lockcorner:hover .lc-c { transform: none; }
}

/* ★★ 2026-10-15（用户 ⑦「点保存按钮**居中上方**提示」）：「保存修改」按钮的小气泡。
   ⚠ 位置 = **按钮正上方、水平居中**（`left:50% + bottom:calc(100% + 8px) + translateX(-50%)`）；
     下面那条 `::after` 是**指向按钮的小三角**（气泡和按钮之间就有个"说话的方向"）。
   ⚠ 别被裁掉：`z-index` 抬到 30（那条 sticky 是 10）；`.commitbar` 与 `#view` 都**没有** `overflow:hidden`；
     气泡只有 20 来像素高，往上让出 8px 后仍然落在表格区里 —— 在页面顶端也不会被视口切掉。
   ⚠ 窄屏**同样是上方居中**（老规则"<900px 改成下方"已删）。 */
.bubble {
  /* ★★ 2026-10-15 重做：改成 `fixed` —— 坐标由 JS 用 getBoundingClientRect() 算（挂在 body 上）。
     ⚠ 以前是 `absolute` + 挂在按钮父节点里 ⇒ 任何一个祖先有 overflow/transform/非定位
       都会让气泡**被裁掉或跑偏**（用户："位置还是没变"就是这么来的）。
     fixed 相对**视口**定位 ⇒ 祖先怎么变都影响不到它 ✅ */
  position: fixed; left: 0; top: 0; z-index: 200;
  white-space: nowrap; pointer-events: none;
  padding: 5px 10px; border-radius: var(--r-pill); font-size: 13px; font-weight: 620;
  background: var(--bg); border: 1px solid var(--line); box-shadow: var(--sh-2);
  color: var(--text-2); transition: opacity .2s var(--ease); animation: pop .16s var(--ease);
}
/* 指向按钮的小三角（和气泡同色同边） */
.bubble::after {
  content: ''; position: absolute; left: 50%; margin-left: -5px; top: 100%;
  width: 0; height: 0; border: 5px solid transparent; border-top-color: var(--bg);
}
.bubble.ok { color: var(--c-ok); border-color: color-mix(in srgb, var(--c-ok) 40%, var(--line)); }
.bubble.err { color: var(--c-danger); border-color: color-mix(in srgb, var(--c-danger) 40%, var(--line)); }
.bubble.warn { color: var(--c-warn); border-color: color-mix(in srgb, var(--c-warn) 40%, var(--line)); }

/* ═══════════════════════════════════════════════════════════════════
   ★ 2026-10-15 设置页加宽 + 锁区设置 UI（用户：「优化设置里的周包锁区设置 UI，
   并且整个设置页面太局促了，太窄了」）
   ⚠ 全部写在末尾做**覆盖**，不动上面的规则 —— 客户页的观感一个像素都不变。
   ═══════════════════════════════════════════════════════════════════ */

/* ① 设置页单独放宽（`.wrap` 全站 1240px 是给客户页定的；后台信息密度高，需要更宽） */
body.is-admin .wrap { max-width: 1760px; }
@media (min-width: 1280px) { body.is-admin .wrap { padding-left: var(--s-5); padding-right: var(--s-5); } }
/* 后台里那些面板也放宽一点内边距，别贴着边 */
body.is-admin .admin .panel { padding: var(--s-5); }

/* ② 「锁区设置」弹窗：加宽 + 内容区留足呼吸（长说明 + 产品表并排看才不挤） */
.modal.modal-wide { width: min(1560px, 97vw); }
.modal.modal-wide .modal-body { padding: var(--s-4) var(--s-5); }

/* ③ 锁区编辑器：外层卡片更松快，标题行加一条分隔线 */
.lockedit { padding: var(--s-4) var(--s-5) var(--s-5); }
.lockedit-head { gap: var(--s-4); padding-bottom: var(--s-3); margin-bottom: var(--s-4);
  border-bottom: 1px solid var(--line-soft); }
.lockedit-thumb img, .lockedit-thumb .ph { width: 84px; height: 48px; border-radius: var(--r-sm); }
.lockedit-title b { font-size: 16px; letter-spacing: .01em; }
.lockedit-title .hint { color: var(--text-2); }

/* ④ 批量条：做成**一眼看清的卡片**（勾了谁 / 填什么 / 作用范围），字段按网格对齐 */
.bulkbar {
  border: 1px solid var(--line); border-radius: var(--r-md);
  background: var(--bg-2); padding: var(--s-4) var(--s-4) var(--s-3);
  box-shadow: var(--sh-1);
}
.bulkbar-t { gap: var(--s-3); align-items: center; }
.bulkbar-t b { font-size: 15px; }
.bulkbar-t .hint { font-size: var(--fs-small); color: var(--text-2); }
/* 说明占满一整行；地区码 + 有效期并排一行（宽屏时） */
.bulkbar-fields { display: grid; grid-template-columns: 1fr; gap: var(--s-2); align-items: start; }
@media (min-width: 860px) {
  .bulkbar-fields { grid-template-columns: minmax(0, 1fr) 220px 220px; }
  .bulkbar-fields > .a-bulk-text { grid-column: 1 / -1; }
}
.bulkbar-fields .a-bulk-text { min-height: 62px; resize: vertical; }
.bulkbar-fields .a-bulk-regions, .bulkbar-fields .a-bulk-until { width: 100%; }
.bulkbar-fields .lk-date { display: flex; flex-direction: column; gap: 4px; }
.bulkbar-act { margin-top: var(--s-1); }

/* ⑤ 产品表：列宽重排（产品更宽、说明给足、地区码/日期不必太宽），行距放松 */
table.tbl.lock-tbl { min-width: 1080px; table-layout: fixed; }
table.tbl.lock-tbl thead th:nth-child(1) { width: 44px; }
table.tbl.lock-tbl thead th:nth-child(2) { width: 260px; }
table.tbl.lock-tbl thead th:nth-child(3) { width: auto; }
table.tbl.lock-tbl thead th:nth-child(4) { width: 226px; }   /* ★ 2026-10-15：同上；原来是 170px，会把日历按钮裁掉 ❌ */
table.tbl.lock-tbl td { padding: var(--s-3) var(--s-3); vertical-align: top; }
table.tbl.lock-tbl td.pn { font-weight: 600; line-height: 1.45; }
table.tbl.lock-tbl tbody tr:hover { background: var(--bg-3); }
/* 勾选中的那一行**高亮**（一眼看出"批量填"会落到谁头上） */
table.tbl.lock-tbl tbody tr:has(.f-prod-pick:checked) { background: color-mix(in srgb, var(--c-brand) 10%, transparent); }

/* ⑥ 锁区里那些输入框：统一高度/圆角，说明框可拉高（几百字也不挤） */
table.tbl.lock-tbl td.ctext textarea { min-height: 68px; resize: vertical; line-height: 1.55; }
table.tbl.lock-tbl td.cregion input { letter-spacing: .06em; }
table.tbl.lock-tbl input[type="text"], table.tbl.lock-tbl input[type="date"] { height: 36px; }

/* ⑦ 「整包设置」折叠块：让它看起来像"另一层"，别和产品表糊在一起 */
.pkgbox { margin-top: var(--s-4); background: var(--bg-2); }
.pkgbox[open] { padding-bottom: var(--s-4); }
.pkgbox-body { display: grid; gap: var(--s-2); margin-top: var(--s-3); }
@media (min-width: 860px) { .pkgbox-body { grid-template-columns: minmax(0, 1fr) 220px 220px; }
  .pkgbox-body > .lk-text, .pkgbox-body > textarea { grid-column: 1 / -1; } }

/* ═══════════════════════════════════════════════════════════════════
   ★ 2026-10-15 设置页打磨（第二轮）
   ═══════════════════════════════════════════════════════════════════ */

/* ① 后台表格：宽度放开后别让「操作 / key」之间空一大片 —— 给各列定宽 */
body.is-admin table.admin-tbl { min-width: 0; width: 100%; }
body.is-admin table.admin-tbl thead th:nth-child(1) { width: 44px; }
body.is-admin table.admin-tbl thead th:nth-child(2) { width: 92px; }
body.is-admin table.admin-tbl thead th:nth-child(3) { width: auto; }
body.is-admin table.admin-tbl thead th:nth-child(4) { width: 190px; }
body.is-admin table.admin-tbl thead th:nth-child(5) { width: 168px; }
body.is-admin table.admin-tbl thead th:nth-child(6) { width: 420px; }
body.is-admin table.admin-tbl thead th:nth-child(7) { width: 132px; }
body.is-admin table.admin-tbl td { padding: var(--s-3) var(--s-3); }
body.is-admin table.admin-tbl td .actrow { gap: var(--s-1); }

/* ② 「① 价格」那块的长说明：别顶着头，收小一号、放宽行高 */
body.is-admin .admin .hint, body.is-admin .admin p.hint { font-size: 12.8px; line-height: 1.7; color: var(--text-2); }
body.is-admin .admin .panel > h2 { font-size: 16.5px; letter-spacing: .01em; }

/* ③ 留白：面板之间、面板里的小块之间都松一点 */
body.is-admin .admin .panel { margin-bottom: var(--s-5); }
body.is-admin .admin .panel .row + .row, body.is-admin .admin .row + .hint { margin-top: var(--s-2); }
body.is-admin .commitbar { padding: var(--s-3) var(--s-4); }

/* ④ 窄屏回退：视口小的时候仍然允许横向滚动（不要把列挤坏） */
@media (max-width: 1180px) {
  body.is-admin table.admin-tbl { min-width: 1080px; table-layout: auto; }
}

/* ═══════════════════════════════════════════════════════════════════
   ★ 2026-10-15 设置页打磨（第三轮：修操作列换行）
   ⚠ 第二轮给「操作」定宽 420px ⇒ IGN 那行（6 个按钮 + 「HOT ✓」）放不下 ⇒
     「清除设置」被挤到第二行、行高不一致 ❌
   ⇒ 改成"内容决定宽度 + 不换行"，既不会挤行，也不会在末尾留一大片空白 ✅
   ═══════════════════════════════════════════════════════════════════ */
body.is-admin table.admin-tbl { table-layout: auto; }
body.is-admin table.admin-tbl thead th:nth-child(6) { width: auto; white-space: nowrap; }
body.is-admin table.admin-tbl thead th:nth-child(7) { width: 1%; white-space: nowrap; }
/* 操作区**一行排开**：按钮再多也不换行（HOT ✓ 比 HOT 宽，正是它把上一版挤爆的） */
body.is-admin table.admin-tbl td .actrow { display: flex; flex-wrap: nowrap; align-items: center; gap: var(--s-1); }
body.is-admin table.admin-tbl td .actrow .btn { flex: none; white-space: nowrap; }

/* ═══════════════════════════════════════════════════════════════════
   ★ 2026-10-15 设置页列宽重排（用户：「这一行 KEY 就不要显示了，而且你加宽了页面
   之后这些按钮布局全部没适配」）
   · key 列已从表里去掉 ⇒ 现在 6 列：勾选 / 封面 / 名称 / 售价 / 加价比例 / 操作
   · **名称列吃掉剩余空间**，其余列按内容定宽；操作区一行排开不换行 ✅
   ═══════════════════════════════════════════════════════════════════ */
body.is-admin table.admin-tbl { table-layout: auto; min-width: 0; width: 100%; }
body.is-admin table.admin-tbl thead th:nth-child(1) { width: 44px; }
body.is-admin table.admin-tbl thead th:nth-child(2) { width: 104px; }
body.is-admin table.admin-tbl thead th:nth-child(3) { width: 100%; }
body.is-admin table.admin-tbl thead th:nth-child(4) { width: 200px; }
body.is-admin table.admin-tbl thead th:nth-child(5) { width: 176px; }
body.is-admin table.admin-tbl thead th:nth-child(6) { width: 1%; white-space: nowrap; }
body.is-admin table.admin-tbl td { padding: var(--s-3) var(--s-3); }
body.is-admin table.admin-tbl td .actrow { display: flex; flex-wrap: nowrap; align-items: center; gap: var(--s-1); }
body.is-admin table.admin-tbl td .actrow .btn { flex: none; white-space: nowrap; }
/* 月包 tab 多一列（采购价 + 加价比例）⇒ 操作列往后挪一位 */
body.is-admin table.admin-tbl.tbl-choice thead th:nth-child(6) { width: 176px; white-space: normal; }
body.is-admin table.admin-tbl.tbl-choice thead th:nth-child(7) { width: 1%; white-space: nowrap; }

/* ═══════════════════════════════════════════════════════════════════
   ★ 2026-10-15 整站换肤 —— 按参考项目（紫色渐变设计）重定义设计变量
   ⚠ 全部**追加在末尾**（后写胜出），不动上面原有 token 与注释 ⇒ 随时可回退。
   参考项目的设计取值：
     · 主色渐变 linear-gradient(135deg, #9a00da → #4e00ff)
     · 浅色底 #f8f9fa / #ffffff / #f0f2f5   深色底 #0f0f1a / #1a1a2e / #151528
     · 描边 rgba(154,0,218,.15)             阴影 rgba(78,0,255,.1)
     · 大圆角、胶囊按钮、半透明导航、大留白
   ═══════════════════════════════════════════════════════════════════ */

/* ---------- ① 浅色 ---------- */
:root {
  --c-brand:        #8a12d6;   /* 主紫（对白字 5.4:1，过 AA）*/
  --c-brand-strong: #4e00ff;
  --c-brand-soft:   #f5ecff;
  --c-brand-ring:   rgba(138, 18, 214, .30);
  /* 品牌渐变（参考项目的核心视觉）*/
  --grad-brand: linear-gradient(135deg, #9a00da 0%, #4e00ff 100%);
  --grad-brand-soft: linear-gradient(135deg, rgba(154,0,218,.10) 0%, rgba(78,0,255,.10) 100%);

  --c-accent:      #8a12d6;   /* 价格：跟主色同系（原来那支金色与紫色打架）*/
  --c-accent-soft: #f5ecff;
  --c-ok:      #0f9d6b;
  --c-ok-soft: #e9f9f2;
  --c-warn:      #b45309;
  --c-warn-soft: #fdf4e8;
  --c-danger:      #dc2626;
  --c-danger-soft: #fdecec;

  --text:      #1a1a1a;
  --text-2:    #5f6470;
  --text-3:    #6b7280;
  --bg:        #ffffff;
  --bg-2:      #f8f9fa;
  --bg-3:      #f0f2f5;
  --line:      #e6e6f0;
  --line-soft: rgba(154, 0, 218, .14);   /* 参考项目那支淡紫描边 */

  /* 圆角整体放大（参考项目是"大圆角胶囊"的调子）*/
  --r-xs: 8px; --r-sm: 10px; --r-md: 14px; --r-lg: 20px;

  /* 阴影换成紫色系（参考项目 shadow-color = rgba(78,0,255,.1)）*/
  --sh-1: 0 2px 8px rgba(78, 0, 255, .07), 0 1px 3px rgba(78, 0, 255, .05);
  --sh-2: 0 14px 36px rgba(78, 0, 255, .13), 0 3px 10px rgba(78, 0, 255, .07);

  /* 字体：本地自托管的 Inter 打头（中文自动落后面的系统字体）*/
  --font: "Inter", "PingFang SC", "HarmonyOS Sans SC", "Microsoft YaHei UI", "Microsoft YaHei",
          "Hiragino Sans GB", "Noto Sans CJK SC", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;

  /* 图片占位也带上紫调 */
  --ph: linear-gradient(135deg, #ece4fb 0%, #f3f0fd 45%, #e6e0fb 100%);
}

/* ---------- ② 深色：跟随系统（没手动选过主题时）---------- */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
  --c-brand:        #b366e0;
  --c-brand-strong: #c79bff;
  --c-brand-soft:   #24183a;
  --c-brand-ring:   rgba(179, 102, 224, .40);
  --grad-brand: linear-gradient(135deg, #b366e0 0%, #7a3fff 100%);
  --grad-brand-soft: linear-gradient(135deg, rgba(179,102,224,.16) 0%, rgba(122,63,255,.16) 100%);

  --c-accent:      #c79bff;
  --c-accent-soft: #24183a;
  --c-ok:      #4cc38a;
  --c-ok-soft: #14291f;
  --c-warn:      #e2b04a;
  --c-warn-soft: #2a2415;
  --c-danger:      #f0715f;
  --c-danger-soft: #2c1a18;

  --text:      #f0f0f5;
  --text-2:    #a0a0b0;
  --text-3:    #9096a6;
  --bg:        #0f0f1a;
  --bg-2:      #1a1a2e;
  --bg-3:      #151528;
  --line:      #2a2a44;
  --line-soft: rgba(179, 102, 224, .18);

  --sh-1: 0 2px 8px rgba(0, 0, 0, .5);
  --sh-2: 0 16px 40px rgba(0, 0, 0, .6), 0 4px 12px rgba(122, 63, 255, .10);

  --ph: linear-gradient(135deg, #221a38 0%, #1a1a2e 45%, #241f42 100%);
  }
}

/* ---------- ③ 深色：手动选了深色 ---------- */
:root[data-theme="dark"] {
  --c-brand:        #b366e0;
  --c-brand-strong: #c79bff;
  --c-brand-soft:   #24183a;
  --c-brand-ring:   rgba(179, 102, 224, .40);
  --grad-brand: linear-gradient(135deg, #b366e0 0%, #7a3fff 100%);
  --grad-brand-soft: linear-gradient(135deg, rgba(179,102,224,.16) 0%, rgba(122,63,255,.16) 100%);

  --c-accent:      #c79bff;
  --c-accent-soft: #24183a;
  --c-ok:      #4cc38a;
  --c-ok-soft: #14291f;
  --c-warn:      #e2b04a;
  --c-warn-soft: #2a2415;
  --c-danger:      #f0715f;
  --c-danger-soft: #2c1a18;

  --text:      #f0f0f5;
  --text-2:    #a0a0b0;
  --text-3:    #9096a6;
  --bg:        #0f0f1a;
  --bg-2:      #1a1a2e;
  --bg-3:      #151528;
  --line:      #2a2a44;
  --line-soft: rgba(179, 102, 224, .18);

  --sh-1: 0 2px 8px rgba(0, 0, 0, .5);
  --sh-2: 0 16px 40px rgba(0, 0, 0, .6), 0 4px 12px rgba(122, 63, 255, .10);

  --ph: linear-gradient(135deg, #221a38 0%, #1a1a2e 45%, #241f42 100%);
}

/* ---------- ④ 形状：胶囊按钮 / 渐变主按钮 / 紫描边次按钮 ---------- */
.btn { border-radius: var(--r-pill); font-weight: 600; letter-spacing: .01em;
  transition: background-color .18s var(--ease), color .18s var(--ease),
    border-color .18s var(--ease), box-shadow .18s var(--ease), transform .12s var(--ease); }
.btn.primary { background: var(--grad-brand); border-color: transparent; color: #fff;
  box-shadow: 0 4px 14px rgba(78, 0, 255, .28); }
.btn.primary:hover { box-shadow: 0 8px 22px rgba(78, 0, 255, .38); transform: translateY(-1px); }
.btn.primary:active { transform: translateY(0); }
.btn.primary:disabled { background: var(--bg-3); color: var(--text-3); box-shadow: none; border-color: var(--line); }
/* 次按钮 = 白底 + 紫描边 + 紫字（参考项目那支"联系我们"按钮）*/
.btn:not(.primary):not(.iconmini) { border-color: color-mix(in srgb, var(--c-brand) 42%, transparent);
  color: var(--c-brand); background: var(--bg); }
.btn:not(.primary):not(.iconmini):hover { background: var(--c-brand-soft);
  border-color: var(--c-brand); box-shadow: 0 4px 14px rgba(78, 0, 255, .14); }

/* ---------- ⑤ 导航：半透明玻璃感 + 底部淡紫描边 ---------- */
.topbar { background: color-mix(in srgb, var(--bg) 88%, transparent); backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--line-soft); box-shadow: 0 1px 0 rgba(78, 0, 255, .04); }

/* ---------- ⑥ 卡片 / 面板：大圆角 + 紫系阴影 ---------- */
.card, .panel, .mblock, .tier, .pkgbox, .lockedit, .modal, .bulkbar, .notice, .errorbox {
  border-radius: var(--r-lg); }
.card { border-color: var(--line-soft); box-shadow: var(--sh-1); }
.card:hover { box-shadow: var(--sh-2); border-color: color-mix(in srgb, var(--c-brand) 30%, transparent); }
.panel { border-color: var(--line-soft); }

/* ---------- ⑦ 标题：更重的字重 + 更紧的字距（参考项目那种"粗黑"观感）---------- */
h1, .h1, .brand-name, .admin h2, .panel > h2 { letter-spacing: -.01em; }
h1, .h1 { font-weight: 800; }

/* ---------- ⑧ 表格表头：淡紫底（参考项目的浅紫调）---------- */
/* ★★ 2026-10-16 修（用户：「夜间模式下这里一条怎么一块一块的」）：
   ⚠ 原来这里是 `background: var(--grad-brand-soft)` —— 那是 **135° 线性渐变**，
     而 `table.tbl thead th` 又是 `position: sticky` ⇒ **每一格各画一遍渐变**，
     每格都从自己的左上角重新起步 ⇒ 窄格里渐变被"切碎" ⇒ 看起来**一块深一块浅** ❌
     （暗色底下对比更强，所以先在夜间模式被发现 ✅）
   ⇒ 改法：表头底改成**纯色**（`--bg-2` 里混一点点品牌紫，保住"淡紫调"的观感），
     再用一条**品牌色下边线**当分隔 —— 纯色 + sticky 天然连续，两种主题都不会碎 ✅
   ⚠ 不要再往单个 th 上放渐变（那是这次碎块的根因）✅ */
table.tbl thead th, table.tbl thead td {
  background: color-mix(in srgb, var(--c-brand) 7%, var(--bg-2));
}
table.tbl thead th {
  border-bottom: 1px solid color-mix(in srgb, var(--c-brand) 30%, transparent);
}
/* 勾选态、聚焦环统一到主紫 */
input[type="checkbox"], input[type="radio"] { accent-color: var(--c-brand); }
:where(a, button, input, select, textarea):focus-visible { outline: 2px solid var(--c-brand-ring); outline-offset: 2px; }

/* ---------- ⑨ 滚动条也带上紫调（参考项目有专门配色）---------- */
* { scrollbar-color: color-mix(in srgb, var(--c-brand) 45%, transparent) transparent; }

/* ═══════════════════════════════════════════════════════════════════
   ★ 2026-10-15 HOT 独立色（换肤收尾）
   ⚠ 换肤时 HOT 按钮跟着主色变成了紫 ⇒ 与「主操作按钮 / tab 选中态」撞成一片，
     一眼看不出谁是主动作、谁是状态开关 ❌
   ⇒ 给 HOT 一个**独立色**（青绿，与紫互补）：状态开关用它，主动作仍用紫 ✅
   ⚠ 价格保持紫色（`--c-accent`）—— 它和整体协调，不动。
   ═══════════════════════════════════════════════════════════════════ */
:root {
  --c-hot:      #0d9488;   /* HOT 开：青绿（对白字 4.6:1，过 AA）*/
  --c-hot-soft: #e6faf7;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { --c-hot: #2dd4bf; --c-hot-soft: #0f2b28; }
}
:root[data-theme="dark"] { --c-hot: #2dd4bf; --c-hot-soft: #0f2b28; }

/* HOT 按钮：开着＝青绿实心白字；关着＝透明底灰字（和后端 HOT 角标同色系）*/
.btn.f-hot[aria-pressed="true"], .btn.f-hot.is-on {
  background: var(--c-hot);
  border-color: color-mix(in srgb, var(--c-hot) 72%, #000 28%);
  color: #fff; font-weight: 700;
  box-shadow: 0 2px 10px color-mix(in srgb, var(--c-hot) 38%, transparent);
}
.btn.f-hot[aria-pressed="true"]:hover, .btn.f-hot.is-on:hover {
  background: color-mix(in srgb, var(--c-hot) 86%, #fff 14%); color: #fff;
}
.btn.f-hot { border-color: var(--line); color: var(--text-2); background: transparent; }

/* 客户页的 HOT 角标也统一到这支青绿 */
/* ⚠ 实际类名是 .badge.hot / .badge.hot-badge（我第一版写成了 .hotbadge，没生效）*/
.badge.hot, .badge.hot-badge { background: var(--c-hot); color: #fff; border-color: transparent; }

/* ═══════════════════════════════════════════════════════════════════
   ★ 2026-10-15 两个「区容器」去掉外框（用户：「月包周包区域都有一大块线条包着，
     这一整个外边那一圈，看着很难受不合理」）
   · `#month-sec`（本月 Choice 区）与 `#weekly-sec`（周包区）只是**分节容器**，
     却套了 `.card` 的边框 + 白底 + 阴影 ⇒ 整页看着被框成两大块 ❌
   ⇒ 去掉这两个容器的 **边框 / 底色 / 阴影 / 圆角裁剪**；
     ⚠ **里面真正的内容卡片**（`.mblock` 月包块、`.card` 单个周包、档位块）**保留**边框 ✅
     ⇒ 层级从"框里套框"变成"一节节摊开"，更像参考项目那种大留白的排布 ✅
   ═══════════════════════════════════════════════════════════════════ */
#month-sec, #weekly-sec {
  background: transparent;
  border: none;
  box-shadow: none;
  border-radius: 0;
  overflow: visible;
}
/* 这两个区的卡头（标题行）去掉那条下边线，改成纯文字标题，别再有"框"的暗示 */
#month-sec > .card-head, #weekly-sec > .card-head { border-bottom: none; padding-bottom: 0; }


/* 内容必须浮在装饰层之上（否则符号会盖在卡片上）*/
.topbar, .wrap, .sitefoot { position: relative; z-index: 1; }

/* ═══════════════════════════════════════════════════════════════════
   ★ HOT 角标：把高特异性旧规则的文字色压回去（对比度 1.13:1 → 达标）
   ⚠ 旧规则 `:root[data-theme="dark"] .card .thumb .badge.hot-badge { color:#201705 }`
     特异性 (0,6,0)，比我那条 `.badge.hot-badge`（0,2,0）高 ⇒ 只改了底、文字还是深棕 ❌
   ⇒ 这里用**同等特异性 + 放在文件最后**（同权后写胜）✅
   ═══════════════════════════════════════════════════════════════════ */
:root .card .thumb .badge.hot,
:root .card .thumb .badge.hot-badge,
:root .badge.hot,
:root .badge.hot-badge,
:root[data-theme="dark"] .card .thumb .badge.hot,
:root[data-theme="dark"] .card .thumb .badge.hot-badge,
:root[data-theme="dark"] .badge.hot,
:root[data-theme="dark"] .badge.hot-badge,
:root:not([data-theme="light"]) .card .thumb .badge.hot,
:root:not([data-theme="light"]) .card .thumb .badge.hot-badge {
  background: var(--c-hot) !important;
  color: #fff !important;
  border-color: transparent !important;
}

/* ═══════════════════════════════════════════════════════════════════
   ★ 角标色修正（2026-10-15，修我自己造的两个问题）
   ⚠ ① 类名搞混：`.badge.hot` 是**「即将结束」**（警示），`.badge.hot-badge` 才是**真 HOT**
        —— 我给 `.badge.hot` 也套了青绿 ⇒ 「即将结束」被染成 HOT 色 ❌
   ⚠ ② 对比度：青绿 #0d9488 + 白字 = 3.74:1；深色 #2dd4bf + 白字 = 1.86:1（几乎看不清）❌
   ⇒ ①「即将结束」恢复原来的**暖橙警示**；青绿只给真 HOT
   ⇒ ② 青绿统一调深到 **#0f766e**（配白字 ≈5.3:1，深浅两套都过 AA）✅
   ═══════════════════════════════════════════════════════════════════ */

/* ① 真 HOT 角标：深青绿 + 白字（把前面那条 `.badge.hot` 一起写进来的覆盖掉）*/
.badge.hot-badge,
:root .card .thumb .badge.hot-badge,
:root[data-theme="dark"] .card .thumb .badge.hot-badge,
:root:not([data-theme="light"]) .card .thumb .badge.hot-badge {
  background: var(--c-hot) !important;
  color: #fff !important;
  border-color: transparent !important;
}

/* ② 「即将结束」（`.badge.hot`）恢复成**暖橙警示** —— 和 HOT 区分开 */
.card .thumb .badge.hot,
:root .card .thumb .badge.hot,
:root[data-theme="dark"] .card .thumb .badge.hot,
:root:not([data-theme="light"]) .card .thumb .badge.hot {
  background: var(--c-warn) !important;
  color: #fff !important;
  border-color: transparent !important;
}

/* ③ 青绿调深（浅深两套都用它；亮底白字不可读，这条是修对比度）*/
:root { --c-hot: #0f766e; --c-hot-soft: #e6f6f4; }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) { --c-hot: #14b8a6; --c-hot-soft: #0d2b28; } }
:root[data-theme="dark"] { --c-hot: #14b8a6; --c-hot-soft: #0d2b28; }
/* ⚠ 深色下 #14b8a6 + 白字只有 ~2.2:1 ⇒ HOT 角标在深色下改用**深字**（#04211f ≈ 8:1）✅ */
:root[data-theme="dark"] .badge.hot-badge,
:root:not([data-theme="light"]) .badge.hot-badge { color: #04211f !important; font-weight: 700 !important; }
/* 后台那个 HOT 开关按钮：深色下同理，用深字 */
:root[data-theme="dark"] .btn.f-hot.is-on,
:root:not([data-theme="light"]) .btn.f-hot.is-on { color: #04211f !important; }

/* ★ 深色 HOT 角标：同等特异性覆盖（浅色已达标，深色白字配亮青绿仍只有 2.49:1 ⇒ 改深字）*/
:root[data-theme="dark"] .card .thumb .badge.hot-badge,
:root:not([data-theme="light"]) .card .thumb .badge.hot-badge {
  background: var(--c-hot) !important;
  color: #04211f !important;
  font-weight: 700 !important;
  border-color: transparent !important;
}

/* ★ 2026-10-15 顶栏搜索去掉后：让 logo 占住留白（原来靠搜索框把左右两侧撑开）
   ⚠ 不这么写的话，logo 与右侧按钮会**挤在左边** ❌ */
.topbar-in > .brand { margin-right: auto; }
/* ⚠ `.searchbar` 的样式暂时留着（万一以后要加回顶栏搜索）；现在页面上已经没有它了。 */

/* ===================================================================
   Background game symbols (2026-10-15)
   User: "just copy that side's code, make it exactly the same effect,
          I want it to be the site's signature symbol."
   => Verbatim port of D:\Ai Tools\Projects\test1 : .game-symbols / .game-symbol /
      the 20 :nth-child positions / @keyframes float-symbol / the stroke color.

   ONE necessary deviation: z-index 0 -> 2.
   That project's body text sits on no white cards, so z-index:0 is still visible there;
   our pages are full of white cards, so copying 0 would hide every symbol
   (that is exactly why the animation looked "not working"). Lifting it above the content
   while keeping pointer-events:none reproduces the SAME effect here.
   Also added prefers-reduced-motion:reduce off-switch (our site-wide rule; theirs lacks it).
   =================================================================== */
.game-symbols {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 2;
  overflow: hidden;
}

.game-symbol {
  position: absolute;
  font-size: 1.8rem;
  color: transparent;
  -webkit-text-stroke: 1.5px var(--game-symbol-color);
  font-weight: 300;
  animation: float-symbol 3s ease-in-out infinite;
  will-change: transform, opacity;
}

.game-symbol:nth-child(1) { top: 8%; left: 5%; animation-delay: 0s; }
.game-symbol:nth-child(2) { top: 12%; left: 25%; animation-delay: 0.3s; }
.game-symbol:nth-child(3) { top: 18%; right: 8%; animation-delay: 0.6s; }
.game-symbol:nth-child(4) { top: 22%; right: 30%; animation-delay: 0.9s; }
.game-symbol:nth-child(5) { top: 35%; left: 3%; animation-delay: 1.2s; }
.game-symbol:nth-child(6) { top: 38%; left: 40%; animation-delay: 1.5s; }
.game-symbol:nth-child(7) { top: 42%; right: 5%; animation-delay: 1.8s; }
.game-symbol:nth-child(8) { top: 48%; right: 25%; animation-delay: 2.1s; }
.game-symbol:nth-child(9) { top: 55%; left: 12%; animation-delay: 2.4s; }
.game-symbol:nth-child(10) { top: 58%; left: 35%; animation-delay: 2.7s; }
.game-symbol:nth-child(11) { top: 62%; right: 10%; animation-delay: 3s; }
.game-symbol:nth-child(12) { top: 68%; right: 35%; animation-delay: 3.3s; }
.game-symbol:nth-child(13) { top: 75%; left: 6%; animation-delay: 3.6s; }
.game-symbol:nth-child(14) { top: 78%; left: 28%; animation-delay: 3.9s; }
.game-symbol:nth-child(15) { top: 82%; right: 8%; animation-delay: 4.2s; }
.game-symbol:nth-child(16) { top: 88%; right: 22%; animation-delay: 4.5s; }
.game-symbol:nth-child(17) { top: 5%; left: 45%; animation-delay: 4.8s; }
.game-symbol:nth-child(18) { top: 92%; left: 15%; animation-delay: 5.1s; }
.game-symbol:nth-child(19) { top: 28%; left: 18%; animation-delay: 5.4s; }
.game-symbol:nth-child(20) { top: 65%; left: 50%; animation-delay: 5.7s; }

/* ⚠ 2026-10-15 smoother opacity curve（用户：「过渡非常生硬，突然间就变暗了，不是渐变暗」）
   原来只有 0% / 50% / 100% 三个点 ⇒ 峰值是尖的，两端长时间压在最暗值上，
   看着就是"亮一下、很快掉下去、长时间暗着"。
   ⇒ **只加中间关键帧**（.28 / .35 / .28），把曲线拉成接近正弦；
     峰值与谷底**仍是参考项目的 .35 / .2**，其它一切不动 ✅ */
@keyframes float-symbol {
  0%   { transform: translateY(0)     rotate(0deg);   opacity: 0.20; }
  25%  { transform: translateY(-9px)  rotate(1.8deg); opacity: 0.28; }
  50%  { transform: translateY(-15px) rotate(3deg);   opacity: 0.35; }
  75%  { transform: translateY(-9px)  rotate(1.8deg); opacity: 0.28; }
  100% { transform: translateY(0)     rotate(0deg);   opacity: 0.20; }
}

@media (prefers-reduced-motion: reduce) {
  .game-symbol { animation: none; opacity: 0.25; }
}

/* stroke color (verbatim from that project) */
:root { --game-symbol-color: rgba(128, 128, 128, 0.2); }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) { --game-symbol-color: rgba(200, 200, 220, 0.15); } }
:root[data-theme="dark"] { --game-symbol-color: rgba(200, 200, 220, 0.15); }

/* ===================================================================
   2026-10-15 match the reference font stack for the glyphs
   User: "the triangle is the same, but the square is too thick."
   The computed styles of .game-symbol are already identical to the reference
   (28.8px / weight 300 / 1.5px stroke / rgba(128,128,128,.2) / 3s). The only
   remaining difference is the FONT STACK these geometric glyphs (● × □ ▲) fall back to:
     reference: Inter, "Noto Sans SC", sans-serif
     ours:      Inter, "PingFang SC", "HarmonyOS Sans SC", ...
   So U+25A1 (□) renders from a different font -> different stroke weight / face size.
   Fix: give .game-symbol the reference font stack verbatim (this is still a copy,
   not my own tuning).
   =================================================================== */
.game-symbol {
  font-family: Inter, "Noto Sans SC", sans-serif;
}

/* ===================================================================
   2026-10-15 fix the delay-phase flash
   User: "it goes dark and never brightens again" / earlier: "it snaps dark, not a fade".
   Measured right after load: symbol #1 (delay 0s) is 0.20, but #2..#8 (delay .3~2.1s)
   are opacity **1** -- because `animation-fill-mode` defaults to `none`, so during the
   delay the element uses its own static opacity (unset -> 1). With delays up to 5.7s
   that means: very dense for several seconds, then the animation starts and JUMPS to
   0.20 -> exactly the "snaps dark" the user reported.
   Fix: base opacity 0 + `animation-fill-mode: backwards` => during the delay the element
   already sits in the first keyframe (faintest), so there is no flash and no jump.
   =================================================================== */
.game-symbol {
  opacity: 0;
  animation-fill-mode: backwards;
}

/* ===================================================================
   2026-10-15 raise stroke alpha so it is visible
   User: "the symbols are too faint to see."
   Simple math: .game-symbol own opacity breathes between 0.20 and 0.35, MULTIPLIED by the
   stroke color alpha 0.2 => effective density is only 0.04~0.07. On the reference page
   (light grey background, much emptier layout) that still read fine; here, with white
   cards everywhere, it gets washed out.
   So ALPHA is the only dial for density: light 0.2 -> 0.55, dark 0.15 -> 0.42.
   The breathing range (.35 / .20) is untouched -- that is amplitude, not density.
   =================================================================== */
:root { --game-symbol-color: rgba(128, 128, 128, 0.55); }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) { --game-symbol-color: rgba(200, 200, 220, 0.42); } }
:root[data-theme="dark"] { --game-symbol-color: rgba(200, 200, 220, 0.42); }

/* ===================================================================
   2026-10-15 HOT badge -> EVA-01 cyan
   User: "change the HOT tag to a cyan background with black text, the kind of cyan
          like Evangelion Unit-01".
   Color: background #43E8C4 (EVA-01 fluorescent green, shifted toward cyan),
          text #04140F (near-black -- pure black looks dirty on fluorescent cyan).
          Contrast ratio with the text is about 13:1, well past AA.
   Both themes use the SAME cyan + black (the user asked for cyan bg + black text,
   not per-theme variants).
   Untouched: .badge.hot ("ending soon") stays warm orange, so the two stay distinct.
   =================================================================== */
:root .card .thumb .badge.hot-badge,
:root[data-theme="dark"] .card .thumb .badge.hot-badge,
:root:not([data-theme="light"]) .card .thumb .badge.hot-badge,
:root .badge.hot-badge,
:root[data-theme="dark"] .badge.hot-badge,
:root:not([data-theme="light"]) .badge.hot-badge,
:root .card .thumb .badge.hot-badge:hover,
:root[data-theme="dark"] .card .thumb .badge.hot-badge:hover,
:root:not([data-theme="light"]) .card .thumb .badge.hot-badge:hover {
  background: #43e8c4 !important;
  background-image: none !important;
  color: #04140f !important;
  font-weight: 800 !important;
  border-color: transparent !important;
  box-shadow: 0 2px 12px rgba(67, 232, 196, .42) !important;
  text-shadow: none !important;
}

/* ===================================================================
   2026-10-15 detail-page HOT badge -> small pill
   User: "the HOT on the weekly detail page looks jarring, fix it."
   `.badge` has no radius/padding/font-size of its own (only `.card .thumb .badge` does),
   and `.viewhead` is `display:flex; align-items:baseline`. So the detail-page HOT inherited
   the h1 font-size, had square corners, and sat on the title baseline -> looked pasted on.
   Fix (scoped to the detail title row only; the thumbnail corner badges are untouched):
     small size + pill radius + padding + `align-self:center` + no glow.
   Also trim the neighbouring `.chip` slightly so the three items balance.
   =================================================================== */
.viewhead .badge,
.viewhead .badge.hot-badge {
  align-self: center;
  font-size: .72rem;
  line-height: 1;
  padding: .34em .66em;
  border-radius: 999px;
  letter-spacing: .05em;
  font-weight: 800;
  margin-left: -.25em;
}
.viewhead .badge.hot-badge {
  background: #43e8c4 !important;
  background-image: none !important;
  color: #04140f !important;
  border-color: transparent !important;
  box-shadow: none !important;      /* 标题旁不发光，别抢标题 */
  text-shadow: none !important;
}
.viewhead .chip { font-size: 13px; }

/* ===================================================================
   2026-10-15 customizable select（下拉菜单套上设计语言）
   User: "restyle these dropdowns so they match the UI language."
   截图里那三个下拉（排序 / 结束于 / 每页）的**控件本身**早就美化过了，
   但**弹出的列表**是浏览器原生渲染的（蓝底高亮、方角、系统字体）⇒ 和设计语言完全脱节 ❌

   做法：Chrome 135+ 的 Customizable Select —— `appearance: base-select` + `::picker(select)`。
   ⭐ 纯 CSS，不写自定义组件 ⇒ 原生无障碍 / 键盘 / 首字母跳转 / 移动端全保留 ✅
   ⭐ 不支持的浏览器（Firefox / Safari 老版）**直接忽略**这些声明 ⇒ 自动退回原生列表 ⇒ 零风险 ✅
   全部颜色/圆角/阴影走 tokens.css 的变量 ⇒ 换配色时下拉自动跟着变 ✅
   =================================================================== */

/* ① 让 <select> 和它的弹出列表都变成"可完全定制的" */
select,
::picker(select) {
  appearance: base-select;
}

/* ② 控件本身：和现有输入框同一套观感（原来只改了一半，这里补齐）*/
select {
  display: inline-flex;
  align-items: center;
  gap: var(--s-2);
  height: 38px;
  padding: 0 var(--s-3);
  background: var(--bg);
  color: var(--text);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-md);
  font: inherit;
  font-size: 14.5px;
  cursor: pointer;
  transition: border-color .16s var(--ease), box-shadow .16s var(--ease), background .16s var(--ease);
}
select:hover { border-color: color-mix(in srgb, var(--c-brand) 34%, var(--line-soft)); }
select:focus-visible {
  outline: none;
  border-color: var(--c-brand);
  box-shadow: 0 0 0 3px var(--c-brand-ring);
}

/* ③ 右侧那个箭头：用自家的 SVG，跟着文字颜色走 */
select::picker-icon {
  color: var(--text-3);
  font-size: 11px;
  transition: transform .18s var(--ease);
}
select:open::picker-icon { transform: rotate(180deg); }

/* ④ 弹出的列表：白底 + 大圆角 + 软描边 + 二级阴影，和一整套卡片一个语言 */
::picker(select) {
  margin-top: 6px;
  padding: 6px;
  background: var(--bg);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-md);
  box-shadow: var(--sh-2);
  /* ⚠⚠ 2026-10-15 修（用户截图：列表横跨整个页面）：
     原来这里写了 `min-width: 100%` —— **那个 100% 不是"控件宽度"**，
     而是外层容器的宽度 ⇒ 弹出列表被撑满整页 ❌
     `::picker(select)` **默认宽度就等于控件宽度**（和原生一样），不用写 min-width；
     这里只做两件事：内容自适应 + 给一个"别超屏"的上限 ✅ */
  width: max-content;
  max-width: min(96vw, 360px);
  /* 打开时来一点上浮，跟全站的过渡手感一致 */
  opacity: 0;
  translate: 0 -4px;
  transition: opacity .14s var(--ease), translate .14s var(--ease), display .14s allow-discrete, overlay .14s allow-discrete;
}
::picker(select):popover-open {
  opacity: 1;
  translate: 0 0;
}
@starting-style {
  ::picker(select):popover-open { opacity: 0; translate: 0 -4px; }
}

/* ⑤ 每一项：小圆角；悬停/键盘高亮走淡紫，选中走品牌紫 + 加粗 */
option {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  padding: 8px 10px;
  border-radius: var(--r-sm);
  color: var(--text);
  font-size: 14.5px;
  cursor: pointer;
}
option:hover,
option:focus-visible {
  background: var(--c-brand-soft);
  color: var(--c-brand-strong);
  outline: none;
}
option:checked {
  background: var(--c-brand-soft);
  color: var(--c-brand-strong);
  font-weight: 700;
}
/* 选中项前面那个对勾：用品牌色 */
option::checkmark {
  color: var(--c-brand);
  font-weight: 700;
  margin-left: auto;
  order: 2;
}
/* 每一项左侧的自定义标记槽（base-select 会渲染它），留空不占位 */
option::before { content: none; }

/* ⑥ 深色主题：让弹出列表的底和阴影跟着走（变量已经切了，这里只补阴影的观感）*/
:root[data-theme="dark"] ::picker(select),
:root:not([data-theme="light"]) ::picker(select) {
  background: var(--bg-2);
}

/* ⑦ 减弱动效偏好：关掉上浮过渡 */
@media (prefers-reduced-motion: reduce) {
  select::picker-icon,
  ::picker(select) { transition: none; }
}

/* ===================================================================
   2026-10-15 activation-expiry tip（激活有效期「特别标注」）
   User: "the activation expiry deserves a callout / hint."
   它是「买完能不能用」的关键信息，最容易漏看 ⇒ 标签后面挂一个圆形小提示，
   值本身用强调色 + 等宽数字，扫一眼就能抓住 ✅
   =================================================================== */
.kv-tip { display: inline-flex; align-items: center; gap: 5px; }   /* 一行内放得下 ✅ */
.tipdot {
  display: inline-flex; align-items: center; justify-content: center;
  width: 15px; height: 15px;
  border-radius: 50%;
  background: var(--c-warn-soft);
  color: var(--c-warn);
  border: 1px solid currentColor;
  font-size: 10.5px; font-weight: 800; line-height: 1;
  cursor: help;
  flex: none;
}
.kv-strong {
  color: var(--c-brand-strong) !important;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* ===================================================================
   2026-10-15 lock prefix + code chips（激活地区说明的口径前缀与码标签）
   用户要求：前缀「仅在」绿、「不可」红；地区码标签挂在文本框下方，同样绿/红区分 ✅
   =================================================================== */
.lockmode { display: flex; align-items: center; gap: var(--s-2); margin-bottom: 6px; flex-wrap: wrap; }
.lockmode .f-p-mode { width: auto; min-width: 96px; height: 30px; font-size: 13px; padding: 0 var(--s-2); }
.lockprefix { font-size: 13px; color: var(--text-2); }
.lk-allow { color: var(--c-ok); font-weight: 800; }
.lk-deny  { color: var(--c-danger); font-weight: 800; }
.lockcode { display: flex; align-items: center; gap: 6px; margin-top: 6px; flex-wrap: wrap; min-height: 20px; }
.lk-codes-label { font-size: 12.5px; color: var(--text-3); }
.lk-chips { display: inline-flex; gap: 4px; flex-wrap: wrap; }
.lk-chip {
  display: inline-flex; align-items: center;
  padding: 1px 7px; border-radius: var(--r-pill);
  font-size: 12px; font-weight: 700; letter-spacing: .02em;
  background: var(--bg-3); color: var(--text-2);
  border: 1px solid var(--line-soft);
  font-variant-numeric: tabular-nums;
}
.lk-chip.allow { background: var(--c-ok-soft); color: var(--c-ok); border-color: color-mix(in srgb, var(--c-ok) 30%, transparent); }
.lk-chip.deny  { background: var(--c-danger-soft); color: var(--c-danger); border-color: color-mix(in srgb, var(--c-danger) 30%, transparent); }
.lk-none { color: var(--text-3); font-size: 12.5px; font-style: normal; }
/* 客户页展示那两句时，「仅在」/「不可」也要加粗上色 */
.lb-msg .lk-allow { color: var(--c-ok); font-weight: 800; }
.lb-msg .lk-deny  { color: var(--c-danger); font-weight: 800; }

/* ===================================================================
   2026-10-15 pkg auto summary（整包设置 = 自动汇总）
   把各产品的锁区合并展示：没说明的显示「无限制_全球KEY」✅
   =================================================================== */
.pkglist { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
/* ⚠ 2026-10-15 修（用户图1：说明文字被挤成**竖排一字一行**）：
   原来是三列 `minmax(120px,200px) 1fr auto`，第三列 `auto` 里塞了 26+ 个地区码 chip
   ⇒ 它把宽度**吃光** ⇒ 中间那列 `1fr` 被压到只剩 1 个字宽 ❌
   ⇒ 改成**两行网格**：第一行【名字 | 说明】，第二行【（空） | 地区码】✅ */
.pkg-line {
  display: grid;
  grid-template-columns: minmax(112px, 186px) minmax(0, 1fr);
  grid-template-areas: "name text" "name codes";
  gap: 3px var(--s-3); align-items: start;
  padding: 8px 10px; border-radius: var(--r-sm);
  background: var(--bg-2); border: 1px solid var(--line-soft);
  font-size: 13px;
}
.pkg-name { grid-area: name; color: var(--text-2); font-weight: 600; word-break: break-word; }
/* 说明文字：正常换行，绝不逐字竖排 ✅ */
.pkg-text { grid-area: text; color: var(--text); line-height: 1.6; overflow-wrap: anywhere; word-break: normal; }
.pkg-line.pkg-none .pkg-text { color: var(--c-ok); font-weight: 700; }
/* 地区码单独一行，左对齐、允许换行 ✅ */
.pkg-rs { grid-area: codes; display: flex; gap: 4px; flex-wrap: wrap; justify-content: flex-start; margin-top: 5px; }
.pkg-union { margin: var(--s-2) 0 0; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
/* ⚠ 2026-10-15 修：合并后的地区码 chip **全粘在一起**（ARCNAECRGFKZ…）——
   它们是拼成一串塞进来的，`gap` 只管直接子元素 ⇒ 这里给 chip 之间补间距 ✅ */
.pkg-union .lk-chip { margin: 2px 3px 2px 0; }
@media (max-width: 720px) {
  .pkg-line { grid-template-columns: 1fr; gap: 4px; }
  .pkg-rs { justify-content: flex-start; }
}

/* ===================================================================
   2026-10-15 color-scheme（原生控件跟着我们的主题走）
   用户截图：管理页点开 `<input type="date">` 弹出的是**黑日历**，而页面是浅色的 ⇒ 割裂 ❌
   根因：那个日历弹窗是**浏览器原生 UI**，它跟随的是 `color-scheme`（没设就读系统深色偏好）——
         我们这个项目**从来没设过它** ⇒ 用户 Windows 是深色 ⇒ 弹黑日历，
         而页面被锁成浅色 ⇒ 两个世界 ✅
   ⇒ 把 `color-scheme` 和我们的主题绑定：浅色 = light，深色（含"跟随系统"那支）= dark。
     顺带把**滚动条**等原生控件的配色也一起对上 ✅
   =================================================================== */
:root { color-scheme: light; }
:root[data-theme="dark"] { color-scheme: dark; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { color-scheme: dark; }
}

/* ===================================================================
   2026-10-15 date input width + 自绘日历弹层（骨架样式）
   用户：「搞这么宽而且还是原生的样式，跟之前让你改的下拉选项栏一个逻辑美化一下」
   ⇒ ① 输入框固定宽度；② 日历改成我们自己画的弹层（见 .dtp* 那一套）
   =================================================================== */
.dtp { position: relative; display: inline-flex; align-items: center; gap: 6px; }
.dtp > input[type="date"] {
  width: 148px; height: 36px; padding: 0 10px;
  background: var(--bg); color: var(--text);
  border: 1px solid var(--line-soft); border-radius: var(--r-sm);
  font: inherit; font-size: 13.5px; font-variant-numeric: tabular-nums;
}
.dtp > input[type="date"]:focus-visible { outline: none; border-color: var(--c-brand); box-shadow: 0 0 0 3px var(--c-brand-ring); }
.dtp-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; flex: none;
  background: var(--bg); color: var(--text-2);
  border: 1px solid var(--line-soft); border-radius: var(--r-sm);
  cursor: pointer; font-size: 15px; line-height: 1;
}
.dtp-btn:hover { border-color: color-mix(in srgb, var(--c-brand) 34%, var(--line-soft)); color: var(--c-brand); }

/* ---- 弹层：和下拉/卡片同一套语言（白底 + 大圆角 + 软描边 + 二级阴影）---- */
.dtp-pop {
  position: absolute; z-index: 60; top: calc(100% + 6px); left: 0;
  width: 286px; padding: 10px;
  background: var(--bg); border: 1px solid var(--line-soft);
  border-radius: var(--r-md); box-shadow: var(--sh-2);
}
.dtp-pop[hidden] { display: none; }
.dtp-head { display: flex; align-items: center; gap: 6px; margin-bottom: 8px; }
.dtp-title { flex: 1 1 auto; font-size: 13.5px; font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums; }
.dtp-nav {
  width: 28px; height: 28px; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--bg-2); color: var(--text-2);
  border: 1px solid var(--line-soft); border-radius: var(--r-xs);
  cursor: pointer; font-size: 13px; line-height: 1;
}
.dtp-nav:hover { background: var(--c-brand-soft); color: var(--c-brand-strong); border-color: transparent; }
.dtp-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.dtp-dow { text-align: center; font-size: 11.5px; color: var(--text-3); padding: 2px 0 4px; }
.dtp-day {
  height: 32px; display: flex; align-items: center; justify-content: center;
  font-size: 13px; color: var(--text); border-radius: var(--r-xs);
  cursor: pointer; font-variant-numeric: tabular-nums;
  background: none; border: 1px solid transparent;
}
.dtp-day:hover { background: var(--c-brand-soft); color: var(--c-brand-strong); }
.dtp-day.out { color: var(--text-3); opacity: .45; }
.dtp-day.today { border-color: var(--c-brand); color: var(--c-brand); font-weight: 700; }
.dtp-day.on { background: var(--c-brand); color: #fff; font-weight: 700; }
.dtp-day.on:hover { background: var(--c-brand); color: #fff; }
.dtp-foot { display: flex; justify-content: space-between; gap: 8px; margin-top: 8px; }
.dtp-foot button {
  flex: 1 1 auto; height: 30px;
  background: var(--bg-2); color: var(--text-2);
  border: 1px solid var(--line-soft); border-radius: var(--r-xs);
  cursor: pointer; font: inherit; font-size: 12.5px;
}
.dtp-foot button:hover { background: var(--c-brand-soft); color: var(--c-brand-strong); border-color: transparent; }

/* ===================================================================
   2026-10-15 lockpkg（客户端弹窗里的「本包各游戏」清单）
   用户要的：「xxx游戏 无限制 / xxx游戏 有锁区」——设置端和客户端同一份数据 ✅
   =================================================================== */
.lockpkg { margin-top: var(--s-4); border-top: 1px solid var(--line-soft); padding-top: var(--s-3); }
.lp-h { margin: 0 0 var(--s-2); font-size: 13px; font-weight: 700; color: var(--text-2); }
.lockpkg-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.lockpkg-row {
  display: grid;
  grid-template-columns: minmax(110px, 180px) minmax(0, 1fr);
  grid-template-areas: "n t" "n r";
  gap: 2px var(--s-3); align-items: start;
  padding: 7px 10px; border-radius: var(--r-sm);
  background: var(--bg-2); border: 1px solid var(--line-soft);
  font-size: 13px;
}
.lockpkg-row .lp-n { grid-area: n; color: var(--text-2); font-weight: 600; word-break: break-word; }
.lockpkg-row .lp-t { grid-area: t; color: var(--text); line-height: 1.6; overflow-wrap: anywhere; }
.lockpkg-row .lp-r { grid-area: r; display: flex; gap: 4px; flex-wrap: wrap; margin-top: 4px; }
/* 没锁区那些：绿色「无限制」，一眼区分 ✅ */
.lockpkg-row.none .lp-t { color: var(--c-ok); font-weight: 700; }


/* 2026-10-15 lock toggle（锁区信息开关按钮）✅ */
.btn.f-locktoggle { font-variant-numeric: tabular-nums; }
.btn.f-locktoggle.is-on { background: var(--c-ok-soft); color: var(--c-ok); border-color: color-mix(in srgb, var(--c-ok) 34%, transparent); font-weight: 700; }
.btn.f-locktoggle:disabled { opacity: .6; cursor: progress; }

/* ===================================================================
   2026-10-15 lock-switch（「客户端显示锁区信息」开关，放在锁区设置弹窗里）
   =================================================================== */
.lockedit-head .spacer { flex: 1 1 auto; }
.lock-switch {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 6px 11px; border-radius: var(--r-pill);
  background: var(--bg-2); border: 1px solid var(--line-soft);
  font-size: 12.5px; color: var(--text-2); cursor: pointer; user-select: none;
  flex: none;
}
.lock-switch:hover { border-color: color-mix(in srgb, var(--c-brand) 34%, var(--line-soft)); }
.lock-switch input { width: 15px; height: 15px; accent-color: var(--c-brand); cursor: pointer; flex: none; }
.lock-switch .ls-t { white-space: nowrap; }
.lock-switch:has(input:checked) { background: var(--c-ok-soft); color: var(--c-ok); border-color: color-mix(in srgb, var(--c-ok) 32%, transparent); font-weight: 600; }

/* 主列表那个按钮：**开关打开时点亮** ✅ */
.btn.f-lockset.is-on {
  background: var(--c-ok-soft); color: var(--c-ok);
  border-color: color-mix(in srgb, var(--c-ok) 34%, transparent);
  font-weight: 700;
}

/* ★ 2026-10-16：`.modal-badge`（标题右边的药丸）已删 —— 样式搬给 `.modal-meta` ✅ */

/* ⚠ 2026-10-16 ②（用户：「把周包锁区信息的弹窗宽度改和网页左右卡片布局宽度一样」）：
   详情页是 `.wrap`（`--wide` = 1240px）那种「左边信息 + 右边售价」的布局，
   锁区说明弹窗原来用的 `.modal-wide`（1560px）比它还宽 ⇒ 改成**和卡片布局同宽** ✅ */
.modal.modal-lockview { width: min(var(--wide), 97vw); }

/* ===================================================================
   2026-10-16「新上架」角标（48 小时内采集到的包）
   ⚠ 和 HOT 并存（用户拍板"都显示"）⇒ 用**品牌紫**，
     和 HOT（--c-hot 青绿）、即将结束（--c-warn 暖橙）区分开 ✅
   =================================================================== */
.badge.new-badge {
  background: var(--c-brand);
  color: #fff;
  letter-spacing: .02em;
  box-shadow: 0 1px 3px color-mix(in srgb, var(--c-brand) 40%, transparent);
}
:root[data-theme="dark"] .badge.new-badge {
  background: var(--c-brand);
  color: #14101c;
}

/* ===================================================================
   2026-10-16 锁区信息「复制」按钮（月包卡片右下角 / 详情页「激活地区提示」右边）
   复制出来的文本用**制表符**分隔 ⇒ 直接粘进 Excel 就能分列 ✅
   =================================================================== */
.lk-copy { flex: none; font-size: 12.5px; padding: 3px 10px; }
/* 详情页那一行的标题 + 按钮（按钮靠右，「激活地区提示」左边）✅ */
.panel-head { display: flex; align-items: center; justify-content: space-between; gap: var(--s-3); margin-bottom: var(--s-2); }
.panel-head > h2 { margin: 0; }
/* 月包卡片：按钮落在**卡片右下角** ✅ */
.mblock { position: relative; }
.mcopy { display: flex; justify-content: flex-end; margin-top: var(--s-3); }
.mcopy:empty { display: none; }

/* ===================================================================
   2026-10-16「无限制」药丸（用户：「无限制这三个字用价格标签围起来」）
   ⚠ 和地区码 chip 一个观感：绿底绿字 + 圆角胶囊 ✅
   =================================================================== */
.none-pill {
  display: inline-flex; align-items: center;
  padding: 1px 9px; border-radius: var(--r-pill);
  background: color-mix(in srgb, var(--c-hot) 14%, transparent);
  color: var(--c-hot);
  border: 1px solid color-mix(in srgb, var(--c-hot) 34%, transparent);
  font-size: 12.5px; font-weight: 650; white-space: nowrap;
}

/* ===================================================================
   2026-10-16 ①「已结束的包」折叠区（用户：「单独设置一个区域收纳起来；默认折叠」）
   ⚠ 它下面是**同一张表格**（只是换了个位置），所以列宽 / 样式天然一致 ✅
   =================================================================== */
.endedbox {
  margin-top: var(--s-4);
}
.endedbox > summary {
  cursor: pointer;
  user-select: none;
  padding: var(--s-2) var(--s-3);
  border: 1px dashed var(--c-line);
  border-radius: var(--r-md);
  background: var(--c-bg-soft);
  font-weight: 650;
  display: flex;
  align-items: baseline;
  gap: var(--s-2);
  flex-wrap: wrap;
}
.endedbox > summary:hover { border-color: var(--c-brand); }
.endedbox > summary .cnt {
  font-weight: 500;
  opacity: .8;
}
.endedbox[open] > summary { margin-bottom: var(--s-2); }

/* ═══════════════════════════════════════════════════════════════
   2026-10-18 · 细节与动效层（用户：只想加"设计细节 + 更流畅的动画"）

   ⚠ 这一层**不改任何颜色、不改任何布局结构**，只做观感与手感：
     ① 统一缓动曲线（--ease-out / --ease-in-out）
     ② 卡片：进场动画（列表渲染/切筛选时轻轻浮起）+ hover 更顺滑 + 按下回弹
     ③ 按钮 / 图标按钮 / chip：过渡 + 按压 scale(.97) 反馈
     ④ 弹窗：遮罩淡入 + 面板淡入上浮（180–220ms）
     ⑤ 缩略图加载完淡入（不再"啪"一下出现）
     ⑥ 价格数字等宽 + 卡片 hover 时极轻微提亮
     ⑦ 键盘焦点环（无障碍）
     ⑧ 细滚动条
     ⑨ 全部动效都在 `prefers-reduced-motion: reduce` 下自动关闭 ✅
   ═══════════════════════════════════════════════════════════════ */

:root {
  --ease-out: cubic-bezier(.22, 1, .36, 1);
  --ease-in-out: cubic-bezier(.65, 0, .35, 1);
}

/* ② 卡片进场 + hover + 按压 */
@keyframes ui-card-in {
  from { opacity: 0; transform: translateY(7px); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: no-preference) {
  .card { animation: ui-card-in .34s var(--ease-out) both; }
  /* 轻微阶梯，列表看起来是"依次落位"而不是"一起闪出来" */
  .card:nth-child(2) { animation-delay: .025s; }
  .card:nth-child(3) { animation-delay: .05s; }
  .card:nth-child(4) { animation-delay: .075s; }
  .card:nth-child(5) { animation-delay: .1s; }
  .card:nth-child(6) { animation-delay: .12s; }
  .card:nth-child(n + 7) { animation-delay: .14s; }
}
.card {
  transition: transform .26s var(--ease-out), box-shadow .26s var(--ease-out),
              border-color .26s var(--ease-out);
  will-change: transform;
}
.card:hover { transform: translateY(-2px); }
.card:active { transform: translateY(0) scale(.997); }

/* ③ 控件过渡与按压反馈（只加手感，不改颜色）*/
.btn, .iconbtn, .seg button, .chip, .badge, .card .thumb .badge,
.tierlist li, .lockmore, .lk-copy {
  transition: background-color .18s var(--ease-out), color .18s var(--ease-out),
              border-color .18s var(--ease-out), box-shadow .18s var(--ease-out),
              transform .12s var(--ease-out), filter .18s var(--ease-out);
}
.btn:active, .iconbtn:active, .seg button:active, .chip:active { transform: scale(.97); }
.btn:hover, .iconbtn:hover { transform: translateY(-1px); }

/* ④ 弹窗：遮罩淡入 + 面板淡入上浮 */
@keyframes ui-modal-in { from { opacity: 0; transform: translateY(9px) scale(.985); } to { opacity: 1; transform: none; } }
@keyframes ui-mask-in { from { opacity: 0; } to { opacity: 1; } }
@media (prefers-reduced-motion: no-preference) {
  .modal { animation: ui-modal-in .22s var(--ease-out) both; }
  .modal-mask { animation: ui-mask-in .18s ease both; }
}

/* ⑤ 缩略图淡入（图片解码完再显现，避免"闪白→图"的硬切）*/
.card .thumb img, .mitem img, .mstagewrap img {
  animation: ui-img-in .32s ease both;
}
@keyframes ui-img-in { from { opacity: 0; } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  .card .thumb img, .mitem img, .mstagewrap img { animation: none; }
}

/* ⑥ 价格：等宽数字（列表里不跳）+ hover 极轻提亮 */
.pricebadge .pb-value {
  font-variant-numeric: tabular-nums;
  transition: filter .2s var(--ease-out);
}
.card:hover .pricebadge .pb-value, .mblock:hover .pricebadge .pb-value { filter: brightness(1.06); }
.tierlist .tp, .chip.tchip { font-variant-numeric: tabular-nums; }

/* ⑦ 键盘焦点环（无障碍；鼠标点击不显示）*/
:focus-visible { outline: 2px solid var(--c-brand-ring); outline-offset: 2px; border-radius: inherit; }

/* ⑧ 细滚动条（Firefox + Chromium）*/
* { scrollbar-width: thin; scrollbar-color: color-mix(in srgb, var(--text-3) 42%, transparent) transparent; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--text-3) 34%, transparent);
  border-radius: 8px; border: 2px solid transparent; background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover { background: color-mix(in srgb, var(--text-3) 55%, transparent); background-clip: content-box; }

/* ⑨ 全局 reduced-motion 兜底：所有过渡/动画一律降为 0.01ms */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}


/* 弹窗里的「复制」那一行（细节层）—— 打开锁区说明后才有这一行 */
.lk-copyrow {
  display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap;
  margin-top: var(--s-4); padding-top: var(--s-3);
  border-top: 1px dashed var(--line);
}
.lk-copyhint { font-size: var(--fs-small); color: var(--text-3); }


/* 2026-10-18 修：缩略图上的「新上架」浅色没颜色
   ⚠ 原因：`.card .thumb .badge`（3 个类）把底色写死成白 92%，**优先级高于**
     `.badge.new-badge`（2 个类）⇒ 浅色下被盖成白灰；深色那条因为带 `:root[data-theme]`
     优先级更高才保住颜色 ❌
   ⇒ 这里补一条**更高优先级**的规则，两套主题都给品牌紫 ✅（白字 5.4:1，过 AA）*/
.card .thumb .badge.new-badge,
.card .thumb .new-badge.badge {
  background: var(--c-brand);
  color: #fff;
  box-shadow: 0 1px 3px color-mix(in srgb, var(--c-brand) 45%, transparent);
}
:root[data-theme="dark"] .card .thumb .badge.new-badge,
:root:not([data-theme="light"]) .card .thumb .badge.new-badge {
  background: var(--c-brand);
  color: #14101c;
}


/* 2026-10-18 修：深色下「即将结束」角标对比度
   ⚠ 实测：深色模式 `--c-warn` = #e2b04a（浅琥珀），而 `.badge.hot` 的文字写死 #fff
     ⇒ 白字压浅橙只有 **1.99:1** ❌（「换肤验收」因此挂掉；以前没包"即将结束"所以没暴露）
   ⇒ 深色下这枚角标统一改**深色字**（#201705 压 #e2b04a ≈ 8.9:1，压青绿也够）✅
   ⚠ 不动浅色：浅色 `--c-warn` = #9c5f08（深琥珀），白字 5.4:1 本来就达标 ✅ */
/* ⚠ 老规则写的是 color:#fff !important（L1591）⇒ 这里也必须 !important 才能压过它 ✅ */
:root[data-theme="dark"] .badge.hot,
:root:not([data-theme="light"]) .badge.hot,
:root[data-theme="dark"] .card .thumb .badge.hot,
:root:not([data-theme="light"]) .card .thumb .badge.hot {
  color: #201705 !important;
  font-weight: 700 !important;
}


/* 2026-10-21 修：手机端下拉点了不出选项
   ⚠ 原因：`appearance: base-select` + `option { display:flex }` 这套「可定制 select」
     在移动端 / 内嵌浏览器上**不可靠** —— 弹窗不出现、或出现一片空白 ❌
   ⇒ 触屏设备（pointer: coarse）或窄屏（≤720px）**一律退回原生 select**（原生弹窗最稳）✅
   ⚠ 桌面端保持可定制外观（Chrome 135+），零损失 ✅ */
@media (pointer: coarse), (max-width: 720px) {
  select {
    appearance: auto;          /* 覆盖 appearance: base-select */
    -webkit-appearance: menulist;
    display: inline-block;      /* 覆盖 base-select 的 inline-flex */
    min-height: 40px;
  }
  select::picker-icon { display: none; }
  option { display: block; }    /* 覆盖 flex：否则部分 Android WebView 列表空白 */
}
