/* ─────────────────────────────────────────────────────────────
   learn.css —— 学习页样式（07-23 方案二·从 learn.html <style> 原样抽出）
   两页共用：learn.html（独立直链薄壳）+ words.html（同文档内嵌 mount）。
   1:1 照搬 miniprogram/pages/learn/learn.wxss，换算 1rem = 100rpx。
   与小程序的差异只有一处（是实现路径不同，不是视觉走样）：
   例句/场景点读改成【静态 mp3】：小程序走 tts 云函数实时合成（在旧环境，
   与本环境物理隔离调不到），H5 直接播 scripts/edge_audio 早就批量生成好的
   同一批 edge-tts 音频（英 Ryan / 中 Xiaoxiao·与视频/小程序同一把嗓子），
   视觉仍是小程序那套 speaker-wave 四根金色声波条。
   播放序列与小程序一致：英文 2 遍 → 中文 1 遍（07-22 老郑真机审④）。

   ⚠️ 07-22 老郑真机审 6 点打磨（改学习页前先读 docs/h5-learn-polish-handoff.md）：
   ① 进词自动播视频·首次连播 3 遍（微信 WeixinJSBridgeReady 兜底·被拦降级大播放钮）
   ② 单词卡不显示音标（视频里已有·且格式可能不一致）
   ③ 声波图标默认不显示·点句子播放时才显·播完隐藏
   ④ 点句子 = 英 2 遍 + 中 1 遍（中文缺失静默降级只播英文）
   ⑤ 点单词卡 → 视频重播 1 遍
   ⑥ 全局播放互斥：视频与任何一句音频永远只响一个

   🔴 SPA 唯一改动：原「body { padding-bottom: … }」是全局规则，本文件现在也会被
   words.html 引入——不收窄的话词表页的 tabbar 让位（app.css 的 body padding）会被
   永久覆盖。收窄成 body.lv-page（学习视图 mount 时 JS 加、unmount 摘），
   两页各自的底部让位互不打架。其余选择器全是 learn 专属类名，与 words.html
   现有 class（pay-tip、cat-、word-row、wr-、locked-more…）零撞名，无需加前缀。
   ⚠️ 这行原来写成 `cat-*` 加斜杠的形式，里头那个「星号+斜杠」把本条注释**提前关掉**了，
      于是紧跟其后的 `body.lv-page{…}` 规则被解析器当成错误一起吃掉——从 07-23 起就没生效过
      （08-16 老郑说底部按钮条「没一直吸底」时查出来的）。CSS 注释里永远别出现这两个字符相连。
   ───────────────────────────────────────────────────────────── */

/* 覆盖 app.css 给 tabbar 的底部让位（learn 视图没有 tabbar）。
   🔴 08-16 从 `1.8rem + safe` 改成只留 safe：那 1.8rem 是**给早期 fixed 按钮条留的位**，
      08-07 按钮条改成 sticky（在文档流里）之后就变成了页尾的一段死白 ——
      而且 `position:sticky;bottom:0` 只在「容器下面还有空间」时吸底，
      这段死白恰好让按钮条在滑到底时提前脱开、悬在半空（老郑 08-16 iPhone 上看到的
      「没一直吸底」就是它）。sticky 自己占流内空间，不需要再额外让位。 */
body.lv-page { padding-bottom: env(safe-area-inset-bottom, 0px); }

/* ── 导航（◀ 返回列表 + 进度）── */
.nav {
  display: flex; align-items: center; justify-content: space-between;
  padding: .28rem .4rem; background: #1B2A4A;
  position: sticky; top: 0; z-index: 100;
}
.nav-back {
  font-size: .3rem; color: #F5F0E8; letter-spacing: .01rem;
  text-decoration: none; background: none; padding: 0;
}
.nav-progress {
  font-family: var(--serif);
  font-size: .3rem; color: rgba(245,240,232,.7); letter-spacing: .02rem;
}
.nav-placeholder { width: 1.2rem; }
/* 10-18 连播按钮：占原占位的宽度（进度数字仍居中）。连播中变金底「■ 停止」，一眼看得出在连播。 */
.nav-cont {
  width: 1.2rem; height: .56rem; padding: 0; border-radius: .28rem;
  background: rgba(212,175,55,.16); border: .02rem solid rgba(212,175,55,.6);
  color: #D4AF37; font-size: .26rem; letter-spacing: .01rem; white-space: nowrap;
  -webkit-tap-highlight-color: transparent;
}
.nav-cont.on { background: #D4AF37; color: #1B2A4A; font-weight: 600; }

/* ── 视频区（固定高度防切换抖动）── */
.video-area { width: 100%; height: 4.2rem; background: #000; position: relative; }
.video-player { width: 100%; height: 4.2rem; display: block; background: #000; object-fit: contain; }
/* 07-23 老郑定稿：video 只在「无 src 空窗」藏着——内核的破媒体图标只在没地址时出现；
   地址一挂上（playUrl 里 showVideo）就显形，加载转圈用内核【原生】的，不自绘。 */
/* 🔵 09-02：封面**不用 <video> 的 poster 属性**，改成自己盖一张普通图。
   原因：老郑测未购用的是 iPhone（已购是华为安卓，两边根本不是同一条播放路径 ——
   iPhone 只能用系统自带 HLS 播放器）。poster 属性在 WKWebView 内联播放下会牵动
   系统自己那套封面/画面层逻辑，正是 07-27 记过的「只出声不出画」的高发区。
   自绘的图就是页面上一张普通 img，两个平台行为一致，什么时候收也完全由我们说了算。
   🔴 放在 video 之后、水印和播放钮之前 —— 同一层叠上下文里按 DOM 顺序，
      它盖住视频，而水印和播放钮盖住它。 */
.video-cover {
  position: absolute; left: 0; top: 0; width: 100%; height: 4.2rem;
  object-fit: contain; z-index: 1; pointer-events: none;
}
/* 🔴 09-14 13:51 面板录屏（pizza→plate·只按下没点击·没换地址）定案：封面从收起变显现的**第一帧就是它自己的黑底**，
      图要下一帧才上 ⇒ 黑底去掉：第一帧露的是底下还在播的旧视频，肉眼无感。只有纯黑兜底（.blank）才要黑底。 */
.video-cover.blank { background: #000; }
.video-cover[hidden] { display: none; }
/* 🔵 09-14：收起 = 透明，元素留在布局里（老郑 iPhone「慢切闪、快切不闪」：display:none→显示 + 换图
   会露一帧黑底）。没有过渡：文档 hidden 时过渡不推进，会卡在半透明。显隐由 learn-core mountCover/hideCover 管。
   🔴 09-14 10:21 录屏（停 ≥2 秒再切必黑 1–3 帧，停 ≤1.7 秒零黑；黑帧里连视频自带的角标都没有，
      只剩 HTML 水印 ⇒ 黑的是封面自己的黑底，图晚 1–3 帧才上）定案：
      opacity:0 的层内核**不画**，收着超过约 1 秒它的画面缓冲就被回收；再显现时要重建层、重解码、重上传，
      正好差这 1–3 帧。所以收起 = .01 而不是 0（1% 叠在视频上肉眼看不出），层一直画着、一直在；
      will-change 让它始终是独立合成层 —— 显现只是改一个合成参数，不重画、不解码、不建层。 */
.video-cover { will-change: opacity; }
.video-cover.off { opacity: .01; }
.video-player { visibility: hidden; }
.video-player.vp-ready { visibility: visible; }
/* 大播放钮（样式呼应首页试看卡 .trial-play·中老年加大）
   07-22①：自动播成功时它一直不显；只有【自动播被拦 / 视频暂停或播完】才露出来。 */
.video-play-btn {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%);
  width: 1.28rem; height: 1.28rem; border-radius: 50%; padding: 0;
  background: rgba(0,0,0,.42); border: .02rem solid rgba(255,255,255,.85);
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 .02rem .12rem rgba(0,0,0,.35);
  -webkit-tap-highlight-color: transparent;
}
.video-play-btn[hidden] { display: none; }
.video-play-btn:active { background: rgba(0,0,0,.6); }
/* ▶ CSS 边框三角（同首页：字符渲染随字体飘·此法几何精确）+ margin-left 补偿左重心 */
.video-play-tri {
  width: 0; height: 0; border-style: solid;
  border-width: .2rem 0 .2rem .32rem;
  border-color: transparent transparent transparent #fff;
  margin-left: .08rem;
}

/* ── 盗录溯源水印（阶段③B-①·纯前端）──
   视频区飘一行「学号 + openid 后 6 位」：录屏转发出去能追到是哪个账号，威慑为主。
   不遮挡观看：小字 + 低透明度 + 60 秒一圈的慢飘，且始终贴着画面边缘走。
   🔴 pointer-events:none —— 绝不吃点击，原生控件 / 大播放钮照常可点。
   z-index:1 低于后面的 .video-play-btn，不会盖住播放钮。
   （.video-hint 已于 08-22 删除：老郑要求视频报错提示一律不给用户看。）
   ⚠️ 只在页内播放时可见；用户若切到系统全屏播放器，覆盖层不随之进入（H5 的天花板）。 */
.video-mark {
  position: absolute; left: 0; top: 0; right: 0; bottom: 0;
  overflow: hidden; pointer-events: none; z-index: 1;
}
.video-mark[hidden] { display: none; }
.video-mark-text {
  position: absolute; left: 0; top: 0;
  font-family: var(--serif); font-size: .24rem; letter-spacing: .03rem;
  white-space: nowrap; color: rgba(255,255,255,.16);
  /* 07-23 老郑真机反馈：深色描边发糊发黑影响观看 → 去描边、再降透明度（溯源威慑够用即可） */
  animation: markDrift 60s linear infinite;
}
@keyframes markDrift {
  0%   { transform: translate(.5rem,  .5rem); }
  20%  { transform: translate(4.8rem, 1.2rem); }
  40%  { transform: translate(2.0rem, 3.3rem); }
  60%  { transform: translate(5.1rem, 2.6rem); }
  80%  { transform: translate(1.0rem, 1.8rem); }
  100% { transform: translate(.5rem,  .5rem); }
}

/* ── 单词信息 ── */
.word-info {
  background: #fff; padding: .4rem;
  display: flex; align-items: flex-start; justify-content: flex-start;
  border-bottom: 1px solid #F0EBE0; position: relative;
}
.word-info:active { background: #FAF7F2; }
.wi-left { flex: 0 1 auto; max-width: 72%; }
.wi-word {
  font-family: var(--serif);
  font-size: .72rem; font-weight: 700; color: #1B2A4A; line-height: 1.2;
}
/* 07-22②：音标行已删（视频里有音标·下方再写一份格式可能对不上） */
.wi-meaning { font-size: .44rem; color: #4A4A4A; display: block; margin-top: .06rem; }

/* 已学/未学标签（右上角，与词表页统一） */
.wi-tag {
  position: absolute; top: .24rem; right: .28rem;
  font-size: .28rem; padding: .1rem .22rem .08rem;
  line-height: .3rem; border-radius: .05rem; letter-spacing: .01rem;
  z-index: 2; font-weight: 500; border: 1px solid #B8943A;
}
.wi-tag.done { background: #1B2A4A; color: #D4AF37; font-weight: 600; }
.wi-tag.pending { background: #fff; color: #1B2A4A; }

.wi-emoji {
  width: 1.4rem; height: 1.4rem;
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0; margin-left: .24rem; margin-right: .4rem;
}
.wi-emoji span { font-size: 1rem; line-height: 1; }

/* ── 单词卡（08-16 老郑截图定稿）──
   有插画的词：**整张卡搬过来**——图在上、单词和中文在卡片里面的下半部分，整张卡居中。
   就是词表页那张卡的放大版，不是「图归图、字归字」。
   没插画的词维持上面的小表情行（1776 词目前只有 48 张插画）。切换由 JS 加 .has-card。
   📐 3.4rem 宽 / 图高 4.08rem = 与词表页卡片同一个 1:1.2 竖版比例，cover 铺满不留白边。
   ⚠️ 卡里的单词字号必须比原来小（原 .72rem 在 3.4rem 宽的卡里放不下 watermelon 这种长词）。 */
/* 🔴 老郑 08-16 三次定稿（就是现在这版，别再往回改）：
   ① **不裁卡片**——图区回到最初那张卡的 1:1.2 竖版比例、居中 cover，不做方图、不偏移窗口；
   ② **深色衬底**——卡后面用品牌深蓝，把白卡衬出来（上接视频黑边、顶接导航深蓝，连成一片）；
   ③ **深色底与例句版块之间不留缝**——`.word-info.has-card + .card { margin-top: 0 }`；
   ④ **卡适度放大**——5.2rem（375 屏上 260px），不是占满整行：老郑「卡片太大效果反而不好」。 */
.word-info.has-card {
  flex-direction: column; align-items: center;
  padding: .26rem .28rem .26rem;
  background: #1B2A4A;          /* 品牌深蓝·衬托白卡（要纯黑就把这里和下面 :active 一起换成 #000） */
  border-bottom: none;
}
.word-info.has-card:active { background: #1B2A4A; }   /* 点整块重播视频时别闪色 */
.word-info.has-card + .card { margin-top: 0; }        /* 深色底 ↔ 例句版块 零缝 */
/* 🔴 卡宽 = min(4.4rem, 26vh)（老郑 08-16 第十一版·档位表里的 168px 那档：
      390×700 可视高度下第 1 条例句露出约 47%。要切回 182px 那档＝把两个 24 改回 26）。三行写法是**降级链**，都别删：
        ① 4.4rem  —— 不认 min() 的老内核
        ② 24vh    —— 认 min() 不认 dvh
        ③ 24dvh   —— iOS 上更准：vh 按「工具栏收起时的大视口」算，
                      而微信底部那条工具栏在时可视高度更小，dvh 才是真的可视高度
                      （老郑 08-16：iPhone 底部自带按钮条让例句更难看到）。 */
.wi-cardbox {
  width: 4.4rem;
  width: min(4.4rem, 24vh);
  width: min(4.4rem, 24dvh);
  position: relative;                /* 已学/未学角标改成挂在卡片里（跟着卡片尺寸走） */
  border-radius: .2rem; overflow: hidden;
  background: #fff; box-shadow: 0 .08rem .24rem rgba(0,0,0,.28);
  transition: transform .09s ease, box-shadow .09s ease;
}
.wi-cardbox[hidden] { display: none; }
/* 图区用 padding-bottom 撑比例（同词表页做法）：1:1.2 竖版 = 最初那张卡的比例，
   cover 居中，源图 832×1216 上下各只裁约 9%，主体（量过 5 张·纵向 17%~86%）稳稳在框内。 */
.wi-pic { position: relative; width: 100%; padding-bottom: 120%; background: #FAF7F2; }
.wi-pic picture,
.wi-pic img {
  position: absolute; top: 0; left: 0; width: 100%; height: 100%;
  object-fit: cover; display: block;
}
/* 图没到之前别露脸（08-30 老郑「先看到裂图，再看到单词图」·同词表页）：
   裂图是旧 webview 自己画的加载占位。透明着等，load 到了由 js/h5res.js 加 .rdy 显出来。
   html.imgfade 由 h5res.js 自己加 ⇒ 那份 js 没加载成功时这条不生效，图片照常显示。
   🔴 不加 transition：文档 hidden 时过渡一帧都不推进，图会卡在透明上（同 words.html）。 */
html.imgfade .wi-pic img { opacity: 0; }
html.imgfade .wi-pic img.rdy { opacity: 1; }
/* 卡里的文字区（wi-left 被 JS 搬进卡后套这套样式） */
.wi-cardbox .wi-left {
  max-width: 100%; text-align: center;
  padding: .2rem .18rem .26rem;
}
.wi-cardbox .wi-word {
  /* 老郑 08-16 第十版：比 .56 小一点、比最初的 .46 大一点 ⇒ .5rem。
     放不下的长词由 learn-core 的 fitWord() 自动往下缩，缩到还是放不下才走省略号。 */
  font-size: .5rem; line-height: 1.2;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.wi-cardbox .wi-meaning { font-size: .34rem; margin-top: .06rem; }

/* 声波图标压在图片右下角（老郑 08-16：放单词后面会把单词挤得不居中）。
   🔴 这个图标跟着【视频在不在播】走，与「这个词有没有点读音频」无关，
      所以不能沿用 `body.has-audio .speaker-wave` 那条显隐规则，自己覆盖一份（选择器更具体）。 */
.speaker-wave.wi-wave {
  display: inline-flex; visibility: hidden;
  /* 🔴 图片左上角（老郑 08-16 第十版）：试过右下角、也试过压正中（挡图）—— 现在放左上 */
  position: absolute; left: .16rem; top: .16rem; z-index: 2;
  height: auto; padding: .12rem .2rem; border-radius: 1rem; gap: .05rem;
  margin: 0;                            /* 清掉 .speaker-wave 基类的 margin-left */
  background: rgba(27,42,74,.62);       /* 图片颜色不定，垫一层深底才看得清 */
  backdrop-filter: saturate(140%) blur(2px);
}
.speaker-wave.wi-wave.playing { visibility: visible; }
/* 压在图正中 ⇒ 比行内那套大一号，条也粗一点，隔着一臂距离也看得出在动 */
.wi-wave .voice-bar { width: .09rem; background: #F5D98A; opacity: .92; }
.wi-wave .vb1 { height: .16rem; }
.wi-wave .vb2 { height: .32rem; }
.wi-wave .vb3 { height: .26rem; }
.wi-wave .vb4 { height: .16rem; }
/* 大一号图标要配大一号的跳动幅度，否则跑起来反而比静止时还矮 */
.wi-wave.playing .voice-bar { animation-name: wechatBarBig; }
@keyframes wechatBarBig {
  0%   { height: .12rem; opacity: .35; }
  30%  { height: .36rem; opacity: 1; }
  60%  { height: .22rem; opacity: .75; }
  100% { height: .12rem; opacity: .35; }
}

/* 按下反馈（老郑 08-16：iPhone 和安卓都要有）。
   🔴 `.tapped` 由 JS 加（learn-core bindEvents）——**iOS Safari 对普通 div 不触发 :active**，
      只写 :active 的话安卓有、iPhone 没有。:active 仍然保留，桌面浏览器兜底。 */
.word-info.tapped .wi-cardbox,
.word-info:active .wi-cardbox {
  transform: scale(.975);
  box-shadow: 0 .02rem .1rem rgba(27,42,74,.10);
}
/* 没插画的词（小表情行）按下去给底色，同样两条路都留 */
.word-info.tapped:not(.has-card) { background: #FAF7F2; }
/* 已学/未学角标：🔴 有卡时**挂进卡片里**（renderCard 里搬进 wi-cardbox），
   位置就是卡片自己的右上角 ⇒ 卡片大小怎么变它都跟着，不会跑到卡外面
   （老郑 08-16：之前用 calc(50% - 卡宽/2) 算的，卡一改尺寸就跑出去了）。 */
.wi-cardbox .wi-tag { top: .16rem; right: .16rem; }

/* ── 卡片（极轻阴影）── */
.card {
  background: #fff; margin: .24rem 0; padding: .4rem .36rem;
  border-radius: .12rem; box-shadow: 0 .01rem .06rem rgba(27,42,74,.04);
}
.card-header {
  font-family: var(--serif);
  font-size: .4rem; font-weight: 600; color: #1B2A4A;
  margin-bottom: .24rem; padding-bottom: .16rem;
  border-bottom: .02rem solid #B8943A; letter-spacing: .02rem;
  /* 🔵 08-21：标题左、「点句子听发音」右（老郑「小字可加」） */
  display: flex; align-items: baseline; justify-content: space-between; gap: .16rem;
}
/* 明说那一行。默认不显示——没铺点读音频的词写了就是骗人（同图标口径，由 body.has-audio 开） */
.card-hint {
  display: none; flex-shrink: 0;
  font-family: var(--sans); font-size: .28rem; font-weight: 400;
  color: #6B6B6B; letter-spacing: 0;
}
body.has-audio .card-hint { display: inline-block; }

/* ── 点读声波图标（1:1 照搬 learn.wxss .speaker-wave / .voice-bar / @keyframes wechatBar）──
   07-22③：图标【默认不显示】，点句子播放时才出现跑动画，播完/停下立刻消失（同小程序 wx:if）。
   实现用 visibility 而不是 display：有音频的词一律把图标那点宽度先占住，
   播放时图标冒出来不会把整句文字挤得重排（中老年看着最忌讳字跳）。
   .has-audio 由 JS 按「当前词有没有音频」加在 <body> 上：没铺音频的词连位置都不占，
   绝不让用户点一个必然 404 的东西。 */
.speaker-wave {
  display: none; align-items: center; gap: .04rem;
  flex-shrink: 0; align-self: center; margin-left: .12rem; height: .28rem;
}
/* 🔵 08-21 老郑：「不知道例句可点进播放声音」⇒ 行内图标**一直显示**：
   静止＝喇叭，播放＝跑动的声波（下面 .spk-horn / .voice-bar 对调 display）。
   07-22③ 那版是「静止什么都不显示」，把"可以点"这件事藏起来了。
   🔴 卡片上那个 .wi-wave 是**另一套**（跟视频在不在播走，与点读无关），
      它的选择器比这条基础规则**低**（(0,2,0) < (0,2,1)），会被这条顺走 ⇒
      下面两条把它明确挡回原样。改这三条的顺序/写法前先想清楚这件事。 */
body.has-audio .speaker-wave { display: flex; visibility: visible; }
body.has-audio .speaker-wave.wi-wave { visibility: hidden; }
body.has-audio .speaker-wave.wi-wave.playing { visibility: visible; }
/* 行内那套（例句/场景）——🔵 08-21 老郑二改：**风格对齐 wordcards.html 那枚圆形喇叭按钮**
   （`.card .play`：圆底 + 白色实心喇叭），只是按行高缩了一号：32px→.46rem、15px→.26rem。
   播放时**同一枚圆底**里换成跑动的金声波 —— 与单词卡上那枚 .wi-wave 同一套语言
   （深底 + 金声波），全页只有一种"正在响"的样子。
   🔴 容器**定死方形**（.46×.46）：喇叭与四根声波条不等宽，不定住的话每次起播/停播
      整句文字会横向抖一下（中老年最忌讳字跳——也正是 07-22 当初用 visibility 的理由）。
      wi-wave 是绝对定位的胶囊，宽度得留 auto，所以这里全用 :not(.wi-wave) 圈住。 */
.speaker-wave:not(.wi-wave) {
  width: .46rem; height: .46rem; border-radius: 50%;
  justify-content: center;
  background: rgba(27,42,74,.85);   /* 同 wordcards 的 rgba(31,58,95,.85)，换成本站 --navy */
}
.spk-horn { display: block; width: .26rem; height: .26rem; fill: #fff; flex-shrink: 0; }
/* 🔵 08-21 老郑三改：「播放中状态不要播放动画图标后的背景，只要播放动画图标就可以」⇒
   播放时把圆底撤掉，只剩四根跑动的金条（压白底，用基类的 #B8943A，不用压深蓝那套 #F5D98A）。
   🔴 撤的只是背景色 —— 宽高仍是 .46×.46（上面那条给的），所以起播/停播文字**照旧零位移**。
      别顺手把 width/height 也挪进非 .playing 的选择器里，那会把这个保证弄丢。 */
.speaker-wave:not(.wi-wave).playing { background: none; }
.speaker-wave.playing .spk-horn { display: none; }
.speaker-wave:not(.playing) .voice-bar { display: none; }
.voice-bar {
  width: .06rem; border-radius: .03rem; background: #B8943A;
  opacity: .38; flex-shrink: 0;
}
/* 静态时高低错落 = 一眼认得出的声波/喇叭形，比一排等高小方块清楚（中老年） */
.vb1 { height: .1rem; }
.vb2 { height: .2rem; }
.vb3 { height: .16rem; }
.vb4 { height: .1rem; }
.speaker-wave.playing .voice-bar { animation: wechatBar .9s ease-in-out infinite; }
.speaker-wave.playing .vb1 { animation-delay: 0s; }
.speaker-wave.playing .vb2 { animation-delay: .18s; }
.speaker-wave.playing .vb3 { animation-delay: .36s; }
.speaker-wave.playing .vb4 { animation-delay: .54s; }
@keyframes wechatBar {
  0%   { height: .08rem; opacity: .3; }
  30%  { height: .28rem; opacity: 1; }
  60%  { height: .16rem; opacity: .7; }
  100% { height: .08rem; opacity: .3; }
}
/* 整行可点（同小程序：bindtap 在整行上，不是只有图标）·点下去给底色反馈 */
body.has-audio .example-item,
body.has-audio .scene-title,
body.has-audio .scene-dialog { cursor: pointer; -webkit-tap-highlight-color: transparent; }
body.has-audio .example-item:active {
  background: #F5F0E8; border-radius: .06rem;
  margin-left: -.12rem; margin-right: -.12rem; padding-left: .12rem; padding-right: .12rem;
}
body.has-audio .scene-title:active { background: #F5F0E8; }
body.has-audio .scene-dialog:active { filter: brightness(.97); }

/* ── 外教例句 ── */
.example-item { display: flex; align-items: flex-start; margin-bottom: .28rem; padding: .08rem 0; }
.example-item:last-child { margin-bottom: 0; }
.example-num {
  font-family: var(--serif);
  font-size: .34rem; color: #B8943A; min-width: .52rem; margin-right: .08rem;
  flex-shrink: 0; font-weight: 600; font-variant-numeric: tabular-nums;
}
.example-content { flex: 1; min-width: 0; display: flex; flex-direction: column; align-self: center; }
.example-en { font-family: var(--sans); font-size: .38rem; color: #1B2A4A; line-height: 1.5; }
.example-zh { font-size: .36rem; color: #6B6B6B; display: block; line-height: 1.5; margin-top: .08rem; }

/* ── 场景用法 ── */
.scene-block { border: 1px solid #E8E0D0; border-radius: .1rem; padding: .24rem; margin-bottom: .24rem; }
.scene-block:last-child { margin-bottom: 0; }
.scene-title {
  display: flex; align-items: baseline;
  padding-bottom: .16rem; border-bottom: .02rem solid #B8943A; margin-bottom: .16rem;
}
.scene-num { font-size: .34rem; color: #B8943A; min-width: 1rem; flex-shrink: 0; font-weight: 600; }
.scene-title-content { flex: 1; min-width: 0; }
.scene-place-en { font-size: .4rem; font-weight: 600; color: #1B2A4A; display: block; line-height: 1.8; }
.scene-place-zh { font-size: .36rem; color: #1B2A4A; display: block; line-height: 1.5; margin-top: .04rem; }

.scene-dialog {
  display: flex; align-items: baseline;
  padding: .16rem; border-radius: .08rem; margin-bottom: .08rem;
}
.scene-dialog:last-child { margin-bottom: 0; }
.scene-dialog-a { background: #F5F0E8; }
.scene-dialog-b { background: #EEF2F7; }
.scene-role { font-size: .34rem; color: #B8943A; min-width: .44rem; flex-shrink: 0; font-weight: 600; }
.scene-dialog-content { flex: 1; min-width: 0; }
.scene-en { font-family: var(--sans); font-size: .38rem; color: #1B2A4A; display: block; line-height: 1.5; margin-bottom: .08rem; }
.scene-zh { font-size: .36rem; color: #6B6B6B; display: block; line-height: 1.5; }

/* ── 操作按钮（固定底部）── */
.actions {
  /* 08-07 老郑 iPhone 二次复发 → 根治：fixed 改 sticky。
     微信 iOS 滚动时收放自家底部工具栏 = 视口高度变化，fixed 元素重排滞后会"脱开"
     （08-05 的 translateZ 硬化只减轻没断根）。sticky 在文档流内参与正常重排，
     视口变化被排版吸收；.actions 本就位于内容流末尾，正是 sticky-bottom 的标准位。 */
  position: -webkit-sticky; position: sticky; bottom: 0;
  display: flex; align-items: center; justify-content: center;
  padding: .16rem .24rem;
  padding-bottom: calc(.16rem + env(safe-area-inset-bottom, 0px));
  background: #fff; border-top: .02rem solid #B8943A;
  /* 🔴 08-16 去掉了原来的 `transform: translateZ(0)`（它是 08-05 fixed 时代为了防抖加的硬化）。
     老郑 08-16 iPhone 反馈：**手指按住慢慢往上拉时按钮条不吸底**（像普通元素一样跟着内容走、
     下面露出正文），快速一划反而会瞬间归位——这是 WebKit 对**被提升成独立合成层**的 sticky
     元素在触摸拖动期间不更新吸附位置的老毛病，`translateZ(0)` 正是那个提升开关。
     它在 08-07 fixed→sticky 之后就没用了，留着只剩副作用。
     ⚠️ 本条改动本地无法复现（只在 iOS 真机的触摸拖动里出现），改完要老郑真机复验；
        若仍不吸底，下一步是回退成 position:fixed + 恢复 body 底部让位（08-07 之前那套）。 */
  z-index: 100;
  box-shadow: 0 -.02rem .12rem rgba(27,42,74,.08);
  gap: .16rem;
}
.btn-prev, .btn-next {
  display: flex; align-items: center; justify-content: center;
  height: .8rem; padding: 0 .28rem;
  background: #F5F0E8; border-radius: .06rem; flex-shrink: 0;
}
/* 🔴 08-21：头尾词的上/下一个是**占位隐藏**，不是抽走（display:none）。
   切词时这根 sticky 条子的排版一格都不许变 —— 变了 iOS 微信会漏掉重绘，
   把上一帧的排版留在屏幕上，新旧「标记已学」叠在一起（老郑 08-21 截图）。
   详细根因与量出来的证据见 js/learn-core.js 的 nudgeStickyBar() 那段注释。
   ⚠️ 不能改回 hidden 属性做：app.css 的 [hidden]{display:none!important} 会直接抽走它。 */
.btn-prev.btn-void, .btn-next.btn-void { visibility: hidden; }
.btn-arrow { font-size: .3rem; color: #1B2A4A; }
.btn-label { font-size: .3rem; color: #4A4A4A; margin: 0 .06rem; }
.btn-mark {
  flex: 1; max-width: 3.6rem;
  display: flex; align-items: center; justify-content: center;
  height: .8rem; font-size: .32rem; border-radius: .06rem;
  letter-spacing: .02rem; font-weight: 500;
}
/* 标记已学 — 藏蓝底金字；取消已学 — 白底藏蓝字（与标签统一） */
.btn-mark.mark-pending {
  background: #1B2A4A; color: #D4AF37;
  border: 1px solid #B8943A; box-shadow: 0 .02rem .08rem rgba(27,42,74,.25);
}
.btn-mark.mark-done { background: #fff; color: #1B2A4A; border: 1px solid #B8943A; }

/* ── 购买引导弹窗（照搬小程序 wx.showModal「需要购买课程」）── */
.buy-mask {
  position: fixed; inset: 0; background: rgba(13,23,53,.5);
  z-index: 500; display: none; align-items: center; justify-content: center;
}
.buy-mask.show { display: flex; }
.buy-modal {
  width: 6.1rem; background: #fff; border-radius: .16rem;
  padding: .48rem .4rem .36rem; text-align: center;
  border-top: .03rem solid #B8943A;
}
.buy-modal-title {
  font-family: var(--serif);
  font-size: .42rem; font-weight: 600; color: #1B2A4A; letter-spacing: .02rem;
}
.buy-modal-content {
  font-size: .34rem; color: #4A4A4A; line-height: 1.7;
  margin-top: .24rem; white-space: pre-line;
}
.buy-modal-btns { display: flex; gap: .2rem; margin-top: .44rem; }
.buy-modal-btns button {
  flex: 1; height: .88rem; border-radius: .08rem; font-size: .34rem; letter-spacing: .02rem;
}
.buy-modal-cancel { background: #F5F0E8; color: #1B2A4A; }
.buy-modal-ok {
  background: linear-gradient(135deg, #B8943A 0%, #D4AF37 100%);
  color: #1B2A4A; font-weight: 600;
  box-shadow: 0 .02rem .08rem rgba(184,148,58,.35);
}
.buy-modal-ok:disabled { opacity: .6; }
/* 券有效期倒计时（老郑 08-21 二稿）。只在真拿着券时出现（见 renderBuyCountdown）。
   金色小字 + 浅金底，不抢主标题与价格那两行的权重。 */
.buy-modal-cd {
  margin-top: .2rem; padding: .1rem .2rem; border-radius: .06rem;
  display: inline-block; background: #FBF4E2; color: #8A6A1F;
  font-size: .3rem; letter-spacing: .01rem;
}
/* 购买弹窗里的领券入口（老郑 09-14）：与券条态②同一副长相——虚线金框「可领 ¥N 优惠券 ›」。
   只在「有码可领、还没领、未购」时由 h5land 插进来（见 claimLineFor），点它关本窗、开领券窗。
   中老年学员：整行可点、字号不小于正文，不做成小字链接。 */
.buy-modal-claim {
  margin-top: .24rem; padding: .16rem .2rem; border-radius: .1rem;
  display: block; background: rgba(212,175,55,.14); border: .02rem dashed rgba(184,148,58,.8);
  color: #8A6A1F; font-size: .32rem; font-weight: 600; letter-spacing: .01rem;
  -webkit-tap-highlight-color: transparent;
}
.buy-modal-claim:active { opacity: .8; }
.buy-modal-claim[hidden] { display: none; }


/* ── 连播设置（10-18 老郑）：底部弹出。选项是大按钮（中老年学员好点），选中的是深蓝金字 ── */
.cont-mask {
  position: fixed; inset: 0; background: rgba(13,23,53,.5);
  z-index: 500; display: none; align-items: flex-end; justify-content: center;
}
.cont-mask.show { display: flex; }
.cont-sheet {
  width: 100%; max-width: 7.5rem; background: #fff;
  border-radius: .2rem .2rem 0 0; border-top: .03rem solid #B8943A;
  padding: .36rem .36rem calc(.32rem + env(safe-area-inset-bottom, 0px));
}
.cont-title {
  font-family: var(--serif); text-align: center;
  font-size: .4rem; font-weight: 600; color: #1B2A4A; letter-spacing: .02rem; margin-bottom: .2rem;
}
.cont-row { display: flex; align-items: center; padding: .14rem 0; border-bottom: 1px solid #F0EADC; }
.cont-label { width: 2.1rem; flex: none; font-size: .3rem; color: #4A4A4A; }
.cont-opts { flex: 1; display: flex; gap: .14rem; }
.cont-opts button {
  flex: 1; height: .72rem; border-radius: .08rem; padding: 0;
  background: #F5F0E8; color: #1B2A4A; border: .02rem solid transparent;
  font-size: .3rem; white-space: nowrap; -webkit-tap-highlight-color: transparent;
}
.cont-opts button.on { background: #1B2A4A; color: #D4AF37; border-color: #B8943A; font-weight: 600; }
.cont-note { margin-top: .2rem; text-align: center; font-size: .26rem; color: #8A6A1F; }
.cont-sheet .buy-modal-btns { margin-top: .3rem; }
