/* Lunaloo 网页版 —— 单文件样式。全部颜色与品牌视觉常量一致，零外部依赖。 */

:root {
  --bg: #080E26;        /* 夜空背景 */
  --text: #FCF6E0;      /* 正文 */
  --text-dim: #C6D0F0;  /* 次要文字 */
  --moon: #F8F6E8;      /* 月亮/高亮 */
  --card: #0B1021;      /* 卡片 */
  --card-deep: #060916; /* 卡片更深一层 */
  --radius: 16px;
}

* { box-sizing: border-box; margin: 0; padding: 0; }

/* FIX-1：JS 用 hidden 属性控制显隐，而类选择器设置的 display 特异性高于 UA 样式
   表的 [hidden]{display:none}，会把它压掉（timer-sheet 默认摊开就是这里）。
   全站统一补一条最高优先级兜底，再对已知坑位逐个显式补。 */
[hidden] { display: none !important; }

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

body {
  background: var(--bg);
  color: var(--text);
  font-family: system-ui, -apple-system, "PingFang SC", "Microsoft YaHei",
               "Noto Sans CJK SC", "Source Han Sans SC", sans-serif;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

.page {
  max-width: 560px;
  margin: 0 auto;
  padding: 24px 20px 40px;
}

/* ===== 品牌头 ===== */
.brand { text-align: center; padding: 12px 0 8px; }
.brand-moon { filter: drop-shadow(0 0 14px rgba(248, 246, 232, .35)); }
.brand h1 {
  font-size: 26px;
  letter-spacing: .04em;
  margin-top: 6px;
}
.brand-tagline { color: var(--text-dim); font-size: 14px; margin-top: 2px; }
/* 语言选项（中文 / English）：胶囊分段控件，放在品牌头下方 —— 家长一眼能看到。
   视觉沿用音色 chip 的语言（亮色=选中），但更轻，不抢播放键的重心。 */
/* 语言切换胶囊：**内容页共用**（story.html / privacy.html / contact.html）。
   播放页 index.html 用的是控制条里的「中 / EN」按钮（.btn-ctl.btn-lang），不用这个。
   ⚠ 2026-09-30 曾因为「播放页不再用它」把这几行误删过一次，结果 story.html 的胶囊变成
   裸按钮；恢复时补上这条注释 —— 只要还有内容页在用，这个样式就不能删。 */
.lang-switch { display: inline-flex; gap: 2px; margin-top: 12px; padding: 3px; background: var(--card); border-radius: 999px; border: 1px solid rgba(255,255,255,.07); }
.lang-switch button { font: inherit; font-size: 13px; line-height: 1; color: var(--text-dim); background: none; border: 0; padding: 7px 16px; border-radius: 999px; cursor: pointer; }
.lang-switch button[aria-checked="true"] { background: var(--moon); color: #080E26; font-weight: 600; }
.lang-switch button:active { transform: scale(.97); }
.brand-intro {
  color: var(--text-dim);
  font-size: 13px;
  margin-top: 10px;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* ===== 卡片通用 ===== */
.card {
  background: var(--card);
  border-radius: var(--radius);
  padding: 18px;
  margin-top: 16px;
}
.card-title { font-size: 16px; font-weight: 600; }
.card-hint { color: var(--text-dim); font-size: 13px; margin-top: 4px; }

/* ===== 联系我们的账号名片（抖音 / 小红书 / 微信视频号）=====
   ★ 固定「三张一行 + 三张一样大」：用户 2026-09-30 要求并排、紧凑、三张大小相同。
   1) 三张海报原图比例不同（0.670 / 0.733 / 0.769）→ 部署前由 tools/pad-qr-posters.py 用
      **每张自己的边缘色补边**成同一比例（只加宽、不裁高；抖音那张内容顶到四边，裁一点就切字）；
   2) 页面这层再用 10:13 图片盒子 + object-fit:contain 兜底 —— 将来换图比例不一致时最多留点白边，
      **永远不会裁掉内容**（computed 后三张图框逐像素相同）；
   3) 卡片里只有「图 + 平台名 + 账号名」，flex 默认 stretch → 三张卡片自然等高。 */
.qrs { display: flex; flex-wrap: nowrap; gap: 12px; justify-content: center; margin: 14px 0 6px; }
.qrs .q { flex: 1 1 0; min-width: 0; max-width: 250px; background: var(--card); border-radius: var(--radius); padding: 12px; text-align: center; }
/* ⚠ 图片链接必须用 .qimg 限定 —— 卡片里还有一个「平台名」链接（.qt），
   不限定的话这个 10:13 图片盒子会被套到标题上（真踩过，2026-09-30） */
.qrs .q a.qimg { display: block; width: 100%; aspect-ratio: 10 / 13; background: #fff; border: 1px solid rgba(0,0,0,.07); border-radius: 12px; overflow: hidden; }
.qrs .q img { display: block; width: 100%; height: 100%; object-fit: contain; }
.qrs .q b { display: block; font-size: 15px; margin-top: 8px; }
/* 平台名 = 可点进主页（抖音/小红书有网页地址；微信视频号只在微信里，标题就不加链接，与参考页一致）。
   保留标题的字号字重，只加下划线 —— 不靠颜色变化告诉家长「这里能点」 */
.qrs .q a.qt { display: block; text-decoration: none; color: inherit; }
.qrs .q a.qt b { text-decoration: underline; text-underline-offset: 3px; }
.qrs .q a.qt:active { opacity: .7; }
.qrs .q span { display: block; font-size: 12.5px; color: var(--text-dim); margin-top: 3px; }
.fallback { color: var(--text-dim); font-size: 14px; text-align: center; padding: 18px 0 6px; }

/* ===== 上次听到 ===== */
.resume-bar {
  width: 100%;
  margin-top: 16px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  background: var(--card-deep);
  border: 1px solid rgba(248, 246, 232, .4);
  border-radius: 12px;
  color: var(--text);
  padding: 12px 16px;
  font-size: 14px;
  font-family: inherit;
  min-height: 48px;
  cursor: pointer;
}
.resume-bar[hidden] { display: none !important; }
.resume-cta { color: var(--moon); font-weight: 600; white-space: nowrap; }

/* ===== 播放器 ===== */
/* 播放器卡片恢复纯色底；星空图挪进下面的 .stage（2026-09-30 用户定的布局：
   播放键/进度条/上下集/定时一律在图**下方**，图里只放标题与字幕）。
   保留 position:relative 是给 #timer-sheet 那类浮层当定位基准；
   这里**不能**加 overflow:hidden —— 会把定时浮层裁掉。 */
.player {
  text-align: center;
  position: relative;
}

/* 舞台：图 + 标题 + 字幕 合成一个 4:3 画框（不是 16:9 —— 用户要「标题与字幕之间留白，
   以后放弹幕之类」，16:9 时那块只剩 32px，4:3 才有 ~75px）。
   标题贴上部、字幕贴底部（半透明底），中间留白。
   用 aspect-ratio 而不是固定高度：窄屏宽屏自动成比例；字幕框用 absolute 定底，
   所以图高变化不会错位。图 1280×720 被 cover 到 4:3 ⇒ 左右各裁掉约 16%（月亮靠右，仍完整可见）。 */
.stage {
  position: relative;
  aspect-ratio: 4 / 3;
  border-radius: 12px;
  overflow: hidden;
  /* right center：4:3 裁切时左右各裁掉约 16%（原图 x∈[158,1122]），右对齐才能把那轮月亮
     （原图 x≈1070）完整留在舞台内 —— 实测居中时月亮右缘会被切掉约 14px。 */
  background: var(--card-deep) url("media/cover-night.png") right center / cover no-repeat;
  padding: 14px 14px 12px;
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
}
/* 暗色渐变：上淡下浓 —— 标题与字幕都压在星空上，字必须读得清 */
.stage::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(180deg, rgba(6, 9, 22, .26) 0%, rgba(6, 9, 22, .38) 45%, rgba(6, 9, 22, .74) 100%);
}
.stage > * { position: relative; z-index: 1; }
/* 图已当 .stage 的背景，元素本身不再占位；但**不能删元素** —— app.js 与 MediaSession
   （手机锁屏封面）还在用它。 */
.player-cover { display: none; }
.player-title { font-size: 18px; font-weight: 600; }
.player-subtitle { color: var(--text-dim); font-size: 13px; margin-top: 2px; }

/* 音色 chips */
.voice-chips {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  padding: 14px 2px 6px;
  scrollbar-width: none;
}
.voice-chips[hidden] { display: none !important; }   /* 只有一个音色时整行不占位（app.js 里那条 ≥2 规则） */
.voice-chips::-webkit-scrollbar { display: none; }
.chip {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  background: var(--card-deep);
  border: 1px solid rgba(198, 208, 240, .25);
  border-radius: 999px;
  color: var(--text);
  font-family: inherit;
  font-size: 13px;
  padding: 8px 14px;
  min-height: 36px;
  cursor: pointer;
  position: relative;
}
.chip .chip-check { display: none; color: var(--moon); }
.chip .chip-check svg { display: block; }
.chip[aria-checked="true"] {
  border: 1.5px solid var(--moon);
}
.chip[aria-checked="true"] .chip-check { display: inline; }
.chip:disabled {
  opacity: .45;
  cursor: not-allowed;
}
.chip .chip-badge {
  font-size: 10px;
  color: var(--text-dim);
  border: 1px solid rgba(198, 208, 240, .35);
  border-radius: 999px;
  padding: 0 6px;
  line-height: 16px;
}

/* 大播放按钮 */
.btn-play {
  width: 72px;
  height: 72px;
  border-radius: 50%;
  border: none;
  background: var(--moon);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin: 14px auto 0;
  cursor: pointer;
  box-shadow: 0 4px 18px rgba(248, 246, 232, .22);
}
.btn-play:disabled { opacity: .5; cursor: default; }
.btn-play svg { margin-left: 2px; }
.btn-play #icon-pause { margin-left: 0; }

/* BUG-1 兜底：保证 .btn-play/.btn-ctl 内任意时刻只有一个图标显示 */
.btn-play svg[hidden], .btn-ctl svg[hidden] { display: none !important; }

/* 进度条 */
/* FIX-3：窄视口下的首屏高度优化 */
@media (max-width: 430px) {
  .page { padding: 16px 14px 32px; }
  .brand { padding: 4px 0 0; }
  .brand h1 { font-size: 22px; margin-top: 2px; }
  .brand-intro {
    display: block;
    margin-top: 4px;
    white-space: nowrap;
    text-overflow: ellipsis;
    overflow: hidden;
  }
  .card { padding: 12px; margin-top: 10px; }
  .voice-chips { padding: 10px 0 4px; }
  .btn-play { margin-top: 8px; }
  .progress-row { margin-top: 8px; }
  /* 2026-09-30 加了第 7 个按钮（循环模式）后，48px + 10px 间距在 390px 手机上会溢出
     （7×48 + 6×10 = 396px > 可用宽度 338px）→ 窄屏收紧到 44px / 2px = 320px，留 18px 余量。
     44px 仍是 iOS HIG 的最小触控尺寸，不再往下压。 */
  /* 选择器必须写成 .player .controls / .player .btn-ctl：
     这一段写在 .controls{}(第 270 行)、.btn-ctl{}(第 322 行) **之前**，
     同特异性时后面的定义会赢 —— 实测只写 .controls 的话 gap:10px 照样生效，
     7 个按钮算成 7×44+6×10=368px > 卡内宽 323px，右边溢出 11px。
     改法：提特异性 + 两端贴边铺开（44×7=308px，余下 15px 自动分给 6 个间隙）。 */
  .player .controls { margin-top: 6px; gap: 0; justify-content: space-between; }
  .player .btn-ctl { flex: 0 0 44px; width: 44px; height: 44px; }
}

.progress-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 14px;
}
.time-label {
  color: var(--text-dim);
  font-size: 12px;
  min-width: 44px;
  font-variant-numeric: tabular-nums;
}
.time-label:last-child { text-align: right; }
/* 左边那个数字可以点：已播放 ⇄ 剩余（用户 2026-10-03）。点击目标要看着能点。 */
.time-toggle { cursor: pointer; user-select: none; }
.time-toggle:hover { color: var(--text); }
.time-toggle:focus-visible { outline: 2px solid var(--moon); outline-offset: 2px; border-radius: 6px; }
.seek {
  flex: 1;
  accent-color: var(--moon);
  height: 24px; /* 触控目标 */
  cursor: pointer;
}
.seek:disabled { opacity: .4; cursor: default; }

/* 控制行 */
.controls {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 10px;
  margin-top: 10px;
  position: relative;
}
/* 图标按钮的悬停提示：自己画一个气泡放在**按钮上方**（用户 2026-09-30：
   「鼠标悬浮提示统一调整到光标上方，不要被光标遮挡」）——
   原生 title 的位置由浏览器定、固定在光标下方，所以这里一律用 data-tip 自绘。
   气泡不参与点击（pointer-events:none），出现/消失只改 opacity+位移。 */
.btn-ctl[data-tip] { position: relative; }
.btn-ctl[data-tip]::after {
  content: attr(data-tip);
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%) translateY(4px);
  z-index: 30;
  padding: 6px 9px;
  border-radius: 8px;
  background: rgba(6, 9, 22, .95);
  border: 1px solid rgba(255, 255, 255, .14);
  color: var(--moon);
  font-size: 12px;
  line-height: 1;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: opacity .14s ease, transform .14s ease;
}
.btn-ctl[data-tip]:hover::after,
.btn-ctl[data-tip]:focus-visible::after {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}
/* 一行两端的按钮（上一集 / 语言）：气泡贴边，免得冒出卡片外面被裁掉 */
#btn-prev[data-tip]::after { left: 0; transform: translateY(4px); }
#btn-prev[data-tip]:hover::after,
#btn-prev[data-tip]:focus-visible::after { transform: translateY(0); }
.btn-lang[data-tip]::after { left: auto; right: 0; transform: translateY(4px); }
.btn-lang[data-tip]:hover::after,
.btn-lang[data-tip]:focus-visible::after { transform: translateY(0); }
/* 语言按钮里的字（「中」/「EN」） */
.lang-tag {
  font-size: 15px;
  font-weight: 600;
  letter-spacing: .02em;
  line-height: 1;
  color: var(--moon);
}
.btn-ctl {
  width: 48px;
  height: 48px;
  border-radius: 12px;
  border: none;
  background: transparent;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.btn-ctl:active { background: var(--card-deep); }
/* 定时状态（2026-09-30 用户：位置要调整到合适的地方）。
   以前是绝对定位贴在控制条右端 = 直接压在语言按钮上；现在改成控制条下面独立一行、居中。
   注意别再加 display，否则会压掉 UA 的 [hidden]{display:none}。 */
.timer-label {
  margin-top: 8px;
  text-align: center;
  color: var(--moon);
  font-size: 12px;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.timer-label[hidden] { display: none !important; }

/* 载入提示（2026-09-30 用户：第 1 次打开 / 切语言时要等一会儿才出声，必须有反馈）。
   绝对定位落在标题与字幕之间的留白区 → 不参与排版，出现/消失不会让标题、字幕抖动。
   呼吸感而不是转圈：睡前场景要「安静地在准备」，不是「努力加载中」。 */
.loading-tip {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 30%;
  text-align: center;
  color: var(--moon);
  font-size: 13px;
  pointer-events: none;
  animation: ll-breathe 1.7s ease-in-out infinite;
}
.loading-tip[hidden] { display: none !important; }
@keyframes ll-breathe {
  0%, 100% { opacity: .32; }
  50%      { opacity: .92; }
}

/* 播放键的载入态：藏掉播放/暂停图标，中间转一个圈（.btn-play 已是 flex 居中，转圈自然居中） */
.btn-play.is-loading svg { display: none !important; }
.btn-play.is-loading::after {
  content: '';
  width: 24px;
  height: 24px;
  border: 2.5px solid rgba(8, 14, 38, .25);
  border-top-color: #080E26;
  border-radius: 50%;
  animation: ll-spin .8s linear infinite;
}
@keyframes ll-spin { to { transform: rotate(360deg); } }

/* 定时关闭浮层 */
.timer-sheet {
  margin-top: 14px;
  background: var(--card-deep);
  border-radius: 12px;
  padding: 14px;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}
.timer-sheet[hidden] { display: none !important; }
.timer-sheet-title {
  grid-column: 1 / -1;
  font-size: 14px;
  color: var(--text-dim);
  margin-bottom: 2px;
}
.timer-opt {
  background: var(--card);
  border: 1px solid rgba(198, 208, 240, .25);
  border-radius: 10px;
  color: var(--text);
  font-family: inherit;
  font-size: 14px;
  padding: 10px;
  min-height: 48px;
  cursor: pointer;
}
.timer-opt:active { border-color: var(--moon); }
.timer-off { color: var(--text-dim); }

/* ===== 集列表 ===== */
.episode-list { list-style: none; margin-top: 8px; }
/* 一行一集：封面 ·（标题 /「第 X 集 · 故事稿」）· 勾+时长。
   整行可点，但落点是一枚**透明覆盖按钮** `.ep-hit`，不是「一个大 button 套全部」——
   因为「故事稿」是真链接（要能长按新开标签页），而 <a> 套在 <button> 里是非法嵌套。
   做法：行内文字一律 pointer-events:none，只有链接把事件收回来（见下面 .ep-story）。
   用户 2026-10-03：故事稿从「独立一行」挪到「第 X 集」后面 —— 一行更紧凑、更好用。 */
.ep-row {
  position: relative;
  display: grid;
  grid-template-columns: 48px minmax(0, 1fr) auto;
  align-items: center;
  column-gap: 12px;
  padding: 10px;
  min-height: 64px;
  border: 1.5px solid transparent;   /* 占位：当前集只换颜色，行高不跳 */
  border-radius: 12px;
}
.ep-row.current { border-color: var(--moon); background: var(--card-deep); }
.ep-row > * { position: relative; z-index: 1; pointer-events: none; }
.ep-row .ep-cover {
  grid-column: 1; grid-row: 1 / span 2;
  width: 48px;
  height: 48px;
  border-radius: 10px;
  object-fit: cover;
  background: var(--card-deep);
}
.ep-row .ep-title {
  grid-column: 2; grid-row: 1;
  min-width: 0;                       /* 少了这句，标题的省略号不生效（grid item 默认 min-width:auto） */
  font-size: 15px;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ep-row .ep-line2 {
  grid-column: 2; grid-row: 2;
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}
.ep-row .ep-sub { color: var(--text-dim); font-size: 12px; white-space: nowrap; }
.ep-row .ep-story {
  pointer-events: auto;               /* 只有它把点击收回来 */
  color: var(--text-dim);
  font-size: 12px;
  text-decoration: underline;
}
.ep-row .ep-story:hover { color: var(--text); }
.ep-row .ep-right {
  grid-column: 3; grid-row: 1 / span 2;
  display: flex;
  align-items: center;
  gap: 8px;
}
.ep-row .ep-dur {
  color: var(--text-dim);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* 整行的落点：盖满整行、透明、在文字**下面**（z-index:0）⇒ 文字照常看得见、
   点哪儿都算点这一行。 */
.ep-row .ep-hit {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: auto;
  width: 100%;
  background: transparent;
  border: 0;
  border-radius: 12px;
  cursor: pointer;
  font: inherit;
  color: inherit;
}
.ep-row .ep-hit:active { background: var(--card-deep); }

/* ===== 试听区 ===== */
.preview-list { list-style: none; margin-top: 10px; }
.preview-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 4px 0;
}
/* BUG-2：播放中：整行高亮（比卡片略亮的背景 + 左侧 3px 月亮色竖条 + 圆角），
   深色主题里也能一眼分辨当前播的是哪一条 */
.preview-item.playing {
  background: rgba(248, 246, 232, 0.06);
  border-radius: 10px;
  padding: 4px 8px 4px 11px;
  position: relative;
}
.preview-item.playing::before {
  content: '';
  position: absolute;
  left: 0;
  top: 2px;
  bottom: 2px;
  width: 3px;
  border-radius: 2px;
  background: var(--moon);
}
.preview-btn {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  border: 1.5px solid rgba(198, 208, 240, .4);
  background: var(--card-deep);
  color: var(--moon);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  flex: 0 0 auto;
}
.preview-btn { transition: border-color .15s, background .15s; }
/* BUG-1/2 兜底：明确写死 [hidden] 的 svg，保证任意时刻只有一个图标显示
   （全局 [hidden]{display:none!important} 之上） */
.preview-btn svg[hidden] { display: none !important; }
.preview-btn .preview-icon-pause { position: absolute; }
.preview-btn.playing { border-color: var(--moon); background: rgba(248,246,232,.08); }
.preview-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 14px; }
.preview-timer {
  color: var(--text-dim);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  flex: 0 0 auto;
  margin-left: auto;
  min-width: 64px;
  text-align: right;
}

/* ===== 订阅 ===== */
.subscribe { display: flex; gap: 8px; margin-top: 12px; }
.subscribe-input {
  flex: 1;
  min-width: 0;
  background: var(--card-deep);
  border: 1px solid rgba(198, 208, 240, .3);
  border-radius: 12px;
  color: var(--text);
  font-family: inherit;
  font-size: 15px;
  padding: 12px 14px;
  min-height: 48px;
}
.subscribe-input::placeholder { color: rgba(198, 208, 240, .6); }
.subscribe-input:focus { outline: none; border-color: var(--moon); }
.subscribe-btn {
  flex: 0 0 auto;
  background: var(--moon);
  color: #080E26;
  border: none;
  border-radius: 12px;
  font-family: inherit;
  font-size: 14px;
  font-weight: 600;
  padding: 0 16px;
  min-height: 48px;
  cursor: pointer;
}
.subscribe-error { color: #ff9c9c; font-size: 13px; margin-top: 8px; }
.subscribe-ok { color: var(--text-dim); font-size: 13px; margin-top: 8px; }

/* ===== 页脚 ===== */
.footer {
  text-align: center;
  color: var(--text-dim);
  font-size: 12px;
  padding: 28px 0 8px;
}
.footer-links { display: flex; justify-content: center; gap: 20px; margin-top: 6px; }
.footer-links a { color: var(--text-dim); text-decoration: underline; }

/* ===== 字幕（2026-09-30：默认关闭，给需要的家长一个按钮）=====
   字号 17px：手机上一臂距离躺着能看清，又不会把播放器挤变形；
   min-height 锁两行高度 —— 句子长短不一，不锁的话每翻一句整块播放器都跳一下。 */
/* 字幕开关（图标按钮，位置在 .controls 里定时图标右边 —— 2026-09-30 用户定）。
   开的时候整块点亮：深色主题下只改边框或文字色是看不见的，背景与图标要同时上色。 */
#btn-caption[aria-pressed="true"] { background: rgba(246, 244, 226, .14); }
#btn-caption[aria-pressed="true"] svg { fill: var(--moon); }
.caption-box {
  /* 贴在图内底部。绝对定位 → 不吃舞台的排版高度：舞台高度永远由 4:3 决定，
     字幕长句短句都不会把图撑变形、也不会顶开标题。
     2026-09-30 用户「字幕背景感觉不太美观」→ 不再用一块方框式的半透明底：
     改成**从上到下渐深**的一层暗色（顶部与星空无缝，看不出边界），
     同时给文字加多层投影兜底可读性 —— 字幕正好压在星空上，纯文字在亮处会糊。
     去掉原来那圈 border（有边框就有"方框感"），圆角只留底部一点点。 */
  position: absolute;
  left: 12px;
  right: 12px;
  bottom: 12px;
  padding: 18px 14px 12px;
  background: linear-gradient(180deg, rgba(6, 9, 22, 0) 0%, rgba(6, 9, 22, .40) 42%, rgba(6, 9, 22, .68) 100%);
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
  border-radius: 12px;
}
.caption-box[hidden] { display: none !important; }
.caption-text {
  font-size: 17px;
  line-height: 1.5;
  color: #fff;
  font-weight: 500;
  letter-spacing: .01em;
  text-shadow: 0 1px 2px rgba(0, 0, 0, .92), 0 2px 8px rgba(0, 0, 0, .80), 0 0 22px rgba(0, 0, 0, .55);
  min-height: 4.4em;      /* 锁三行高度（英文长句就是三行）：翻句时字幕框不跳动 —— 图里的框跳最扎眼 */
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
}
@media (max-width: 480px) { .caption-text { font-size: 16px; } }

/* ===== 故事稿页（story.html）— 2026-09-30 追加 =====
   只追加新类，不动上面任何现有规则（app.js 的字幕 UI 也是追加，两边并行）。
   视觉沿用 index.html 的卡片语言：.card 底 + --text/--text-dim 两级文字。
   2026-09-30 用户：「内容要自适应宽度，美化一下页面」→
   · 宽屏时正文栏放宽（手机不加 max-width 就等于满宽，所以这里只对宽屏生效）
   · 正文放大一档、行距段距放松，段首时间戳做成小胶囊（可点这件事一眼看得见）
   · 语言切换**暂时不动**（用户明确：故事稿页语言切换先保持不变）
   2026-09-30 二轮（用户：「正文的风格要一致」+「点胶囊在本页播这一段」）→
   · 删掉 .story-para-edge：开场/收尾语与正文同一套样式（原来做淡做小，看着像两种正文，用户点名不一致）
   · 新增 .story-para.is-playing（正在读的段高亮，只加水平 padding + 等量负 margin，文字位置不动）
   · 新增 .para-time.is-counting（正在读的那段胶囊变剩余倒计时） */
body.story-page .page { max-width: 760px; }
.story-card { text-align: left; }
.story-title {
  font-size: 22px;
  font-weight: 600;
  line-height: 1.35;
  letter-spacing: .01em;
}
.story-sub {
  color: var(--text-dim);
  font-size: 13px;
  margin-top: 4px;
}
.story-hint {
  color: var(--text-dim);
  font-size: 13px;
  line-height: 1.7;
  margin-top: 10px;
  padding-left: 10px;
  border-left: 2px solid rgba(198, 208, 240, .26);
}
.story-body { margin-top: 18px; }
.story-para {
  color: var(--text);
  font-size: 16px;
  line-height: 2;
  margin-top: 20px;
}
/* 正在读的那一段：柔和高亮（睡前，不用刺眼色块）。
   ⚠ 这里刻意只加**水平**方向的 padding，再用等量负 margin 抵消回来 ——
   文字的实际位置一个像素都不动，只有背景块向左右各宽出 12px；
   但凡动了垂直 padding 或段距，高亮一出现整页文字就会上下抖。 */
.story-para.is-playing {
  background: rgba(246, 244, 226, .055);
  border-radius: 8px;
  padding: 0 12px;
  margin: 20px -12px 0;
  box-shadow: inset 3px 0 0 0 var(--moon);   /* 左侧一道月亮色竖线：比整块底色更含蓄 */
}
/* 段首时间戳：小胶囊 + tabular 数字。点击 = **在本页**从这一段开始听（不跳页），
   正在读的那一段会变成剩余倒计时。做成胶囊是为了让人一眼看出"这里可以点"。 */
.para-time {
  display: inline-flex;
  align-items: center;
  font-family: inherit;          /* ⚠ <button> 不继承字体：不写这一行，时间戳会换成系统 UI 字体 */
  line-height: 1.2;
  color: var(--text-dim);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  margin-right: 10px;
  padding: 4px 9px;
  min-height: 24px;              /* 手机上手指点得到（原来 18px 太小，像不可点的小标签） */
  border-radius: 999px;
  background: rgba(198, 208, 240, .08);
  border: 1px solid rgba(198, 208, 240, .16);
  transform: translateY(-1px);   /* 胶囊与正文基线对齐 */
  text-decoration: none;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
}
.para-time:focus, .para-time:hover {
  color: var(--moon);
  background: rgba(246, 244, 226, .14);
  border-color: rgba(246, 244, 226, .40);
  outline: none;
}
/* 正在读的这段：胶囊从「起始时间」变「剩余倒计时」（暂停时冻在最后一个整秒上，再点继续） */
.para-time.is-counting {
  color: var(--moon);
  background: rgba(246, 244, 226, .16);
  border-color: rgba(246, 244, 226, .48);
}
/* 语言切换：两套文案都常驻 DOM（SEO），用 hidden 属性显隐（全局 [hidden] 兜底已保证不占位） */
.only-en[hidden], .only-zh[hidden] { display: none !important; }
.story-back {
  margin-top: 26px;
  text-align: center;
}
.back-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--text);
  background: var(--card-deep);
  border: 1px solid rgba(198, 208, 240, .3);
  border-radius: 12px;
  font-size: 14px;
  min-height: 48px;
  padding: 0 20px;
  text-decoration: none;
  transition: border-color .15s ease, color .15s ease;
}
.back-link:hover, .back-link:focus-visible { border-color: var(--moon); color: var(--moon); outline: none; }
.back-link:active { border-color: var(--moon); }

/* 正文里的行内链接（隐私说明页的「联系我们」这类）。
   浏览器默认给的是宝蓝色 + 实心下划线，在深色底上又扎眼又像没做设计 ——
   换成月亮色 + 一条 38% 透明度的细下线，hover 时下线变实、文字提到纯白。
   刻意不做成按钮/胶囊：正文里的链接不该抢戏。 */
.inline-link {
  color: var(--moon);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-decoration-color: rgba(246, 244, 226, .38);
  text-underline-offset: 3px;
  transition: color .15s ease, text-decoration-color .15s ease;
}
.inline-link:hover, .inline-link:focus-visible {
  color: #fff;
  text-decoration-color: rgba(246, 244, 226, .95);
  outline: none;
}

/* ===== 段首胶囊的悬浮控制组（2026-09-30 用户要求）=====
   鼠标移到段首时间胶囊上时，在**胶囊上方**浮出一组控制：从头播放本段 / 暂停·继续 / 循环播放本段。
   面板是单个元素、挂在 <body> 下（正文容器会被 renderOne() 重建，面板不能跟着重建），
   JS 用文档坐标（body / .page 都没有 position）把它贴到被悬停的那颗胶囊上方。 */
.para-tools {
  position: absolute;
  z-index: 40;
  display: flex;
  flex-wrap: wrap;                 /* 极窄屏换行，不撑出视口 */
  max-width: calc(100vw - 16px);
  gap: 6px;
  padding: 6px;
  border-radius: 12px;
  background: rgba(10, 14, 30, .97);
  border: 1px solid rgba(198, 208, 240, .22);
  box-shadow: 0 10px 28px rgba(0, 0, 0, .5);
}
.para-tools[hidden] { display: none !important; }
.pt-btn {
  font-family: inherit;            /* ⚠ <button> 不继承字体：与 .para-time 同一条坑 */
  display: flex;                   /* 图标按钮：只有一枚 svg 可见，居中 */
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;                    /* 手指也点得到（>32px） */
  padding: 0;
  color: var(--text);
  background: rgba(198, 208, 240, .10);
  border: 1px solid rgba(198, 208, 240, .18);
  border-radius: 8px;
  position: relative;              /* 悬停气泡的定位基准 */
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
  transition: background .15s ease, border-color .15s ease, color .15s ease;
}
.pt-btn svg { display: block; }
/* BUG-1 兜底：SVG 靠 hidden 属性显隐（面板是 flex，别让 svg 抢位） */
.pt-btn svg[hidden] { display: none !important; }
/* 悬停气泡：与 .btn-ctl[data-tip] 同一套画法（自绘、放在按钮上方、不挡手）； */
.pt-btn[data-tip]::after {
  content: attr(data-tip);
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%) translateY(4px);
  z-index: 30;
  padding: 6px 9px;
  border-radius: 8px;
  background: rgba(6, 9, 22, .95);
  border: 1px solid rgba(255, 255, 255, .14);
  color: var(--moon);
  font-size: 12px;
  line-height: 1;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: opacity .14s ease, transform .14s ease;
}
.pt-btn[data-tip]:hover::after,
.pt-btn[data-tip]:focus-visible::after {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}
.pt-btn:hover, .pt-btn:focus-visible {
  color: var(--moon);
  background: rgba(246, 244, 226, .16);
  border-color: rgba(246, 244, 226, .42);
  outline: none;
}
/* 「循环播放本段」开着时的按下态 */
.pt-btn.is-on {
  color: var(--moon);
  background: rgba(246, 244, 226, .20);
  border-color: rgba(246, 244, 226, .55);
}

/* ---------- 跟读高亮（karaoke，2026-09-30 用户：「朗读时字幕随进度高亮已读文字」）----------
   两处用法不同，别合成一条：
   · 播放页字幕：底下是渐深暗底 → 未读压到半透明、已读纯白（对比够，也不刺眼）。
   · 故事稿页正文：正文本身是米白，**不能全页压暗**（一次亮起来两千字会很吓人）⇒
     只给已读部分盖一层很淡的暖白光带。纯 background + color，**不动字号/行高/边距**，
     所以一个字都不会跳位；圆角让相邻的连续字带自然接成一条。
   ⚠ span 是 karaoke.js 切的；拿不到数据时正文/字幕是**纯文本节点**，这两条规则不生效。 */
.caption-text .k { color: rgba(255, 255, 255, .52); }
.caption-text .k-on { color: #fff; }
.story-para .k { border-radius: 3px; }
.story-para .k-on { background: rgba(248, 246, 232, .14); color: var(--moon); }


/* ---------- 离线可听标记（2026-10-03）----------
   站上只在「真开始播过 + 切片补到本机」之后才出现，它是「断网也能重听」这个能力的入口。
   2026-10-03 用户：「已存本机这句文案 去掉，鼠标悬浮在……已存图标上时再出现该提示即可，
   界面保持尽可能简洁清爽」→ **只在集列表每行留一个勾**，勾上悬停才弹一句说明；
   播放器里那行常显文案撤掉。气泡画法与 .btn-ctl[data-tip] 同一套（自绘、放元素上方、
   不参与点击），只是改成右对齐 —— 勾在行的右段，右对齐才不会冒出卡片。 */
.ep-offline {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  border-radius: 999px;
  border: 1px solid rgba(198, 208, 240, .3);
  background: rgba(6, 9, 22, .5);
  color: var(--text-dim);
  /* 只有它把指针事件收回来：勾被 .ep-hit 那枚覆盖按钮压在下面，不收回来就 hover 不到、
     也就弹不出气泡（要的是「悬浮在勾上才出现」，不是整行都弹）。 */
  pointer-events: auto;
  cursor: pointer;
  position: relative;
  z-index: 2;
}
.ep-offline[hidden] { display: none; }
.ep-offline[data-tip]::after {
  content: attr(data-tip);
  position: absolute;
  bottom: calc(100% + 8px);
  right: 0;
  z-index: 30;
  padding: 6px 9px;
  border-radius: 8px;
  background: rgba(6, 9, 22, .95);
  border: 1px solid rgba(255, 255, 255, .14);
  color: var(--moon);
  font-size: 12px;
  line-height: 1.2;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transform: translateY(4px);
  transition: opacity .14s ease, transform .14s ease;
}
.ep-offline[data-tip]:hover::after,
.ep-offline[data-tip]:focus-visible::after { opacity: 1; transform: translateY(0); }
