  :root {
    /* 告诉浏览器/系统本页支持的明暗能力：滚动回弹区、表单控件、
       独立窗口状态栏文字颜色都据此绘制，缺了它深色页面会被按浅色处理。 */
    color-scheme: light;
    --bg: #f5f3ee;
    --text: #2b2b2b;
    --text-dim: #9a948a;
    --focus: #d9480f;
    --bar-bg: rgba(245, 243, 238, 0.8);
    --btn-bg: rgba(0, 0, 0, 0.06);
    --btn-text: #2b2b2b;
    --shadow: rgba(0, 0, 0, 0.12);
    /* 卡片描边专用：--shadow 是给投影用的（深色下更是半透明黑），
       拿来画边框会在米白底上压出一圈硬线。卡片本身已有毛玻璃和投影分层，
       边框只需要极轻的一点边界感，别让它抢戏。 */
    --card-border: rgba(0, 0, 0, 0.07);
    /* 顶栏底部分割线专用：比 --shadow 淡得多。顶栏有毛玻璃本身就在分层，
       用 --shadow 会在米白底上压出一道硬线，很突兀。 */
    --divider: rgba(0, 0, 0, 0.06);
    /* 封面懒加载骨架：底色比卡片再实一档才看得出「在占位」，扫光用高透明度白 */
    --skeleton-bg: rgba(0, 0, 0, 0.05);
    --skeleton-sheen: rgba(255, 255, 255, 0.75);
    /* 数字专用：等宽 / 等数字的西文字体栈，喜爱次数这种纯数字用它比宋体更利落。
       系统字体优先（零下载），一路退到通用 monospace；不随明暗变化，所以只在这里定义一次。 */
    --font-num: "SF Mono", SFMono-Regular, ui-monospace, "JetBrains Mono", Menlo, Consolas, monospace;
  }
  [data-theme="dark"] {
    color-scheme: dark;
    --bg: #15171a;
    --text: #e8e6e1;
    --text-dim: #6b6f76;
    --focus: #ffa94d;
    --bar-bg: rgba(21, 23, 26, 0.8);
    --btn-bg: rgba(255, 255, 255, 0.08);
    --btn-text: #e8e6e1;
    --shadow: rgba(0, 0, 0, 0.5);
    /* 深色底上黑色描边等于没有描边（只会把卡片边缘压暗），
       极低透明度的白线才是深色 UI 里"有边界但不刺眼"的做法。 */
    --card-border: rgba(255, 255, 255, 0.06);
    /* 深色底上黑色分割线只会"更黑"，低透明度白线才是深色 UI 的通行做法。 */
    --divider: rgba(255, 255, 255, 0.10);
    /* 深色下骨架靠一点点白提亮；扫光必须压低，否则黑底上会闪得像故障 */
    --skeleton-bg: rgba(255, 255, 255, 0.06);
    --skeleton-sheen: rgba(255, 255, 255, 0.13);
  }

  * { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
  html, body { margin: 0; height: 100%; }
  body {
    background: var(--bg);
    color: var(--text);
    /* 前台统一宋体（衬线）：系统宋体优先（苹果零下载）→ 自托管 Noto Serif SC 兜底 → 藏文 → 通用衬线。
       藏文字体必须排在中文字体之后，否则它 unicode-range 里的 U+2000-206F / U+3008-300B
       会截胡中文引号、破折号、书名号。 */
    font-family: "Songti SC", "STSong", "Source Han Serif SC", "Source Han Serif TC", "Noto Serif SC", "Noto Serif TC", "Noto Serif Tibetan", "Kailasa", "Microsoft Himalaya", "Noto Sans Tibetan", serif;
    /* 宋体笔画细，macOS/iOS 默认的次像素渲染会让横画发糊、整体发灰；
       关掉后笔画干净利落，接近印刷宋体的观感。 */
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    transition: background 0.4s ease, color 0.4s ease;
    overflow: hidden;
    touch-action: manipulation;
  }

  /* 表单控件不继承父级字体（浏览器默认给它们套系统字体），
     不显式继承的话，按钮里的中文仍是黑体，和周围宋体不搭。 */
  button, input, select, textarea { font-family: inherit; }

  /* 顶部栏 */
  .topbar {
    position: fixed;
    top: 0; left: 0; right: 0;
    height: 54px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0 14px;
    background: var(--bar-bg);
    backdrop-filter: saturate(180%) blur(12px);
    -webkit-backdrop-filter: saturate(180%) blur(12px);
    z-index: 20;
    border-bottom: 1px solid var(--divider);
  }
  /* 标题绝对定位居中。必须给 max-width：绝对定位元素没有宽度上限时会按内容撑宽，
     超长书名（如《药师琉璃光如来本愿功德经》）会一直铺到顶栏两端、压在左右按钮之上。
     100% 是顶栏的 padding box 宽（视口 - 左右各 14px 内边距），
     单侧避让 92px = 右侧按钮区最宽 84px（2 × 38px 按钮 + 8px 间距）再留 8px 呼吸，
     故减去 184px；放不下时由 text-overflow 出省略号。 */
  .title {
    position: absolute; left: 50%; transform: translateX(-50%);
    max-width: calc(100% - 184px);
    font-size: 17px; font-weight: 700; letter-spacing: 0.5px;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .avatar { width: 34px; height: 34px; border-radius: 50%; background: var(--focus); color: #fff; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 15px; text-decoration: none; flex-shrink: 0; cursor: pointer; transition: opacity .2s; overflow: hidden; }
  .avatar:hover { opacity: .9; }
  .avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
  /* margin-left: auto 是给「顶栏左侧没有元素」的页面兜底（如书架页去掉头像后），
     只有一个子元素时 space-between 会把它推到左边，靠这个保持右对齐。 */
  .actions { display: flex; gap: 8px; margin-left: auto; }
  .btn {
    border: none;
    background: var(--btn-bg);
    color: var(--btn-text);
    width: 38px; height: 38px;
    border-radius: 10px;
    font-size: 17px;
    cursor: pointer;
    display: flex; align-items: center; justify-content: center;
    transition: transform 0.15s ease, background 0.3s ease;
  }
  /* 右侧按钮列同款样式：课堂人数、分享、喜爱、放大、缩小（宽度与底色必须完全一致） */
  #classCountBtn, #bookShareBtn, #fontMinus, #fontPlus, #favBtn {
    width: 48px;
    font-size: 16px;
    font-weight: 600;
    background: #f2eee6;
    color: #2b2b2b;
  }
  [data-theme="dark"] #classCountBtn, [data-theme="dark"] #bookShareBtn, [data-theme="dark"] #fontMinus, [data-theme="dark"] #fontPlus, [data-theme="dark"] #favBtn {
    background: #3a3f47;
    color: #e8e6e1;
  }
  .btn:active { transform: scale(0.9); }
  .btn svg { width: 20px; height: 20px; }
  /* 已喜爱：爱心转实心 + 主题色（须排在深浅色规则之后；填充由 JS 换 .ico-fav-filled 承担，
     这里补的是颜色，两者合起来才是"点上了"的完整反馈） */
  #favBtn.on { color: var(--focus); }

  /* 课堂人数：按钮沿用 .btn 的规格，人数做成右上角标（角标要探出按钮外沿，故按钮 relative） */
  .class-count-btn { position: relative; }
  .class-count-n {
    position: absolute; top: -4px; right: -4px;
    min-width: 17px; height: 17px; padding: 0 4px; box-sizing: border-box;
    border-radius: 9px; background: var(--focus); color: #fff;
    font-size: 10px; line-height: 17px; font-weight: 600;
    display: flex; align-items: center; justify-content: center;
  }

  /* 阅读区 */
  .reader {
    position: absolute;
    top: 54px; bottom: 0;
    left: 0; right: 0;
    margin: 0 auto;
    width: 100%;
    max-width: 720px;
    overflow-y: auto;
    overflow-x: hidden;
    -webkit-overflow-scrolling: touch;
    /* 聚焦行的放大倍数。放大只用 transform（见 .line.active .line-text）：
       不重排、不改行盒高度、不改滚动高度，所以才能「跟着滚动逐行放大」而不卡。
       代价是放大后的字会向左右各多占一点，--zoom-room 就是这个「多占」，
       取 (倍率 - 1) / 2 再宽一点给最坏情况（整行排满）留量。
       想调放大力度只改 --zoom 一处，左右留白与行号间距会自动跟着变。 */
    --zoom: 1.12;
    --zoom-room: calc((var(--zoom) - 1) * 50%);
    /* 页面左侧留白线：与顶栏 .topbar 的 padding（0 14px）同值。
       行号列就从这条线起排，视觉上与顶部按钮的左边缘落在同一条竖线上。 */
    --gutter: 14px;
    /* 行号列宽（.line-no 的 width 共用这个值） */
    --no-col: 26px;
    padding: 40vh var(--zoom-room);
    padding-left: var(--gutter);
    /* 右侧留白必须等于左侧的实际占用（留白线 + 行号列 + 行号与正文的间距）。
       原来右边只有 6% 的放大留白，行号那一列在左边白占位，正文块整体就偏右。
       左右补平后，正文块的中心正好落在屏幕中线上。 */
    padding-right: calc(var(--gutter) + var(--no-col) + var(--zoom-room));
    line-height: 1.9;
    font-size: 18px;
    text-align: center;
    overflow-wrap: anywhere;
    word-break: break-word;
  }
  .reader::-webkit-scrollbar { display: none; }

  .reader p { margin: 0 0 0.6em; text-align: center; }

  /* 逐行阅读：行号 + 当前行聚焦放大 */
  .reader .line {
    display: flex;
    align-items: baseline;
    padding: 0.1em 0;
    color: var(--text);
    opacity: 0.5;
    /* 行本身仍然一条过渡都不留：滚动中每换一行都要重绘这一行，若每行还拖 0.2s 的
       颜色/不透明度补间，屏幕上会同时挂着十几行在过渡，等于每帧重绘整个视口，
       合成器本来只需平移已有画面，被这些重绘一掺和就变成一路拖影。
       所以高亮（颜色）保持瞬切，补间只留给下面 .line-text 的 transform —— 那是
       合成器属性，不重绘也不重排，可以放心地跟着滚动走。 */
  }
  .reader .line-no {
    flex: none;
    width: var(--no-col);
    /* 与正文之间留一段和左右留白同量的间距：放大的字会向左压过来，靠它挡在行号之外。
       右侧 padding 用同一组值补了对称留白，正文块因此仍然居中。 */
    margin-right: var(--zoom-room);
    /* 左对齐：个位/两位数字的左边缘都落在 --gutter 这条线上，与顶部按钮左边缘对齐 */
    text-align: left;
    font-size: 12px;
    color: var(--text-dim);
    opacity: 0.7;
    user-select: none;
  }
  .reader .line-text {
    flex: 1;
    /* 单行内容居中；折成多行的段落两端对齐，左右边缘齐平。
       只有一行时它同时是「最后一行」，text-align-last 因此也管得到它 ——
       于是单行自动居中、多行的末行也居中，整段看起来是一个四边齐整的居中块。
       justify 只对一行以上的段落起作用，单行永远不会被拉开字距。
       下面的默认值保持居中，真启用两端对齐的是紧随其后的 @supports 分支。 */
    text-align: center;
    /* 放大只动 transform。原先用 font-size 放大：字号一变，浏览器必须把整个 .reader
       重排一遍（几千行挨个重新定位），滚动中每换一行排一次，主线程必然追不上合成器，
       表现就是发涩 + 拖影；而且会改滚动高度，放大落下时下面的字会整体跳一下。
       transform 走合成：不重排、不改行盒高度、不改滚动高度，于是放大可以「滚动即到」，
       而不是等停稳再补一下（那种补一下反而更像卡了一下）。 */
    transform-origin: center center;
    transition: transform 0.16s ease-out;
  }
  /* 多行段落两端对齐 + 末行居中。text-align-last 是这套排版的开关：
     不认识它的老浏览器整块忽略，退回上面的全居中，不会把末行掉成左对齐。 */
  @supports (text-align-last: center) {
    .reader .line-text {
      text-align: justify;
      text-align-last: center;
    }
  }
  /* 聚焦：颜色瞬切负责跟手，transform 补间负责顺滑，两者落在同一行上同时发生 */
  .reader .line.active {
    color: var(--focus);
    opacity: 1;
    /* 抬一层：放大后的字会略微越出本行盒，避免被相邻行压住 */
    position: relative;
    z-index: 2;
  }
  .reader .line.active .line-no { color: var(--focus); opacity: 1; }
  .reader .line.active .line-text { transform: scale(var(--zoom)); }
  /* 改字号时临时禁用补间：那一下要按最终布局定位，不能停在过渡中间 */
  .reader.no-anim .line-text { transition: none; }
  /* 标记行：正文高亮为蓝色（含聚焦态） */
  .reader .line.marked .line-text { color: #2563eb; }
  /* 行内加粗（**文字**）：只加字重，颜色继承所在行 —— 蓝色标记行里的加粗仍是蓝的 */
  .reader .line-text strong { font-weight: 700; color: inherit; }
  /* 音频播放中：正文行可点击跳到该行音频，给出可点提示（未播放时不出现） */
  .reader[data-seek="1"] .line { cursor: pointer; }
  .reader[data-seek="1"] .line:hover:not(.active) { opacity: 0.85; }
  .reader .word {
    display: inline-block;
    color: var(--text-dim);
    opacity: 0.45;
    transform: scale(0.82);
    transform-origin: center center;
    transition: opacity 0.2s ease, color 0.2s ease;
    will-change: transform, opacity;
  }
  /* 为放大预留水平空间，避免焦点字压到上下文 */
  .reader.zh .word { padding: 0 0.25em; margin: 0 0.06em; }
  .reader.en .word { padding: 0 0.45em; margin: 0 0.1em; }
  .reader .space { display: inline-block; width: 0.28em; }

  /* 自动滚动控制：左下收起为播放按钮，点击展开速度调节面板，调节完自动收起 */
  .controls {
    position: fixed;
    bottom: 28px; left: 10px;
    display: flex;
    align-items: center;
    gap: 10px;
    z-index: 20;
    transition: transform 0.3s ease, opacity 0.3s ease;
  }
  /* 向下滚动隐藏：向左滑出屏幕外 */
  .controls.hidden { transform: translateX(-130%); opacity: 0; pointer-events: none; }
  .controls.expanded .speed {
    max-width: 320px;
    opacity: 1;
    transform: translateX(0);
    padding: 0 14px;
  }
  /* 屏幕右侧按钮列（喜爱 / 字体放大 / 字体缩小）：底部固定，上下垂直排列（上=喜爱，中=加大，下=减小） */
  .font-stack {
    position: fixed;
    bottom: 28px;
    right: 10px;
    display: flex;
    flex-direction: column;
    gap: 10px;
    z-index: 30;
    transition: transform 0.3s ease, opacity 0.3s ease;
  }
  /* 向下滚动隐藏：向右滑出屏幕外 */
  .font-stack.hidden { transform: translateX(130%); opacity: 0; pointer-events: none; }
  .play-btn {
    width: 40px; height: 40px;
    border-radius: 50%;
    border: none;
    background: var(--focus);
    color: #fff;
    font-size: 17px;
    font-weight: 700;
    cursor: pointer;
    display: flex; align-items: center; justify-content: center;
    transition: transform 0.15s ease;
  }
  .play-btn:active { transform: scale(0.9); }
  /* 左下按钮列：音乐按钮（上）叠在滚动播放按钮（下）上方 */
  .ctrl-col {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 10px;
  }
  .music-btn {
    width: 40px; height: 40px;
    border-radius: 50%;
    border: none;
    background: var(--bar-bg);
    color: var(--focus);
    font-size: 16px;
    cursor: pointer;
    display: flex; align-items: center; justify-content: center;
    box-shadow: 0 4px 16px var(--shadow);
    backdrop-filter: saturate(180%) blur(12px);
    -webkit-backdrop-filter: saturate(180%) blur(12px);
    transition: transform 0.15s ease;
  }
  .music-btn:active { transform: scale(0.9); }
  .music-btn svg { width: 20px; height: 20px; }
  /* 电频图标：播放时三根柱条动态跳动，暂停时静止 */
  .eq-bar {
    transform-box: fill-box;
    transform-origin: center;
  }
  .music-btn.playing .eq-bar { animation: eq-bounce 1s ease-in-out infinite; }
  .music-btn.playing .eq-bar:nth-child(1) { animation-delay: -0.15s; animation-duration: 0.8s; }
  .music-btn.playing .eq-bar:nth-child(2) { animation-delay: -0.55s; animation-duration: 1.1s; }
  .music-btn.playing .eq-bar:nth-child(3) { animation-delay: -0.35s; animation-duration: 0.9s; }
  @keyframes eq-bounce {
    0%, 100% { transform: scaleY(0.35); }
    50%      { transform: scaleY(1); }
  }
  /* 「回到音频位置」浮标：跟随被手动滚动打断后出现，点击恢复跟随 */
  .follow-btn {
    width: 40px; height: 40px;
    border-radius: 50%;
    border: none;
    background: var(--bar-bg);
    color: var(--focus);
    cursor: pointer;
    display: flex; align-items: center; justify-content: center;
    box-shadow: 0 4px 16px var(--shadow);
    backdrop-filter: saturate(180%) blur(12px);
    -webkit-backdrop-filter: saturate(180%) blur(12px);
    transition: transform 0.15s ease;
  }
  .follow-btn:active { transform: scale(0.9); }
  .follow-btn svg { width: 20px; height: 20px; }
  /* 课堂同步浮标：老师正在上课时脉冲提示；已进入同步则实心高亮 */
  @keyframes live-pulse {
    0%, 100% { box-shadow: 0 4px 16px var(--shadow); }
    50%      { box-shadow: 0 4px 22px var(--focus); }
  }
  .follow-btn[data-live] { animation: live-pulse 1.8s ease-in-out infinite; }
  .follow-btn.on { background: var(--focus); color: var(--bar-bg); animation: none; }
  .speed {
    display: flex; align-items: center; gap: 6px;
    height: 44px;
    background: var(--bar-bg);
    backdrop-filter: saturate(180%) blur(12px);
    -webkit-backdrop-filter: saturate(180%) blur(12px);
    border-radius: 30px;
    box-shadow: 0 6px 24px var(--shadow);
    color: var(--text); font-size: 13px;
    max-width: 0; opacity: 0; overflow: hidden; white-space: nowrap;
    padding: 0;
    transform: translateX(-10px);
    transition: max-width .35s ease, opacity .35s ease, transform .35s ease, padding .35s ease;
  }
  .speed input { width: 90px; accent-color: var(--focus); }
  .speed-val { flex: none; min-width: 34px; text-align: center; font-variant-numeric: tabular-nums; color: var(--focus); font-weight: 600; }

  /* 书架 */
  .shelf {
    position: absolute;
    top: 54px; bottom: 0;
    left: 50%;
    transform: translateX(-50%);
    width: 100%;
    max-width: 960px;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    /* 底部留白须大于 Dock 总高（56 + 18 安全距离），避免最后一行书卡被压住 */
    padding: 18px 16px 110px;
    align-content: start;
  }
  /* 真正承载书卡的网格（.shelf 为滚动容器，刷新指示器 ptr 同处其中） */
  .shelf-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(108px, 1fr));
    gap: 20px 14px;
  }
  .shelf::-webkit-scrollbar { display: none; }
  /* 下拉刷新指示器：固定在滚动容器顶部，高度由手势控制 */
  .ptr { height: 0; overflow: hidden; color: var(--text-dim); font-size: 12px; transition: height .25s ease; }
  .ptr .ptr-text { display: flex; align-items: center; justify-content: center; height: 50px; }
  .book { cursor: pointer; display: flex; flex-direction: column; gap: 8px; }
  .cover {
    width: 100%;
    aspect-ratio: 3 / 4;
    border-radius: 10px;
    box-shadow: 0 6px 18px var(--shadow);
    position: relative;
    overflow: hidden;
    display: flex;
    align-items: flex-end;
    padding: 12px;
    color: #fff;
    transition: transform 0.15s ease;
  }
  .book:active .cover { transform: scale(0.97); }
  .cover img.cover-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
  .cover-placeholder { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; background: var(--bg-card-hover, #18181d); color: var(--text-dim, #71717a); font-size: 12px; }
  /* ── 封面懒加载：骨架 + 扫光 ──
     约定「封面图设了 loading="lazy" 即生效」，由 lib/cover-skeleton.js 接管，卡片构建代码不用改：
     取图期间图 .cover-lazy（透明，露出下面的骨架）、容器 .is-cover-loading（底色 + 循环扫光）；
     出图后摘掉骨架、图补 .cover-ready 淡入。class 一没动画就停，不会在出图后空转。
     扫光走 transform（合成层），不用 background-position —— 后者每帧重绘，滚动格子时容易掉帧。
     注意：骨架由脚本延迟 150ms 才亮 —— 封面是 30 天强缓存的静态图，命中缓存时不该闪。 */
  .is-cover-loading { background-color: var(--skeleton-bg); }
  .is-cover-loading::after {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: linear-gradient(100deg, transparent 30%, var(--skeleton-sheen) 50%, transparent 70%);
    transform: translateX(-100%);
    animation: cover-sweep 1.4s ease-in-out infinite;
  }
  @keyframes cover-sweep {
    from { transform: translateX(-100%); }
    to   { transform: translateX(100%); }
  }
  img.cover-lazy { opacity: 0; }
  img.cover-lazy.cover-ready { opacity: 1; transition: opacity 0.35s ease; }
  @media (prefers-reduced-motion: reduce) {
    .is-cover-loading::after { animation: none; }   /* 关掉扫光，只留静态骨架 */
  }
  .cover-title {
    font-size: 15px;
    font-weight: 700;
    line-height: 1.3;
    text-align: center;
    text-shadow: 0 1px 4px rgba(0,0,0,0.35);
  }
  .book .meta { padding: 0 2px; text-align: center; }
  .book-title {
    font-size: 13px; font-weight: 700; color: var(--text);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .book-author { font-size: 11px; color: var(--text-dim); margin-top: 2px; }

  /* ── 书架底部 Dock：分类（当前分类首字）/ 音频 / AI（居中）/ 喜爱 / 我的（日历入口在个人中心顶栏） ── */
  .dock {
    position: fixed;
    bottom: 18px;
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 7px 9px;
    border-radius: 26px;
    background: var(--bar-bg);
    backdrop-filter: saturate(180%) blur(12px);
    -webkit-backdrop-filter: saturate(180%) blur(12px);
    border: 1px solid var(--divider);
    box-shadow: 0 8px 28px var(--shadow);
    z-index: 20;
  }
  /* a / button 两种标签共用同一外观，全部圆形 */
  .dock-item {
    position: relative;
    width: 44px; height: 44px;
    flex-shrink: 0;
    padding: 0;
    border: none;
    border-radius: 50%;
    background: transparent;
    color: var(--text-dim);
    font-family: inherit;
    text-decoration: none;
    cursor: pointer;
    display: flex; align-items: center; justify-content: center;
    transition: background 0.2s ease, color 0.2s ease, transform 0.15s ease;
  }
  .dock-item svg { width: 20px; height: 20px; display: block; }
  .dock-item:hover { background: var(--btn-bg); color: var(--text); }
  .dock-item:active { transform: scale(0.9); }
  /* 分类项：沿用原文字按钮外观（显示当前分类首字），点击在已有分类间循环切换 */
  .dock-item.cat {
    color: var(--text);
    font-size: 17px;
    font-weight: 700;
  }
  /* 中间 AI：比其他项大一圈，强调色实心并略微上浮。点击进入 AI 对话页 */
  .dock-item.ai {
    width: 56px; height: 56px;
    margin: 0 3px;
    background: var(--focus);
    color: #fff;
    box-shadow: 0 6px 18px rgba(217, 72, 15, 0.32);
    transform: translateY(-4px);
  }
  .dock-item.ai svg { width: 38px; height: 38px; }
  .dock-item.ai:hover { background: var(--focus); color: #fff; opacity: 0.92; }
  .dock-item.ai:active { transform: translateY(-4px) scale(0.9); }
  /* 深色下 --focus 是亮橙，白图标对比不足，改用深色图标 */
  [data-theme="dark"] .dock-item.ai {
    color: #15171a;
    box-shadow: 0 6px 18px rgba(255, 169, 77, 0.30);
  }
  /* 书架空状态（如"该分类下暂无书籍"）：跨满整行、水平居中，并适度下移避免贴顶 */
  .shelf-grid .empty-tip {
    grid-column: 1 / -1;
    justify-self: center;
    width: 100%;
    padding: 22vh 20px 40px;
  }

  /* ── 搜索浮层（左上角搜索按钮）：顶部输入框 + 结果网格 ── */
  .search-overlay {
    position: fixed; inset: 0;
    z-index: 40;                      /* 盖住顶栏与 Dock，但低于弹窗(50) */
    display: flex; flex-direction: column;
    background: var(--bg);
  }
  .search-bar {
    flex: none;
    height: 54px;
    display: flex; align-items: center; gap: 8px;
    padding: 0 14px;
    background: var(--bar-bg);
    backdrop-filter: saturate(180%) blur(12px);
    -webkit-backdrop-filter: saturate(180%) blur(12px);
    border-bottom: 1px solid var(--divider);
  }
  .search-ico { width: 17px; height: 17px; flex: none; color: var(--text-dim); }
  .search-bar input {
    flex: 1; min-width: 0;
    height: 36px;
    padding: 0 12px;
    border: 1px solid transparent;
    border-radius: 10px;
    background: var(--btn-bg);
    color: var(--text);
    font-size: 16px;                  /* 小于 16px 时 iOS 聚焦会自动放大整页 */
    outline: none;
    -webkit-appearance: none;
  }
  .search-bar input::placeholder { color: var(--text-dim); }
  .search-bar input:focus { border-color: var(--focus); }
  /* 隐藏 Safari 原生清除按钮，避免和「取消」挤在一起 */
  .search-bar input::-webkit-search-cancel-button { -webkit-appearance: none; }
  .search-cancel {
    flex: none;
    border: none; background: none;
    color: var(--focus);
    font-size: 15px; font-weight: 600;
    cursor: pointer;
    padding: 6px 2px;
  }
  .search-body {
    flex: 1;
    width: 100%;
    max-width: 960px;
    margin: 0 auto;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    padding: 18px 16px 40px;
  }
  .search-body::-webkit-scrollbar { display: none; }
  /* 结果网格沿用书架样式；空状态不需要书架那样的大留白 */
  .search-grid .empty-tip { padding: 56px 20px; }

  /* 开课按钮（盾牌图标） */
  .class-btn { color: var(--focus); }

  /* 弹窗 */
  .modal-overlay {
    position: fixed; inset: 0;
    background: rgba(0, 0, 0, 0.5);
    display: flex; align-items: center; justify-content: center;
    z-index: 50; padding: 20px;
  }
  .modal {
    width: 100%; max-width: 320px;
    background: var(--bar-bg);
    backdrop-filter: saturate(180%) blur(20px);
    -webkit-backdrop-filter: saturate(180%) blur(20px);
    border: 1px solid var(--card-border);
    border-radius: 16px;
    padding: 22px 20px;
    text-align: center;
    box-shadow: 0 12px 40px var(--shadow);
  }
  /* 开课弹窗要放下「选择课程」下拉，比通用弹窗宽一些 */
  .modal-wide { max-width: 380px; }
  .modal h3 { margin: 0 0 14px; font-size: 17px; }
  .modal-sub { font-size: 13px; color: var(--text-dim); margin: -8px 0 14px; }
  .class-code-box { margin: 10px 0 18px; }
  .class-code {
    font-family: "SF Mono", "Fira Code", "Cascadia Code", monospace;
    font-size: 34px; font-weight: 700; letter-spacing: 6px; color: var(--focus);
  }
  .class-hint { font-size: 12px; color: var(--text-dim); margin-top: 8px; }
  /* 开课弹窗：绑定书籍下拉 */
  .class-pick { display: flex; align-items: center; gap: 10px; margin: 4px 0 14px; text-align: left; }
  .class-pick > label { font-size: 13px; color: var(--text-dim); white-space: nowrap; }
  .class-pick > select {
    flex: 1; min-width: 0; height: 40px; padding: 0 10px;
    border-radius: 10px; border: 1px solid var(--shadow);
    background: var(--bg); color: var(--text); font-size: 14px;
  }
  .modal-input {
    width: 100%; height: 46px; box-sizing: border-box;
    border-radius: 10px; border: 1px solid var(--shadow);
    background: var(--bg); color: var(--text);
    font-size: 20px; text-align: center; letter-spacing: 4px;
    font-family: "SF Mono", "Fira Code", "Cascadia Code", monospace;
    outline: none;
  }
  .modal-input:focus { border-color: var(--focus); }
  .modal-error { color: #f87171; font-size: 13px; min-height: 18px; margin-top: 8px; }
  .modal-actions { display: flex; gap: 10px; margin-top: 16px; }
  .btn-primary, .btn-secondary {
    flex: 1; height: 44px; border: none; border-radius: 10px;
    font-size: 15px; font-weight: 600; cursor: pointer;
  }
  .btn-primary { background: var(--focus); color: #fff; }
  .btn-secondary { background: var(--btn-bg); color: var(--btn-text); }
  .btn-primary:disabled, .btn-secondary:disabled { opacity: .5; cursor: not-allowed; }
  .modal-close {
    margin-top: 22px; background: none; border: none; color: var(--text-dim);
    font-size: 13px; cursor: pointer;
  }
  /* 开课弹窗内的「加入他人的课堂」：默认折叠，点标题才展开，免得弹窗太长 */
  .class-join { margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--shadow); text-align: left; }
  .class-join > summary {
    display: flex; align-items: center; justify-content: space-between;
    font-size: 13px; color: var(--text-dim); cursor: pointer;
    list-style: none; user-select: none;
  }
  .class-join > summary::-webkit-details-marker { display: none; }
  .join-arrow { width: 16px; height: 16px; flex: 0 0 16px; transition: transform .18s ease; }
  .class-join[open] > summary .join-arrow { transform: rotate(180deg); }
  .class-join-row { display: flex; gap: 10px; align-items: center; margin-top: 12px; }
  .class-join-row > .modal-input { flex: 1; min-width: 0; height: 44px; font-size: 18px; }
  .class-join-row > .btn-secondary { flex: 0 0 84px; height: 44px; }
  .class-join-in { display: flex; gap: 10px; align-items: center; justify-content: space-between; margin-top: 12px; }
  .class-join-in > span { font-size: 13px; color: var(--text-dim); }
  .class-join-in > .btn-secondary { flex: 0 0 auto; padding: 0 16px; height: 38px; font-size: 14px; }

  /* 加载失败 / 空状态提示（不回退静态内容） */
  .empty-tip {
    padding: 48px 20px;
    text-align: center;
    color: var(--text-dim);
    font-size: 15px;
    line-height: 1.6;
  }

  /* 添加到手机桌面引导 */
  .a2hs-icon { width: 64px; height: 64px; border-radius: 14px; display: block; margin: 0 auto 12px; }
  .a2hs-modal { max-width: 340px; }
  .a2hs-steps { text-align: left; font-size: 13px; color: var(--text-dim); line-height: 1.75; margin: 2px 0 4px; }
  .a2hs-steps > div { margin-bottom: 8px; }
  .a2hs-steps > div:last-child { margin-bottom: 0; }
  .a2hs-steps .n {
    display: inline-block; width: 18px; height: 18px; border-radius: 50%;
    background: var(--focus); color: #fff; font-size: 11px; font-weight: 600;
    line-height: 18px; text-align: center; margin-right: 6px; vertical-align: 1px;
  }
  .a2hs-steps b { color: var(--text); font-weight: 600; }

  /* ── 引导注册页（深色）── */
  body.guide-page {
    background: #0e1013;
    color: #e8e6e1;
    margin: 0;
  }
  .guide-wrap {
    position: relative;
    min-height: 100vh;
    display: flex; flex-direction: column;
    align-items: center; justify-content: center;
    padding: 0 24px;
    box-sizing: border-box;
  }
  /* 中上部 Logo + 文案 */
  .guide-logo {
    position: absolute; top: 7vh; left: 0; right: 0;
    display: flex; flex-direction: column; align-items: center; gap: 12px;
    z-index: 2; pointer-events: none;
  }
  .guide-logo-img {
    width: 84px; height: 84px; border-radius: 18px;
    object-fit: cover; box-shadow: 0 6px 24px rgba(0,0,0,.35);
  }
  .guide-logo-text {
    font-family: "Noto Serif SC", serif; font-size: 30px; font-weight: 700;
    letter-spacing: 4px; color: #f2efe9;
  }
  .guide-tagline {
    font-size: 15px; letter-spacing: 3px; color: rgba(232, 230, 225, .7);
  }
  /* 摘录流：占满全屏（除底部按钮区），多行从右往左滑入、停留、再滑出消失（JS 错峰生成） */
  .guide-stream {
    position: relative;
    width: 100%; max-width: 560px;
    flex: 1;
    overflow: hidden;
  }
  @keyframes guideFlow {
    0%   { opacity: 0; transform: translateX(64px); }
    15%  { opacity: 1; transform: translateX(0); }
    100% { opacity: 1; transform: translateX(0); }   /* 停留；退场交由粒子动画 */
  }
  /* 外层只负责竖向定位（按 JS 给的 top 错开，避免多条文字重叠），内层做从右往左滑入滑出 */
  .guide-line {
    position: absolute; left: 0; right: 0; top: 50%;
    transform: translateY(-50%);
    text-align: center;
    padding: 0 10%;
    pointer-events: none;
  }
  .guide-line-inner {
    display: inline-block;
    font-family: "Noto Serif SC", serif;
    font-size: 23px; line-height: 1.8; letter-spacing: 2px;
    color: rgba(232, 230, 225, .92);
    opacity: 0;
    animation: guideFlow 4.2s ease forwards;
  }
  /* 粒子消散层：文字退场时被采样成粒子，随风（向右）向上飘散 */
  .guide-particles {
    position: fixed; inset: 0; z-index: 5; pointer-events: none;
  }
  .guide-foot {
    position: fixed; left: 0; right: 0; bottom: 0;
    padding: 18px 24px calc(18px + env(safe-area-inset-bottom));
    display: flex; flex-direction: column; align-items: center; gap: 10px;
    background: linear-gradient(to top, rgba(14, 16, 19, .96), rgba(14, 16, 19, 0));
  }
  .guide-cta {
    width: 100%; max-width: 360px; height: 50px;
    border: none; border-radius: 25px;
    background: linear-gradient(135deg, #c9a36a, #e6c894);
    color: #2a1f0e; font-size: 16px; font-weight: 700; letter-spacing: 1px;
    cursor: pointer;
  }
  .guide-cta:active { transform: scale(.98); }
  .guide-hint { font-size: 13px; color: rgba(232, 230, 225, .55); }
  .guide-hint a { color: #e6c894; text-decoration: none; }
  .guide-field { text-align: left; margin-bottom: 12px; }
  .guide-field label {
    display: block; font-size: 13px; color: var(--text-dim); margin-bottom: 6px;
  }
  .guide-field input {
    width: 100%; height: 44px; box-sizing: border-box;
    border-radius: 10px; border: 1px solid var(--shadow);
    background: var(--bg); color: var(--text);
    font-size: 15px; padding: 0 14px; outline: none;
  }
  .guide-field input:focus { border-color: var(--focus); }
  .guide-submit {
    width: 100%; height: 46px; margin-top: 4px;
    border: none; border-radius: 10px;
    background: var(--focus); color: #fff;
    font-size: 15px; font-weight: 600; cursor: pointer;
  }
  .guide-submit:disabled { opacity: .5; cursor: not-allowed; }

  /* ── 喜爱页（书 / 音频 / 合集 三个 Tab）── */
  /* 底部留白要比贴底悬浮的 Tab 条再高一截（条 ~36 高 + 10 底距 + 富余），最后一行才不被压住 */
  .bm-shelf { padding: 18px 16px 84px; }
  /* 类型切换（阅读 / 音频 / 合集）：贴底悬浮的一条。
     宽度口径与全局迷你播放器对齐（lib/player.js 的 MINI_W = 280）：左右各让 12px、上限 280px，
     两者同屏时同宽居中，看着像一套东西。层级刻意低于迷你条（25），万一被拖过来也与它叠出层次。
     有迷你条时不用自己让位：它把 .bm-tabs 当作贴底元素之一（见 lib/player.js 的 BOTTOM_SEL），
     自己停在条的上方 —— Tab 始终钉在底部，不做上下位移。
     外层不画底色也不带留白 —— 悬浮那块底连同内边距全交给 .bm-tab-group。 */
  .bm-tabs {
    position: fixed;
    left: 50%;
    bottom: calc(10px + env(safe-area-inset-bottom, 0px));
    transform: translateX(-50%);
    z-index: 20;
    display: flex;
    width: calc(100% - 24px);
    max-width: 280px;            /* = MINI_W（展开态宽度），改它记得同步 lib/player.js */
    padding: 0;
  }
  .bm-tab-group {
    flex: 1; min-width: 0;
    display: flex; gap: 0;              /* 三个 Tab 贴在一起，中间不再有缝 */
    padding: 3px;
    border: 1px solid var(--card-border);
    border-radius: 13px;
    background: var(--bar-bg);
    box-shadow: 0 4px 14px var(--shadow);
    backdrop-filter: saturate(180%) blur(12px);
    -webkit-backdrop-filter: saturate(180%) blur(12px);
  }
  .bm-tab {
    flex: 1; min-width: 0;
    height: 30px;
    display: flex; align-items: center; justify-content: center; gap: 4px;
    border: 0;                          /* 描边由组统一给，Tab 自身不再各自画一圈 */
    border-radius: 10px;
    background: transparent;
    color: var(--text-dim);
    font-family: inherit; font-size: 13px;
    cursor: pointer;
    transition: color .15s ease, background .15s ease;
  }
  .bm-tab:active { transform: scale(0.97); }
  .bm-tab.on { color: var(--focus); background: var(--btn-bg); font-weight: 600; }
  .bm-tab .n { font-size: 11px; opacity: .6; font-variant-numeric: tabular-nums; }
  .bm-tab.on .n { opacity: 1; }
  .bm-list { display: flex; flex-direction: column; gap: 12px; }
  .bm-item {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px;
    border-radius: 14px;
    background: var(--bar-bg);
    border: 1px solid var(--card-border);
    box-shadow: 0 4px 16px var(--shadow);
    backdrop-filter: saturate(180%) blur(12px);
    -webkit-backdrop-filter: saturate(180%) blur(12px);
    text-decoration: none;
    color: var(--text);
    transition: transform 0.15s ease;
  }
  .bm-item:active { transform: scale(0.98); }
  .bm-cover {
    flex: none;
    position: relative;   /* 扫光 ::after 的定位基准 */
    width: 52px; height: 70px;
    border-radius: 8px;
    overflow: hidden;
    background: var(--btn-bg);
    color: var(--text-dim);
    font-size: 20px;
    display: flex; align-items: center; justify-content: center;
  }
  .bm-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
  /* 音频 / 合集的封面是 1:1（播客惯例），按 3:4 会把上下裁掉一截 */
  .bm-cover.sq { width: 52px; height: 52px; }
  .bm-info { flex: 1; min-width: 0; }
  .bm-title {
    font-size: 14px; font-weight: 700; color: var(--text);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .bm-author {
    font-size: 11px; color: var(--text-dim); margin-top: 3px;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  /* 阅读进度条 + 百分比 */
  .bm-progress { display: flex; align-items: center; gap: 8px; margin-top: 10px; }
  .bm-bar {
    flex: 1;
    height: 4px;
    border-radius: 2px;
    background: var(--btn-bg);
    overflow: hidden;
  }
  .bm-bar > span {
    display: block;
    height: 100%;
    border-radius: 2px;
    background: var(--focus);
    transition: width 0.3s ease;
  }
  .bm-pct { flex: none; font-size: 11px; color: var(--text-dim); font-variant-numeric: tabular-nums; }
  .bm-pct .hl { color: var(--focus); font-weight: 600; }
  .bm-list .empty-tip { padding-top: 22vh; animation: bmEmptyIn 0.3s ease both; }
  .bm-empty-sub { font-size: 13px; }

  /* ── 喜爱按钮（爱心在上，次数在下）──
     音频播放页 / 合集详情页共用（见 lib/fav.js）。无底色、无边框、无胶囊，
     落在封面 / 氛围底上时就是一枚悬着的爱心 + 下面一行小数字。
     已喜爱：染主题色 + 把描边补成实心，1.28 倍的弹一下给点反馈 ——
     比单纯换色更能说明"这一下点到了"。 */
  .fav-btn {
    display: inline-flex; flex-direction: column; align-items: center; gap: 1px;
    padding: 0;
    border: 0;
    background: transparent;
    color: var(--text-dim);
    font-family: inherit; font-size: 13px;
    cursor: pointer;
    transition: color .15s ease, transform .12s ease;
  }
  .fav-btn svg { width: 20px; height: 20px; display: block; }
  .fav-btn:active { transform: scale(0.92); }
  .fav-btn:disabled { opacity: .55; }
  .fav-btn.on { color: var(--focus); }
  .fav-btn.on .fav-ico { fill: currentColor; animation: favPop 0.32s ease; }
  /* 次数独立一行：数字用等宽字体栈，比正文字号再小一档 */
  .fav-count {
    min-width: 0;
    font-family: var(--font-num);
    font-size: 10px;
    line-height: 1;
    letter-spacing: .02em;
    font-variant-numeric: tabular-nums;
    opacity: .85;
  }
  .fav-count:empty { display: none; }   /* 还没数据时别占一行，爱心居中不被顶偏 */
  @keyframes favPop {
    0%   { transform: scale(1); }
    40%  { transform: scale(1.28); }
    100% { transform: scale(1); }
  }

  /* 滑动删除：.bm-row 是裁剪容器兼阴影载体，.bm-item 左滑让出右侧 .bm-del */
  .bm-row {
    position: relative;
    border-radius: 14px;
    overflow: hidden;              /* 裁掉滑出行的卡片，并在收起动画时剪掉内部内容 */
    box-shadow: 0 4px 16px var(--shadow);
  }
  .bm-row > .bm-item {
    position: relative;
    z-index: 1;
    box-shadow: none;              /* 阴影移到 .bm-row，否则会被自身 overflow 裁掉 */
    will-change: transform;
    -webkit-user-drag: none;
    -webkit-touch-callout: none;   /* 长按不弹链接菜单，避免干扰滑动 */
    user-select: none;
  }
  /* 删除键铺满整行、文字贴右（相当于原来 84px 那一列的居中位置），
     再用 clip-path 只露出被卡片让开的那条 —— 卡片背景是半透明的（--bar-bg），
     裸露的删除键会透出粉色，所以不能靠"被遮住"，只能"裁掉"。
     左裁切量 = 卡片右边缘的位置，由 JS 写入（0 = 整行变红）。 */
  .bm-del {
    position: absolute;
    top: 0; right: 0; bottom: 0; left: 0;
    display: flex; align-items: center; justify-content: flex-end;
    padding-right: 28px;
    background: #e5484d;
    color: #fff;
    font-size: 13px;
    font-weight: 600;
    letter-spacing: 1px;
    cursor: pointer;
    clip-path: inset(0 0 0 100%);
  }

  /* 卡片消失的动画值由 JS 直接写在行 / 卡片 / 删除键的 inline 样式上
     （起点得先量一次 offsetHeight 把 auto 落成像素值，才有可过渡的起点，
     所以不写在这里），.removing 只作为"这一行正在消失"的标记：
     双击去重，并让正在消失的行吞掉点击，免得穿透到下层卡片。 */

  /* 删空后的空状态淡入，接住卡片消失的收尾 */
  @keyframes bmEmptyIn {
    from { opacity: 0; transform: translateY(6px); }
    to   { opacity: 1; transform: none; }
  }

  /* ── 居中轻提示（Toast）── */
  .toast {
    position: fixed;
    left: 50%; top: 50%;
    transform: translate(-50%, -50%) scale(0.92);
    max-width: 76vw;
    padding: 12px 20px;
    border-radius: 12px;
    background: rgba(0, 0, 0, 0.82);
    color: #fff;
    font-size: 14px;
    line-height: 1.45;
    text-align: center;
    opacity: 0;
    pointer-events: none;
    z-index: 500;
    transition: opacity 0.22s ease, transform 0.22s ease;
  }
  .toast.show { opacity: 1; transform: translate(-50%, -50%) scale(1); }
  [data-theme="dark"] .toast { background: rgba(245, 245, 247, 0.92); color: #1c1c1e; }

  /* ══════════════════════════════════════════════
     音频（播客）：列表页
     卡片直接复用书架的 .shelf-grid / .book / .cover，仅补两处差异
     ══════════════════════════════════════════════ */
  .pod-shelf { padding-bottom: 110px; }   /* 本页也有 Dock，与书架留同样高度的底部让位 */
  .pod-card { text-decoration: none; color: inherit; }
  /* 副标题取后台「副标题」字段（未填时退回简介首行），可能很长：单行截断，保证卡面高度与书籍一致 */
  .pod-card .book-author { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

  /* ── 音频合集卡 ──
     列表页把合集排在单集之前；合集卡仍是书架 .book 结构，只把封面从 3:4 换成 1:1
     （播客封面惯例），让"合集"和"单集"一眼可分 */
  .pod-album .cover { aspect-ratio: 1 / 1; }

  /* ── 合集卡的一行容器 ──
     宽屏下与 .shelf-grid 用同一套列定义与间距，看起来仍是一整片网格；
     窄屏（≤ 640）下退成单行左右滑动，让合集只占一行高度，单集列表能尽早露出来。 */
  .pod-album-row {
    grid-column: 1 / -1;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(108px, 1fr));
    gap: 20px 14px;
  }
  @media (max-width: 640px) {
    .pod-album-row {
      display: flex;
      gap: 0 14px;
      overflow-x: auto;
      -webkit-overflow-scrolling: touch;
      overscroll-behavior-x: contain;   /* 滑到头不再把回弹传染给整页 */
      scroll-snap-type: x proximity;
      scrollbar-width: none;
      /* 吸附起点也要内缩同样的 16px：snapport 默认从 padding box 起点算，
         卡片的 scroll-snap-align: start 会把首卡吸到容器最左，把 padding 整个吃掉
         （实测 scrollLeft 会被吸附成 16，首卡贴在屏幕边缘）。只内缩起始侧，
         末尾侧保持 0，末卡仍能滑到贴边。改 .shelf 内边距时这里要同步。 */
      scroll-padding-inline-start: 16px;
      /* 破出 .shelf 的 16px 内边距：首卡与列表左对齐，末卡能滑到贴边，
         滑到底时右端不再留一截空白。改 .shelf 内边距时这里要同步。 */
      margin-inline: -16px;
      padding-inline: 16px;
    }
    .pod-album-row::-webkit-scrollbar { display: none; }
    /* 卡宽固定，不再被 flex 压缩；与列表卡同宽，保证两侧封面成列 */
    .pod-album-row > .pod-card {
      flex: none;
      width: 108px;
      scroll-snap-align: start;
    }
  }

  /* 网格里的分区小标题（合集 / 单集）：跨满整行。
     左右不留内缩 —— 与网格列起点（也就是下方卡片封面的左边缘）严格对齐；
     窄屏下合集行靠破出 .shelf 内边距回到同一列起点（见 .pod-album-row），同样对得上。 */
  .pod-section-head {
    grid-column: 1 / -1;
    font-size: 16px;
    font-weight: 700;
    letter-spacing: 0.06em;
    color: var(--text);
    margin: 6px 0 -2px;
  }

  /* 合集页：头部（封面 + 标题 + 副标题）与集列表 */
  .album-head { display: flex; gap: 16px; align-items: flex-start; padding: 4px 2px 16px; }
  .album-cover {
    flex: none; width: 108px; aspect-ratio: 1 / 1; border-radius: 14px; overflow: hidden;
    position: relative; background: var(--btn-bg); box-shadow: 0 6px 20px var(--shadow);
  }
  .album-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
  .album-cover .cover-placeholder { font-size: 12px; }
  .album-info { flex: 1; min-width: 0; padding-top: 2px; }
  .album-info h1 { font-size: 19px; line-height: 1.4; margin: 0 0 6px; }
  .album-sub { font-size: 13px; color: var(--text-dim); }
  /* 喜爱按键：跟在集数统计下面。按钮无底色（爱心 + 下方次数），这里只给间距 */
  .album-fav { margin-top: 10px; }
  /* 集列表顶部右侧：放"播放全部"等动作 */
  .ep-head { display: flex; justify-content: space-between; margin: 2px 2px 10px; }
  /* 播放全部：线条框（描边胶囊），图标 + 文字一行 */
  .album-playall {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 6px 12px; margin: 0; border: 1px solid var(--card-border);
    border-radius: 999px; background: none;
    color: var(--text-dim); cursor: pointer;
    font-size: 13px; line-height: 1;
    transition: color .15s ease, border-color .15s ease, transform .12s ease;
  }
  .album-playall svg { width: 18px; height: 18px; display: block; }
  .album-playall:active { transform: scale(0.95); }
  .album-playall:hover { color: var(--focus); border-color: var(--focus); }
  .album-intro {
    font-size: 13px; line-height: 1.9; color: var(--text-dim);
    margin: 0 0 18px; padding: 0 2px;
    white-space: pre-line;   /* 简介按行保存，这里保住换行 */
  }
  .ep-list { display: flex; flex-direction: column; gap: 10px; padding-bottom: 8px; }
  .ep-item {
    display: flex; align-items: center; gap: 12px;
    padding: 12px 14px; border-radius: 14px;
    background: var(--bar-bg);
    border: 1px solid var(--card-border);
    backdrop-filter: saturate(180%) blur(12px);
    -webkit-backdrop-filter: saturate(180%) blur(12px);
    text-decoration: none; color: var(--text);
  }
  .ep-item:active { transform: scale(0.99); }
  .ep-no { flex: none; width: 24px; text-align: center; font-size: 12px; color: var(--text-dim); font-variant-numeric: tabular-nums; }
  .ep-info { flex: 1; min-width: 0; }
  .ep-title { font-size: 14px; font-weight: 700; line-height: 1.45; }
  .ep-sub { font-size: 12px; color: var(--text-dim); margin-top: 3px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
  /* 卡片上的播放按钮：点了就地起播（不跳播放页）。
     正在放这一条时染主题色、图标变暂停，与迷你条的状态保持一致。 */
  .ep-play {
    flex: none; display: flex; align-items: center; justify-content: center;
    width: 34px; height: 34px; border-radius: 50%;
    background: var(--btn-bg); color: var(--btn-text);
    border: 1px solid var(--card-border);
    transition: transform .15s, background-color .15s, color .15s, border-color .15s;
  }
  .ep-play svg { width: 15px; height: 15px; display: block; }
  .ep-play:active { transform: scale(0.92); }
  /* 实心主题色 + 取页面底色当文字色（与 .follow-btn.on 同一套对比度处理：
     --bar-bg 在浅色下近白、深色下近黑，两种主题下都压得住 --focus） */
  .ep-play.on { background: var(--focus); color: var(--bar-bg); border-color: transparent; }

  /* ══════════════════════════════════════════════
     音频（播客）：播放页
     封面居中铺满整屏 + 底部毛玻璃控制条；轻点封面切到文稿
     ══════════════════════════════════════════════ */

  /* 播放页顶栏去掉毛玻璃底与底部分割线：本页有封面模糊铺底，顶栏再糊一层会压出一条
     与氛围底色不同的横带（也和大屏下铺满的 .pod-bg 对不齐）。
     高度保持 54px，.pod-screen 的 top: 54px 无需跟着改。
     选择器与 .topbar 同权重，靠「写在后面」生效，不影响其他页面的顶栏。 */
  .pod-topbar {
    background: transparent;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    border-bottom: 0;
  }

  .pod-screen {
    position: absolute;
    top: 54px; bottom: 0;
    left: 50%;
    transform: translateX(-50%);
    width: 100%;
    max-width: 480px;
    display: flex;
    flex-direction: column;
    /* 底部留白 = 控制条高度，避免封面 / 文稿被压住 */
    padding: 0 16px calc(172px + env(safe-area-inset-bottom, 0px));
    overflow: hidden;
  }
  /* 封面模糊铺底：fixed 铺满整个视口（大屏下也不留白），只做氛围；
     inset 外扩是给 46px 模糊留出过渡，否则四边会透出一条淡边。 */
  .pod-bg {
    position: fixed;
    inset: -60px;
    background-position: center;
    background-size: cover;
    filter: blur(46px) saturate(150%);
    opacity: .38;
    pointer-events: none;
    z-index: 0;
  }

  /* ── 封面视图：整屏居中 ── */
  .pod-stage {
    position: relative;
    z-index: 1;
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 14px;
    padding: 8px 0 10px;
    text-align: center;
    cursor: pointer;
  }
  .pod-cover-xl {
    width: min(68vw, 296px);
    max-width: 100%;
    aspect-ratio: 1 / 1;
    border-radius: 22px;
    overflow: hidden;
    background: var(--btn-bg);
    color: var(--text-dim);
    font-size: 60px;
    font-weight: 700;
    display: flex; align-items: center; justify-content: center;
    box-shadow: 0 18px 50px var(--shadow);
  }
  .pod-cover-xl img { width: 100%; height: 100%; object-fit: cover; display: block; }
  .pod-stage-title { font-size: 21px; font-weight: 700; line-height: 1.45; margin: 6px 0 0; max-width: 94%; }
  /* 标题下方文案：用主题正文色（而非过淡的 --text-dim），保证深浅两套主题下都清晰 */
  .pod-stage-meta { font-size: 12px; color: var(--text); opacity: .82; line-height: 1.6; }
  /* 合集名做成可点的下划线（点回合集页），颜色跟随 meta 文字 */
  .pod-meta-album { color: inherit; text-decoration: none; border-bottom: 1px solid currentColor; }
  .pod-stage-hint { font-size: 12px; color: var(--text); opacity: .7; }
  .pod-stage-tip { padding: 0; font-size: 14px; }

  /* ── 文稿（歌词）视图：简介按行拆分，打点行可点击跳转 ── */
  .pod-lyrics {
    position: relative;
    z-index: 1;
    flex: 1;
    min-height: 0;
    flex-direction: column;   /* display 由 JS 在 none / flex 之间切换 */
    padding-top: 6px;
  }
  .pod-notes-head {
    flex: none;
    display: flex; align-items: center; justify-content: space-between; gap: 10px;
    font-size: 12px; color: var(--text-dim);
    padding: 0 4px 10px;
  }
  .pod-notes {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    display: flex; flex-direction: column; gap: 2px;
    padding-bottom: 8px;
  }
  .pod-notes::-webkit-scrollbar { display: none; }
  .pod-notes .empty-tip { padding: 32px 16px; }
  .pod-note {
    display: flex; gap: 10px;
    padding: 9px 10px;
    border-radius: 10px;
    cursor: pointer;
    transition: background 0.25s ease;
  }
  .pod-note.plain { cursor: default; }
  .pod-note-time {
    flex: none; width: 46px;
    font-size: 11px; color: var(--text-dim);
    font-variant-numeric: tabular-nums;
    padding-top: 4px;
  }
  .pod-note:not(.plain) .pod-note-time { color: var(--focus); }
  .pod-note-text { flex: 1; min-width: 0; font-size: 15px; line-height: 1.75; }
  .pod-note.active { background: var(--btn-bg); }
  .pod-note.active .pod-note-text { color: var(--focus); font-weight: 600; }

  /* 「回到当前」：文稿头部的小按钮，仅在手动滚动脱离跟随后出现 */
  .pod-follow-btn {
    flex: none;
    padding: 2px 12px;
    border: none;
    border-radius: 14px;
    background: var(--focus); color: #fff;
    font-size: 12px; font-weight: 600;
    cursor: pointer;
  }
  /* display:flex 等会覆盖 hidden 的默认样式，必须显式兜回 */
  .pod-follow-btn[hidden] { display: none; }
  .pod-follow-btn[disabled] { opacity: .45; pointer-events: none; }
  .pod-follow-btn.on { background: transparent; color: var(--focus); box-shadow: inset 0 0 0 1.5px var(--focus); }
  .pod-note-actions { display: flex; gap: 8px; align-items: center; }
  /* 金句入口展开的选择 / 生成按钮：默认 hidden（由金句图标点击显示），显示时横向排开 */
  .pod-note-actions #podQuoteActions { display: flex; gap: 8px; align-items: center; }
  .pod-note-actions #podQuoteActions[hidden] { display: none; }
  .pod-note.selected { background: rgba(217,72,15,.10); }
  .pod-note.selected .pod-note-text { color: var(--focus); }

  /* 金句卡预览弹层 */
  .card-modal { position: fixed; inset: 0; z-index: 50; display: flex; align-items: center; justify-content: center; padding: 20px; }
  .card-modal[hidden] { display: none; }
  .card-modal-mask { position: absolute; inset: 0; background: rgba(0,0,0,.6); }
  .card-modal-box { position: relative; z-index: 1; width: 100%; max-width: 360px; display: flex; flex-direction: column; gap: 12px; max-height: calc(100vh - 48px); }
  .card-modal-scroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; overflow-x: hidden; -webkit-overflow-scrolling: touch; }
  .card-modal-box img { width: 100%; border-radius: 12px; box-shadow: 0 12px 40px rgba(0,0,0,.4); background: #fff; }
  .card-modal-tip { flex: none; color: rgba(255,255,255,.8); font-size: 12px; text-align: center; }
  .card-modal-actions { flex: none; display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; }
  .card-modal-actions .btn {
    flex: 1 1 auto; min-width: 0;
    height: 32px; padding: 0 6px;       /* 整体缩小一点 */
    border-radius: 8px; font-size: 13px;
  }
  /* 前三个（保存 / 分享 / 复制）在深色遮罩上改为白底深字，否则与遮罩同色看不见 */
  .card-modal-actions .btn-secondary {
    background: #ffffff; color: #222222;
    border: 1px solid rgba(0,0,0,.08);
  }

  /* 进度条上方那一行：只放喜爱（贴右）。控制条没有底色，
     所以这里也不给它任何背景，留白即可 —— 加了背景反而会在封面上压出一块方块。 */
  .pod-bar-top-wrap {
    display: flex; align-items: center; justify-content: space-between; gap: 12px;
    margin-top: 12px; margin-bottom: 2px;
  }
  .pod-bar-top {
    display: flex; align-items: flex-start; justify-content: flex-end;
    min-height: 36px;          /* 喜爱是「爱心在上、次数在下」两行；顶部对齐，让爱心始终贴盒子上沿，
                                   与金句框同高，不受「是否有收藏次数」影响而上下跳动 */
    margin-left: auto;         /* 金句按钮隐藏时，收藏仍保持在最右侧，位置不左移 */
  }

  /* 金句图标：与收藏(爱心)同款风格——透明无底、线性描边引号图标(20px)、
     默认色用与 .fav-btn 一致的 var(--text-dim)（不能用 inherit，否则会从 dock 继承到更深的 --text，
     浅色模式下就和爱心颜色不一致）；激活态同收藏用主题色 */
  .pod-quote-ico {
    flex: none;
    display: inline-flex; align-items: flex-start; justify-content: center;
    min-height: 42px;                  /* 比收藏按钮(36px)略高，不再强制同高；更大的点击区域 */
    padding: 0;
    border: 0;
    background: transparent;
    color: var(--text-dim);            /* 与收藏按钮默认色一致 */
    cursor: pointer;
    transition: color .15s ease, transform .12s ease;
  }
  .pod-quote-ico svg { width: 24px; height: 24px; display: block; }
  .pod-quote-ico:active { transform: scale(0.92); }
  .pod-quote-ico.on { color: var(--focus); }

  /* ── 底部控制条：去掉容器底色，让进度与控件直接浮在封面上 ── */
  .pod-dock {
    position: fixed;
    left: 50%;
    bottom: calc(10px + env(safe-area-inset-bottom, 0px));
    transform: translateX(-50%);
    width: calc(100% - 24px);
    max-width: 456px;
    padding: 10px 16px 12px;
    background: transparent;
    border: 0;
    box-shadow: none;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    z-index: 20;
  }
  .pod-bar { position: relative; height: 28px; display: flex; align-items: center; cursor: pointer; touch-action: none; }
  .pod-bar-track { position: absolute; left: 0; right: 0; height: 5px; border-radius: 3px; background: var(--btn-bg); overflow: hidden; }
  .pod-bar-fill { position: absolute; left: 0; top: 0; bottom: 0; width: 0; border-radius: 3px; background: var(--focus); }
  .pod-bar-knob {
    position: absolute; left: 0; top: 50%;
    width: 14px; height: 14px; margin: -7px 0 0 -7px;
    border-radius: 50%; background: #fff;
    box-shadow: 0 1px 5px var(--shadow);
    pointer-events: none;
  }
  .pod-times {
    display: flex; justify-content: space-between;
    font-size: 11px; color: var(--text-dim);
    font-variant-numeric: tabular-nums;
    margin-top: 2px;
  }
  .pod-ctrls { display: flex; align-items: center; justify-content: center; gap: 10px; margin-top: 4px; }
  /* 图标按钮：无底色，只留图标；点按反馈靠缩放 + 变色 */
  .pod-ico {
    flex: none;
    width: 44px; height: 44px;
    padding: 0;
    display: grid; place-items: center;
    border: 0;
    border-radius: 50%;
    background: transparent;
    color: var(--text);
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    transition: transform 0.15s ease, color 0.2s ease, opacity 0.2s ease;
  }
  .pod-ico svg { width: 23px; height: 23px; display: block; }
  .pod-ico:active { transform: scale(0.9); color: var(--focus); }
  .pod-ico:disabled { opacity: .35; }
  /* 随机 / 单曲：图标染成主题色，一眼看出不是默认顺序播放 */
  .pod-ico.on { color: var(--focus); }
  /* 主按钮：唯一带底色的实心大圆，和图标按钮形成主次 */
  .pod-play {
    flex: none;
    width: 56px; height: 56px;
    margin: 0 4px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: var(--focus); color: #fff;
    cursor: pointer;
    display: grid; place-items: center;
    box-shadow: 0 6px 18px var(--shadow);
    -webkit-tap-highlight-color: transparent;
    transition: transform 0.15s ease;
  }
  .pod-play:active { transform: scale(0.94); }
  .pod-play svg { width: 24px; height: 24px; display: block; }

  /* ── 播放列表面板：底部抽屉，从控制条上方滑出 ── */
  .pod-mask {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, .35);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.24s ease;
    z-index: 30;
  }
  .pod-mask.show { opacity: 1; pointer-events: auto; }
  .pod-panel {
    position: fixed;
    left: 50%;
    bottom: calc(10px + env(safe-area-inset-bottom, 0px));
    transform: translate(-50%, calc(100% + 40px));
    width: calc(100% - 24px);
    max-width: 456px;
    max-height: min(62vh, 460px);
    display: flex;
    flex-direction: column;
    border-radius: 22px;
    background: var(--bar-bg);
    border: 1px solid var(--divider);
    box-shadow: 0 12px 34px var(--shadow);
    backdrop-filter: saturate(180%) blur(20px);
    -webkit-backdrop-filter: saturate(180%) blur(20px);
    transition: transform 0.28s cubic-bezier(.32, .72, .3, 1);
    z-index: 31;
  }
  .pod-panel.show { transform: translate(-50%, 0); }
  .pod-panel-head {
    flex: none;
    display: flex; align-items: center; justify-content: space-between; gap: 10px;
    padding: 14px 16px 10px;
    font-size: 13px; font-weight: 700;
    border-bottom: 1px solid var(--divider);
  }
  .pod-panel-close {
    flex: none;
    padding: 4px 10px;
    border: none; border-radius: 12px;
    background: var(--btn-bg); color: var(--text-dim);
    font-size: 12px; font-weight: 600;
    cursor: pointer;
  }
  .pod-panel-list {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    padding: 6px 8px calc(8px + env(safe-area-inset-bottom, 0px));
  }
  .pod-panel-list::-webkit-scrollbar { display: none; }
  .pod-panel-item {
    display: flex; align-items: center; gap: 12px;
    padding: 8px 10px;
    border-radius: 12px;
    cursor: pointer;
    transition: background 0.2s ease;
  }
  .pod-panel-item:active { background: var(--btn-bg); }
  .pod-panel-item.cur { background: var(--btn-bg); }
  .pod-panel-cover {
    flex: none;
    width: 44px; height: 44px;
    border-radius: 10px;
    overflow: hidden;
    background: var(--btn-bg);
    color: var(--text-dim);
    font-size: 18px; font-weight: 700;
    display: flex; align-items: center; justify-content: center;
  }
  .pod-panel-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
  .pod-panel-info { flex: 1; min-width: 0; }
  .pod-panel-title {
    font-size: 14px; font-weight: 700; line-height: 1.45;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .pod-panel-item.cur .pod-panel-title { color: var(--focus); }
  .pod-panel-sub {
    font-size: 11px; color: var(--text-dim); line-height: 1.5;
    margin-top: 2px;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .pod-panel-list .empty-tip { padding: 32px 16px; }

  /* 列表面板打开时，控制条跟着沉到面板下方，避免叠在一起 */
  .pod-dock { transition: opacity 0.24s ease; }
  body.pod-panel-open .pod-dock { opacity: 0; pointer-events: none; }

  /* ── 迷你条占的底部留白 ──
     条的组件会在首屏之前给 <html> 挂上 .has-mini（见 lib/player.js 的 setGap），
     这里把各页滚动容器的底部内边距加上「条高 62 + 间距 12 = 74」，免得最后一行被压住。
     每条的值 = 该容器原本的 padding-bottom + 74，改容器原值时记得同步改这里：
       .shelf   110（书架 / 音频 / 合集，原本就在给 Dock 让位） → 184
       .bm-shelf 84（喜爱：本身还要给贴底的 Tab 条让位）        → 132
         不是 +74：这一页的条要避让贴底的 Tab，落点比常规再高 44（见下面的算式），
         而 84 里已经含了 Tab 占的 46 —— 条底距 56 + 条高 62 + 14 呼吸 ≈ 132，够了。
       .ai-list   6（AI）                                      →  80
       .pc-wrap  40（我的）                                    → 114
       .cal-wrap 40（日历）                                    → 114
     阅读页（.reader）故意不在这张表里，它不需要也不能加：
       · 不需要：.reader 的 padding-bottom 是 40vh（阅读区标定值，见 .reader 规则），
         约 340px，本来就远高于条占的 74px，最后一行不会被条压住。
       · 不能加：那 40vh 的作用是让「最后一行也能被滚到视口中心」——聚焦高亮和放大就发生在
         视口中心，这是逐行阅读的前提。照「原值 + 74」的算法会写成 74px，
         等于把 40vh 抹掉：末尾整屏再也滚不进阅读区，最后一行被钉死在屏幕底部
         （表现就是「书翻不到底」）。要留白也得是 calc(40vh + 74px)，但那会让
         末尾比其它位置多滚 74px，最后一行跑到中心之上，反而不如原样。
     为什么不交给 JS 逐个容器读 getComputedStyle 再写 inline padding：
     那是一次「读布局 → 写样式」的强制重排，而且落在首屏之前 —— 阅读页几千行时最贵。 */
  html.has-mini .shelf          { padding-bottom: 184px; }
  /* 喜爱页特殊：迷你条这一页要避让贴底的 Tab，落得比常规高，留白照着加 */
  html.has-mini .shelf.bm-shelf { padding-bottom: 132px; }
  html.has-mini .ai-list        { padding-bottom:  80px; }
  html.has-mini .pc-wrap        { padding-bottom: 114px; }
  html.has-mini .cal-wrap       { padding-bottom: 114px; }

  /* ══ 全局迷你播放器（lib/player.js）══
     由组件在「非播放页」动态插入，页面本身不用写任何 DOM：
     点条身 → 回播放页；▶ → 播放 / 暂停；✕ → 结束这次播放（清掉接力状态）。
     没拖动过时停在底部，并按页面里已有的贴底元素（.dock / .ai-bar）让开；
     拖动过一次之后就吸在左 / 右某一条边上，垂直位置随用户，并收缩成半圆小标签。
     位置由 JS 写成 --mx / --my（展开态左上角的视口坐标），CSS 只负责 translate 过去：
     拖动时改的是 transform，走合成层、不触发重排，这是拖动不掉帧的关键。
     没有曲目时组件不会加 .on，整条始终不可见也不接管点击。 */
  .dojo-mini {
    position: fixed;
    left: 0;
    top: 0;
    /* 抬升时的起点是下方 18px；--mh 是条高，用来兜住底部安全区，
       免得条被拖到底时压到 home indicator 上。
       上面一条是保底写法：老 WebView 不认 min() / env() 时，整条声明会作废，
       条就会卡在左上角 —— 所以先写一个必然成立的，再让下面那条在支持时覆盖。 */
    --mh: 62px;
    transform: translate3d(var(--mx, 0px), calc(var(--my, 0px) + 18px), 0);
    transform: translate3d(var(--mx, 0px),
                           min(calc(var(--my, 0px) + 18px),
                               calc(100vh - var(--mh) - env(safe-area-inset-bottom, 0px))),
                           0);
    width: calc(100% - 24px);
    /* 展开态宽度上限由 JS 写进 --mini-w（同一份常量也用来算定位，避免两处各写一个数）。
       比原先的 456px 窄一截：条是常驻浮层，浮在正文上时会挡字。 */
    max-width: var(--mini-w, 280px);
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 10px 10px;
    border-radius: 18px;
    overflow: hidden;
    /* 这一条是常驻浮层，长期压在滚动内容之上，所以刻意不用 backdrop-filter。
       毛玻璃要合成器每帧重新采样背后的内容再模糊一次，页面一滚就是每帧重算，
       在 iOS / 微信 WebView 上直接掉帧（blur(20px) 比顶栏的 12px 更贵）。
       改成不透明底色后观感几乎一致，但滚动时零额外开销。
       想换回毛玻璃：把 background 换成 var(--bar-bg)，再打开下面两行 backdrop-filter，
       代价是列表滚动会明显发涩。 */
    background: var(--bg);
    border: 1px solid var(--card-border);
    box-shadow: 0 10px 30px var(--shadow);
    /* 过渡只留 opacity / transform —— 只有这两个是合成层属性。
       绝不能把 width、padding、border-radius 挂上来：它们都是布局属性，一旦参与过渡，
       浏览器就得在这 200ms 里逐帧重排整条 —— 连带整个 flex 子树重新分配、
       标题的 ellipsis 截断逐帧重算、30px 模糊的 box-shadow 逐帧重新栅格化。
       低端机 / 微信 WebView 上这就是「收缩展开必卡」的直接来源。
       改成瞬变后尺寸只花一帧，随后的位移交给 transform，纯合成、零重排。
       visibility 也不挂：它是离散属性，挂上去只会让元素多一份待过渡状态，没有收益。 */
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: opacity 0.22s ease, transform 0.24s ease;
    cursor: pointer;
    /* 拖动由组件自己接管：少了这一句，手机上按住条一滑就变成滚页面了 */
    touch-action: none;
    -webkit-user-select: none;
    user-select: none;
    -webkit-tap-highlight-color: transparent;
    z-index: 25;
  }
  .dojo-mini.on {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transform: translate3d(var(--mx, 0px), var(--my, 0px), 0);
    transform: translate3d(var(--mx, 0px),
                           min(var(--my, 0px),
                               calc(100vh - var(--mh) - env(safe-area-inset-bottom, 0px))),
                           0);
  }
  /* 跟手期间任何过渡都会拖后腿 */
  .dojo-mini.dragging { transition: none; cursor: grabbing; }
  /* 换页时条本来就该在原位接着显示，JS 会在挂 .on 的那一帧临时加上这个类，
     跳过入场（淡入 + 上移）。省下的是每次跳页都要重放一遍的合成开销：
     那 0.24s 里要逐帧栅格化一条带 30px 模糊投影的浮层，正好压在新页面的首屏渲染上。 */
  .dojo-mini.instant { transition: none; }

  /* 贴边收缩：只剩封面，露出半个圆（48px 的条露 24px，半径 24px 正好是半圆；
     封面同为 42px 正圆且与条同心，露出来的那半边正好补上被切掉的那半边） */
  .dojo-mini.docked {
    --mh: 48px;
    width: 48px;
    padding: 2px;
    gap: 0;
  }
  .dojo-mini.docked.dl { border-radius: 0 24px 24px 0; }
  .dojo-mini.docked.dr { border-radius: 24px 0 0 24px; }
  .dojo-mini.docked .dojo-mini-info,
  .dojo-mini.docked .dojo-mini-btn,
  .dojo-mini.docked .dojo-mini-progress { display: none; }

  /* 封面：做成一张黑胶唱片 —— 盘面（黑圈）在播放时旋转。
     封面盒子必须保持 42px 正圆：收缩态下条是 48×48、外侧圆角 24px，而封面 42px 正好
     铺满内容区（48 - padding 2×2 - border 1×2 = 42），两圆心都落在条的中心，露出的那半个
     封面才能与条的半圆边严丝合缝（JS 里 MINI_H = 62 也是照 42 算的）。
     所以黑圈不能往外长 —— 往外长会同时破坏「同心对齐」和条高常量，还正好顶到条的
     overflow: hidden 上被切掉一圈。改成「内缩」：封面自己染成唱片黑，照片靠 padding
     缩进 3px 当唱片中心那枚圆标，外观上就是封面外围多了一圈黑。 */
  .dojo-mini-cover {
    flex: none;
    width: 42px; height: 42px;
    border-radius: 50%;
    overflow: hidden;
    /* 唱片黑 + 一道随盘面转的高光（一束光扫过黑胶的感觉，也顺便让中心对称的封面
       看得出在转）。高光单独写成 background-image：不认识 conic-gradient 的老 WebView
       只会丢掉这一层，盘面仍是黑的。 */
    background-color: #131313;
    background-image: conic-gradient(from 200deg, rgba(255, 255, 255, .15), rgba(255, 255, 255, 0) 28%);
    /* 极淡的一圈内描边当盘纹，免得整块黑得死板 */
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .07);
    /* 黑圈宽度：照片往里缩这么多 */
    padding: 3px;
    /* 没有封面时居中显示书名首字：黑盘上的字得用浅色才看得见 */
    color: rgba(255, 255, 255, .7);
    font-size: 18px; font-weight: 700;
    display: flex; align-items: center; justify-content: center;
    /* 播放时匀速转。6s 一圈：真唱片 33⅓ 转是 1.8s，缩到 42px 上会转得发晕。
       动画常驻、只切 animation-play-state —— 若改成「暂停时去掉动画」，角度会立刻归零，
       唱片会「跳」回起点；这样写是停在原地，续播时接着转。
       转的是 transform：走合成层，不重排也不重绘（拖整条条时也是纯合成）。 */
    animation: dojo-spin 6s linear infinite;
    animation-play-state: paused;
  }
  .dojo-mini.playing .dojo-mini-cover { animation-play-state: running; }
  @keyframes dojo-spin {
    from { transform: rotate(0deg); }
    to   { transform: rotate(360deg); }
  }
  /* 封面图缩成圆标：盒子被 padding 收到 36px，再自己滚圆 ——
     不滚圆的话四个角会被外圈裁成方形贴圆的怪形状 */
  .dojo-mini-cover img { width: 100%; height: 100%; object-fit: cover; display: block; border-radius: 50%; }
  /* 系统开了「减弱动态效果」就不转：盘面、高光、圆标都还在，只是静着 */
  @media (prefers-reduced-motion: reduce) {
    .dojo-mini-cover { animation: none; }
  }
  .dojo-mini-info { flex: 1; min-width: 0; }
  .dojo-mini-title {
    font-size: 13px; font-weight: 700; line-height: 1.4;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .dojo-mini-sub {
    font-size: 11px; color: var(--text-dim); line-height: 1.4; margin-top: 2px;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .dojo-mini-btn {
    flex: none;
    width: 36px; height: 36px;
    padding: 0;
    display: grid; place-items: center;
    border: 0; border-radius: 50%;
    background: transparent; color: var(--text);
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    transition: transform 0.15s ease, color 0.2s ease;
  }
  .dojo-mini-btn:active { transform: scale(0.9); }
  .dojo-mini-btn svg { width: 20px; height: 20px; display: block; }
  /* 播放是主操作：实心圆主按钮，与 ✕ 拉开主次 */
  .dojo-mini-btn.main { background: var(--focus); color: #fff; }
  .dojo-mini-btn.main:active { color: #fff; }
  /* 细进度线贴在浮动条下沿 */
  .dojo-mini-progress {
    position: absolute;
    left: 14px; right: 14px; bottom: 4px;
    height: 2px;
    border-radius: 2px;
    background: var(--btn-bg);
    overflow: hidden;
  }
  .dojo-mini-fill { height: 100%; width: 0; background: var(--focus); }

  /* ── 阅读页选中文本后的「AI 解读」角标（去掉复制/分享浮窗，避免被 iOS 系统菜单遮挡）── */
  .ai-badge {
    position: fixed;
    z-index: 9999;
    display: none;
    padding: 7px 14px;
    background: var(--focus);
    color: #fff;
    font-size: 13px;
    font-weight: 600;
    line-height: 1;
    border-radius: 999px;
    box-shadow: 0 4px 16px rgba(0, 0, 0, .22);
    cursor: pointer;
    user-select: none;
    -webkit-user-select: none;
    -webkit-tap-highlight-color: transparent;
  }
  .ai-badge:active { filter: brightness(.92); }
  [data-theme="dark"] .ai-badge { box-shadow: 0 4px 16px rgba(0, 0, 0, .5); }
  /* 禁用阅读正文长按链接/图片弹出的系统条（文本选择后的系统编辑菜单无法用 Web 禁止） */
  #reader { -webkit-touch-callout: none; }
