/* ============================================================
   促销活动弹窗样式（2026-11-04，2026-11-07 改版：外框定尺寸 + 顶部图标卡片）
   ★ 2026-11-07 产品方改版要点（和上一版差别很大，改之前先看这段）：
     · 外框**固定尺寸**（宽 clamp 到 75vw、高 660px 但不超过 88-90vh），切换活动不跳大小
     · 顶部一张 `.pm-tabs`（4 个「图标 + 文字」卡片），**只有点击切换**，没有左右箭头
     · 渐变区高度固定：有图 → 图铺满整个渐变区 + 文字压下方；没图 → 文字放大居中
     · 白色区：礼金金色大按钮（主）；优惠码小胶囊（辅）；**没礼金时码才是主按钮**
   ⚠ 改版删掉的东西：`.pm-nav` 左右箭头、`.pm-dots` 圆点、胶片缩略图（`.pm-thumb img/.has-art`）、
     `.tpl-b min-height` —— 如果又看到这些选择器，说明改漏了。
   复用站点既有 .modal-mask / .modal / .btn / CSS 变量，不引入新框架。
   （原规格第三节的"桌面 520px 固定宽"已被 2026-11-07 的 75vw 方案取代，见下面外框那段。）
   泰语余量：CTA 不设固定宽度，min-width + padding，允许换行

   ★ 2026-11-04 豆包验收修正：
     `overflow-wrap: anywhere` 会把**单词内部**拆开 —— 泰语实测「เส้นทาง」被断成「เส้น/ทาง」、
     （当年是卖点卡末行孤一个词）。泰语没有词间空格，浏览器本身有断行规则，用 `break-word` 即可
     （只在**整词放不下**时才断，且优先保持在词边界）。
     ★ 2026-11-09：卖点卡已删，现在断行规则只作用于标题 / 副标 / CTA。
   ============================================================ */

#promoModal { z-index: 90; }          /* 高于普通弹窗(80)、低于未付款拦截(92) */

/* ★ 2026-11-07 产品方改版：**外框尺寸固定**，切换活动时宽高都不变。
   上一版是 min-height + 过渡（"高度不要跳"的折中），这版产品方要求**彻底固定**：
     · 宽度：75vw，clamp(460px, 75vw, 760px) —— 手机上撑满可用宽度（见断点）
     · 高度：固定 660px，但**永远不超过 90vh / 88vh（手机）** ⇒ 矮屏上自动变矮，
       保证"在窗口里放得下、四周能看到遮罩外露的网站"
     · 内容多 ⇒ 只滚中间的 `.pm-scroll`（关闭按钮、顶部卡片**不动**）
     · 内容少 ⇒ 正文用 `margin-top:auto` 推到底部留白区居中（不是挤在渐变区下面）
   ⚠ 不再有 min-height 过渡 —— "所有活动尺寸统一"就是不要过渡。 */
#promoModal .pm-modal {
  /* ★ 2026-11-08（产品方：弹窗放中间）：站点通用 `.modal{margin:auto}` 会作用到这个外框，
     在 `flex + overflow-y:auto` 的 mask 里 `margin:auto` 会把内容**推到最底部**（实测 top 399px）。
     显式归零，交给 mask 的 `align-items` 负责居中。
     （选择器 `#promoModal .pm-modal` 的权重本来就高于 `.modal`，只是之前没写这条。） */
  margin: 0;
  /* ★ 2026-11-10（产品方："电脑版的广告弹窗等比例缩小百分之20"）：**整框等比系数**。
     桌面由 `promo-modal.js` 的 fitDesktop() 写内联 `--pm-k: 0.8`（开弹窗 / 改窗口时都会重写）；
     这里默认 1 ⇒ **没 JS / 手机（≤480 时 fitDesktop 直接 return）一律保持原尺寸**。
     写法约定：凡是"宽高写死的 px"（卡片条 78 / 底栏 126 / 主按钮 53 / 关闭钮 40 / 卡片 146×58 …）
     以及字号，全部写成 `calc(Npx * var(--pm-k))` —— 只缩外框不缩这些，底栏会被挤、字会显大 = 变形。
     ⚠ 手机上那些断点里的数值**不要套 var()**：手机断点带 !important 且 --pm-k 恒为 1，
       套上去只是多一层 calc，反而让"桌面/手机扫同一个数"时容易看错。 */
  --pm-k: 1;
  /* ★ 2026-11-11（4:5 纵版）：文字压图那套的基准 = 外框宽（JS 在 fitDesktop 的 4:5 支里写，
     和 `--pm-u` 同一处；标题/副标/间距都按它的比例算，这里只是没 JS 时的兜底）。 */
  --pm-up: 500px;
  /* ★ 2026-11-10.4（背景图骨架）：**几何量也交给 JS** —— `fitDesktop()` 每次都会写
       `--pm-img-h` = 图区高（px），外框高 = 图区 + 卡片条 + 底栏。
     这里给的值只是没 JS 时的兜底（桌面 ≈ 图 480 / 外框 643），手机断点里另有一套 !important
     （手机图区是 `aspect-ratio:4/3`，不读这个变量）。 */
  --pm-mh: 643px;
  --pm-img-h: 480px;
  /* ★ 2026-11-09.5（产品方："电脑端图片等比例缩小点、需要让图片完全展示出来，顶部大小也要调，
     等比例对齐下面的按键"）：
     宽 760 时 4:3 图要 570 高，加上卡片条 78 + 底栏 126 = 774 > 外框 660 ⇒ 图必滚 114px、首屏看不全。
     现在的口径：4:3 图**整张**塞进滚动区（外框高 − 78 − 126）⇒ 图盒 4:3 高 ≤ 456 ⇒
     图盒宽 ≤ 608、外框宽 = 图盒 + 左右各 20 = **645**，图 605×454（原 760×570，等比例缩小约 20%），
     745/660 高外框内：1440/1920/1366/1024 各屏实测**零滚动、整图可见、底部标题可见**。
     ⚠ 这条 `width` 只是"没有 JS 时的兜底"，**真正的值由 `assets/promo-modal.js` 的 `fitDesktop()`
       在打开弹窗 / 尺寸变化时写成内联 px**（宽度随视口高走）。为什么不用 CSS 算式：
       2026-11-09.5 实测，本机 Edge 上 `calc((min(660px,90vh) − 214px) * 4 / 3 + 48px)` 这种写法
       整个 `max()` 被判无效、静默回落 760；`calc((100vw − 40px) * 3/4)` 又按错误的百分比基准算
       （量到 1050）。CSS 里"写对了但不生效"，改在 JS 里算，所见即所得。
     ⚠ 手机（≤480）不受影响：`@media (max-width:1023px)` 等断点里的 width/height 优先级更高。 */
  /* ★ 2026-11-10.3：宽 516 → **488**（图 1 是竖版骨架：宽收一档、字卡才有两行的余地）。
     真实宽高都由 JS 的 fitDesktop() 写内联 px，这里只兜底。 */
  width: 488px;
  /* 兜底：窗口很窄时不超出屏宽的 75% */
  max-width: calc(100% - 32px);
  height: var(--pm-mh);
  max-height: 90vh;
  padding: 0;
  /* ★ 2026-11-08（产品方：弹窗改回圆角）
     外框**圆角 + 裁切**一起回来 —— 但关不上 `overflow:visible`（X 是半悬在外框外的负偏移，
     hidden 会把 X 切掉一半）。解法：**外层不裁、内层裁**：
       外框 `.pm-modal`  → 圆角 + overflow:visible（X 露得出来）
       `.pm-clip`        → 同半径圆角 + overflow:hidden（把渐变区/底栏的白角切干净）
     所以 DOM 里卡片条 / 滚动区 / 底栏都包在 `.pm-clip` 里，X 是它的**兄弟**。 */
  border-radius: 18px;
  overflow: visible;
  position: relative;
  text-align: left;
  display: flex;
  flex-direction: column;
  transition: none;
  /* 圆角外框需要阴影来"立起来"（原来靠裁切 + 蒙层） */
  box-shadow: 0 24px 70px rgba(8, 24, 44, .34);
}
/* 内层裁切容器：顶到底、左右到底，圆角和外框一致 ⇒ 视觉上就是"外框圆角" */
#promoModal .pm-clip {
  border-radius: inherit;
  overflow: hidden;
  /* ★ 2026-11-09：`overflow:hidden` 会把没有滚动条的"未滚动容器"也按 `scrollable overflow` 算动画类型，
     ⇒ `.pm-modal` 上那条 `transition:none` 只关掉了外框，`.pm-clip` 仍会有隐式动画，
     父级按"首帧 = 内容高 661"参与布局（实测底栏 655、内容 661，滚到底就是 1px 白边）。原因不明，
     但设 `transition:none` 后 661 → 660、零白边，且不影响关闭动画（内层跟外框一样瞬切）。 */
  transition: none;
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;                  /* flex 子项能真正收缩，否则内容一多就把外框撑破 */
}
#promoModal .pm-scroll {
  /* ★ 2026-11-08（产品方：卖点卡减到 2 条后"往上收、做紧凑"）
     原来是 `flex: 1 1 auto` —— 它吃掉渐变区和底栏之间**所有**剩余空间，
     所以卖点卡变少了，底栏仍被顶在外框最底下，按钮根本不会上移。
     现在是 `flex: 0 0 auto`：滚动区**严格等于内容高度**，底栏紧跟其后 ⇒ 整块贴顶排，
     空出来的空间留在底部（外框高度固定，活动之间依旧不跳）。
     ⚠ 不写 `max-height:100%`：那会让它把剩余空间吃满，等于变回 `flex:1`。
     内容超长时由外层 `.pm-clip` 的 `overflow:hidden` 兜住（这条路径本来就走"贴底栏+滚动"）。
   ★ 2026-11-09（产品方：主按钮要和图隔开、间距对齐图的上边距，桌面按钮还必须看得见）：
     滚动区的高度写成 **"外框 − 卡片条 − 底栏"**（下面 `flex-basis`），于是渐变区底边
     正好落在底栏顶上，主按钮只隔底栏的 10px 上内边距。
     外层 `.pm-clip` 的 `min-height:0` + 这里 `overflow-y:auto`（自动最小高 = 0）⇒ 滚动区真的
     按这个高度排；一旦内容比它高（两图并排、超长文案、矮屏窄框），多出来的部分
     **在滚动区里滚**，`.pm-clip` 不会被顶穿、底栏也不会被推到外框外面。
     （★ 11-09.5 起单图活动在 ≥900 高的屏上滚动区里已经没有滚动条了，这里只是兜底。）
     ⚠ 实测踩过的弯路（别再回去）：
       ① 只写 `flex:1 1 auto` 不行 —— `overflow-y:auto` 让自动最小高变成 0，它照样被压到剩余空间；
       ② `.pm-clip` 改成 `min-height:auto` 更糟 —— 先长到内容高，再被压成"内容 − 图片高"的负值，
          弹窗塌成 189px、按钮全跑外面；
       ③ 用 `max-height` 封顶滚动区不行 —— 只限了盒子，flex-basis 仍是内容高，照样溢出 86px；
       ④ 滚动区里写 padding 也不行 —— 图上边距 26px 会变成一个白条跑进去，还把内容高撑成
          570 = 盒子高，"装得下"就不滚了。
     算账：可用高 = min(660,90vh) − 78（卡片条）− 126（底栏）；底栏 126 = 按钮 57 + 说明 26 +
     冷却 49 + 上 10 + 下 8。⇒ 900 屏滚动区 456，4:3 图 605×454 正好放下（11-09.5 实测零滚动）。 */
  /* ★ 手机：外框写的是 `height:auto`（按内容收缩），上面那条 flex-basis 会算成固定数，
     外框再被 `max-height:88vh` 封顶 ⇒ 中间白出一大截、图片被挤出可见区。
     ⇒ 手机上改回 `flex: 1 1 auto`（自动最小高 = 0，照样能收缩），让外框按内容长；
       底栏 128 不进这个算式，图底到按钮只隔底栏的 10px。 （见下方 @media 480 那条） */
  flex: 0 0 auto;
  flex-basis: calc(min(660px, 90vh) - 78px * var(--pm-k) - 126px * var(--pm-k));   /* 78/126 同步缩（★ 11-10） */
  /* ⚠ `transition: none` 必须有（2026-11-09 实测）：没有它，父级 `.pm-clip` 会按"首帧 = 内容高
     570"参与布局，滚动区多长出 1px、底栏跟着挪 1px（655/781 而不是 654/780），滚到底白 1px。 */
  transition: none;
  overflow-y: auto;               /* ⚠ 一定保住：它是 auto，自动最小高才是 0，才会按 flex-basis 排 */
  overscroll-behavior: contain;   /* 滚到底不把滚动传给背景页 */
  display: flex;
  flex-direction: column;
  -webkit-overflow-scrolling: touch;
}
/* ⚠ 渐变区必须 `flex: 0 0 auto`：滚动区是纵向 flex，默认 `flex-shrink:1` 会把它压扁。 */
#promoModal .pm-scroll > .pm-banner { flex: 0 0 auto; }
/* ★ 2026-11-07 晚（产品方）：**不再 margin-top:auto 贴底居中** —— 那会让"内容少的活动"
   动作区被推到最底部、"内容多的"贴在上面，切换时位置跳动。
   ★ 2026-11-09：卖点卡正文区 `.pm-body` 已删（前后台），原来给它写的 `flex:none; margin-top:0` 一并作废。 */
/* 细滚动条（Windows 默认滚动条很宽，压在圆角边上很难看） */
#promoModal .pm-scroll::-webkit-scrollbar { width: 8px; }
#promoModal .pm-scroll::-webkit-scrollbar-thumb { background: rgba(18, 40, 69, .18); border-radius: 4px; }
#promoModal .pm-scroll { scrollbar-width: thin; scrollbar-color: rgba(18, 40, 69, .2) transparent; }

/* ---------- 渐变区（★ 2026-11-07：高度**固定**，图/文字都塞进同一个尺寸） ----------
   产品方："上半部分深蓝青渐变背景 + 白色大标题；图片广告用图片占满渐变区，
   文字广告把文字放大放中间；所有活动的弹窗外框高度统一。"
   ⇒ 用**固定高度**（而不是 aspect-ratio 或内容撑开），这样有图没图、文案长文案短，
      外框都不变，只有内部换内容。高度随视口微调（矮屏别占满整屏）。 */
#promoModal .pm-banner {
  background: linear-gradient(135deg, #0f2a4a 0%, #1b5e7a 55%, #2fb8a8 100%);
  /* ★ 2026-11-07 晚（产品方）：卖点卡改一行后腾出高度 ⇒ 渐变区放大（210-300 → 240-340）。
     ★ 2026-11-08（产品方：做紧凑）：卖点卡 4 格→2 格后再收一档（→ 220-300）。
       顺带好处：宽 760 下 760:300 ≈ 2.53:1，比原来的 2.24:1 更接近 3:1 原图 ⇒ 裁得更少。 */
  height: clamp(220px, 34vh, 300px);
  /* ⚠ 必须 `flex:none`：`.pm-scroll` 是纵向 flex，默认 `flex-shrink:1` ——
     内容一多（比如手机上台卡 4 行）渐变区会被**压扁到 190px**，把文字挤掉。 */
  flex: none;
  min-height: 0;        /* 空间不够时允许被压（手机媒体查询里写回了 flex:none，所以手机照旧不缩） */
  position: relative;
  overflow: hidden;
  color: #fff;
  display: flex;
  flex-direction: column;
  /* 关闭按钮压在右上角 ⇒ 右侧和上方各留出位置（关闭按钮 40×40 + 12 边距） */
  padding: calc(26px * var(--pm-k)) calc(60px * var(--pm-k)) calc(22px * var(--pm-k)) calc(26px * var(--pm-k));
}
/* 有图：图铺满整个渐变区（绝对定位铺底），文字压在上面 */
/* ⚠ 默认必须 `display:block` 而不是 none：JS 用 `hero.style.display = ''` 来"显示"，
   那是**清掉内联样式**、回落到 CSS —— 如果 CSS 写 none，清了也还是 none，图片永远不显示。
   （2026-11-07 这个坑真的踩了一次：外框/文字都对，就是图不出来。）
   没图时由 JS 设内联 display:none，这里不用管。 */
#promoModal .pm-hero {
  position: absolute;
  inset: 0;
  display: block;
}
#promoModal .pm-hero img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* ★ 2026-11-09（产品方：广告图按 4:3 重做、电脑端尽量别裁图）：
   4:3 图（JS 在 `onload` 里按 naturalWidth/Height 判定后加 `.is-4x3`）**整张显示**：
     · 高度 = 图盒的 4:3 高 = 图盒宽 × 3/4（图盒 = 外框 − 左右各 20；滚动区出竖向滚动条时 gutter≈0）。
     · `contain` 不裁一像素；多出来的露 `.pm-banner` 渐变底色 + 一条暗色投影，看着像"框住的图"。
     · ★ 2026-11-09.5（产品方："电脑端图片等比例缩小点、完全展示出来"）：外框宽 760 → 645，
       图盒 605×454 正好等于滚动区 456 ⇒ **整图可见、滚动区零滚动**（1440/1920/1366/1024 实测）。
   ⚠ 这条的 height 也只是兜底 —— 桌面真实高度由 `promo-modal.js` 的 `fitDesktop()` 写内联 px
     （`外框高 − 78 − 126 − 2` 与 `图盒宽 × 3/4` 取小）。别在这里改用 `min()`/`max()`/`vw`：
     2026-11-09.5 实测，用函数整条 height 会被判无效、退回 base 的 `clamp(220px,34vh,300px)`，
     `(100vw − 40px) × 3/4` 又按错误基准算成 1050 ⇒ 图被裁、要滚。
   ⚠ 顺序必须在 `.pm-hero img{object-fit:cover}` **之后**（同权重、后者胜）。 */
#promoModal .pm-banner.is-4x3 {
  height: 363px;   /* 454 × 0.8（★ 11-10）；桌面真实值由 fitDesktop 写内联 */
  min-height: 0;
  /* 图盒左右内衬归零：图本身宽 = 图盒宽（见下面 `.pm-hero` 那段），banner 不用留边 */
  padding-left: 0;
  padding-right: 0;
}
/* ★ 2026-11-09.6（产品方："电脑版图片左右还有多余的，把它裁掉"）：
   4:3 图的宽高比和 图盒 一样，高度由比例定死之后，只有"比 4:3 更窄"的图盒才会左右露底色。
   原先 `.pm-hero{position:absolute; inset:0}` 的包含块是 banner 的**内边距盒**（左右内衬 16 / 22），
   图只能缩在里面 ⇒ 左右各留一条底色（实测 1440 屏图 605 却从 banner 左 +16 开始）。
   解法：`.is-4x3` 的 banner **左右内衬归零**（上面那条），`.pm-hero` 正常 `inset:0` 绝对定位、
   图 `width:100%` ⇒ 图宽 = 图盒宽 = 外框 − 40 = 卡片条 / 主按钮同宽（605），左右不留边。
   ⚠ 试过 fixed（视口坐标）：**不行**——固定定位的后代一旦宽度超出祖先，Chrome 会按祖先裁，
     而且被裁时"居中"的左偏移会失效（实测 800 屏图 560 却顶到 97.5 的位置）。所以退回绝对定位。
   ⚠ banner 的 `padding-top/bottom` 不变（不影响左右）；标题仍在 `has-hero .pm-head` 里铺满整个图。 */
#promoModal .pm-banner.is-4x3 .pm-hero {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
#promoModal .pm-banner.is-4x3 .pm-hero img {
  width: 100%;                         /* = 图盒宽（左右内衬已归零）⇒ 不再盖 base 的 100% 就够了 */
  max-width: none;
  height: 100%;
  object-fit: contain;
  filter: drop-shadow(0 10px 26px rgba(4, 16, 32, .45));
}
/* ★ 2026-11-09.6：`.is-4x3` 的 banner 左右内衬归零后，`.has-hero .pm-head` 的 `-26px/-60px`
   负边距会**多伸出去**（左边 −26 直接出到外框外）⇒ 这里改写成 0，蒙层正好贴住图的两边。 */
/* ================= ★ 2026-11-10.4 背景图骨架（产品方："背景图就是 V2 和 V3，然后文字标题是放在背景上的"）=================
   目标：**图 = 背景**（满幅铺满图区），**文字标题压在图上**（贴图的下缘）。
   DOM 结构（promo-modal.js 的 build() 同步改了，老库里的活动不用动）：
     .pm-clip
       ├ .pm-scroll              ← 图区
       │   └ .pm-banner
       │       ├ .pm-hero        ← 背景图（绝对定位铺底，object-fit: cover）
       │       └ .pm-head        ← ★ 压在图上：眉标 / 大标题 / 副标（白字 + 底部渐变压暗）
       ├ .pm-tabs-wrap           ← 卡片条 78
       ├ [.pm-body]              ← 空壳（历史遗留，不再往里装东西）
       └ .pm-foot                ← 底栏 126（主按钮 / 说明行 / 冷却）
   ★ 11-10.3 的"图下面一张白卡 + 虚线兑换券框"（`.pm-card` / `.pm-coupon`）**整块删掉**：
     文字压在图上 ⇒ 不再需要白底、也不再需要第二个"复制"入口（领取/复制只有底栏 `#pmCta`）。
   ★ 尺寸恒定：文字不占版式高度 ⇒ 外框 = 图高 + 卡片条 + 底栏，**跟活动 / 语言无关**（fitDesktop 算）。
   ⚠ 尺寸全部由 promo-modal.js 的 fitDesktop() 写内联 px（本机 Edge 上 min()/max()+vh 不可靠，见上）。 */

/* —— 层叠：卡片条和底栏是白底，必须压在滚动区之上（否则滚上来的图盖住按钮）。 */
#promoModal .pm-tabs-wrap { position: relative; z-index: 2; }
#promoModal .pm-foot { z-index: 2; }

/* —— 图区：高度 = JS 写的 `--pm-img-h`，四周内衬归零（图顶满、左右贴外框边）。 */
#promoModal .pm-modal.pm-mode-hero .pm-scroll { flex: none; padding: 0; overflow: hidden; }
/* ⚠ 这条选择器权重比 `.pm-foot` 高，改底栏内衬必须一起改（历史坑）。 */
#promoModal .pm-modal.pm-mode-hero .pm-scroll > .pm-banner {
  width: 100%;
  height: auto;                 /* ★ 11-11：= 图段 + 白字段，由内容撑（图段高见下面的 `> .pm-hero`） */
  min-height: 0;
  padding: 0;
  border-radius: 0;             /* 顶部两角由 .pm-clip 的 overflow:hidden 裁 */
}
/* 图 = 背景：**满幅 cover**（图区宽高比由 fitDesktop 定，跟图本身比例无关 ⇒ 一律裁满、不留边）。
   ⚠ 11-10.3 那套 `contain` + `.is-4x3` 整张显示的口径已废（那是"图在上、字在下"的排版）。 */
#promoModal .pm-scroll > .pm-banner .pm-hero img { object-fit: cover; }
/* 背景图不要再加投影：满幅贴边，投影只会露在图区四周 */
#promoModal .pm-banner.is-4x3 .pm-hero img,
#promoModal .pm-scroll > .pm-banner .pm-hero img { filter: none; }

/* ================= ★ 2026-11-11 骨架回到 V1（产品方两次否掉"文字压图"：标题完全不对 / 背景也不对）=================
   量的是参考图 `测试图\_small\_v1.jpg`（900×1117，像素结构见记忆 gdpeptides-promo-v1-geometry）：
     倒计时条（图顶）→ 满宽场景图 → **白底 + 深色大标题**（左右内衬 ~36，标题 3 行）→ 金条 → 按钮。
   ⇒ `.pm-banner` 分成上下两段：`.pm-hero` 是**实高**的图段（flex:none），`.pm-head` 紧接在它下面、白底深字。
   ⚠ 文字**不再压在图上**：11-10.4/11-10.5 那套 `margin-top:auto` + 底部渐变压暗 + 白字**全部作废**
     （下面那组选择器保留但不出效果——`has-hero > .pm-head` 的暗色背景已被这条显式白底覆盖）。
   ⚠ 图段高 = `min(--pm-img-h, 图宽 × 3/4)`：canvas 的 `--pm-img-h` 仍按 4:3 算，图段按 4:3 封顶
     ⇒ 图段永远是 4:3，`object-fit:cover` 裁掉多余的部分，文字段的地盘不被挤。 */
/* ⚠ 字号基准（11-11 晚重定）：`--pm-k` 是"整框等比 0.8"，V1 的几何是 900 宽的稿子
     ⇒ **所有 V1 尺寸写成 `V1 的 px × var(--pm-u)`**，`--pm-u` = k × 900/w × fit
       （V1 白字段实测 208 高，640 宽时 148 —— "量字高再按比例收字号（fit）"那条路 11-11 已全部否决：
        三版算法都收敛到 0.32～0.44、标题缩到 10px；现在 fit 恒为 1，长文案交给 clamp 裁）。
     ⚠ 别写成 `px × k`：V1 是 900 宽的稿子，在 640 宽的框里直接乘 k 会小 29%（标题 32→25.6→实测 20px，
       这正是"标题完全不对"的一半原因）。
     ⚠ 别把按框宽算的 `--pm-u` 再拿去量字高回喂框宽 —— 会自锁（实测锁在 320 宽 / 72px 标题）。
   ⚠ 外框高、图段高、内衬全部由 JS 的 fitDesktop() 按 V1 比例算好写内联 px
     （`--pm-h-hero/--pm-h-head/--pm-h-gold/--pm-h-cream`、`--pm-pad-head/--pm-pad-gold`），
     这里只用变量、不再自己算比例 —— 否则条/栏/字各算各的，一行文案换长度就跳。 */
/* ================= ★★ 2026-11-11 4:5 纵版（产品方拍板："完全正确"）=================
   独立演示页 `_pmdbg/popup-45.html` 原样接进线上弹窗。只在 `.pm-banner.is-4x5` 下生效
   （JS：4:3 图 load 成功才挂；其它比例的图 / 无图 / 图挂了 ⇒ 不挂 ⇒ **V1 横版骨架一个字不变**）。
   结构（跟演示页同一套）：
     满宽 4:5 竖图（4:3 图 ⇒ cover，左右各裁 ~22%）
     + `.pm-head` **绝对定位压在图上**：宽 80% / top 50% / 居中 / flex 纵向
     + 倒计时条回到**图顶**（V1 里它靠 `-1 × --pm-hero-h` 抬到图顶，这里 head 就在图顶上 ⇒ 归零）
     → 卡片条 → 底栏。
   尺寸：`--pm-up` = 外框宽（JS 写在 `.pm-modal` 上，和 `--pm-u` 同一处）——
     标题 / 副标 / 间距一律 `calc(比例 * --pm-up)`，所以 **900 稿比例在 524 / 640 / 358 上都成立**。
   ⚠ 和 V1 的 900 宽稿不是一个概念：V1 的 `--pm-u` 是 maxW/900（≈0.71 @640），这里 `--pm-up` 是整宽。
     别混用（标题 = 32/500 ⇒ 0.064 ⇒ 524 宽 22px、640 宽 41px、590 宽 37.76px，实测）。
   ⚠ `cqw` 别用（2026-11-11 实测本档里它按整页宽算，见下面 `.pm-title` 那条）。
   ⚠ 下面前两条选择器**必须和 V1 骨架那两条逐条打平**（`#promoModal .pm-scroll > .pm-banner`），
     否则 V1 的 `height: min(--pm-img-h, 宽×3/4)` 会把 4:3 图压扁。
   ★ 2026-11-11 起图区 = 框宽 × 3/4（4:3 整图，contain 不裁），文字层绝对定位压在图中间；
     上面"满宽 4:5 竖图 / cover 左右各裁 ~22%"是演示页那一版的历史，别再照它设 `--pm-h-hero`。 */
#promoModal .pm-scroll > .pm-banner.is-4x5 {
  display: flex;
  flex-direction: column;
  padding: 0;
  /* container-type 放在 banner 上：`cqw` 按 banner 宽（= 外框宽）算，标题才跟框走 */
  container-type: inline-size;
}
#promoModal .pm-scroll > .pm-banner.is-4x5 > .pm-hero {
  position: relative;
  inset: auto;
  width: 100%;
  flex: none;
  height: var(--pm-img-h, 480px);       /* JS：外框宽 × 5/4（不再被 4:3 的 cap 夹住） */
  --pm-hero-h: 0px;                    /* 倒计时条：head 本身就在图顶上（见下面 .pm-count） */
  overflow: hidden;
}
#promoModal .pm-banner.is-4x5 > .pm-hero img { object-fit: contain; object-position: center; filter: none; }
/* 文字层：★ 2026-11-11 回到"压在图中间"（产品方："文字标题那些全部恢复到图片居中哪个版本"），
   图区仍是 4:3 整图（`object-fit: contain`，一个像素不裁）。
   和"压在 4:5 竖图中间"那一版的区别只在落点：图区高 = 框宽 × 3/4，**不是**框宽 × 5/4
   ⇒ `top` 必须按图高算（bottom 0 + margin auto 居中，对任何图高都成立），不能写死 50% 的框高分。
   宽 80% / 居中 / flex 纵向 / 白字段全部沿用旧版；`height:auto` + 不占 flex 高度（绝对定位）。
   ⚠ 整条必须 `#promoModal .pm-scroll >` 打头（= 5 个 id/class）：基础档那条
     `@media (max-width:480px) #promoModal .pm-banner > .pm-head { padding:14px 9px 12px }` 只有
     2 个 class，手机档不这么写就会被那条内衬顶开。 */
#promoModal .pm-scroll > .pm-banner.is-4x5 > .pm-head {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 50%;
  margin: auto 0 auto -40%;
  transform: none;
  z-index: 2;
  flex: none;
  height: auto;
  width: 80%;
  max-width: none;
  padding: 0;
  box-sizing: border-box;
  background: transparent;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  /* 三行之间的空气：0.004 × 框宽 = 2.56px @640 ⇒ 眉标贴着标题，读起来像一坨。
     2026-11-11 放到 0.012 × 框宽（640 宽 7.7px、手机 4.3px）。图高 480 有富余（桌面字块 145），加得起。 */
  gap: max(4px, calc(0.012 * var(--pm-up, 500px)));
  text-align: center;
  color: #fff;
  overflow: hidden;
  overflow-wrap: anywhere;
  hyphens: auto;
  pointer-events: none;
}
/* ★ 2026-11-11（4:5）：清掉上面三条断点在内联 style 上留下的 `87vh / calc(...)` 残留（都在 501 宽以上
   重算）。**这里不写 width/height** —— 那两个只认 inline `!important`（见 .pm-modal 基础规则里的注释），
   内联优先，写了也是白写。只把 fitDesktop 自己 `setProperty` 过的 min/max-* 收回默认。
   ⚠ `max-height: none` 不加的话：481～1023 断点的 `@media` 里 `height:80vh`（无 !important）会被内联
     `!important` 压过，但 1023 断点的 `max-height:80vh` 依然生效 ⇒ 矮屏上 4:5 框被截一刀，图下留白。
   （4:5 档的 `height/flex/overflow` 三件套为什么必须写死，见上面 `.pm-head` 那段。） */
@media (min-width: 501px) {
  #promoModal .pm-modal.pm-mode-45 {
    min-width: 0 !important;
    max-width: none !important;
    min-height: 0 !important;
    max-height: none !important;
  }
}
/* 压暗层：文字回到图中间 ⇒ 必须恢复（白字要有底才读得清）。
   ★ 2026-11-11 改：4:3 整图宽过裁，整张压太狠会把图洗掉 ⇒ 压暗**只给中间那一段**兜底，
   上下各留 12% 透明（图顶的倒计时条、图底的高亮自己就有底色）。
   ⚠ 真彩色的图只压到 0.38 还是读不清（实测带亮色商品的图，白标题压在亮黄块上几乎看不见）
     ⇒ 中间那一段加一层很淡的模糊色块：只在文字所在的中段，图的上/下三分之一原样清晰。
   回滚（不要压暗）：把下面 `display` 那行注释掉即可。 */
#promoModal .pm-banner.is-4x5::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  display: block;
  pointer-events: none;
  background:
    linear-gradient(180deg, rgba(6, 12, 20, 0) 0%, rgba(6, 12, 20, .40) 20%, rgba(6, 12, 20, .52) 50%, rgba(6, 12, 20, .40) 80%, rgba(6, 12, 20, 0) 100%),
    radial-gradient(120% 70% at 50% 50%, rgba(6, 12, 20, .45) 0%, rgba(6, 12, 20, 0) 100%);
}
/* 倒计时条：★ 2026-11-11 回到"图中间那块字"这个版式 ⇒ 本档**不显示倒计时条**。
   它在 V1 里是"图顶那条"（靠 `-1 × --pm-hero-h` 抬上去）；文字层改成绝对定位后，同样的位置
   会掉进字块里（实测手机：条 42 + 眉标 15 + 标题 104 + 副标 34 挤在 269 高的图区里，标题末行被切）。
   JS 在 onload 里给条挂同名 `.is-4x5`（见 `bannerEl.querySelectorAll('[data-cd]')` 那段）。
   要恢复：删掉下面这一条即可（V1 那条 `top: calc(-1 * var(--pm-hero-h))` 会把条抬到图顶）。
   ⚠ `~` 兄弟选择器不够：条 `#pmCountWrap` 在 `.pm-modal` 里、**不是** `.pm-banner` 的子节点，
     写 `.pm-banner.is-4x5 > .pm-head .pm-count` 根本不命中（实测条照样亮着）。 */
#promoModal .pm-modal:has(.pm-banner.is-4x5) #pmCountWrap {
  display: none;
}
/* 三行字：压在图中间 ⇒ 白字 + 阴影（选择器权重与 V1 那三条打平） */
#promoModal .pm-banner > .pm-head .pm-eyebrow,
#promoModal .pm-banner > .pm-head .pm-title,
#promoModal .pm-banner > .pm-head .pm-sub { width: 100%; }
#promoModal .pm-banner.is-4x5 > .pm-head .pm-eyebrow {
  color: #f6d27a;
  opacity: 1;
  font-size: max(11px, min(16px, calc(0.024 * var(--pm-up, 500px))));
  line-height: 1.45;              /* 1.25 时大写字母上下沿挤在一起，加一点行盒呼吸 */
  letter-spacing: .16em;
  font-weight: 800;
  text-transform: uppercase;
  text-align: center;
  text-wrap: balance;
  text-shadow: 0 1px 3px rgba(0, 0, 0, .55);
}
#promoModal .pm-banner.is-4x5 > .pm-head .pm-title {
  color: #fff;
  /* 字号只跟 `--pm-up`（= 外框宽 px，JS 在 fitDesktop 的 4:5 支里写；兜底 500px）走。
     ⚠ 2026-11-11 实测：本档 banner 上的 `cqw` 算出来是**整页宽**（1280 屏量到 13.4px / 63.2px），
       不是外框宽；同一时刻 `6.4cqw` 与 `calc(0.064 * --pm-up)` 在标题上又能对上 37.76px
       —— 容器的行为说不清，别再赌。改成纯 calc + min/max，所见即所得。
     压图版没有白字段的高度约束了，字号回到演示页那档 0.06 × 框宽（640 宽 38.4px），
     空间靠图高（640 宽 = 480）兜，最多 3 行。 */
  font-size: max(18px, min(36px, calc(0.06 * var(--pm-up, 500px))));
  line-height: 1.24;              /* 1.15 专给白字段定高写的；压图版 480 高有余量，回到演示页那种行距 */
  font-weight: 900;
  letter-spacing: -.01em;
  margin-top: 0;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  overflow: hidden;
  flex: none;
  word-break: normal;
  overflow-wrap: break-word;
  text-align: center;
  text-wrap: balance;
  text-shadow: 0 2px 10px rgba(0, 0, 0, .55), 0 1px 2px rgba(0, 0, 0, .45);
  -webkit-text-size-adjust: none;
  text-size-adjust: none;
}
#promoModal .pm-banner.is-4x5 > .pm-head .pm-sub {
  color: rgba(255, 255, 255, .92);
  opacity: 1;
  font-size: max(12px, min(16px, calc(0.022 * var(--pm-up, 500px))));
  line-height: 1.5;
  margin-top: 0;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
  flex: none;
  text-align: center;
  text-wrap: pretty;
  text-shadow: 0 1px 4px rgba(0, 0, 0, .6);
}
#promoModal .pm-banner.is-4x5 > .pm-head .pm-codehl { color: #f6d27a; }

#promoModal .pm-scroll > .pm-banner {
  display: flex;
  flex-direction: column;
  padding: 0;                    /* 基础规则那条 26/60/22/26 的内衬必须归零（V1 图顶满） */
  /* ★ 2026-11-12：**字体基准 `--pm-u` 在这里兜底声明**（= 框宽 / 900，V1 稿是 900 宽）。
     ⚠ 桌面 `.pm-modal` 上有 JS 写的 inline `--pm-u`（同值），这里是给**手机**用的：
       fitDesktop 在 ≤480 直接 return、什么都不写 ⇒ 不声明的话所有 `calc(32px * var(--pm-u))` 整条作废，
       白字段掉回浏览器默认字号（标题 16px，实测）。
     ⚠ 就是 `calc(...)` 引用父级变量，不是上面那条血坑（那条说的是**在本元素上把 `--pm-u` 重新定义成另一个值**，
       导致子树里的 `--pm-u` 换成"本地替身"）。这里没有替身：`.pm-banner` 自己引用 `.pm-modal` 的 `--pm-modal-w`，
       `--pm-u` 在本元素上同样由 `.pm-modal` 的 inline 值优先 ⇒ 桌面口径不变。
     ⚠ 必须写在 `--pm-img-h-cap` **之后**：两者都靠 `--pm-modal-w`，顺序写反过一次（`--pm-u` 拿到初值）。 */
  --pm-img-h-cap: calc(var(--pm-modal-w, 640px) * 3 / 4);
  /* ★ 2026-11-12（第二次实测）：这一行**必须用 JS 在 `.pm-modal` 上写的值**，不能在 `.pm-banner` 上再声明 ——
     实测：在 `.pm-banner` 上声明 `--pm-u: calc(var(--pm-modal-w)/900)` 后，`.pm-head / #pmTitle` 拿到的
     就是这个 `calc()`，而 `calc(32px * calc(640px/900))` 在 Chromium 里**整条作废** ⇒ 标题掉回 16px。
     （`--pm-u` 在 `.pm-banner` 上读出来是 0.7111 是因为它继续从 `.pm-modal` 继承，不代表子树里没被替身。）
     手机档改为由 JS 写 px（见 promo-modal.js 里 `syncUnitMobile`），CSS 不再兜底。 */
  /* ★★ 2026-11-11 血坑：自定义属性会在**声明它的元素的子树里**做替换（CSS 变量替换规范），
     ⇒ `.pm-banner` 自己再声明一个 `--pm-u`（哪怕只是 `calc(...)`），整棵 `.pm-head` 拿到的就是
     这个"本地替身"，`.pm-modal` 上 JS 写的 inline `--pm-u` 被**整个盖掉**。
     实测（_pmdbg OUT_STATE）走链里 `.pm-head / #pmTitle` 的 `--pm-u` = `calc(640px / 900)`，
     `calc(32px * calc(640px/900))` 无效 → 字号整条失效 → 标题掉回 16px、白字段撑到 110。
     ⇒ 这里**绝不**声明 `--pm-u`；白字段的尺寸只用 JS 写在 `.pm-modal` 的那一个 `--pm-u`。 */
}
/* ★ 2026-11-12（合格版）：图段高**直接用 JS 算好的 `--pm-img-h`**。
   ⚠ 别再套 `min(--pm-img-h, --pm-img-h-cap)`：`--pm-img-h-cap` 要等 `--pm-modal-w` 到、并且要在容器尺寸
     变化后再触发一次重算，图 load 前那一帧会按兜底 640 算 ⇒ 窄屏上图和白字段对不上（11-11 踩过）。
     fitDesktop 已经把 `--pm-img-h` = min(框宽×3/4, 90vh 里放得下的) 算死，这里照用即可。 */
#promoModal .pm-scroll > .pm-banner > .pm-hero {
  position: relative;
  inset: auto;
  width: 100%;
  flex: none;
  height: var(--pm-img-h, 480px);
  --pm-hero-h: var(--pm-img-h, 480px);   /* 给倒计时条算偏移用 */
  overflow: hidden;
}
/* ⚠ 图 load 回调里把 img 的 height 改成 auto（避开「百分比基准为 0 ⇒ 图高塌成 0」的坑，见 promo-modal.js），
   这时非 4:3 的图按自然比例会比图段高 ⇒ 靠 `max-height:100%` 裁在图段里。
   （百分比当**约束**用很安全：基准缺失时只是不生效，不会把图压成 0。） */
#promoModal .pm-scroll > .pm-banner > .pm-hero img { max-height: 100%; }
#promoModal .pm-scroll > .pm-banner > .pm-head {
  position: relative;
  z-index: 1;
  flex: none;
  display: block;                /* V1 档保持 block；4:5 档由下面 `.is-4x5 > .pm-head` 那条改成 flex column */
  height: var(--pm-h-head, auto);   /* fitDesktop 算好的固定高：长文案裁掉，不许把卡片条推出外框 */
  min-height: 0;
  margin: 0;
  padding: var(--pm-pad-head, calc(28.8px * var(--pm-k)));
  background: #fff;
  text-align: left;
  overflow: hidden;
}
/* 图顶的倒计时条：图区右上角一条半透明暗带（V1 里 `02:14:17:59` 就挂在这儿） */
#promoModal .pm-banner > .pm-head .pm-count {
  position: absolute;
  top: calc(-1 * var(--pm-hero-h, 0px));   /* 抬到**图段**顶部（`.pm-head` 紧接在图下，top:0 会落在图的下缘） */
  left: 0; right: 0;
  display: none;
  align-items: center;
  justify-content: flex-end;
  gap: calc(8px * var(--pm-k));
  padding: calc(7px * var(--pm-k)) calc(14px * var(--pm-k));
  background: linear-gradient(to bottom, rgba(4, 16, 32, .62), rgba(4, 16, 32, 0));
  color: #fff;
  font-size: calc(12px * var(--pm-k));
  font-weight: 700;
  letter-spacing: .5px;
  text-shadow: 0 1px 3px rgba(0, 0, 0, .5);
  pointer-events: none;
}
#promoModal .pm-banner > .pm-head .pm-count.on { display: flex; }
#promoModal .pm-banner > .pm-head .pm-count #pmCount {
  font-variant-numeric: tabular-nums;
  color: #ffe1a0;
}

/* —— ★ 文字压在图上（旧版几何：11-10.4/11-10.5）。**V1 骨架下已作废**，这里整体清空：
   白字段的 `margin-top:auto` / 负边距 / 44-60-20-26 内衬 全部归零，
   否则 V1 那条（权重更高）虽然盖得住一部分，但 `has-hero` 的渐变压暗还在白底上叠一层灰。
   ⚠ 这三条**必须留**（删了旧规则还在外面生效）：归零比覆盖安全。 */
#promoModal .pm-banner > .pm-head {
  margin-top: 0;
  margin-left: 0;
  margin-right: 0;
}
#promoModal .pm-banner.has-hero > .pm-head { background: #fff; }
#promoModal .pm-banner.is-4x3 > .pm-head { margin-left: 0; margin-right: 0; }
#promoModal .pm-banner.has-hero > .pm-head { background: transparent; }
/* —— 白字段三行字（V1：深色字、标题 ≈32px×900/w×k、3 行）。 */
#promoModal .pm-banner > .pm-head .pm-eyebrow {
  color: #1b5e7a;
  opacity: 1;
  font-size: calc(13px * var(--pm-u));
  line-height: 1.25;
  letter-spacing: calc(2.5px * var(--pm-u));
  font-weight: 800;
  text-transform: uppercase;
}
/* ⚠ 选择器 / 属性**必须和下面通用的 `.pm-title`（`24px × --pm-k`）逐条打平**：
   之前这里只写了 color / font-size / margin-top，`line-height`、`-webkit-line-clamp`、
   `display:-webkit-box; flex:none` 都没写 ⇒ 通用那条的 `line-height:1.32` 和 `display:-webkit-box`
   把标题压成了 **1 行 9px 高**（实测 白字段 62 里只量到 9px 的标题）。
   ⇒ 凡是"尺寸类"属性这里一律重写一遍，别指望权重自动继承。 */
#promoModal .pm-banner > .pm-head .pm-title {
  color: #0d1b2e;
  font-size: calc(32px * var(--pm-u));
  line-height: 1.24;               /* ★ 2026-11-12：显式写死（别落到通用那条 1.32，V1 是 1.24） */
  font-weight: 800;
  margin-top: calc(6px * var(--pm-u));
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  overflow: hidden;
  flex: none;
  word-break: normal;
  overflow-wrap: break-word;
}
#promoModal .pm-banner > .pm-head .pm-sub {
  color: #4a5b70;
  opacity: 1;
  font-size: calc(17px * var(--pm-u));
  line-height: 1.5;
  margin-top: calc(8px * var(--pm-u));
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
  flex: none;
}
/* ★★ 2026-11-11 白字段字盒宽度＝可视宽（父 padding 框 − 2px）。
   ⚠ 必须**同时**给通用 `.pm-title` 写同一个值，见下面那条的注释（宽度回落到 auto 会让
     `-webkit-line-clamp` 的盒子只算成 1 行高：实测 22.76px 时盒高 31、副标只剩 8px）。 */
#promoModal .pm-banner > .pm-head .pm-eyebrow,
#promoModal .pm-banner > .pm-head .pm-title,
#promoModal .pm-banner > .pm-head .pm-sub {
  width: calc(100% - 2px);
}
/* 没有眉标 / 副标时不占位（后台可以不填） */
#promoModal .pm-banner > .pm-head .pm-eyebrow:empty,
#promoModal .pm-banner > .pm-head .pm-title:empty,
#promoModal .pm-banner > .pm-head .pm-sub:empty { display: none; }
/* 副标里的码高亮：压在图上 ⇒ 金色（白色底板上品牌青会糊） */
#promoModal .pm-banner > .pm-head .pm-codehl { color: #b8860b; letter-spacing: .3px; }

/* ================= ★★ 2026-11-12（合格版）：无图 / 图挂了 ⇒ **品牌底 + 白字居中** =================
   产品方：「没上传图，我就预设固定一个颜色的背景图，类似于主站前台的颜色」「标题也是在左上不是居中」
   ⇒ JS 在"没传图 / 图 onerror"时给 `.pm-banner` 挂 `.pm-brand`，并把外框高 = 品牌块 + 卡片条 + 底栏。
   背景 = 主站前台同一套青蓝渐变（`#pm-head` 那一套：#0f2a4a → #1b5e7a → #2fb8a8），再叠一层淡青径向光。
   ⚠ 白字段(#fff) 那条 `has-hero > .pm-head` 管不到这里（没有 has-hero），但下面几条要在它**之后**写，
     并且选择器带 `.pm-brand` 才压得住 `#promoModal .pm-banner > .pm-head` 的底色 / 文字色。
   ⚠ 块高 = 白字段 148（JS 写的 `--pm-h-head`），和"有图"那一档的图段+白字段（480+148）不是一个数：
     无图时没有图可看，再留 480 的空白只剩一片越来越淡的渐变 ⇒ 用 148 的紧凑卡。 */
/* ⚠ 类名是 `.pm-brand`（**没有点**）：`promo-modal.js` L644/L786/L793/L802 挂的都是
   `classList.add('pm-brand')`。这里曾误写成 `.is-brand`（11-11 的旧名）⇒ 下面整段品牌底规则
   一条都不命中，无图档变成"没有底色的白底板 + 白字标题"（= 什么都看不见）。改名时两边必须一起改。 */
#promoModal .pm-banner.pm-brand {
  background:
    radial-gradient(120% 90% at 78% 8%, rgba(47, 184, 168, .38) 0%, rgba(47, 184, 168, 0) 62%),
    linear-gradient(135deg, #0f2a4a 0%, #1b5e7a 55%, #2fb8a8 100%);
  overflow: hidden;
}
/* ⚠ `padding` 必须自己整条重写：JS 写的 `--pm-pad-head` 是**三值**（上 右 下），
   `0 var(--pm-pad-head)` 会被整条判无效 ⇒ 内衬归零，长标题顶到框边（2026-11-12 实测）。 */
#promoModal .pm-banner.pm-brand > .pm-head {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  height: var(--pm-h-head, 148px);
  padding: var(--pm-pad-head, 0 16px);
  background: transparent;
  text-align: center;
  overflow: hidden;
}
#promoModal .pm-banner.pm-brand > .pm-head .pm-eyebrow,
#promoModal .pm-banner.pm-brand > .pm-head .pm-title,
#promoModal .pm-banner.pm-brand > .pm-head .pm-sub {
  width: 100%;
  text-align: center;
  text-wrap: balance;
}
#promoModal .pm-banner.pm-brand > .pm-head .pm-eyebrow { color: #a8f0e2; }
#promoModal .pm-banner.pm-brand > .pm-head .pm-title { color: #fff; text-shadow: 0 2px 10px rgba(4, 16, 32, .45); }
#promoModal .pm-banner.pm-brand > .pm-head .pm-sub { color: rgba(255, 255, 255, .92); }
#promoModal .pm-banner.pm-brand > .pm-head .pm-codehl { color: #f6d27a; }
/* 倒计时条：`top: -1 × --pm-hero-h` 是给"图段在上"算的；品牌底没有图段 ⇒ 归零、贴卡片上沿 */
#promoModal .pm-banner.pm-brand > .pm-head .pm-count,
#promoModal .pm-modal.pm-mode-brand .pm-banner > .pm-head .pm-count {
  top: 0;
  justify-content: center;
  background: linear-gradient(to bottom, rgba(4, 16, 32, .32), rgba(4, 16, 32, 0));
}

/* ================= ★★ 2026-11-12（产品方："把标题全部放到图片中间去，就是广告图的中间"）=================
   有图（`.has-hero`）＝ **图仍然是 4:3 整图、占位不变**，但 `.pm-head` 不再接在图下面的白底块里，
   而是**满幅盖在图段上、白字居中**：
     · 尺寸全走 `--pm-u`（= 框宽 / 900，和 V1 白字段同一个尺）⇒ 640 宽：标题 32u=22.8px、副标 17u、眉标 13u；
       手机 358 宽：u≈0.398 ⇒ 标题 12.7px —— 手机另有一条按"可读优先"抬到 19px 的规则（见 480 断点里那组）。
     · `.pm-hero` 去掉 `overflow:hidden`（JS 同时写内联 `overflow:visible`）：`.pm-head` 是 `.pm-hero` 的**兄弟**、
       要盖到它上面去，图段的裁剪改由 `.pm-clip` 那一层兜底（本来就是 overflow:hidden + 圆角）。
     · 压暗 = 中间一段的径向暗底（只给字找底，图的四周保持原样），不给整张图铺黑。
   ⚠ 无图 / 图挂了（`.pm-brand`）**不进这条**：那是预设品牌底 + 白字居中，本来就已经在块中间了。
   ⚠ 白字段的三行字（`.pm-eyebrow/.pm-title/.pm-sub`）的**字号/行高/宽度**在下面「白字段三行字」那几条里，
     这里只写颜色 / 对齐 / 阴影（同权重、后写的赢 ⇒ 这几条必须排在它们**后面**，即本段位置）。
   ⚠ 倒计时条回到**图顶**：旧公式 `top:-1 × --pm-hero-h` 是给"head 在图下面"算的，现在 head 就在图上 ⇒ 归零。 */
#promoModal .pm-banner.has-hero > .pm-head {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 2;
  width: 100%;
  height: var(--pm-img-h, 480px);          /* 手机档由 aspect-ratio 那条撑（见 480 断点） */
  padding: 0 calc(20px * var(--pm-k, 1));
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: calc(6px * var(--pm-u, .71));
  background:
    radial-gradient(78% 66% at 50% 50%, rgba(4, 14, 26, .52) 0%, rgba(4, 14, 26, .26) 58%, rgba(4, 14, 26, 0) 100%);
  text-align: center;
  overflow: hidden;
  pointer-events: none;                    /* 只让关闭钮可点，字挡不住右上角的 X */
}
#promoModal .pm-banner.has-hero > .pm-head .pm-eyebrow,
#promoModal .pm-banner.has-hero > .pm-head .pm-title,
#promoModal .pm-banner.has-hero > .pm-head .pm-sub {
  width: 100%;
  text-align: center;
  text-shadow: 0 2px 12px rgba(0, 0, 0, .55), 0 1px 2px rgba(0, 0, 0, .45);
}
/* ★ 2026-11-12（产品方："标题做大一点，电脑版尤其要大" → "满 $300 包邮可以再大一点，这行只有几个字"
   "满额包邮、科研级多肽下单满 $300 全球冷链包邮 也要同步放大"）：压在图上的这一档**不吃白字段的 32u**，
   另给一套"短句大字"字阶（u = 框宽/900，640 宽 u≈0.711）：
     眉标 30u ≈ 21.3 · 标题 **90u ≈ 64**（约 3 倍）/ 行高 1.3 · 副标 32u ≈ 22.8。
   排版：三行**都居中**、字块整体在图正中；眉标金色 + 字距，和白色标题分层；超长时标题最多 3 行、
   副标最多 2 行，再长就裁（字块仍居中对齐，不会顶穿图区）。短句一行能放：标题 ≈ 8.5 个全角字 / 15 个半角。
   ⚠ 这几条**必须**排在上面「白字段三行字」之后（同权重、后写的赢），否则字号被 32u 盖回去。 */
#promoModal .pm-banner.has-hero > .pm-head .pm-eyebrow,
#promoModal .pm-banner.has-hero > .pm-head .pm-title,
#promoModal .pm-banner.has-hero > .pm-head .pm-sub {
  width: 100%;
  text-align: center;
  text-shadow: 0 2px 12px rgba(0, 0, 0, .55), 0 1px 2px rgba(0, 0, 0, .45);
}
#promoModal .pm-banner.has-hero > .pm-head .pm-eyebrow {
  color: #ffe7ad;
  font-size: calc(30px * var(--pm-u, .71));         /* 640 宽 ≈ 21.3 */
  letter-spacing: calc(4px * var(--pm-u, .71));
}
#promoModal .pm-banner.has-hero > .pm-head .pm-title {
  color: #fff;
  font-size: calc(81px * var(--pm-u, .71));         /* 640 宽 ≈ 57.6。81 = 90 × 0.9（★ 11-12 产品方："中间最大的标题缩小百分之10"；
                                                       90 那档 ≈ 64px 是按"满 $300 包邮"定的短句大字） */
  /* ⚠ 行高必须留余量：`-webkit-box` 的行盒按**整数**算，64px × 1.22 = 78.08 被取成 78，
     而中文 ascent+descent 是 1.15em（= 73.6）+ 行距的合计会到 81 ⇒ 单行 scrollHeight 81 > clientHeight 78，
     体检判"标题被截断"（字其实看得见）。1.3 = 83.2 之后单行 / 2 行实测 sh=ch。
     （★ 11-12 缩到 81u = 57.6px 后 1.3 仍留足余量，不用跟着改。）
     并且字号放大后**不能用负字距**：全角字跟着缩，量出来的行宽会和行盒对不上。 */
  line-height: 1.3;
  margin-top: calc(6px * var(--pm-u, .71));
  letter-spacing: 0;
  /* ★ 2026-11-12（档位实测抓到）：压图档只写了 `width:100%`，没写 `word-break` ⇒ 落到通用
     `.pm-title` 的 `overflow-wrap: break-word`。`break-word` 只在「单个词本身就超宽」时才断词，
     而 `-webkit-line-clamp` 是**按行盒算**的：法/俄那种长词（Économisez / исследование）
     整词换行后行宽 > 容器 ⇒ `scrollWidth > clientWidth`，体检判"横向溢出"。
     `anywhere` 才允许为了排得下在**词内**断（⚠ 泰语会因此被断词，那正是这里要的：
     反正已经贴不下，断词比溢出好）。 */
  word-break: normal;
  overflow-wrap: anywhere;
}
#promoModal .pm-banner.has-hero > .pm-head .pm-sub {
  color: rgba(255, 255, 255, .94);
  font-size: calc(32px * var(--pm-u, .71));         /* 640 宽 ≈ 22.8（"科研级多肽下单满 \$300，全球冷链包邮"） */
  line-height: 1.38;
  margin-top: calc(10px * var(--pm-u, .71));
  /* 副标最多 2 行：字阶放大后"1 行"反而可能被 `-webkit-line-clamp:2` 顶掉（行高取整差 1px），
     `2.9em` ≈ 2 行 + 余量，真正的字数上限由折行的自然结果决定。 */
  max-height: 2.9em;
}
#promoModal .pm-banner.has-hero > .pm-head .pm-codehl { color: #ffe1a0; text-shadow: 0 2px 10px rgba(0, 0, 0, .6); }
/* ================= ★★ 2026-11-13：「AI 排版」的**字号档位**（真正生效的那一半）=================
   产品方口径（11-13）：「一般大概率只会用到文字缩小，因为毕竟是广告肯定是短」。
   后台「AI 排版」核实行数后给出的 `tier`（100/90/80/70）记在**这门语言**的文案里
   （`i18n[lang].tier`），存库 → 前台 `curCopy().tier` → `paint()` 写
   `<div class="pm-modal" data-pm-tier="90">` ⇒ 下面这几条按档位乘字号。
   ⚠ 刻意的设计：
     ① **只乘字号，不动别的东西** —— 行高用的是无单位倍数，会跟着字号走；间距/内衬不乘（省着点，缩字就是为了腾地方）。
     ② **外框尺寸一个像素都不动**（文字层盖在图上、不占版式高）⇒ 「切卡片不跳」不变。
     ③ **绝不在这里 `--pm-u`**：`.pm-banner` 上一旦声明 `--pm-u` 就成了"本地替身"，
        把 `.pm-modal` 上 JS 写的内联 `--pm-u` 整个盖掉（11-12 踩过，见本文件 L471 那条警告）。
        所以档位只写成字号本身的比例，`--pm-u` 原样透传。
     ④ 选择器权重要比上面的基础字阶**高**（同权重、后写的赢）：这里多了 `[data-pm-tier]` 一层，
        排在基础字阶后面 ⇒ 双保险。
     ⑤ 档位只认 100/90/80/70 这四个值（JS 也按同样四档收敛）；品牌底（无图）不吃这套 ——
        那块的块高是固定的 148，缩字只会让字更小、地方还是那么多，没有意义。 */
#promoModal .pm-modal[data-pm-tier="90"] .pm-banner.has-hero > .pm-head .pm-eyebrow { font-size: calc(27px * var(--pm-u, .71)); letter-spacing: calc(3.6px * var(--pm-u, .71)); }
#promoModal .pm-modal[data-pm-tier="90"] .pm-banner.has-hero > .pm-head .pm-title { font-size: calc(72.9px * var(--pm-u, .71)); }
#promoModal .pm-modal[data-pm-tier="90"] .pm-banner.has-hero > .pm-head .pm-sub { font-size: calc(28.8px * var(--pm-u, .71)); }
#promoModal .pm-modal[data-pm-tier="80"] .pm-banner.has-hero > .pm-head .pm-eyebrow { font-size: calc(24px * var(--pm-u, .71)); letter-spacing: calc(3.2px * var(--pm-u, .71)); }
#promoModal .pm-modal[data-pm-tier="80"] .pm-banner.has-hero > .pm-head .pm-title { font-size: calc(64.8px * var(--pm-u, .71)); }
#promoModal .pm-modal[data-pm-tier="80"] .pm-banner.has-hero > .pm-head .pm-sub { font-size: calc(25.6px * var(--pm-u, .71)); }
#promoModal .pm-modal[data-pm-tier="70"] .pm-banner.has-hero > .pm-head .pm-eyebrow { font-size: calc(21px * var(--pm-u, .71)); letter-spacing: calc(2.8px * var(--pm-u, .71)); }
#promoModal .pm-modal[data-pm-tier="70"] .pm-banner.has-hero > .pm-head .pm-title { font-size: calc(56.7px * var(--pm-u, .71)); }
#promoModal .pm-modal[data-pm-tier="70"] .pm-banner.has-hero > .pm-head .pm-sub { font-size: calc(22.4px * var(--pm-u, .71)); }
/* 倒计时条：head 本身就在图顶上 ⇒ 不再抬 `-1 × --pm-hero-h`，改为贴 head 顶沿、右对齐（和关闭钮错开） */
#promoModal .pm-banner.has-hero > .pm-head .pm-count {
  top: 0;
  padding-right: calc(56px * var(--pm-k, 1));
}
/* 图段**仍然自己裁**（2026-11-12 修：手机上图片变白）。
   ⚠ 别再写 `overflow: visible`：`.is-4x3` 基础规则里 `.pm-hero` 是 `display:flex; align-items:center`，
     head 盖上去之后 flex 子项（img）的 `height:100%` 在"百分比高 + 滚动区 overflow:auto"下解不出来 ⇒
     图塌成 0 高，只剩 banner 的浅色底 ⇒ **整块图区变白**（电脑档 fitDesktop 写死了内联 `--pm-img-h`
     和容器高，所以没露）。head 是绝对定位的**兄弟**，本来就不需要 `.pm-hero` 放它出来，
     裁剪交给 `.pm-hero` 自己的 overflow:hidden（圆角由 `.pm-clip` 兜）最稳。 */
#promoModal .pm-scroll > .pm-banner.has-hero > .pm-hero { overflow: hidden; }
#promoModal .pm-banner.has-hero > .pm-hero img { height: 100%; }

/* ================= ★ 2026-11-11 临时（版式比对用，比对完删）：V1 的"金条" + "米色横条" =================
   参考图 V1 在白字段下面还有两段：金色横条（卖点短句）和米色 `#ffefd6` 横条（补充说明）。
   位置 = `.pm-banner` 的最后两个子项（由 paintAction 动态插入，没有文案就不渲染）⇒ 自下而上排：
     白字段 → 金条(.pm-gold) → 米色条(.pm-cream)。
   ⚠ 高度由 fitDesktop 算好写内联 `--pm-h-gold` / `--pm-h-cream`（白字段拿走剩下的），
     这里的 `min-height` 只是没算过时的兜底。 */
#promoModal .pm-banner > .pm-gold {
  flex: none;
  display: flex; align-items: center; justify-content: center; gap: calc(12px * var(--pm-u));
  height: var(--pm-h-gold, auto);
  min-height: calc(16px * var(--pm-u));
  overflow: hidden;
  word-break: break-word;
  padding: 0 var(--pm-pad-gold, calc(22.4px * var(--pm-k)));
  background: linear-gradient(180deg, #f6dc9a 0%, #e8c06a 52%, #d9a94b 100%);
  color: #3a2708;
  font-size: calc(26px * var(--pm-u));
  font-weight: 900;
  letter-spacing: .5px;
  text-align: center;
  text-shadow: 0 1px 0 rgba(255, 255, 255, .45);
}
#promoModal .pm-banner > .pm-cream {
  flex: none;
  height: var(--pm-h-cream, auto);
  min-height: calc(10px * var(--pm-u));
  overflow: hidden;
  word-break: break-word;
  padding: 0 var(--pm-pad-head, calc(22.4px * var(--pm-k)));
  display: flex; align-items: center; justify-content: center;
  background: #ffefd6;
  color: #6b4a14;
  font-size: calc(13px * var(--pm-u));
  line-height: 1.35;
  text-align: center;
}

/* —— 关闭钮（框内右上角）：现在压在**图**上，白描边 + 阴影让它"浮"出来。
      ★ 2026-11-10：底色不在这里写死 —— 由 JS 按"钮底下那块图是亮是暗"给外框打 `.on-light` / `.on-dark`。 */
#promoModal .pm-modal.pm-mode-hero .pm-close {
  box-shadow: 0 2px 12px rgba(0, 0, 0, .45), 0 0 0 3px rgba(255, 255, 255, .14);
}
/* ---- 基础字排（文字压图那套在 `.pm-banner > .pm-head` 里单独写死了；这里只兜底无图活动的
      纯渐变底，以及 `.pm-body` 空壳。留这几条别删。 ---- */
/* ⚠ 「11-10 那套 11px / 24px / 13px × --pm-k」作废（V1 白字段里它会把字撑爆）：
   实测标题 12.8px + 0 行副标 + 眉标 5.2 = 62 把白字段顶满，副标被裁 ⇒ 白字段里必须**一律走 --pm-u（V1 稿比例）**。
   `--pm-k` 只剩卡片条 / 底栏 / 关闭钮那几个非 V1 元素在用。 */
#promoModal .pm-eyebrow {
  font-size: calc(13px * var(--pm-u));
  letter-spacing: calc(2.5px * var(--pm-u));
  font-weight: 800;
  opacity: .82;
}
#promoModal .pm-title {
  font-size: calc(32px * var(--pm-u));
  font-weight: 800;
  margin-top: calc(6px * var(--pm-u));
  line-height: 1.24;
  /* ⚠ `width` 要给到**像素**：宽度是 auto / `100%` 时（父 padding 框 624 已经扣过内衬），
     `-webkit-line-clamp` 盒子只算成 1 行高（实测 22.76px 时盒高 31、副标只剩 8px），
     给 624 之后行数正常 —— 和 V1 那组选择器里的同名属性保持一致。 */
  width: calc(100% - 2px);
  /* 泰语没有词间空格，不设 nowrap，允许自然换行 */
  word-break: normal;
  overflow-wrap: break-word;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
#promoModal .pm-sub {
  font-size: calc(17px * var(--pm-u));
  opacity: .93;
  margin-top: calc(8px * var(--pm-u));
  line-height: 1.5;
  overflow-wrap: break-word;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
/* 旧的"纯文字活动放大一档"作废：无图活动也走同一条骨架（纯渐变底 + 文字贴下缘），字号和图片档一致；
   `.pm-banner > .pm-head .pm-title` 的权重更高会盖掉基础字排；留这几条是为了老浏览器/异常路径不塌。 */
#promoModal .pm-codehl {
  color: #ffe08a;
  letter-spacing: .5px;
}

/* ---------- 关闭按钮：热区 ≥ 40×40 ----------
   ★ 2026-11-08（产品方问题三：右上角缺关闭按钮 / 跟透明的一样）：
   原来是 `rgba(255,255,255,.18)` 半透明白、**还没给 border-radius** ⇒ 叠在浅色渐变区上几乎看不见，
   还被 `.btn.ghost` 的透明底 + 边框盖掉。
   现在改成**实心深色圆钮**：深蓝底 + 白 ×，在任何底色（浅渐变 / 深色海报图）上都稳。
   ⚠ `!important` 是为了压过 `.btn.ghost` 那套透明样式（它作用在同一个元素上）。 */
#promoModal .pm-close {
  position: absolute;
  /* ★ 2026-11-08 修（产品方：右上角 X 和红色角标重叠）：
     X 原来在 `top:11px`，正好落在顶部卡片条的垂直范围（10~58）里，
     而红色角标也在卡片条右侧垂直居中 ⇒ 两者必然叠在一起（实测 375 宽下重叠 21×24px）。
     ✅ 正解：把 X **升到卡片条上方**（top:-12px / right:-6px，半悬在外框边缘）——
        卡片条那一整片右侧就只剩角标，各占各的位置，任何宽度都不会再打架
        （只调 top 到 0 也不行：卡片条从 10px 开始，X 还是探进去 30px）。
     配 `.pm-modal { overflow: visible }`（见上），否则负偏移会被圆角裁掉。 */
  /* ⚠ 数值是**量出来的**：卡片条 10~58，角标垂直居中 ⇒ 上沿约在 16px。
     X 高 40，要整个底部落在 16 之上 ⇒ top 必须 ≤ -24（-12 时还剩 6~10px 压住角标）。 */
  /* ★ 2026-11-10（产品方："电脑端和手机端的 X 都放在框内，颜色得挑个合适的，不然不明显"）：
     **收进外框内** —— 不再半悬。桌面 32px（40×0.8）：上沿/右沿 12×k = 9.6，
     底边落在圆角拐点（半径 18×k = 14.4）之上 ⇒ 整颗都在框里、又避开弧角。
     手机断点单独钉 8 / 40（见下方 @media）。 */
  top: calc(12px * var(--pm-k));
  right: calc(12px * var(--pm-k));
  color: #fff !important;
  /* 底色分两档，按**背后是什么**决定（JS 给外框打 .on-light / .on-dark）：
       压在深蓝青渐变 / 图片上 ⇒ 深藏青实心 + 白 ×，任何图上都够对比；
       压在浅色海报上       ⇒ 白底 + 深藏青 ×，浅底上也不会糊成一片白。 */
  background: rgba(13, 33, 56, .78) !important;
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  border: 1.5px solid rgba(255, 255, 255, .92) !important;
  border-radius: 50%;
  z-index: 4;                                     /* 压在顶部卡片条之上，不会被卡片挡住 */
  width: calc(40px * var(--pm-k));      /* ★ 11-10：40×40 → 32×32（热区仍 ≥32，够点） */
  height: calc(40px * var(--pm-k));
  min-width: calc(40px * var(--pm-k));
  /* ★ 2026-11-10（产品方："电脑端的右上角形状不对"）：桌面这个圆钮被拉成了 32×44 竖椭圆 ——
     元凶是站点通用 `.btn{min-height:44px}`（.pm-close 带 btn 类，CSS 的 calc 算不进 min-height）。
     手机断点里早有一份 `min-height:…!important`，桌面**一条都没有** ⇒ 桌面单独被撑高。
     现在显式钉死 hot square：高度 / 最小高都跟宽度同一个值，`border-radius:50%` 才是正圆。 */
  min-height: calc(40px * var(--pm-k));
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: calc(16px * var(--pm-k));
  line-height: 1;
  cursor: pointer;
  box-shadow: 0 2px 10px rgba(10, 25, 45, .3);
  transition: background .16s, transform .16s;
}
/* ★ 2026-11-10：浅底海报上反色那一档（.on-light 由 JS 打在外框上） */
#promoModal .pm-modal.on-light .pm-close {
  color: #0d2138 !important;
  background: rgba(255, 255, 255, .94) !important;
  border-color: rgba(13, 33, 56, .18) !important;
  box-shadow: 0 2px 10px rgba(10, 25, 45, .22);
}
#promoModal .pm-close:hover { background: rgba(13, 33, 56, .92) !important; transform: scale(1.06); }
#promoModal .pm-modal.on-light .pm-close:hover { background: #fff !important; }
#promoModal .pm-close:active { transform: scale(.94); }
#promoModal .pm-close:focus-visible { outline: 2px solid #2fb8a8; outline-offset: 2px; }

/* ---------- ★ 2026-11-07 顶部卡片条（图标 + 文字） ----------
   产品方："一排 4 个小卡片，每个有图标 + 文字；当前选中的加白边高亮，其他缩小半透明；
   不要左右滑动按键。"  ⇒ 卡片条是**桌面唯一的切换入口**，也是最显眼的状态指示。
   ⚠ 卡片条在外框内、**滚动区之外**（.pm-tabs 是 .pm-modal 的直接子节点），
     这样内容滚到底时仍然能切活动。 */
/* ★ 2026-11-07 晚（产品方）：卡片 = **图标 + 文字**。
   ★ 2026-11-08（产品方问题一）：广告可以配 **5 条以上**，原来写死只画 3 张（`TAB_MAX=3`）是 bug。
   现在的口径：
     · **桌面 / 手机同一套**：卡片条可以放多少放多少，**放不下就横向滑动**
       （手机 375 宽放 3 张、桌面 760 宽放 5 张，都靠 `flex:0 0 auto` + `overflow-x:auto` 自然决定，
        不再用 JS 数字切条数）
     · 超出可视范围时右侧中间浮一个**红色数字角标**（`.pm-more`，如「+2」= 右边还有 2 条），
       点它滑到下一屏；滑到最右自动隐藏
   ⚠ 卡片条在外框内、**滚动区之外**（.pm-tabs 是 .pm-modal 的直接子节点），
     这样内容滚到底时仍然能切活动。 */
#promoModal .pm-tabs-wrap {
  flex: none;
  position: relative;            /* 红色角标的定位基准 */
  background: #eef4f7;
  border-bottom: 1px solid #dfe9ee;
  /* ★ 2026-11-09：高度**写死**（内容 + padding 20 + 边框 1）。
     下面 `.pm-scroll` 的 flex-basis 里减的就是这个高度 —— 不写死的话实际量到 +1，
     整条「卡片条 / 滚动区 / 底栏」就会超外框 1px（滚动区多 1px、底栏多 1px，滚到底露出 1px 白）。
     ★ 2026-11-12 卡片放大：78 → 102（卡 58 → 74）；二改（产品方："卡片后面那整条底这个高度缩小点，
       然后按钮的上下间距要一样"）：102 → **97**（卡 74 不变，上下内衬都改成 10，两边均等 + 卡片条自带的
       1px 下边框 ⇒ 上 10 / 下 11）。桌面上 JS 的 `DESK_TABS_H = 97` 会写内联 78，
       这里的 `calc(97 × k)` 只是兜底，两处必须同时改。 */
  /* ★ 2026-11-12 四改（**结构收敛**，产品方："把坑按你建议来处理 + 前后台 UI 优化"）：
     坑②：卡片条高度原来有两个来源（JS 内联 !important ↔ CSS calc 兜底），手机还得三处 !important 盖回来。
     ⇒ **高度只留 JS 一个来源**：`--pm-tabs-h` 由 fitDesktop() 按视口写，CSS 只读它；
        手机 / 极窄屏由 JS 按同一张表算出 63 / 61（不再靠媒体查询 !important）。
     坑③：底栏 `justify-content:flex-end` 把余量全堆在顶部，改按钮尺寸会"反向移动"。
     ⇒ 改成 `flex-start` + **显式间距**：`.pm-cta` 距图片底 6、说明行 3、冷却 2 —— 三行位置由数字决定，不看余量。 */
  height: var(--pm-tabs-h, calc(97px * var(--pm-k)));   /* 兜底值只在 JS 还没跑时用；正常都被 --pm-tabs-h 接管 */
  box-sizing: border-box;
  overflow: hidden;
  /* ★ 2026-11-09.5：外框宽改由 JS 按视口高算（645~760 都会出现）⇒ 卡片条要**用满外框宽**。
     原来没写 padding：卡片条一旦比外框窄（比如 max-width 封顶），它会按"内容宽"收缩，
     卡片就横滑不全、右侧 +N 角标把最后一张盖掉（1440 屏实测：外框 760 时卡片条只有 645）。
     写 `width:100%` 后卡片条永远跟外框同宽。 */
  width: 100%;
}
#promoModal .pm-tabs {
  display: flex;
  gap: calc(10px * var(--pm-k));
  /* ★ 2026-11-09.9：左右 11 → **8**。一张卡占位 146 + 2×8 = 162，5 张 + 4×10(gap) = 850，
     正好等于 `800 − 2×8 − 1(卡片条边框)` ⇒ 桌面 5 张一眼看全、不横滑、没有 +N 角标。
     第一张卡左缘 = 外框 + 8 + 2(卡片边框) = 外框 + 10 ⇒ 和图 / 主按钮 / 「7 天内不再提示」同一条竖线。 */
  padding: calc(10px * var(--pm-k)) calc(8px * var(--pm-k));
  justify-content: flex-start;
  align-items: center;
  flex-wrap: nowrap;
  overflow-x: auto;               /* ★ 放不下就横滑（手机 3 张 / 桌面 5 张都是这里决定的） */
  scrollbar-width: none;
  scroll-behavior: smooth;
  scroll-padding: 0 calc(14px * var(--pm-k));
}
#promoModal .pm-tabs::-webkit-scrollbar { display: none; }
/* 内容比容器窄时**居中**（3 条以内的老场景保持原样），宽时从左排起、可滑
   ★ 2026-11-09.8：`.is-fit` 由 syncMoreBadge 按 scrollWidth 判，`.fits` 是同一判定的外框版
     （两者一个来源，留 `.is-fit` 兼容老测试）。居中时左右内衬都归零，否则"居中"会偏右 6px。 */
#promoModal .pm-tabs.is-fit,
#promoModal .pm-tabs-wrap.fits .pm-tabs { justify-content: center; }
#promoModal .pm-tabs.is-fit { padding-left: 0; padding-right: 0; }
/* ★ 红色数字角标：右侧中间，显示「右边界外还有几条」；滑到底自动隐藏 */
#promoModal .pm-more {
  position: absolute;
  right: calc(6px * var(--pm-k));
  top: 50%;
  transform: translateY(-50%);
  min-width: calc(26px * var(--pm-k));
  height: calc(26px * var(--pm-k));
  padding: 0 calc(7px * var(--pm-k));
  border: 0;
  border-radius: calc(13px * var(--pm-k));
  background: #e8453c;           /* 红：和品牌青绿区分开，一眼能看见 */
  color: #fff;
  font-size: calc(12px * var(--pm-k));
  font-weight: 800;
  line-height: 1;
  display: none;                 /* 由 JS 按"是否还有没露出来的卡片"开关 */
  align-items: center;
  justify-content: center;
  cursor: pointer;
  box-shadow: 0 2px 8px rgba(232, 69, 60, .45), 0 0 0 2px #fff;
  z-index: 3;
  transition: transform .16s, background .16s;
}
#promoModal .pm-more.show { display: flex; }
#promoModal .pm-more:active { transform: translateY(-50%) scale(.92); }
#promoModal .pm-more:hover { background: #d63a31; }
/* 左边缘的浅色渐隐：提示"左边还有内容，已经滑过去一段了" */
#promoModal .pm-tabs-wrap.scrolled::before {
  content: '';
  position: absolute; left: 0; top: 0; bottom: 0; width: 22px;
  background: linear-gradient(90deg, #eef4f7 30%, rgba(238, 244, 247, 0));
  pointer-events: none; z-index: 2;
}
#promoModal .pm-thumb {
  /* ★ 2026-11-08：改成 `0 0 150px`（不收缩）—— 卡片按 150px 定宽排开，
     放不下就溢出 → 由 .pm-tabs 横滑。原来 `0 1 150px` 会把卡片**挤扁**而不是滑出去。 */
  /* ★ 2026-11-09.8（产品方："折扣购物金这些向左移动一些，让三个框对齐居中"）：
     宽度 150 → **146**：`flex-basis` **不含**边框（2+2）⇒ 一张卡实际占 150。
     5 张 = 5×150 + 4×10(gap) + 2×11(padding−卡片条边框 1) = **802**，正好等于卡片条内容宽 ⇒
     判"放得下"⇒ 卡片条居中（`.fits`），3 张 / 5 张起点都和主按钮、图同一条左边缘。
   ⚠ 别再改回 150：那一档 5 张占 830 > 800，永远判"放不下"、永远横滑，第一张卡就比按钮左缘多出 10px。 */
  /* ★ 2026-11-09.9 定稿（"三个框对齐居中"）：外层尺寸单一来源 = `flex-basis`，
     **不用 width**（flex 项的 flex-basis 是 border-box，`box-sizing` 不影响它，
      写 width 只会和 flex-basis 打架、把一排卡片宽度算歪）。
     一张卡占位 = basis(146) + 左右 padding(2×8) = 162；5 张 = 5×162 + 4×10(gap) = 850 = 内容宽
     ⇒ 刚好放得下（scrollWidth ≤ clientWidth）⇒ 不横滑、卡片条居中时不推、第一张卡左缘 = 外框 + 20 = 主按钮/图左缘。
     ⚠ 上下左右内衬 8 是唯一来源：`.on` / `:not(.on)` 都**不再单独改内衬**
       （改一侧会让整排宽度变化 ⇒ 一会儿放得下一会儿放不下，卡片条在"左排/居中"之间来回跳）。 */
  flex: 0 0 calc(146px * var(--pm-k));
  width: auto;
  box-sizing: border-box;
  min-width: 0;
  height: calc(58px * var(--pm-k));
  border-radius: calc(11px * var(--pm-k));
  border: calc(2px * var(--pm-k)) solid transparent;
  background: #fff;
  color: #2c4a66;
  cursor: pointer;
  padding: calc(5px * var(--pm-k)) calc(8px * var(--pm-k)) calc(4px * var(--pm-k));
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: calc(3px * var(--pm-k));
  /* 未选中：缩小 + 半透明（产品方原话） */
  opacity: .55;
  transform: scale(.94);
  transition: opacity .16s, transform .16s, border-color .16s, box-shadow .16s, background .16s;
  box-shadow: 0 1px 3px rgba(18, 40, 69, .06);
}
#promoModal .pm-thumb i { font-size: calc(20px * var(--pm-k)); line-height: 1; color: #1b7f8a; }
#promoModal .pm-thumb span {
  font-size: calc(12px * var(--pm-k));
  font-weight: 800;
  line-height: 1.15;
  max-width: 100%;
  /* 短词最多 20 字，卡片 ~150px 宽 ⇒ 一行截断，全文看 title 悬停 / 读屏 */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
#promoModal .pm-thumb.on {
  opacity: 1;                     /* 选中：不透明 + 白边 + 微放大 */
  transform: scale(1);
  border-color: #fff;
  background: #fff;
  /* 白边在浅底上看不出来 ⇒ 外面再套一圈品牌青绿 */
  box-shadow: 0 3px 12px rgba(18, 40, 69, .18), 0 0 0 2px #2fb8a8;
}
#promoModal .pm-thumb.on i { color: #0f7d72; }
#promoModal .pm-thumb:hover { opacity: .85; }
#promoModal .pm-thumb:focus-visible { outline: 2px solid #2fb8a8; outline-offset: 2px; }

/* ★ 2026-11-12（产品方："电脑版底部的选择框放大一点试试仅电脑版"）：**只放桌面**那一档。
   原来桌面档卡片 116.8 × 46.4、图标 16、文字 9.6（整体 0.8 缩下来，字小得几乎读不出来）。
   现在桌面档用一套**只属于桌面**的尺寸（手机 ≤480 的媒体查询在后面，会把卡片条覆盖回去，不受影响）：
     卡片条  62 → **82**（卡 58 → 74）        图标 16 → **20.8**        文字 9.6 → **11.6**
     （下面写的都是 900 宽基准值，桌面实际 = 值 × k 0.8）
   卡片宽 146 → **128**（× k = 102.4）。5 张占 102.4×5 + gap 8×4 + 内衬 2×6.4 = **557 ≤ 640**
   ⇒ 桌面一排 5 张一眼看全、不横滑、不弹 +N 角标。
   ⚠ 卡片文字本来就 `nowrap + ellipsis`（一行截断，悬停 / 读屏看全文）。放大后桌面一行约 **11 个全角字 / 18 个半角字**
     （原来 9.6px 时约 13 个全角字）⇒ 想一眼看完就配短词（后台「卡片短词」），别直接退回长标题。
   ⚠ 卡片条高度只有两个来源，必须同时改：JS `fitDesktop()` 的 `DESK_TABS_H = 102`（写内联 82）和这里。
   ★ 2026-11-12 二改（产品方："卡片后面那整条底这个高度缩小点，然后就是让按钮的上下间距要一样"）：
     **卡本身不缩**（还是 128 / 74 / 图标 26 / 字 14.5），只收卡片后面那条浅青底：
       内衬 上 10 / 下 14（衬在 1px 下边框上）→ **上下都是 10** ⇒ 卡片条 82 → **78**，卡上下各 8（×k）、对称。
     （"底色高度缩小"最初那一版还缩了卡本身，产品方已撤销，别按那版改。） */
#promoModal .pm-tabs { gap: calc(10px * var(--pm-k)); padding: calc(10px * var(--pm-k)) calc(8px * var(--pm-k)); }
#promoModal .pm-thumb {
  flex: 0 0 calc(128px * var(--pm-k));
  height: calc(74px * var(--pm-k));
  border-radius: calc(12px * var(--pm-k));
  padding: calc(4px * var(--pm-k)) calc(8px * var(--pm-k)) calc(4px * var(--pm-k));
  gap: calc(3px * var(--pm-k));
}
#promoModal .pm-thumb i { font-size: calc(26px * var(--pm-k)); }
#promoModal .pm-thumb span { font-size: calc(14.5px * var(--pm-k)); line-height: 1.18; }

/* ---------- 模板差异 ----------
   ★ 2026-11-07：外框**高度已固定**，模板不再改高度（原来 B 是 min-height:380 压缩空白，
   现在靠"内容少 ⇒ 居中留白"解决同一个问题，不需要每个模板一套高度）。
   模板现在只影响**文字层**的松紧，不影响外框尺寸。 */
#promoModal .pm-modal.tpl-b .pm-title { margin-top: 6px; }
#promoModal .pm-modal.tpl-b .pm-sub { margin-top: 5px; line-height: 1.55; }
/* ★ 2026-11-07 晚：模板 B 原来把正文区 padding-top 改成 12px —— 正文区现在只有卖点卡，
/* ★ 2026-11-07 晚：模板 B 原来把正文区 padding-top 改成 12px —— 正文区现在只有卖点卡，
   这一改会让卖点卡在活动之间上下差 4px（实测），所以**模板不再动正文内边距**。 */
/* ★ 2026-11-12 五改（**手机切换广告时主按钮上下跳 6px 的第二个根因**）：原来这里是
   `margin-top: 0` —— 模板 A 的按钮上距 6（手机断点）/ 10（桌面 ×k），模板 B 却写死 0
   ⇒ 只要活动的模板不是 A，`#pmCta` 就整体上移 6px（桌面 × k 也一样会动，只是 8 个点更不明显）。
   ⇒ 这里必须**显式写 0**：桌面档 CTA 的上距来自 `fitDesktop()` 写在 `.pm-foot` 上的内联 `padding-top`，
     基础规则里没有 `margin-top`（= 0）才是桌面正确的口径；手机上由断点里的 `margin-top: 6px` 覆盖。
   ⚠ 别写 `margin-top: inherit`（取的是父元素 `.pm-foot` 的 0，会把手机那 6 也吃掉），
     也别写 `calc(10px * var(--pm-k))`（桌面会多 8px，实测 CTA 686.5 → 694.5）。 */
#promoModal .pm-modal.tpl-b .pm-cta { margin-top: 0; }
/* ★ 2026-11-10（产品方：取消折中方案）：模板 B 恢复给说明行单独加 5px 上边距（栈已撤掉）。
   ★ 2026-11-12 四改：底栏改 `flex-start` 后，模板 B 的这 4px 差异（5/2 vs A 的 4/2）改由这里**统一钉成 4/2**，
     否则同一排按钮会因为"是不是模板 B"上下差 1px —— 产品方一条铁律是切活动不许抖。 */
#promoModal .pm-modal.tpl-b .pm-code { margin-top: calc(4px * var(--pm-k)); }
#promoModal .pm-modal.tpl-b .pm-cool { margin-top: calc(2px * var(--pm-k)); padding-top: calc(2px * var(--pm-k)); }

/* ============ 正文（白色区） + ★ 固定底栏（2026-11-07 晚 产品方） ============
   产品方原话："把领取礼金和选购和上面优惠码礼金全部做成固定大小固定位置，颜色可以不一样，
                一个广告不会出现两个可领取的东西 —— 前台广告就是为了让用户切换广告感觉不到大小变化。"

   ⇒ 主按钮 / 说明行 / 冷却 三行组成一条**贴底固定栏**（`.pm-foot`），排在 `.pm-scroll` **之外**
     ⇒ 它根本不参与滚动，也不受有没有码 / 冷却文案长短影响：
        · 高度写死（桌面 126px / 手机 118px，2026-11-09 删卖点卡时同步做紧凑），三行位置永远一样
        · 上面 `.pm-scroll` 是滚动区，里面只有渐变区 `.pm-banner`
        · 外框高度固定（桌面 660 / 手机 80vh）不变

   ⚠ 踩过四种坑（都实测过，别再改回去）：
     ① `margin-top:auto` 把动作区推底 → 滚动容器的行盒不撑满，auto 拿到 0（差 90px）
     ② `position:absolute; bottom:0` 挂 `.pm-scroll` → 父级 static，且绝对定位按内容高（差 100px）
     ③ sticky 压底但内容不够高 → 粘在渐变区下面（按钮跑到渐变中间）
     ④ 靠 `justify-content:flex-end` + 写死槽高 → 槽内子项溢出/底部定位顺序反了，按钮会偏上
   ⇒ 最终方案：**不要把它放进滚动容器**。DOM 顺序：.pm-tabs → .pm-scroll(.pm-banner) → .pm-foot。
   （2026-11-09 删卖点卡后 `.pm-scroll` 里只剩渐变区，顺序不变。） */

/* 滚动容器只负责滚，内容就是渐变区本身 */
#promoModal .pm-scroll > .pm-banner { flex: 0 0 auto; }   /* ⚠ 不能用 flex:none 之外的收缩值：.pm-scroll 已 flex:1，默认 shrink 会压扁渐变区 */
/* ★ 2026-11-09（产品方：**前后台都删掉卖点卡**）：正文区 `.pm-body`（白底 + 渐变蒙层 + 卖点卡网格）
   **整块删除** —— DOM 里已经没有了，这里的规则也一并清掉，不留"看起来还在"的死 CSS。
   连带作废的历史口径：卖点卡补空占位格 / 最多 2 条 / `[data-n="1"]` 单格撑满 / 手机 72px 行高。
   底栏不再有 `flex:1` 的竞争对象 ⇒ 渐变区下面直接接底栏，天然紧凑、切换活动高度零跳动。 */

/* ★ 固定底栏：高度写死，三行位置固定
   ★ 2026-11-09（产品方：删卖点卡 + 布局同步做紧凑）：高度 150 → 126。
     账：按钮 57 + 说明行 18(+8 mt) + 冷却 40(+8 mt +1 虚线) + 底部留白 10 = 142，
     原来 150 留了 8px 余量；现在压到 126 ⇒ 三行只留 6px 呼吸，按钮直接贴在渐变区下面。
     ⚠ 别再小于 126：`#pmCool` 是 `height:40px` 写死的，再压就会被裁。
   ★ 2026-11-12 三改（产品方："电脑端的点击选购、7 天提示、本月不再提示、结算输入都放大一点"）：
     底栏高度 **不动**（126），只把三行的**字号**抬一档，省下的行高从行盒里拿（行盒不超就行）：
       主按钮 14.5 → **16**（53 高的按钮塞得下）· 说明行 12 → **13.5**（行盒 18 不动）·
       冷却按钮 11.5 → **12.5**（行盒 16 不动，两枚各限 48% 宽，长语言仍靠 ellipsis）。 */
#promoModal .pm-foot {
  position: relative;
  flex: none;
  height: calc(126px * var(--pm-k));  /* ★ 11-10：桌面由 JS 写内联 101，这里只兜底 */
  /* ★ 2026-11-09（产品方：按钮和渐变区要隔开、上下左右留白对齐）：
     上面留 10px、左右仍 20px ⇒ 主按钮 **左右缩进 20 / 上 10 / 下 8**，四周不对称也比现在贴边强；
     底栏总高不变 126（留给冷却那行的余量正好让出这 10px，实测不裁）。 */
  padding: calc(10px * var(--pm-k)) calc(20px * var(--pm-k)) calc(8px * var(--pm-k));
  /* ★ 2026-11-12 四改（坑③）：`flex-end` → **`flex-start`**。
     原来是"内容贴底、余量堆在按钮头顶"⇒ 任何尺寸变化都会把按钮往反方向推（缩按钮 = 按钮下沉，实测手机 547→551）。
     现在三行从上往下排、位置由 `.pm-cta` / `.pm-code` / `.pm-cool` 的 margin 显式给定
     （桌面 10 / 板 10 都是内衬、与原来渲染位置一致），余量统一落在**底部**。
     ⚠ 两条铁律仍在：主按钮 `flex:none`、说明行恒占 18px（×k）。 */
  display: flex; flex-direction: column; justify-content: flex-start;
  background: #fff;
}

/* ★ 2026-11-09：`.pm-body` / `.pm-perks` / `.pm-perk` 全部作废（前后台已删卖点卡）。
   如果以后又要卖点卡，**别只把 CSS 加回来**：JS 的 DOM、后台编辑框、排版体检（db.js 3154 那段）
   都要一起恢复，否则又是"只改一半"。 */

/* ================= ★ 2026-11-07 晚（产品方）：动作区「固定尺寸 + 固定位置」 =================
   **一个主按钮**（#pmCta）承担礼金领取 / 去选购两种动作，尺寸写死，只换颜色和文案：
     · 礼金活动   → 金色渐变（`body.pm-has-grant` 由 paintAction 挂/摘）
     · 码 / 宣传  → 品牌绿（`pm-cta-main`）
   说明行（#pmCode）恒占位（18px，单行截断）：显示码 / 期限 / 空着。
   ⚠ 按钮高 **57px 固定**，padding 不参与：德语/俄语长文案单行截断，不靠换行撑高。
   ★ 2026-11-09 实测：桌面（1440）渲染 50px、手机 51~53px —— 因为 `.pm-foot` 写死高度、
     内容超高时 flex 子项会收缩（`flex-shrink` 默认 1）。11-09 已加 `flex: none`：
     复量 1440=57 / 375=53 / 414=53，底栏 126/118 与按钮顶端 y（499 / 512）都没变。 */
#promoModal .pm-cta {
  display: flex;
  align-items: center;
  justify-content: center;
  /* ★ 2026-11-12 五改（**手机切换广告时主按钮上下跳 6px**，根因②）：`.pm-cta` 以前没写 `box-sizing`，
     content-box ⇒ 「去选购」那条（图标 + 大写文字）的内联内容宽于可用内容宽，flex 居中后文字
     在按钮里上下各溢出一截，实测按钮盒 44（声明 42 + 上下边框）而其它活动 42，位置差半个行高。
     ⇒ 显式 `border-box`：声明多高就渲染多高，4 条活动逐张点实测顶边 534.8 恒定。
     （根因① 是上面 `.tpl-b .pm-cta` 的 margin-top，见那一处注释——两条都要在，缺一条还会抖 6px。） */
  box-sizing: border-box;
  /* ⚠ 数字要用**等宽数字**：`¥10.00` 里的数字（比例字形 542px）比中日文字宽（552px），
     行盒高度差 2px ⇒ 按钮在活动之间上下抖 2px（实测）。tabular-nums 之后三行都一致。 */
  font-variant-numeric: tabular-nums;
  gap: calc(8px * var(--pm-k));
  width: 100%;
  min-width: 0;
  /* ★ 2026-11-09.8（产品方："礼金领取那个框往上移一点，然后高度缩小点"）：57 → **53**。
     底栏总高 126 不动（卡片条 / 关闭钮 / 角标全按它调的），省下的 4px 全部让给下面两行留白：
     说明行和冷却行不再被压扁 —— 产品方看到的"冷却只能看到一半"就是这里被挤出来的。 */
  height: calc(53px * var(--pm-k));       /* ★ 11-10 桌面 42.4 */
  flex: none;   /* ★ 2026-11-09：`.pm-foot` 写死高度，默认 flex-shrink:1 会把 57 压成 50/51~53。 */
  /* ⚠⚠ 五改实测（桌面 1440，真 44px）：**这里的 `margin-top` 什么都不写** ——
     11-10 四改的「手机按钮上距 = 6」只写在手机断点里，桌面靠 `fitDesktop()` 给 `.pm-foot` 写的内联
     `padding-top: 10px(!important)` 顶下来（实测 CTA 顶 686.5 = 底栏 678.5 + 8，和手机断点的 6 是两个口径）。
     我这一轮先把它写成 10×k（8px）⇒ 桌面活动切到**模板 B**（规则优先级更高）时 CTA 从 686.5 掉到 694.5，
     正是产品方报的"手机那 6px 抖动"的桌面翻版。⇒ 见上面 `.tpl-b .pm-cta { margin-top: 0 }`。
     以后再动底栏间距：**桌面和手机必须分开验**（`fitDesktop` 的内联 padding-top 只在桌面那档存在）。 */
  padding: 0 calc(18px * var(--pm-k));
  font-size: calc(16px * var(--pm-k));    /* ★ 2026-11-12：14.5 → 16（产品方："点击选购放大一点"，桌面实际 12.8） */
  font-weight: 800;
  line-height: 1;
  background: var(--mint-deep);
  color: #fff;
  border: none;
  border-radius: calc(12px * var(--pm-k));
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  box-shadow: 0 3px 10px rgba(18, 165, 148, .22);
  transition: filter .12s;
}
#promoModal .pm-cta span { overflow: hidden; text-overflow: ellipsis; }
#promoModal .pm-cta:hover:not([disabled]) { filter: brightness(1.06); }
#promoModal .pm-cta[disabled] { cursor: default; }
#promoModal .pm-cta.pm-cta-main { background: var(--mint-deep); box-shadow: 0 3px 10px rgba(18, 165, 148, .22); }
#promoModal .pm-cta.pm-cta-grant {
  background: linear-gradient(180deg, #ffd75e 0%, #f7c02a 55%, #eaa915 100%);
  color: #4a3200;
  box-shadow: 0 5px 16px rgba(230, 169, 44, .35), inset 0 1px 0 rgba(255, 255, 255, .55);
}
#promoModal .pm-cta.pm-cta-grant:hover:not([disabled]) { filter: brightness(1.04); }
#promoModal .pm-cta.is-ok { background: #0f9d76 !important; color: #fff !important; box-shadow: none !important; }
#promoModal .pm-cta.is-ok[disabled] { opacity: 1; }
#promoModal .pm-cta.is-fail { background: #c2410c !important; color: #fff !important; cursor: default; }

/* 说明行：恒占位（空着也占 18px），所以"有没有码"不影响高度
   ★ 2026-11-10（产品方：取消折中方案）：恢复成按钮下面**独立一行** —— 白底、四角圆角、
     4px 上边距、深色字（走 var(--text-3)，不再吃按钮的配色变量）。高度仍 18 × k。
   ★ 2026-11-12 四改：底栏改 `flex-start` 后，这行就是三行的**间距基准**（上 4 × k、下 2 × k），
     模板 B 已对齐到同一组数字 ⇒ 切活动 / 切模板三行位置零跳动。 */
#promoModal .pm-code {
  flex: none;                       /* ★ 2026-11-09.8：同样不许被 flex 压扁 */
  height: calc(18px * var(--pm-k));       /* ★ 11-10 桌面 14.4 */
  line-height: calc(18px * var(--pm-k));
  padding: 0 calc(10px * var(--pm-k));
  margin-top: calc(4px * var(--pm-k));
  border-radius: calc(8px * var(--pm-k));
  background: #fff;
  text-align: center;
  font-size: calc(13.5px * var(--pm-k));  /* ★ 2026-11-12：12 → 13.5（"结算输入放大一点"，桌面实际 10.8） */
  color: var(--text-3);
  letter-spacing: .3px;
  font-weight: 700;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* 有码的活动：整条说明行都可以点（复制），给个手型；失败态要划选，光标回到文字 */
#promoModal .pm-code[data-pa="coupon"] { cursor: pointer; }
#promoModal .pm-code.fail { cursor: text; }
/* ★ 2026-11-09.8（产品方："48 小时这张有效期放在抢购优惠和领取的旁边，例如抢购优惠码（结算输入 BF15 立减，
   有效期 24 小时这种）"）：
   说明行内部改成**一行两格**：`#pmCodeMain`（码 / 有效期短标签）+ `#pmCodeTime`（48 + 单位）。
   两格都 `nowrap`，外层 `.pm-code` 已经是 `overflow:hidden; text-overflow:ellipsis` ⇒ 长语言（泰语/德语）
   只会把尾巴截成「…」，**高度永远是 18px**，底栏三行位置一个像素都不动。
   ⇒ 这里每个节点都必须 `nowrap + min-width:0`（9 门语言的长度差全由 `.pm-code` 上的 ellipsis 吃）。 */
#promoModal .pm-code-row { display: inline-flex; align-items: baseline; gap: 0; max-width: 100%; min-width: 0; vertical-align: top;
  height: calc(18px * var(--pm-k));   /* ★ 11-10：写死行高 ⇒ 里面任何一格换行 / 字体回退（泰语缅语）都不会把说明条撑高 */
  line-height: calc(18px * var(--pm-k)); white-space: nowrap; }
#promoModal .pm-code-row > span:first-child { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#promoModal .pm-code-row > span:first-child:empty { display: none; }
/* ★ 2026-11-09.9：原来 gap 是 8px，主文案空着（礼金那条只有有效期）时 gap 照样占 8px ——
   行反而比文字长，窄语言下尾巴就被 `text-overflow:ellipsis` 截成「…」。
   改成 `gap:0` + **有主文案时**给主文案 8px 右边距：空文案 0 占位，有文案间隔不变。 */
#promoModal .pm-code-row > span:first-child:not(:empty) { margin-right: calc(8px * var(--pm-k)); }
/* 有效期尾巴：数字稍大 + 等宽数字（切换活动时尾巴宽度不抖）
   ★ 2026-11-09.8 第二版：`flex:none` → `flex:0 1 auto`。实测（窄档 360 / 手机 375）尾巴在
   **没有主文案**时会把行撑出 8px（gap）：`flex:none` 不许收缩 ⇒ 行 min-content = 尾巴 + gap，
   主文案空着也占着 8px，于是尾巴被挤成省略号。允许收缩后 min-content = 尾巴本身，整行放得下就不截。 */
#promoModal #pmCodeTime {
  flex: 0 1 auto;
  min-width: 0;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
#promoModal #pmCodeTime:empty { display: none; }
#promoModal #pmCodeTime .pm-ctnum { font-size: calc(13px * var(--pm-k)); font-weight: 800; color: inherit; margin: 0 1px; }
#promoModal #pmCodeTime .pm-ct { font-style: normal; }
/* 有尾巴时（礼金 / 优惠码）：主文案后用逗号隔开（产品方原话"结算输入 BF15 立减，有效期 24 小时这种"）
   ⇒ 渲染成「结算输入 BF15 立减，有效期 24 小时」；礼金那条主文案为空（`span:empty` 收掉）⇒ 不会有孤立逗号。
   ⚠ 字形用**西文逗号 + 半角空**，不用全角「，」：全角逗号在韩文/俄文行里字面太大，且那一格是 nowrap
     会被当成一整块参与宽度（窄档实测多占 10px，把尾巴挤成省略号）。 */
#promoModal .pm-code-v .pm-code-row > span:first-child:not(:empty)::after { content: ', '; margin-left: 1px; color: var(--text-3); }
#promoModal .pm-code.on { color: var(--mint-deep); }
#promoModal .pm-code .pm-codehl { color: #b8860b; }
#promoModal .pm-code.fail { color: #c2410c; -webkit-user-select: all; user-select: all; }

/* 冷却小字：单行截断（9 门语言总有长句，靠压缩文案治不彻底）
   ★ 2026-11-09.8（产品方："7 天内不再提示 / 本月不再提示 离按钮框近一点，现在只能看到一半"）：
     根因是这行 `height:40px` 写死、外加上下 `margin/padding-top:8px` —— 底栏总高 126 不够分，
     flex 子项被压到 25~29px，文字在**被压扁的盒子里居中被上下裁掉**（实测按钮底 811 → 冷却盒底 864 = 53px，
     底栏只剩 35，冷却 27 装不下 18px 的字）。
     新账（桌面 126）：8(上) + 53(按钮) + 5 + 14(说明) + 2 + 22(冷却) + 8(底) 余量 = **贴住按钮**，不再压；
     手机 128：8(上) + 49(按钮) + 4 + 14 + 2 + 30(冷却) + 6(底) 余量。 */
#promoModal .pm-cool {
  display: flex;
  flex: none;                       /* ★ 不许被 flex 压扁（40→27 就是这么来的） */
  gap: calc(14px * var(--pm-k));
  justify-content: center;
  align-items: center;
  margin-top: calc(2px * var(--pm-k));
  padding-top: calc(2px * var(--pm-k));
  height: calc(22px * var(--pm-k));       /* ★ 11-10 桌面 17.6 */
  border-top: 1px dashed var(--line);
}
#promoModal .pm-cool-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  padding: 0 calc(2px * var(--pm-k));
  font-size: calc(12.5px * var(--pm-k));  /* ★ 2026-11-12：11.5 → 12.5（"7 天 / 本月不再提示放大一点"，桌面实际 10） */
  line-height: calc(16px * var(--pm-k)); /* ★ 写死行高：不再"在压扁的盒子里居中" */
  color: var(--text-3);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 48%;
  height: auto;                     /* ★ 原来 height:100% 跟着被压扁的父亲走 */
  flex: none;
}
#promoModal .pm-cool-btn:hover { color: var(--mint-deep); }

/* ---------- 断点 ---------- */
/* ★ 2026-11-09：4:3 图（`.is-4x3`）在顶部的显式高度会盖掉手机媒体查询里的 `aspect-ratio`，
   ⇒ 只有宽 > 480 时用显式高度（min(滚动区高, 图盒 4:3 高)，见上面那条）；480 及以下回到 `aspect-ratio:4/3`。 */
@media (max-width: 480px) {
  #promoModal .pm-banner.is-4x3 { height: auto; }
  /* ★ 手机不用桌面的 `flex-basis: calc(外框 − 卡片条 − 底栏)`：手机外框 `height:auto`，
     flex-basis 是固定数 ⇒ 渐变区拿不到"内容高"，多出来的空间在渐变区里白出一大截、
     图片被挤出可见区（实测过）。改回 `flex:1 1 auto`：自动最小高 = 0，按内容排。 */
  /* ★ 2026-11-09.7：DOM 改成"图 → 卡片条 → 底栏"，滚动区在**最上面** ⇒ 手机上它分不到剩余空间，
     只能严格等于内容高（渐变区），否则多出来的高度会在图**下面**白出一大截、把卡片条往下推。
     ⇒ `flex: 0 0 auto`（flex-basis:auto = 内容高）。文字活动时渐变卡在 `.pm-body` 里、滚动区是空的，也不影响。 */
  #promoModal .pm-scroll { flex: 0 0 auto; flex-basis: auto; }
}
/* ★ 2026-11-09.5：断点里的 `width` **必须带 `!important`**。
   外框宽度从 11-09.5 起由 JS 的 `fitDesktop()` 写成**内联** `width:645px`（跟手机无关的那一档），
   内联样式天然压过不带 !important 的断点规则 ⇒ 375 屏也被撑成 460/480。
   手机（<481）那条同理。基础规则 `#promoModal .pm-modal{ width:645px }` 是给桌面的兜底。 */
@media (max-width: 1023px) {
  #promoModal .pm-modal {
    width: calc(100vw - 32px) !important;
    max-height: 80vh;
    height: 80vh;
  }
  /* ⚠ 这里**不能再留 `max-width:480px`**（11-09.7 删）：这一档的外框尺寸现在全由 fitDesktop 写内联 px
     （图活动 = 图宽，800 屏 448、1440 屏 800），留个 480 的 max-width 会把 1440 屏的图活动掐到 480。 */

}
/* ★ 2026-11-09.7（桌面一图为主）：**外框宽高由 fitDesktop() 写内联 px**（跟图高走），
   上面 1023 那条的 `width:calc(100vw−32px) !important` / `height:80vh` 会把它们压掉
   （实测 1024×768 外框被压成 649×691、800×600 压成 480×480，图高全错）。
   ⇒ 481～1023 的桌面档在这里把固定宽高"还原"给内联值：带 !important 取消上面那条的 !important，
     高度照旧跟视口走（80vh）；两边各留 16px。
   ⚠ 只在 481～1023 生效 —— 手机（≤480）的外框仍是 height:auto + 比例渐变区，不能被这条沾上。 */
@media (min-width: 481px) and (max-width: 1023px) {
  /* ★ 2026-11-09.8：`.pm-mode-hero` 现在常开（三类活动共用），这条"纯文字活动放行"永远匹配不到，
     留着会让读的人以为还有第三条路 ⇒ 删了。三类活动宽度一律由 fitDesktop 写内联 !important。 */
  #promoModal .pm-modal {
    /* ⚠ 图活动的宽度不在这里还原：只能靠**内联 !important** 压过去
       （fitDesktop 里写的是 `setProperty('width', …, 'important')`），这里写 auto 没用。 */
    height: auto !important;         /* = 内联 height（图高 + 78 + 126） */
    max-width: calc(100vw - 32px);
    max-height: 90vh;
  }
}
/* <481：手机（375 屏 → 343px），底部安全区 + 底栏矮一档（53+18+40 ≈ 136） */
@media (max-width: 480px) {
  /* ★ 2026-11-09.6：手机上的 `.is-4x3` 图 / 文字蒙层完全沿用基础规则（`.pm-hero` inset:0 铺满、
     `.has-hero .pm-head` 负边距贴齐 banner），桌面那条"左右内衬归零"在手机断点里不存在 */
  /* ★ 2026-11-08（产品方：做紧凑）：手机外框**按内容高度收缩**（原来写死 80vh）。
     写死 80vh 时，渐变区缩到 156 之后，多出来的 180px 会变成"正文区空白 + 底栏被顶到最底"，
     按钮根本不上移 —— 这就是"改了高度但没变紧凑"的根因。
     现在 `height:auto`：内容多高弹窗多高，底栏紧跟在卖点卡下面；
     `max-height` 仍兜底（内容超长时封顶，超出部分在正文区里滚）。 */
  #promoModal .pm-modal {
    /* ★ 2026-11-09.7：`max-width` 必须一起给。纯文字活动的外框在 481～1023 档被 JS 写成
       `max-width:calc(100vw−32px) !important`（内联 !important），手机上不盖回来 ⇒ 375 屏被撑到 343-32=311 一档的
       319 宽（实测）。图活动不受影响（这里 width 已 !important）。 */
    width: calc(100vw - 32px) !important;   /* ★ !important 压过 JS 写的内联 width，见上面 1023 断点的注释 */
    max-width: calc(100vw - 32px) !important;
    height: auto !important;                /* 同理：fitDesktop() 会写内联 height，手机不能被它定高 */
    max-height: 88vh;
    margin-bottom: env(safe-area-inset-bottom);
  }
  /* 正文区限高：手机内容过长时只滚这一块，别把底栏挤出屏幕 */
  /* ★ 2026-11-09：`.pm-body{max-height:34vh}` 已删（正文区不存在了）。
     这条曾把 414 屏的 4:3 渐变区从 307 压到 287，删掉后手机比例才真正准。 */
  /* ★ 2026-11-09（产品方拍板）：手机渐变区定 **4:3**。
      比例账本（都实测过）：2.2 → 3.2 → 2.8:1(375 屏 123px) → 4:3 → 3.1:1(111) → **回到 4:3**。
        产品方取舍：宁可比海报原图（3:1）多裁、也要图够大够醒目；`object-fit:cover` 居中裁。
      ✅ 4:3 现在**安全**：标题在手机上已 clamp 到 2 行、副标隐藏 ⇒ 375 屏净高
         (343 − 26)÷(4/3) − 28 ≈ 210px，眉标 12 + 标题 2×20 + 1 行余量 ≈ 70px，余量充足。
        (早先 4:3 出问题是因为当时标题允许 3~4 行、副标也显示，算下来要 220px+。)
     ⚠ 高度封顶 `max-height:320px` 远大于实际（375 屏 257 / 414 屏 309），只防宽屏；
        如果以后把它调小到接近比例高度，就会变成"比例被截、图又扁回去"。
     ⚠ 别再给它 `30vh` 之类的固定高度：会和 aspect-ratio 打架（谁小谁赢），比例就不准了。
     ⚠ 标题行数 / 副标显隐的规则见下方 `.pm-title`、`.pm-sub` 那几条，改比例前先复核净高。 */
  /* ★ 2026-11-10.4（背景图 + 文字压图上）：手机图区回到 **4/3**（11-10.3 的 220px 是"字卡在下面"时
     为给白卡腾地方才定死的高度，现在文字压在图上不占版式高 ⇒ 恢复整图比例）。
     375 屏 343 宽 ⇒ 图区 257；外框 = 257 + 卡片条 78 + 底栏 118 ≈ 453，远低于 88vh。
     `!important` 照旧压掉桌面 fitDesktop() 写的内联 height。 */
  /* ★ 2026-11-12（合格版 · 手机档）：`.pm-banner` **不再定 4:3**。
     现在 banner = 图段（`.pm-hero`，自带 4:3、见基础规则 `height: var(--pm-img-h)`）+ 白字段（`.pm-head`）
     ⇒ 拿 `aspect-ratio:4/3` 套整个 banner 会把「图 + 白字段」压进一个 4:3 盒子，白字段把图挤矮。
     手机也不写内联 `--pm-img-h`（fitDesktop ≤480 直接 return）⇒ 图段按兜底 `--pm-modal-w × 3/4` 算
     （358 → 268），白字段按内容撑。`max-height` 跟着一起删：它会截掉白字段下缘的文字。 */
  #promoModal .pm-banner {
    height: auto !important;        /* ★ 内联 height 是桌面 fitDesktop() 写的，手机必须盖掉 */
    flex: none;                     /* ← 关键：不加就会被别的 flex 子项压扁 */
    aspect-ratio: auto;
    max-height: none;
    padding: 0;                     /* 图**顶满**，四周不留内衬 */
  }
  /* 图段 4:3：框宽 358 ⇒ 268。⚠ 用比例，别写死 px（不同机型框宽不同） */
  #promoModal .pm-scroll > .pm-banner > .pm-hero {
    aspect-ratio: 4 / 3;
    height: auto;
    min-height: 0;
  }
  /* 无图 / 图挂了（品牌底）：手机上整块一个渐变，白字居中；倒计时贴块顶 */
  #promoModal .pm-banner.pm-brand {
    aspect-ratio: auto;
  }
  #promoModal .pm-banner.pm-brand > .pm-head {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    height: auto;
    min-height: 132px;
    padding: 26px 18px 20px;
    text-align: center;
  }
  #promoModal .pm-banner.pm-brand > .pm-head .pm-title,
  #promoModal .pm-banner.pm-brand > .pm-head .pm-sub,
  #promoModal .pm-banner.pm-brand > .pm-head .pm-eyebrow {
    text-align: center;
    width: 100%;
  }
  #promoModal .pm-banner.pm-brand > .pm-head .pm-title { color: #fff; }
  #promoModal .pm-banner.pm-brand > .pm-head .pm-sub { display: block; color: rgba(255, 255, 255, .92); }
  #promoModal .pm-banner.pm-brand > .pm-head .pm-eyebrow { color: #a8f0e2; }
  #promoModal .pm-banner.pm-brand > .pm-head .pm-count { top: 0; }
  /* ★ 2026-11-09：4:3 图在手机上同样是 `contain` ⇒ 渐变区必须是**整图比例**（否则左右出空条）。
     ⚠ 别在这里写 `max-height`：实测会改掉 4:3（375 屏 343/4×3 = 257 → 量到 251），
       375 屏本来就是 257 不算高，封顶只防超宽屏，由 480px 以上媒体查询负责。 */
  /* 渐变区高度与"标题最多两行"这套规则的来历（4:3 下 375 屏净高约 210px，是最宽松的一档）：
       · 标题**最多两行**（`-webkit-line-clamp:2`，全局规则里已有 `display:-webkit-box`，
         不要在这里再写一遍 `display` —— 会把盒类型改掉、在 flex 里塌成 0 高）
         ⚠ 也**不要**给标题加 `max-height`：`.pm-head` 是 `flex:1 + min-height:0`，
           `max-height` 会参与 flex 最小尺寸计算，把标题压成 0px（实测高度 0）。
       · 副标**隐藏**（这块本来就矮；大屏仍有）
       · 眉标（小标签）保留一行，字号收一档
       ⚠ 这两条 `font-size` 必须放在**本媒体查询的最后**：后面原来还有一条
         `#promoModal .pm-title{font-size:20px}` 会把上面所有小字号覆盖掉（同权重、后者胜）。 */
  /* ⚠ 渐变区偏矮时，`.pm-head`（flex:1 + min-height:0）会被压到低于内容高度，
     里面的标题跟着被挤成 **0 高**（实测 titleH=0，文字看不见）。
     解法：`.pm-head` 改成按内容高度 + 溢出裁掉（`overflow:hidden` 由渐变区本身兜着），
     并给标题一个 `flex:none`，不让它参与收缩。 */
  /* ★ 2026-11-10.4（文字压图 · 手机档）：图区 343×257，文字贴下缘、按内容高排；
     内衬收一档（左右 18/56 → 16/52，和关闭钮 40 对齐），压暗带跟着内衬负边距一起收。 */
  /* ★ 2026-11-11（V1 骨架 · 手机档）：白字段**接在图下面**、按内容高排（不再贴底压在图上）。
     内衬 = 22 × 框宽 358 / 900 ≈ 8.7px，跟 V1 的左右内衬同比例。 */
  #promoModal .pm-banner > .pm-head {
    display: block;
    justify-content: normal;
    flex: none;
    min-height: 0;
    overflow: hidden;
    margin-left: 0;
    margin-right: 0;
    padding: 14px 9px 12px;
    background: transparent;   /* ★ 2026-11-12：原来是 #fff —— 有图那档的文字层已改成 radial 暗底（见下），这层白底留着会盖图 */
  }
  /* ★ 2026-11-12（标题压图 · 手机档）：`.pm-head` 改成盖在图段上的居中文字层。
     图段高 = 框宽 × 3/4（358 → 268 / 328 → 246），字块 ≈ 眉标 13 + 标题 24..72 + 副标 36 + 间距 ≈ 130
     ⇒ 3 行标题也放得下，不会被裁。
     ⚠ 必须排在**上面那组**手机白字段规则**后面**（权重更高一条 + 位置更后），否则 display/background/padding 被盖回去。
     ⚠ 这里有 `aspect-ratio`（兼容老机型）⇒ 不给 `bottom:0`，纵向居中交给 flex。 */
  #promoModal .pm-banner.has-hero > .pm-head {
    position: absolute;
    top: 0;
    left: 0;
    z-index: 2;
    width: 100%;
    height: auto;
    aspect-ratio: 4 / 3;
    min-height: 0;
    margin: 0;
    /* ⚠ 上/下内衬必须**对称**：flex 居中算的是"内衬框"的中心，上 26 / 下 12 ⇒ 字块整体下移 7px
       （2026-11-12 实测 390 宽偏移 +7px）。倒计时条是 absolute、不吃内衬，不用给它让位。 */
    padding: 12px 14px 12px;
    background:
      radial-gradient(82% 68% at 50% 52%, rgba(4, 14, 26, .52) 0%, rgba(4, 14, 26, .26) 58%, rgba(4, 14, 26, 0) 100%);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 4px;
    text-align: center;
  }
  /* ★ 2026-11-12（标题做大 · 手机档，和电脑同步放大）：图 358×269，短句 1 行时字块 ≈ 15 + 36 + 1×34 + 间距 ≈ 95
     ⇒ 占图高 35%；最长 3 行标题 + 2 行副标 ≈ 175（65%），仍不顶穿图区。
     ⚠ 行高**别低于 1.25**：26px × 1.2 = 31.2 会被取整成 31，`-webkit-box` 的 1 行盒高就比内容矮 2px，
       体检判"标题被截断"（实测 sh=33 ch=31，字其实都看得见）。1.26 → 32.76 取整 33，盒子富余。 */
  #promoModal .pm-banner.has-hero > .pm-head .pm-eyebrow { color: #ffe7ad; font-size: 15px; letter-spacing: 2px; }
  #promoModal .pm-banner.has-hero > .pm-head .pm-title {
    color: #fff;
    font-size: 34px;
    line-height: 1.26;
    margin-top: 4px;
    letter-spacing: 0;
    word-break: normal;
    overflow-wrap: anywhere;
    text-shadow: 0 2px 10px rgba(0, 0, 0, .55), 0 1px 2px rgba(0, 0, 0, .45);
  }
  #promoModal .pm-banner.has-hero > .pm-head .pm-sub {
    display: -webkit-box;
    color: rgba(255, 255, 255, .94);
    font-size: 16px;
    line-height: 1.45;
    margin-top: 6px;
    max-height: none;        /* 桌面那条 2.9em 上限是按 32u 定的；手机 16px 的 clamp:2 本来就够 */
    text-shadow: 0 1px 8px rgba(0, 0, 0, .55);
  }
  #promoModal .pm-banner.has-hero > .pm-head .pm-codehl { color: #ffe1a0; }
  /* ★ 2026-11-13：「AI 排版」字号档位 · 手机档（基础值 15 / 34 / 16，见上三行）。
     ⚠ 必须写在上面基础值**后面**（同权重后写的赢），而且手机断点在文件更后面 ⇒ 天然压过桌面那组。
     ⚠ 行高 1.26 / 1.45 是无单位倍数 ⇒ 跟着字号缩，不用另写。 */
  #promoModal .pm-modal[data-pm-tier="90"] .pm-banner.has-hero > .pm-head .pm-eyebrow { font-size: 13.5px; letter-spacing: 1.8px; }
  #promoModal .pm-modal[data-pm-tier="90"] .pm-banner.has-hero > .pm-head .pm-title { font-size: 30.6px; }
  #promoModal .pm-modal[data-pm-tier="90"] .pm-banner.has-hero > .pm-head .pm-sub { font-size: 14.4px; }
  #promoModal .pm-modal[data-pm-tier="80"] .pm-banner.has-hero > .pm-head .pm-eyebrow { font-size: 12px; letter-spacing: 1.6px; }
  #promoModal .pm-modal[data-pm-tier="80"] .pm-banner.has-hero > .pm-head .pm-title { font-size: 27.2px; }
  #promoModal .pm-modal[data-pm-tier="80"] .pm-banner.has-hero > .pm-head .pm-sub { font-size: 12.8px; }
  #promoModal .pm-modal[data-pm-tier="70"] .pm-banner.has-hero > .pm-head .pm-eyebrow { font-size: 10.5px; letter-spacing: 1.4px; }
  #promoModal .pm-modal[data-pm-tier="70"] .pm-banner.has-hero > .pm-head .pm-title { font-size: 23.8px; }
  #promoModal .pm-modal[data-pm-tier="70"] .pm-banner.has-hero > .pm-head .pm-sub { font-size: 11.2px; }
  /* 倒计时条：head 盖在图上 ⇒ `-1 × --pm-hero-h` 的旧偏移作废，贴 head 顶沿、右对齐并让开关闭钮 */
  #promoModal .pm-banner.has-hero > .pm-head .pm-count { top: 0; padding-right: 52px; }
  /* 白字段里的深色字：手机档桌面那条 `--pm-u` 没写（fitDesktop ≤480 直接 return）⇒ 字号全落兜底
     —— 兜底就是这里写死的值，别再让它继承页面上其他地方的 `--pm-u`。 */
  #promoModal .pm-banner > .pm-head .pm-eyebrow { color: #1b5e7a; }
  #promoModal .pm-banner > .pm-head .pm-title { color: #0d1b2e; }
  #promoModal .pm-banner > .pm-head .pm-sub { color: #4a5b70; }
  /* 标题：20px、最多 **3 行**（V1 比例 32 × 358/900 ≈ 12.7，手机档按"可读优先"抬到 20）。
     ⚠ `check-promo-copy.js` 的"长标题"用例 34 个汉字 ⇒ 358 宽下 3 行要放得下（20px × 3 行 = 60px）。 */
  #promoModal .pm-banner > .pm-head .pm-title {
    font-size: 20px; line-height: 1.24;
    -webkit-line-clamp: 3;
    margin-top: 4px;
    flex: none;
  }
  #promoModal .pm-banner > .pm-head .pm-sub {
    display: -webkit-box; -webkit-box-orient: vertical; overflow: hidden;
    -webkit-line-clamp: 3; font-size: 12.5px; line-height: 1.4; margin-top: 5px;
  }
  #promoModal .pm-banner > .pm-head .pm-eyebrow { font-size: 11px; line-height: 1.2; letter-spacing: 1.8px; }
  /* 品牌底（无图）手机上换行更松：白字、块高按内容撑，标题 3 行 + 副标 3 行也不会溢出（没有固定高） */
  /* ⚠ 必须带 `.pm-brand`：原来这两条没带，会把**有图**那档的白字标题压成 21px / 眉标压成薄荷绿
     （2026-11-12 改成"标题压图"之后才暴露出来——之前有图档标题在下面的白底上是深色字，写不写都看不出来）。 */
  #promoModal .pm-banner.pm-brand > .pm-head .pm-title { color: #fff; font-size: 21px; }
  #promoModal .pm-banner.pm-brand > .pm-head .pm-eyebrow { color: #a8f0e2; }
  /* ★ 2026-11-12：金条 / 米色条**只认活动自己配的文案**（JS 里没有兜底文案了）⇒ 手机按内容撑，别定高 */
  #promoModal .pm-banner > .pm-gold { height: auto; min-height: 34px; font-size: 15px; padding: 6px 12px; }
  #promoModal .pm-banner > .pm-cream { height: auto; min-height: 26px; font-size: 11.5px; padding: 5px 12px; }
  /* 金条 / 米色条：手机不给 JS 算高（JS 在 ≤480 直接 return），按内容撑 */
  #promoModal .pm-banner.has-hero > .pm-head { background: transparent; }   /* ★ 同上：白底会把图盖成白块 */
  /* ★ 2026-11-09：手机卖点卡（72px 行高那几条）与正文区内边距一并删除（前后台删了卖点卡）。
     ★ 2026-11-09（产品方：布局同步做紧凑）：底栏 **118**（原来 150 → 删卖点卡时一起压）。
     账：53(CTA) + 7+18(说明) + 4+1+29(冷却) + 6(底留白) = 118 —— 刚好，实测零余量、冷却不裁。
     ⚠ 冷却是 `display:flex` 的居中容器，高度只管留白，字不会被裁；
       再往下压就要动 CTA 的 53px 了，别动（按钮热区是硬指标）。 */
  /* ★ 2026-11-09（产品方：手机上"领礼金"也要和图隔开、间距和图的上边距对齐）：
     上内边距 0 → **10**，与桌面、与 `.pm-tabs` 的 10px 一致；高度 118 → **128** 把 10px 补回去，
     CTA(53)/说明/冷却三行位置一动不动（冷却是 `height:29` 写死，压到 118 以下会被裁）。 */
  /* ★ 2026-11-09.8（产品方："手机版 48 小时那些和电脑端一样改，字体可以改小一点"）：
     底栏仍 128，但账变了：8(上) + **49**(按钮，原 53) + 4 + 16(说明) + 2 + 29(冷却) = 108，余 20 ⇒ 不再压扁冷却。
     说明行 18 → 16（字 11.5 → 11），有效期尾巴数字 12（桌面 13）。 */
  /* ★ 2026-11-10（产品方："手机版弹窗，把立即领取的那个框整体向上收缩，这部分排版紧凑一点"）：
     底栏 128 → **118**，上内边距 8 → **6**，说明行离按钮 4 → **3**：
     账：6(上) + 48(按钮) + 3 + 16(说明) + 2 + 27(冷却) + 4(底) = 106，余 12 ⇒ 冷却仍不被裁。
     ⇒ 按钮顶端比原来上移 **2px**、整条底栏矮 10px（图高不变，外框跟着矮 10）。
     ⚠ 按钮 49 → 48 仍 ≥48（触控硬指标），别再压；冷却 29 → 27 是 `height` 写死，低于 26 就会裁字。
     ⚠ 桌面不受影响：桌面底栏由 fitDesktop() 写内联 101 × 0.8 口径，与这里无关。 */
  /* ★ 2026-11-12 三改（产品方："手机版的领取优惠往上移动点，缩小点"）：
     按钮 48 → **42**、字号 14 → **13**。
     ★ 2026-11-12 四改（坑③消除）：底栏不再靠 `justify-content:flex-end` 收口（那样余量全堆在按钮头顶，
       缩按钮反而让它往下走）—— 改成基础规则里的 `flex-start` + 三行**显式间距**：
         上内衬 **0** · 按钮 · 说明行 mt 3 · 冷却 mt 2 · 底部余量全落下。
       账：6 + 42 + 3 + 16 + (2+2+27) = 98 ⇒ 底栏 118 里剩 20，全在冷却行下面。
       ⇒ 按钮顶 553（← 原 547，**上移 6**）、说明行 601、冷却 619 —— 位置全由这几个数字定，跟余量无关。 */
  #promoModal .pm-foot { height: 118px; padding: 0 16px 0; }
  #promoModal .pm-cta { height: 42px; font-size: 13px; }
  /* 五改：模板 B 的底栏三行和模板 A **完全同一条**（按钮上距 6 / 说明行 mt 3 / 冷却 mt 2）——
     基础规则里的 tpl-b 分支在桌面上是 10×k 和 4×k，手机不乘 0.8 就会变成 10 / 4 ⇒ 必须在这里钉平。 */
  #promoModal .pm-modal.tpl-b .pm-cta { margin-top: 6px; }
  #promoModal .pm-modal.tpl-b .pm-code { margin-top: 3px; }
  /* ★ 2026-11-10（产品方："手机端的 X 也放到框内"）：和桌面同一口径 —— 上/右 8px、40×40 正圆，
     **压在图的右上角**（首屏就在，不用滚）。配色由外框的 .on-light/.on-dark 决定，见 `.pm-close` 那节。 */
  #promoModal .pm-close {
    top: 8px !important;
    right: 8px !important;
    width: 40px !important;
    height: 40px !important;
    min-width: 40px !important;
    min-height: 40px !important;
    font-size: 16px !important;
  }
  #promoModal .pm-cool { margin-top: 2px; padding-top: 2px; height: 27px; }
  #promoModal .pm-cta { height: 42px; font-size: 13px; padding: 0 14px; margin-top: 6px; }
  /* 四改：三行位置由显式间距定（底栏 flex-start）—— 按钮上 6、说明行 3、冷却 2、余量全在底部 */
  #promoModal .pm-cool { margin-bottom: 0; }
  #promoModal .pm-code { margin-top: 3px; padding: 0 8px; font-size: 11px; height: 16px; line-height: 16px; border-radius: 8px; }
  /* ★ 2026-11-12 三改：手机冷却行两枚按钮各缩一丢丢（11 → 10.5），行盒 16 / 行高 27 都不动 */
  #promoModal .pm-cool-btn { font-size: 10.5px; }
  #promoModal .pm-code-row { height: 16px; line-height: 16px; }
  #promoModal #pmCodeTime .pm-ctnum { font-size: 12px; }
  /* ★ 2026-11-09（紧凑）：卡片条上下各收 2px（9→7），整屏少 4px
     ★ 2026-11-09.8（产品方："折扣购物金这些向左移动一些，让三个框对齐居中，抢购框和 7 天提示那些都往上移"）：
       根因一：`.pm-thumb` 有 2px 边框 ⇒ 选中态外圈再多 2px 白边/青绿环，卡片**布局盒**改不了，
               但一排 3 张的起点必须和内容（图 / 按钮 / 说明行）的左边缘对齐 ⇒ 内外边距都收 2px 抵消边框。
       根因二：卡片条 78px 是按卡高 58 + 上下 10 算死的，而手机卡高 56 ⇒ 上下各多 3px 死白；
               新卡高 52 + 上下 6 = 64 ⇒ 卡片条整体**上移 14px**（图少 14、底栏抬高 14），卡片条仍写死不变。 */
  /* ★ 2026-11-12 二改：手机跟着一起收，内衬统一 5px（上下对称，下面那条压在卡片的 1px 下边框上） */
  #promoModal .pm-tabs { gap: 7px; padding: 5px 8px; }
  /* ★ 2026-11-12 四改：卡片条高度**不再在这里写 !important** —— 全站由 JS 的 TABS_H_TABLE 写 `--pm-tabs-h`
     （手机 63 / 极窄 61），CSS 基础规则 `height: var(--pm-tabs-h, …)` 直接读。历史坑见那一节的注释。 */

  #promoModal .pm-thumb { height: 52px; }
  /* ★ 2026-11-08（产品方问题一）：**手机保留「一排 3 张」的排版** —— 三张按等分铺满，
     第 4 条起超出可视区 ⇒ 由 .pm-tabs 横滑（右侧红色角标提示还有几条）。
     ⚠ 原来 `flex:1 1 0` 是"挤到刚好放下"，卡片多于 3 条时会被压扁变形；
       改成定宽的 `calc((100% - 2*gap)/3)`：正好 3 张铺满，超出就溢出去滑。 */
  #promoModal .pm-thumb {
    height: 52px; padding: 3px 4px 3px;
  }
  /* 手机可视区只露出 3 张：单卡宽见上一行（11-09.8 重算过，含卡片自己的边框）。 */
  /* ★ 2026-11-09.9（产品方："折扣购物金这些向左移动一些，让三个框对齐居中"）：
     一张卡占位 = basis(146) + 2×4 = 154（`box-sizing` 不影响 flex-basis），3 张 + 2×7(gap) = 476 > 326
     ⇒ **第 4 条起横滑**（产品方原口径：手机一排 3 张、多的滑出去，右侧红角标提示）。
     第 1~3 张的图标/文字左缘固定落在 外框 + 11(选中) / +13(未选中)，与主按钮左缘 32 基本同一条线，
     不随"横向滑到第几条"变化。 */
  #promoModal .pm-tabs > .pm-thumb { flex: 0 0 calc((100% - 35px) / 3); padding-left: 2px; padding-right: 2px; }
  /* 手机上按卡宽反算内衬 2px：卡片内容 = 104 − 4(内衬) − 4(边框) = 96，「购物金」3 字（11px×3 ≈ 34）
     和 18px 图标都放得下，不会截断。 */
  @media (max-width: 360px) {
    #promoModal .pm-tabs > .pm-thumb { padding-left: 0; padding-right: 0; }
  }
  #promoModal .pm-thumb i { font-size: 18px; }
  #promoModal .pm-thumb span { font-size: 11px; }
  /* 手机红角标小一档，别压住第三张卡的文字 */
  #promoModal .pm-more { min-width: 24px; height: 24px; border-radius: 12px; font-size: 11px; right: 4px; }
}
/* <340：极窄 */
@media (max-width: 339px) {
  /* ★ 2026-11-09.5：`!important` 同 1023 断点（否则被 1023 断点的 !important 兜住，280 屏变成 288） */
  #promoModal .pm-modal { width: calc(100vw - 24px) !important; max-height: 80vh; height: 80vh !important; }
  #promoModal .pm-title { font-size: 18px; }
  #promoModal .pm-banner:not(.has-hero) .pm-title { font-size: 20px; }
  /* 卖点卡已删（2026-11-09），极窄屏不需要额外规则 */
  /* ★ 2026-11-12 四改：卡片条高度（61）也由 JS 的 TABS_H_TABLE 写进 `--pm-tabs-h`，这里不再写 !important */
  #promoModal .pm-thumb { height: 50px; }
  #promoModal .pm-thumb i { font-size: 16px; }
  #promoModal .pm-thumb span { font-size: 10px; }
}

/* ============================================================
   ★ 2026-11-12（产品方问题二 · 后台「实时预览」）
   后台用 iframe 嵌**前台真页面**当预览（`多肽购物.html?pmpreview=1`），
   promo-modal.js 给 `<html>` 加 `.pm-preview-root`、给 `<body>` 加 `.pm-preview-host` ⇒ 这里把"整页"改成"只有一个弹窗"：
     · 遮罩铺满 iframe、padding 和真站一致（30 / 12）、**去掉半透明蒙层**（预览要看的是弹窗本身，不是被压暗的框）
     · 定位从 fixed 改 absolute（fixed 在 iframe 里相对的是 iframe 视口，行为一样，但换这种写法
       在 scale/transform 容器里更稳）
     · 页面自身内容全部隐藏 ⇒ iframe 高度 = 弹窗高度，后台只管等比缩放它
   ⚠ 这些规则**只在 .pm-preview-host 下生效**，前台一个像素都不受影响。
   ⚠ 2026-11-12：类名挂在 `<body>` 上（`body.pm-preview-host` 选择器），<html> 上是 `.pm-preview-root`（只管锁滚动）。 */
body.pm-preview-host { margin: 0; padding: 0; background: transparent; }
body.pm-preview-host > *:not(#promoModal):not(script):not(style) { display: none !important; }
body.pm-preview-host #promoModal {
  position: absolute; inset: 0;
  align-items: center; justify-content: center;
  padding: 30px;                 /* 和真站 .modal-mask 的 padding 一致（窄屏断点是 12px）*/
  background: #eef4f7;           /* 预览底色：让白色弹窗有边界，但不压暗内容（真站那层是 50% 蒙层）*/
  overflow: visible;             /* 弹窗比 iframe 高时也不能出滚动条（后台按弹窗实高来定 iframe 高度）*/
  z-index: 1;
}
/* ⚠ 必须写 `#promoModal.show`：真站 `.modal-mask.show{display:flex}`= (0,2,0)，会压过上面的
   `body.pm-preview-host #promoModal`=(0,1,1)+标签 1 ⇒ 预览里**从没显示成 flex**，
   `align-items:center` 失效、弹窗永远贴顶。这里补一条 (0,3,1)。
   不要给上面的基础规则加 display:flex —— 那会让「没 show 的预览」也把弹窗显示出来。 */
body.pm-preview-host #promoModal.show { display: flex; }
/* 预览里弹窗**永远居中**、不贴顶：`.modal-mask` 原来的 `align-items:safe center` 在内容超高时会贴顶 */
body.pm-preview-host #promoModal .pm-modal { margin: 0 auto; }
/* 预览里那把 X 不参与排版（后台自己有关闭按钮），但仍留着 —— 结构不动，调试时和真弹窗一致 */
body.pm-preview-host #pmClose { pointer-events: none; }
