/* ===================================================================
   audio-comic/player.css —— 有声绘本放映器样式（2026-08-05 从 player.html 内联抽出）

   🔴 **这里只放 `.apl-*` 作用域样式，一条全局规则都不许有。**
      本文件会被主 SPA 的 index.html 一起引入（详情页要内嵌放映器），
      `*{box-sizing}` / `html,body{background:#0b1220}` / `#root{height:100%}` 这三条
      跟着进来的话，整个管理后台会变成深色底、并且高度被顶死。
      那三条留在 player.html 里（只有独立页需要）。

   🔴 高度一律 `100%` 不用 `100vh`：内嵌时容器只有几百像素高，
      `100vh` 会让放映器撑破页签、把下面的内容顶到屏幕外。
=================================================================== */

.apl-root { display: flex; flex-direction: column; height: 100%; outline: none; }
.apl-loading { height: 100%; min-height: 220px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 14px; color: #94a3b8; }
.apl-spin { width: 34px; height: 34px; border-radius: 50%; border: 3px solid rgba(148,163,184,.3); border-top-color: #60a5fa; animation: aplspin .8s linear infinite; }
@keyframes aplspin { to { transform: rotate(360deg); } }

/* 内嵌在管理后台里：自带深色底与圆角（宿主页面是浅色的，不铺底就露出白边） */
.apl-embedded { background: #0b1220; border-radius: 12px; overflow: hidden;
                color: #e2e8f0; font-family: "Roboto", "Source Han Sans", "PingFang SC", "Microsoft YaHei", system-ui, sans-serif; }
/* 全屏态：浏览器把根节点提到全屏层，圆角要去掉，否则四角露出黑边 */
.apl-fs { border-radius: 0; }

/* 顶栏：只留书名 + 当前小节名，压在舞台上方（不占画面高度） */
.apl-top { position: absolute; z-index: 6; left: 0; right: 0; top: 0; display: flex; align-items: baseline; gap: 14px;
           padding: 14px 22px 30px; pointer-events: none;
           background: linear-gradient(180deg, rgba(6,14,30,.72) 0%, rgba(6,14,30,.34) 55%, rgba(6,14,30,0) 100%); }
.apl-title-main { font-size: 16px; font-weight: 800; color: #f8fafc; text-shadow: 0 2px 10px rgba(0,0,0,.75); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.apl-title-seq { font-size: 12.5px; color: rgba(226,232,240,.82); text-shadow: 0 1px 8px rgba(0,0,0,.75); white-space: nowrap; }
.apl-note { margin-left: auto; font-size: 12px; color: #fbbf24; text-shadow: 0 1px 8px rgba(0,0,0,.7); }

/* 舞台：padding:0 → 绝对定位子元素的包含块 == 边框盒，JS 量到的图片矩形与浏览器居中一致 */
.apl-stage { position: relative; flex: 1; min-height: 0; display: flex; align-items: center; justify-content: center; padding: 0; overflow: hidden; }
/* ★ 满屏铺底：当前画面放大 + 高斯模糊铺满舞台，消除信箱式黑边。 */
.apl-bgblur { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
              filter: blur(34px) brightness(.55) saturate(1.2); transform: scale(1.12);
              pointer-events: none; z-index: 0; }
/* 画面本体：比例不变、contain 居中，浮在模糊层之上 */
.apl-img { position: relative; z-index: 1; max-width: 100%; max-height: 100%; object-fit: contain;
           border-radius: 10px; box-shadow: 0 18px 60px rgba(0,0,0,.5); display: block; }

/* ── R12 换镜过渡：交叉淡化（旧图叠在上层淡出，新图在下层常驻不透明）──
   ⚠️ 方向不能反。让两层各半透明（新图 0→1 同时旧图 1→0）会在深色底上
      出现「中点变暗」——那是闪一下，不是化过去。
   ⚠️ will-change 只加在这两个**只在过渡期间存在**的类上，动画结束元素被卸掉，
      will-change 随之消失，不会长期占着合成层。

   ── R16（2026-08-06）三处改动 ──
   ① 时长从写死的 .82s 改成读 `--apl-xfade`，由 player.jsx **逐镜**写在 .apl-stage 上：
      Q4 决议 `min(1.5s, 本镜时长 × 0.4)` —— 2.6 秒的短镜上转场只有 1.04 秒，
      不然一个镜头大半时间都在化，看着像糊了。
      🔴 兜底值 1500ms 必须留：JS 还没写变量的第一帧会用它，写死 0 会让首次换镜变成硬切。
   ② 缓动换成标准 Ease In-Out `cubic-bezier(.42,0,.58,1)`（用户明确指定）。
      原来的 (.33,.1,.25,1) 是 ease-out 味，尾巴收得太快。
   ③ 新图入场（scale 1.02→1 + 轻微模糊散开）**不在这里做** —— .apl-img 是常驻元素、
      不重挂，CSS 动画在它身上无法可靠重启。那一段走 player.jsx 的 Web Animations API，
      理由与写法见那边 `runEnterAnim` 的注释。 */
.apl-img-out { position: absolute; inset: 0; z-index: 2; width: 100%; height: 100%; object-fit: contain;
               border-radius: 10px; pointer-events: none; will-change: opacity, transform;
               animation: aplXFade var(--apl-xfade, 1500ms) cubic-bezier(.42,0,.58,1) both; }
/* 淡出的铺底层：属性照抄 .apl-bgblur，但**不复用那个类名** —— e2e 断言铺底层恰好 1 个 */
.apl-bgblur-out { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
                  filter: blur(34px) brightness(.55) saturate(1.2); transform: scale(1.12);
                  pointer-events: none; z-index: 0; will-change: opacity;
                  animation: aplXFadeBg var(--apl-xfade, 1500ms) cubic-bezier(.42,0,.58,1) both; }
/* 淡出的同时极缓慢推近一点点（1 → 1.035）：只有淡化会显得「泄了气」，
   加一丝位移才像镜头化过去。幅度刻意压得很小，大了会晕。
   ⚠️ 这是**旧图**的推远；用户要的 102%→100% 是**新图**的收进，方向相反、两件事，
      新图那一半在 player.jsx 里做。两个都在，才是「一推一收」的交叉。 */
@keyframes aplXFade { from { opacity: 1; transform: scale(1); } to { opacity: 0; transform: scale(1.035); } }
@keyframes aplXFadeBg { from { opacity: 1; } to { opacity: 0; } }
/* 首帧进入（整格第一张）仍给一次淡入，避免从纯黑硬弹出来 */
.apl-fadein { animation: aplFadeIn .42s ease-out both; }
@keyframes aplFadeIn { from { opacity: 0; transform: scale(1.015); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) {
  .apl-fadein, .apl-img-out, .apl-bgblur-out { animation-duration: .01ms; animation-iteration-count: 1; }
}

/* 气泡 / 旁白 / 拟声 高亮框（norm1000 换算后覆盖在图上） */
.apl-box { position: absolute; z-index: 3; border-radius: 14px; pointer-events: none; transition: box-shadow .25s, background .25s, border-color .25s; border: 2px solid transparent; }
.apl-box.kind-bubble { border-radius: 40% 40% 40% 8% / 44% 44% 44% 10%; }
.apl-box.active { border-color: rgba(96,165,250,.95); background: rgba(96,165,250,.16); box-shadow: 0 0 0 3px rgba(96,165,250,.28), 0 0 34px rgba(96,165,250,.55); animation: aplGlow 1.1s ease-in-out infinite; }
.apl-box.kind-caption.active { border-color: rgba(250,204,21,.95); background: rgba(250,204,21,.14); }
.apl-box.kind-sfx.active { border-color: rgba(244,114,182,.95); background: rgba(244,114,182,.14); }
@keyframes aplGlow { 0%,100% { opacity: 1; } 50% { opacity: .68; } }

/* 播放门：首次进入的大播放键（浏览器拦无手势自动播放，必须点一下才有声） */
.apl-gate { position: absolute; inset: 0; z-index: 8; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 16px; cursor: pointer; background: rgba(6,14,30,.42); backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px); animation: aplFadeIn .3s ease; }
.apl-gate-btn { width: 92px; height: 92px; border-radius: 50%; border: none; background: rgba(37,99,235,.96); cursor: pointer; display: flex; align-items: center; justify-content: center; box-shadow: 0 14px 40px rgba(37,99,235,.55); transition: transform .15s; padding: 0; animation: aplGatePulse 1.8s ease-in-out infinite; }
.apl-gate:hover .apl-gate-btn { transform: scale(1.08); }
@keyframes aplGatePulse { 0%,100% { box-shadow: 0 14px 40px rgba(37,99,235,.5); } 50% { box-shadow: 0 14px 52px rgba(37,99,235,.85); } }
.apl-gate-title { font-size: 19px; font-weight: 800; color: #fff; text-align: center; padding: 0 24px; text-shadow: 0 2px 12px rgba(0,0,0,.6); }
.apl-gate-hint { font-size: 13.5px; color: rgba(255,255,255,.9); text-shadow: 0 1px 8px rgba(0,0,0,.6); }
.apl-tri { width: 0; height: 0; border-left: 24px solid #fff; border-top: 15px solid transparent; border-bottom: 15px solid transparent; margin-left: 6px; }

.apl-endcard { position: absolute; inset: 0; margin: auto; z-index: 8; width: 260px; height: 190px; border-radius: 18px; background: rgba(15,23,42,.9); backdrop-filter: blur(8px); display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; border: 1px solid rgba(148,163,184,.24); }
.apl-end-emoji { width: 52px; height: 52px; border-radius: 50%; background: rgba(34,197,94,.2); color: #4ade80; display: flex; align-items: center; justify-content: center; font-size: 26px; }
.apl-end-t { font-size: 16px; font-weight: 700; color: #f1f5f9; }
.apl-end-btn { padding: 8px 22px; border-radius: 999px; border: none; background: #2563eb; color: #fff; font-size: 14px; cursor: pointer; }

/* ── 悬浮 HUD：字幕 + 控制条，浮在画面底部（不占流式高度，画面能吃满屏） ── */
.apl-hud { position: absolute; z-index: 9; left: 0; right: 0; bottom: 0;
           display: flex; flex-direction: column; gap: 6px;
           padding: 64px 20px calc(14px + env(safe-area-inset-bottom, 0px));
           pointer-events: none;                                  /* 渐变区不吃点击，子元素单独开 */
           background: linear-gradient(0deg, rgba(6,14,30,.94) 0%, rgba(6,14,30,.82) 42%,
                                             rgba(6,14,30,.44) 74%, rgba(6,14,30,0) 100%); }
.apl-subtitle { flex: none; text-align: center; font-size: clamp(17px, 2.3vw, 27px); line-height: 1.5; letter-spacing: .5px;
                min-height: calc(2 * 1.5em); display: flex; align-items: center; justify-content: center; padding: 0 8px; }
.apl-word { color: rgba(255,255,255,.46); transition: color .18s, text-shadow .18s; text-shadow: 0 2px 10px rgba(0,0,0,.8); }
.apl-word.spoken { color: rgba(255,255,255,.95); }
.apl-word.active { color: #fff; text-shadow: 0 0 18px rgba(125,211,252,.95), 0 2px 8px rgba(0,0,0,.8); font-weight: 700; }
/* 前奏 / 句间只有背景音乐时的占位（人声常从第 2~3 秒才进，那段不是故障） */
.apl-word.apl-lead { color: rgba(255,255,255,.42); font-size: .78em; letter-spacing: 8px; animation: aplGlow 2.2s ease-in-out infinite; }
/* 内嵌时舞台矮，字幕跟着收一档，免得把画面压没了 */
.apl-embedded:not(.apl-fs) .apl-subtitle { font-size: clamp(14px, 1.6vw, 19px); }
.apl-embedded:not(.apl-fs) .apl-hud { padding: 42px 14px 10px; }

/* 控制条：一行 = 播放/暂停（同一个按钮，互斥）+ 已播时间 + 全片进度条 + 总时长 + 全屏 */
.apl-controls { flex: none; pointer-events: auto; display: flex; align-items: center; gap: 14px;
                max-width: 1080px; width: 100%; margin: 0 auto; padding: 2px 2px 4px;
                transition: opacity .28s ease; }
.apl-play { flex: none; width: 46px; height: 46px; border-radius: 50%; border: none;
            background: linear-gradient(135deg, #2563eb, #60a5fa); color: #fff; cursor: pointer;
            display: inline-flex; align-items: center; justify-content: center; padding: 0;
            box-shadow: 0 6px 18px rgba(37,99,235,.5); transition: transform .15s; }
.apl-play:hover { transform: scale(1.07); }
.apl-play svg { width: 20px; height: 20px; fill: #fff; }
.apl-embedded:not(.apl-fs) .apl-play { width: 38px; height: 38px; }
.apl-embedded:not(.apl-fs) .apl-play svg { width: 17px; height: 17px; }
.apl-time { flex: none; font-size: 12.5px; color: rgba(255,255,255,.9); font-variant-numeric: tabular-nums; width: 46px; text-align: center; text-shadow: 0 1px 6px rgba(0,0,0,.8); }
/* 全屏按钮（R3）：与播放键同一行的最右侧，全屏态图标换成「退出」 */
.apl-fsbtn { flex: none; width: 34px; height: 34px; border-radius: 9px; border: 1px solid rgba(255,255,255,.28);
             background: rgba(15,23,42,.5); color: #fff; cursor: pointer; padding: 0;
             display: inline-flex; align-items: center; justify-content: center; transition: background .15s, transform .15s; }
.apl-fsbtn:hover { background: rgba(37,99,235,.85); transform: scale(1.06); }
.apl-fsbtn svg { width: 17px; height: 17px; fill: #fff; }
/* 全片一条进度条：跨所有格连成一条，拖到哪里就跳到哪里。 */
.apl-track { position: relative; flex: 1; height: 8px; border-radius: 999px; background: rgba(255,255,255,.26); cursor: pointer; touch-action: none; }
.apl-track::before { content: ""; position: absolute; left: 0; right: 0; top: -14px; bottom: -14px; }
/* fill / knob / tick 一律 pointer-events:none */
.apl-track-fill { position: absolute; left: 0; top: 0; bottom: 0; border-radius: 999px; background: linear-gradient(90deg, #2563eb, #7dd3fc); pointer-events: none; }
.apl-track-knob { position: absolute; top: 50%; width: 16px; height: 16px; border-radius: 50%; background: #fff; border: 2px solid #60a5fa; box-shadow: 0 2px 10px rgba(0,0,0,.5); transform: translate(-50%, -50%); pointer-events: none; transition: transform .12s; }
.apl-track.dragging .apl-track-knob { transform: translate(-50%, -50%) scale(1.25); }
/* 刻度：细的 = 换一幅画，粗白的 = 换一格 */
.apl-tick { position: absolute; top: 50%; width: 2px; height: 8px; margin-left: -1px; border-radius: 1px; background: rgba(255,255,255,.42); transform: translateY(-50%); pointer-events: none; }
.apl-tick.page { width: 3px; height: 16px; margin-left: -1.5px; background: rgba(255,255,255,.92); }

/* 加载失败页：未登录 / 无权限 / 不存在 / 空内容 各说各的。
   ⛔ 这里**不许**再回落内置演示片 —— 那会让「没登录」看起来像「放了一部不认识的节目」。 */
.apl-err { min-height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 10px; padding: 32px 24px; text-align: center; color: #e8edf7; background: #0b1220; }
.apl-err-icon { font-size: 44px; line-height: 1; }
.apl-err-title { font-size: 20px; font-weight: 700; letter-spacing: .3px; }
.apl-err-desc { font-size: 14px; color: #9fb0cc; max-width: 30em; line-height: 1.75; }
.apl-err-btn { margin-top: 10px; display: inline-block; padding: 9px 26px; border-radius: 999px;
  background: #2f6bff; color: #fff; text-decoration: none; font-size: 14px; font-weight: 600; }
.apl-err-btn:hover { background: #4b7fff; }
.apl-err-ghost { margin-top: 6px; padding: 7px 18px; border-radius: 999px; cursor: pointer;
  background: transparent; color: #9fb0cc; border: 1px solid rgba(159,176,204,.35); font-size: 13px; }
.apl-err-ghost:hover { color: #e8edf7; border-color: rgba(159,176,204,.7); }
.apl-err-meta { margin-top: 18px; font-size: 12px; color: #6d7c94; word-break: break-all; max-width: 34em; }
.apl-err-url { margin-top: 4px; opacity: .8; }

/* 播放中静置 3s 收起控制条，字幕永远留着（它才是主体内容） */
.apl-chrome-off .apl-controls { opacity: 0; pointer-events: none; }
.apl-chrome-off .apl-top { opacity: 0; transition: opacity .28s ease; }
