/* 三层色彩 token（规范 §3）：原始层 → 语义层 → 主题层。
   组件只允许引用语义层；十六进制色值只允许出现在原始层这一段里。 */
:root {
  /* ── 原始层（palette）：全站唯一允许写死色值的地方 ── */
  --ink: #12394b;
  --ink-deep: #0c2b3a;
  --ink-light: #234f60;
  --paper: #f9f7ed;
  --white: #fffdf5;
  --sun: #f7c75e;
  --sun-soft: #ffe7a6;
  --jade: #78c8b2;
  --muted: #a6b9bd;

  /* ── 语义层（dark，默认）：书架 / 朗读屏 / 投屏 ── */
  --bg: var(--ink-deep);
  --surface: var(--ink);
  --surface-sunken: rgba(255, 255, 255, .05);
  --surface-hover: rgba(255, 255, 255, .08);
  --text: var(--white);
  --text-muted: var(--muted);
  --text-faint: rgba(255, 255, 255, .45);
  --border: rgba(255, 253, 245, .17);
  --border-strong: rgba(247, 199, 94, .65);
  --accent: var(--sun);
  --accent-soft: var(--sun-soft);
  --accent-hover: #ffdb86;
  --primary: var(--sun);
  --primary-ink: var(--ink-deep);
  --support: var(--jade);
  --support-ink: var(--jade);
  --support-soft: rgba(120, 200, 178, .16);
  --focus-ring: var(--sun);
  --line: var(--border); /* 兼容旧规则 */
  --shadow-1: 0 2px 5px rgba(0, 0, 0, .13);
  --shadow-2: 0 10px 35px rgba(1, 17, 24, .32);
  --shadow-3: 0 30px 100px rgba(0, 0, 0, .35);

  /* ── 空间 / 圆角 / 动效（规范 §6）── */
  --sp-1: 4px; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px; --sp-5: 24px; --sp-6: 32px; --sp-7: 48px;
  --r-xs: 8px; --r-sm: 12px; --r-md: 16px; --r-lg: 24px; --r-pill: 999px;
  --dur-fast: 150ms; --dur-base: 220ms; --dur-slow: 280ms;

  /* ── 朗读屏排版尺寸（规范 §9「投屏优先」）──
     字号要「把可用宽吃满」，就必须把横向占位算准，而不是拍一个保守系数。
     这三个值是字号公式的唯一数据源：改了它们，字号自动跟着重算。
     --reading-gap-x       句内字间距（同一句里两个字之间）
     --reading-clause-gap-x 句与句之间
     --reading-rail-x      两侧不可用宽度 = stage + stage-inner 的左右 padding 之和
                            （stage 的 2px 与 stage-inner 的 clamp 必须与此处保持一致）
     --reading-edge-x      每侧的电视过扫描安全边距。电视会把画面边缘裁掉 2–5%，
                            字若顶到屏幕边，「性」的左半边、「远」的右半边会被切掉。 */
  --reading-gap-x: clamp(2px, .55vw, 10px);
  --reading-clause-gap-x: clamp(6px, 1.6vw, 34px);
  --reading-rail-x: calc(2 * (clamp(4px, .6vw, 12px) + 2px));
  --reading-edge-x: 2vw;
  --ease: cubic-bezier(.2, .7, .3, 1);

  /* ── 字体（规范 §4）── */
  --sans: "Microsoft YaHei", "PingFang SC", "Noto Sans SC", system-ui, sans-serif;
  --han: "LXGW WenKai GB", "Kaiti SC", "STKaiti", "KaiTi", "Noto Serif CJK SC", "Songti SC", serif;
}

/* ── 语义层（light）：家长面。同一批 token 重新映射一次，组件代码不变。
   注意主操作：金 #f7c75e 在米白底上对比度仅约 1.5:1，所以浅色面主操作改深墨底米白字。── */
dialog,
.home-view,
.settings-view,
.articles-view {
  --bg: var(--paper);
  --surface: #fffefa;
  --surface-sunken: #eef3ee;
  --surface-hover: #f2f7f3;
  --text: var(--ink-deep);
  --text-muted: #5b7076;
  --text-faint: #8a9a9e;
  --border: #dde5df;
  --border-strong: #8bbeb0;
  --accent: var(--sun);
  --accent-soft: var(--sun-soft);
  --accent-hover: #ffdb86;
  --primary: var(--ink);
  --primary-ink: var(--white);
  --support: #4c847a;
  --support-ink: #2a7c6e;
  --support-soft: #e3efe9;
  --focus-ring: #2a7c6e;
  --line: var(--border);
  /* 语义状态色（规范 §3.4）：推荐 / 中性 / 谨慎 三组，错误态复用谨慎组 */
  --info-soft: #e5ecf3;
  --info-ink: #4a6a86;
  --warn-soft: #f6e8dc;
  --warn-ink: #9a6434;
  --switch-off: #c6d0cc;
}

* { box-sizing: border-box; }
/* 背景挂在最底层的 html 上，body 与 app-shell 保持透明：
   进入全屏或视口高度变化产生 1px 级差异时，也不会露出第二种颜色的边带。 */
html { background: linear-gradient(180deg, rgba(43, 94, 108, .5), rgba(43, 94, 108, 0) 26%), var(--ink); }
html, body { width: 100%; height: 100%; margin: 0; overflow: hidden; }
body { color: var(--white); font-family: var(--sans); -webkit-font-smoothing: antialiased; }
button { font: inherit; cursor: pointer; }
button:focus-visible, input:focus-visible { outline: 3px solid var(--focus-ring); outline-offset: 4px; }
button:disabled { cursor: not-allowed; opacity: .52; }

/* 背景只做一层幅度很小的顶部提亮，快速融入纯色：
   大跨度径向渐变在深色上会产生明显色带（banding），看起来像「两种背景不连续」。 */
.app-shell { height: 100dvh; overflow: hidden; }
/* 视图切换：书架与朗读屏叠在同一个 app-shell 里，用 data-view 决定谁可见。
   不做路由、不销毁 DOM —— 从书架进朗读屏再返回，选中状态与播放位置都还在。 */
.home-view, .reader-view, .settings-view, .articles-view { display: none; height: 100%; min-height: 0; grid-template-rows: auto minmax(0, 1fr) auto; }
.app-shell[data-view="reader"] > .reader-view,
.app-shell[data-view="home"] > .home-view,
.app-shell[data-view="settings"] > .settings-view,
.app-shell[data-view="articles"] > .articles-view { display: grid; }
.topbar { min-height: 68px; display: flex; justify-content: space-between; align-items: center; gap: 24px; padding: 9px clamp(22px, 3.5vw, 64px); }
.brand { display: flex; align-items: center; gap: 13px; flex: none; }
.brand-mark { width: 43px; height: 43px; border: 2px solid var(--sun); border-radius: 11px 11px 11px 2px; display: grid; place-items: center; color: var(--sun); font-family: var(--han); font-weight: bold; font-size: 25px; transform: rotate(-6deg); }
.brand-mark span { transform: rotate(6deg); }
.brand-copy { display: flex; flex-direction: column; line-height: 1.18; gap: 5px; }
.brand-copy strong { font-size: 18px; letter-spacing: .15em; }
.brand-copy small { color: var(--muted); font-size: 10px; letter-spacing: .12em; }
.topbar-right { display: flex; align-items: center; gap: clamp(12px, 2.4vw, 32px); }
.work-label { color: var(--sun-soft); letter-spacing: .15em; font-size: 13px; }
.work-label span { color: var(--muted); padding: 0 8px; }
.text-button { border: 1px solid var(--line); border-radius: 999px; background: transparent; color: var(--white); padding: 10px 18px; font-size: 13px; }
.text-button:hover { background: rgba(255, 255, 255, .08); }
.settings-top-button { border-color: rgba(247, 199, 94, .55); color: var(--sun-soft); }

.reading-stage { min-height: 0; display: flex; position: relative; padding: clamp(3px, .75vh, 8px) max(2px, env(safe-area-inset-right)) clamp(3px, .75vh, 8px) max(2px, env(safe-area-inset-left)); }
.stage-rail { display: none; }
/* 阅读区不再做卡片式边框与底色：手机横屏和电视投屏时，一圈框只会挤占空间、分散注意力。 */
.stage-inner { min-width: 0; width: 100%; min-height: 0; display: grid; grid-template-rows: auto minmax(0, 1fr) auto; position: relative; padding: clamp(8px, 1.4vh, 16px) clamp(4px, .6vw, 12px) clamp(6px, 1vh, 12px); }
.focus-toggle { position: absolute; z-index: 3; top: 9px; right: 13px; width: 32px; height: 32px; display: grid; place-items: center; border: 1px solid rgba(255, 253, 245, .2); border-radius: 9px; color: var(--muted); background: rgba(3, 28, 39, .28); font-size: 20px; line-height: 1; }
.focus-toggle:hover, .focus-toggle[aria-pressed="true"] { color: var(--sun-soft); border-color: rgba(247, 199, 94, .65); background: rgba(3, 28, 39, .5); }
.lesson-meta { display: flex; align-items: center; justify-content: space-between; gap: 20px; padding-right: 43px; color: var(--sun-soft); font-size: clamp(11px, 1.2vw, 15px); letter-spacing: .2em; }
.lesson-count { color: var(--muted); letter-spacing: .04em; white-space: nowrap; }
.reading-content { display: flex; min-height: 0; flex-direction: column; align-items: center; padding: 0; }
.reading-kicker { display: none; }
/* 上下渐隐遮罩：只在「真的有内容被裁在视口外」时才挂（渲染层按实测切 .is-clipped）。
   它的本意是暗示「上面/下面还有内容」，但内容整篇装得下时留着它，
   只会把首行尾行无故调暗 —— 四行古诗正好占满一屏，尾句会整句掉进渐隐区。 */
.reading-viewport { flex: 1; min-height: 0; width: 100%; overflow: hidden; position: relative; overflow-anchor: none; }
.reading-viewport.is-clipped { -webkit-mask-image: linear-gradient(to bottom, transparent, #000 9%, #000 84%, transparent); mask-image: linear-gradient(to bottom, transparent, #000 9%, #000 84%, transparent); }
.reading-stack { position: relative; width: 100%; }
/* 测量期间关掉一切过渡。系统开启「减弱动效」（无头浏览器默认如此）时，
   上面那段 reduced-motion 规则会把每个元素的 transition-duration 从 0s 改成 .01ms ——
   而 transition-property 的初始值就是 all，于是全站凭空多出一个 .01ms 的过渡。
   过渡再短，在「写入后立刻读取」时返回的仍是起始值：字号收敛按行高做二分，
   读到的是上一轮字距下的高度，判出来的字号会一路偏小。
   读数这类「写-读闭环」操作必须先关过渡，否则量到的永远慢一拍。 */
.reading-stack.is-measuring,
.reading-stack.is-measuring * { transition: none !important; }
/* 非当前行刻意压小、压暗，把纵向空间让给正在朗读的那一行。
   排版不再假设固定 6 字（旧版用 Grid 写死 3+中缝+3）：
   文本按标点拆成「句」，每句是一个 .reading-clause，放不下时整句换行，
   所以三字经（3+3）在一行、古诗（五言四句）自动折成两行，共用同一套规则。
   字号由最长一句的字数 --clause-max 反推，让整行把可用宽吃满 —— 这个值由渲染层
   按条目实际断句写入，不同作品自动得到合适字号。 */
.reading-row {
  --clause-max: 3;
  /* 一行放几句。由渲染层按作品写入（见 content/collections.js 的 clausesPerLine）：
     三字经 3 字一句 → 两句拼一行共 6 字；五言/七言 → 一句一行。
     这个值决定「一行字数」，而一行字数反过来决定字号：字数少就得字号大才吃满宽，
     字号一大，行数多的条目就顶到高度天花板、只能整体缩小。所以它是在
     「竖排古籍的观感」和「字尽可能大」之间选平衡点，没有两全解。 */
  --clauses-per-line: 2;
  /* 一行计划容纳的字数。flex-wrap 会自动按这个尺度折行。 */
  --per-line: calc(var(--clause-max) * var(--clauses-per-line));
  /* 该行除汉字之外的横向占位 = 句间 gap ×(句数 − 1) + 句内字间距 ×(每行字数 − 句数)。
     注意字间距是 px 级的 clamp 值、不随字号缩放，所以必须单独算，不能拍成 em 系数。 */
  --row-fixed-x: calc(
    var(--reading-clause-gap-x) * (var(--clauses-per-line) - 1)
    + var(--reading-gap-x) * (var(--per-line) - var(--clauses-per-line))
  );
  /* 按可用宽反推字号：一行刚好放下 --per-line 个字，不留白也不溢出。
     两侧再各减掉电视过扫描边距；末尾减 6px 是亚像素舍入的余量。 */
  --han-size-fit: calc((100vw - var(--reading-rail-x) - 2 * var(--reading-edge-x) - var(--row-fixed-x) - 6px) / var(--per-line));
  /* 当前行的字号（大字）。「上下文行」= 当前行之上、之下的每一行，都以它为基准：
     字号 = 当前行 × --context-ratio（小字），但宽度要与当前行相同 —— 差额全由字距补（见 --row-inner-gap）。 */
  --han-size-strong: clamp(40px, var(--han-size-fit), 30vw);
  --han-size-current: calc(var(--han-size-strong) * var(--stack-scale, 1));
  /* 上下文行的字号比例。这一个数决定一屏能看几行：
     调小 → 行更矮、能多排几行，但字距会被拉得更开；
     调大 → 字更饱满、行更高、字距更自然。等宽锁死了宽度，两者只能此消彼长。
     上界取当前行字号本身 —— 上下文行不可能比正在读的那行还大，所以比例 >1 没有意义；
     下界 15px 保证再小的屏也认得出来。乘的 --stack-scale 由渲染层按可用高度实测写入。 */
  --context-ratio: .22;
  --han-size: calc(clamp(15px, calc(var(--han-size-strong) * var(--context-ratio)), var(--han-size-strong)) * var(--stack-scale, 1));
  --py-size: clamp(12px, calc(var(--han-size) * .3), 5vw);
  width: 100%; margin: 0 auto; display: flex; flex-wrap: wrap; justify-content: center; align-items: flex-end;
  column-gap: var(--row-clause-gap, var(--reading-clause-gap-x)); row-gap: clamp(2px, .6vh, 8px); opacity: .42; color: var(--white);
}
/* 当前行是整屏唯一的大字行，只靠「全亮 + 逐字金色高亮」区分。
   这里刻意不列 .is-read：已读行与未读行同属「上下文行」，字号一律小；
   给已读行单独放大，只会让它比下面的未读行宽出一截、上下错开成台阶。 */
.reading-row.is-current { --han-size: var(--han-size-current); opacity: 1; }
/* 一句一行（竖向堆叠）：由渲染层按作品的 clausesPerLine === 1 打上。
   为什么必须显式改主轴方向，而不是指望 flex-wrap 自己折对：
   折行取决于「当前字号下一行放不放得下两句」，而字号又由高度反推 ——
   屏幕一矮，字号被压小到两句又能并排，它就会自动折回两行，
   「一句一行」在矮屏（也就是电视横屏）上静默失效，且没有任何报错。
   改主轴方向之后，「一行一句」是结构性事实，与字号多大无关。 */
.reading-row.is-stacked { flex-direction: column; align-items: center; }
.reading-clause { display: flex; align-items: flex-end; gap: var(--row-inner-gap, var(--reading-gap-x)); }
/* 上下文行（当前行之外的每一行）要与当前行等宽：字号锁小，宽度只能靠字距补，
   补多少由渲染层实测后直接写进 `gap` / `column-gap`（见 main.js fitContextRows）。
   句内与句间共用同一个补偿值，跨句那处才不会突然大出一块。
   这里不依赖 :not(.is-current) 之类的选择器去区分行态 —— 切行瞬间的样式重算未必及时，
   量出来的是上一轮的值。渲染层对每一行都显式写值，包括把当前行写回基准字距。 */
/* 古诗标题、朝代、作者组成开头的朗读行，字号、拼音与逐字高亮沿用正文。 */
.reading-row-meta { display: flex; align-items: flex-end; justify-content: center; }
.reading-row-meta-group { display: contents; }
.reading-row-meta-separator { align-self: center; color: var(--text-muted); font-weight: 700; opacity: 1; }

.focus-reading .reader-view { grid-template-rows: minmax(0, 1fr); }
.focus-reading .topbar { display: none; }
.focus-reading .reading-stage { padding: 4px 0; }
.focus-reading .stage-inner { padding: 12px clamp(6px, .8vw, 16px) 16px; }
.focus-reading .focus-toggle { top: 13px; right: 17px; }
.focus-controls-toggle { display: none; position: fixed; z-index: 11; right: 17px; bottom: max(14px, env(safe-area-inset-bottom)); width: 40px; height: 32px; place-items: center; border: 1px solid rgba(255, 253, 245, .2); border-radius: 999px; color: var(--muted); background: rgba(3, 28, 39, .72); font-size: 20px; line-height: 1; box-shadow: 0 6px 22px rgba(1, 17, 24, .25); backdrop-filter: blur(10px); }
.focus-controls-toggle:hover, .focus-controls-toggle[aria-pressed="true"] { color: var(--sun-soft); border-color: rgba(247, 199, 94, .65); }
.focus-reading .focus-controls-toggle { display: grid; }
.focus-reading .stage-bottom-text { padding-right: 50px; }
.focus-reading .controlbar { position: fixed; z-index: 10; left: 50%; bottom: max(14px, env(safe-area-inset-bottom)); width: min(calc(100vw - 24px), 430px); min-height: 54px; transform: translate(-50%, 12px); padding: 5px 8px; border: 1px solid rgba(255, 253, 245, .2); border-radius: 999px; background: rgba(3, 28, 39, .84); box-shadow: 0 10px 35px rgba(1, 17, 24, .32), inset 0 1px 0 rgba(255, 255, 255, .08); backdrop-filter: blur(12px); opacity: 0; visibility: hidden; pointer-events: none; transition: opacity .18s ease, transform .18s ease, visibility .18s ease; }
.focus-reading.focus-controls-open .controlbar { transform: translate(-50%, 0); opacity: 1; visibility: visible; pointer-events: auto; }
.focus-reading .control-buttons { width: 100%; justify-content: space-between; gap: 2px; }
.focus-reading .control-button { min-width: 44px; width: 44px; height: 44px; }
.focus-reading .play-button { flex: 1; min-width: 118px; max-width: 170px; height: 44px; }
.char-cell { min-width: 0; display: flex; flex-direction: column; align-items: center; justify-content: flex-end; gap: clamp(2px, .4vh, 5px); position: relative; padding: clamp(1px, .3vh, 4px) 0 clamp(2px, .45vh, 6px); border-radius: 18px; background: transparent; transition: color .28s ease; }
.reading-row.is-current .char-cell { gap: clamp(4px, .9vh, 9px); padding: clamp(6px, 1.1vh, 13px) 0 clamp(9px, 1.5vh, 17px); }
/* 拼音与汉字同色（纯白）：拼音是「这个字怎么读」的标注，染成玉色会让每个字看起来
   分成两截、也削弱了与当前行的整体感。两者只在字号上分层。 */
.char-pinyin { color: var(--white); font-size: var(--py-size); letter-spacing: .035em; font-weight: 600; line-height: 1.15; white-space: nowrap; transition: color .22s ease; position: relative; }
.char-han { font-family: var(--han); font-size: var(--han-size); line-height: 1.03; font-weight: 600; white-space: nowrap; transition: color .22s ease; position: relative; }
/* 回退方案：不支持 clip-path 时，汉字与拼音都整块直接变金色（无渐进填充）。
   高亮色必须统一为 --sun：拼音若用 --sun-soft 会明显比汉字淡一截。 */
.char-cell.is-sung .char-han,
.char-cell.is-active .char-han { color: var(--sun); }
.char-cell.is-sung .char-pinyin,
.char-cell.is-active .char-pinyin { color: var(--sun); }

/* 卡拉OK 式高亮 —— 双字层叠加实现，改动前请先读完这三条：

   为什么不用 background-clip: text：金色在那里是「背景」，而文字本身用 color 绘制在
   背景之上。若底色纯白 → 白色字形把金色完全盖住（字不变金）；若底色半透明/透明 →
   未填充部分看不见字或先变成灰色（闪烁）。两个需求互相排斥，此路不通。

   正确解法是双层叠加：
     · 本体 .char-han      恒为纯白，任何时刻都不改变 → 未填充部分与未读状态完全一致
     · 副本 ::before       同字形金层叠在上面，用 clip-path 从左往右「露出」

   三条防闪烁要点：
     1. 本体颜色永不变化：字被激活的瞬间只有金层开始展开，本体没有颜色过渡。
     2. 金层只做裁剪展开，不做透明度/位移变化，展开边缘之外露出的就是本体纯白。
     3. 「已读」用 clip-path: inset(0)（全开），与动画终态等价 → 交接零突变。
   另外刻意不做位移：逐字上浮会让整句持续上下抖动，卡拉OK 只靠颜色流动就够了。 */
@supports (clip-path: inset(0 100% 0 0)) {
  /* 汉字与拼音共用同一套金层规则：同色（--sun）、同时长、同填充方式，
     视觉上才会像一个整体在推进。两者若分用不同色值会明显分层。 */
  .char-han::before,
  .char-pinyin::before {
    content: attr(data-char);
    position: absolute;
    inset: 0;
    color: var(--sun);
    clip-path: inset(0 100% 0 0);
    pointer-events: none;
  }
  /* 支持金层时，本体恒为未读色（纯白），金色只由金层覆盖产生。 */
  .char-cell.is-sung .char-han,
  .char-cell.is-active .char-han { color: var(--white); }
  .char-cell.is-sung .char-pinyin,
  .char-cell.is-active .char-pinyin { color: var(--white); }
  .char-cell.is-active .char-han::before,
  .char-cell.is-active .char-pinyin::before {
    animation: karaoke-fill var(--char-duration, 420ms) linear forwards;
  }
  .char-cell.is-sung .char-han::before,
  .char-cell.is-sung .char-pinyin::before { clip-path: inset(0 0 0 0); }
}
@keyframes karaoke-fill {
  from { clip-path: inset(0 100% 0 0); }
  to { clip-path: inset(0 0 0 0); }
}
/* 暂停时冻结填充，避免高亮在无声时继续推进。 */
.app-shell.is-paused .char-cell.is-active .char-han::before,
.app-shell.is-paused .char-cell.is-active .char-pinyin::before { animation-play-state: paused; }
.reading-status { flex: none; margin: clamp(3px, .7vh, 8px) 0 0; min-height: 1.4em; color: var(--muted); font-size: clamp(12px, 1.25vw, 16px); text-align: center; }
.reading-status.is-error { color: var(--sun-soft); }
.stage-bottom { width: 100%; margin-top: 5px; }
.progress-track { display: none; }
.progress-fill { display: block; width: 0; height: 100%; background: var(--sun); border-radius: inherit; }
.stage-bottom-text { display: flex; justify-content: flex-end; color: var(--muted); font-size: 11px; margin-top: 6px; letter-spacing: .08em; }
.stage-bottom-text #categoryLabel { display: none; }

.controlbar { min-height: 48px; display: flex; align-items: center; justify-content: center; padding: 2px clamp(24px, 4.3vw, 76px) max(2px, env(safe-area-inset-bottom)); }
.control-caption { display: none; }
.control-caption strong { font-family: var(--han); font-size: clamp(16px, 1.6vw, 24px); font-weight: 600; letter-spacing: .08em; }
.control-caption span, .control-trailing { color: var(--muted); font-size: 11px; letter-spacing: .1em; }
.control-buttons { display: flex; align-items: center; gap: clamp(5px, 1.2vw, 16px); }
.control-button { min-width: 44px; height: 44px; display: flex; align-items: center; justify-content: center; color: var(--white); background: transparent; border: 0; border-radius: 12px; }
.control-button:hover { background: rgba(255, 255, 255, .07); }
.control-button span { font-size: 27px; line-height: 1; font-weight: 300; }
.control-button small { display: none; }
.play-button { min-width: clamp(132px, 12vw, 170px); height: 44px; padding: 0 16px; display: flex; align-items: center; justify-content: center; gap: 9px; color: var(--ink-deep); background: var(--sun); border: 0; border-radius: 12px; font-weight: 750; font-size: clamp(14px, 1.4vw, 18px); box-shadow: 0 8px 24px rgba(247, 199, 94, .16); white-space: nowrap; }
.play-button:hover:not(:disabled) { background: #ffdb86; }
.play-button span:first-child { font-size: 23px; }
.control-trailing { display: none; }

/* ─────────── 图标（规范 §5）───────────
   统一 24px 网格、2px 描边、颜色一律 currentColor，因此图标永远跟随所在按钮的文字色。
   .icon-slot 是占位容器，JS 启动时把内联 SVG 注入进去（不引外部 sprite）。 */
.icon-slot { display: inline-flex; align-items: center; justify-content: center; flex: none; }
.icon-slot svg { display: block; }
.icon-button { display: inline-flex; align-items: center; gap: var(--sp-2); }
.icon-only-button { width: 44px; height: 44px; display: grid; place-items: center; border: 1px solid var(--border); border-radius: var(--r-sm); background: transparent; color: var(--text); transition: background var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease); }
.icon-only-button:hover { background: var(--surface-hover); border-color: var(--border-strong); }

/* ─────────── 首页 · 书架（规范 §8.1 第 5 项）───────────
   定位：产品的第一层，承载「自选听读库」。之前打开即进朗读，等于默认只有一个作品。 */
.home-view { background: var(--bg); color: var(--text); }
.home-view .brand-copy small { color: var(--text-faint); }
.home-view .text-button,
.articles-view .text-button { color: var(--text-muted); border-color: var(--border); }
.home-view .text-button:hover,
.articles-view .text-button:hover { color: var(--text); background: var(--surface-hover); }
.home-topbar { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-4); min-height: 68px; padding: 9px clamp(22px, 3.5vw, 64px); }
.home-body { min-height: 0; overflow-y: auto; padding: clamp(8px, 2vh, 24px) clamp(22px, 3.5vw, 64px) clamp(16px, 3vh, 36px); }
.home-heading { max-width: 1000px; margin: 0 auto clamp(18px, 3vh, 32px); }
.home-heading h1 { margin: 0 0 var(--sp-2); font-family: var(--han); font-size: clamp(26px, 3.4vw, 44px); font-weight: 600; letter-spacing: .08em; }
.home-heading p { margin: 0; color: var(--text-muted); font-size: clamp(13px, 1.3vw, 16px); line-height: 1.6; }
.home-foot { display: flex; align-items: center; justify-content: center; gap: clamp(10px, 2vw, 24px); padding: var(--sp-2) clamp(22px, 3.5vw, 64px) max(var(--sp-3), env(safe-area-inset-bottom)); }
.home-foot .text-button { color: var(--text-muted); }
.home-foot .text-button:hover { color: var(--text); }

.shelf { max-width: 1000px; margin: 0 auto; display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 236px), 1fr)); gap: clamp(12px, 1.6vw, 20px); }

/* 作品卡：整张卡就是点击目标（所以不需要卡内再放按钮），点任意处进入该作品。 */
.shelf-card { position: relative; display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp-2); text-align: left; padding: clamp(18px, 2.4vw, 28px); border: 1px solid var(--border); border-radius: var(--r-md); background: var(--surface); color: var(--text); transition: border-color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease); }
.shelf-card:hover { border-color: var(--border-strong); background: var(--surface-hover); transform: translateY(-2px); }
.shelf-card:active { transform: none; }
.shelf-card-kicker { font-size: 11px; letter-spacing: .2em; color: var(--support); }
.shelf-card-title { font-family: var(--han); font-size: clamp(26px, 2.8vw, 38px); font-weight: 600; letter-spacing: .1em; line-height: 1.15; }
.shelf-card-summary { color: var(--text-muted); font-size: 13px; line-height: 1.6; }
.shelf-card-foot { width: 100%; margin-top: auto; padding-top: var(--sp-3); display: flex; align-items: center; gap: var(--sp-2); font-size: 12px; color: var(--text-faint); }
.shelf-card-foot .is-resume { display: inline-flex; align-items: center; gap: 5px; color: var(--support-ink); }
.shelf-card-foot .sep { opacity: .45; }
.shelf-empty { max-width: 1000px; margin: 0 auto; padding: var(--sp-6) var(--sp-5); border: 1px dashed var(--border); border-radius: var(--r-lg); color: var(--text-muted); text-align: center; font-size: 14px; }

/* 古诗目录：选诗与清单留在普通浏览界面，朗读页继续使用原有投屏排版。 */
.articles-view { background: var(--bg); color: var(--text); }
.articles-topbar { min-height: 68px; display: flex; align-items: center; gap: var(--sp-4); padding: 10px clamp(22px, 3.5vw, 64px); border-bottom: 1px solid var(--border); }
.articles-heading { flex: 1; }
.articles-heading h1, .articles-heading p { margin: 0; }
.articles-heading h1 { font-family: var(--han); font-size: 25px; letter-spacing: .08em; }
.articles-heading p { margin-top: 4px; color: var(--text-muted); font-size: 12px; }
.articles-view .icon-only-button { border-color: var(--border); color: var(--text); }
.articles-body { width: min(1220px, 100%); min-height: 0; overflow-y: auto; margin: 0 auto; padding: clamp(16px, 2.5vw, 32px); display: grid; grid-template-columns: minmax(0, 1fr) minmax(270px, 340px); align-items: start; gap: clamp(var(--sp-4), 2vw, var(--sp-6)); }
.article-library { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 250px), 1fr)); gap: var(--sp-3); }
.article-card { min-height: 180px; padding: var(--sp-4); border: 1px solid var(--border); border-radius: var(--r-md); background: var(--surface); display: flex; flex-direction: column; gap: var(--sp-3); }
.article-card.is-playable { cursor: pointer; transition: border-color var(--dur-fast), box-shadow var(--dur-fast), transform var(--dur-fast); }
.article-card.is-playable:hover { border-color: var(--border-strong); box-shadow: var(--shadow-1); transform: translateY(-1px); }
.article-card.is-selected { border-color: var(--support); box-shadow: 0 0 0 2px var(--support-soft); }
.article-card-top { display: flex; align-items: flex-start; gap: 10px; }
.article-card-top input { width: 18px; height: 18px; margin: 3px 0 0; accent-color: var(--support-ink); }
.article-card-title { flex: 1; font: 600 22px/1.3 var(--han); letter-spacing: .04em; }
.article-card-meta { color: var(--text-muted); font-size: 13px; }
.article-card-summary { color: var(--text-faint); font-size: 12px; line-height: 1.6; }
.article-card-foot { margin-top: auto; display: flex; align-items: center; justify-content: space-between; gap: 10px; color: var(--text-faint); font-size: 12px; }
.article-status { padding: 4px 8px; border-radius: var(--r-pill); background: var(--support-soft); color: var(--support-ink); white-space: nowrap; }
.article-status.is-pending { background: var(--warn-soft); color: var(--warn-ink); }
.playlist-panel { position: sticky; top: 0; padding: var(--sp-4); border: 1px solid var(--border); border-radius: var(--r-md); background: var(--surface); }
.playlist-heading h2, .playlist-heading p { margin: 0; }
.playlist-heading h2 { font-size: 17px; }
.playlist-heading p { margin-top: 5px; color: var(--text-muted); font-size: 12px; }
.playlist-items { display: grid; gap: 8px; margin: 16px 0; max-height: min(48vh, 420px); overflow: auto; }
.playlist-empty { padding: 20px 10px; border: 1px dashed var(--border); border-radius: var(--r-sm); color: var(--text-faint); text-align: center; font-size: 12px; }
.playlist-item { display: grid; grid-template-columns: minmax(0, 1fr); gap: 8px; align-items: center; padding: 10px; border-radius: var(--r-sm); background: var(--surface-sunken); }
.playlist-item-copy { min-width: 0; }
.playlist-item-copy strong, .playlist-item-copy small { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.playlist-item-copy strong { font-size: 13px; }
.playlist-item-copy small { margin-top: 4px; color: var(--text-muted); font-size: 11px; }
.playlist-item-controls { display: flex; flex-wrap: wrap; align-items: center; gap: 4px; }
.playlist-item-controls button { min-width: 44px; height: 44px; border: 1px solid var(--border); border-radius: var(--r-xs); background: var(--surface); color: var(--text); }
.playlist-item-controls button:hover:not(:disabled) { border-color: var(--border-strong); background: var(--surface-hover); }
.playlist-item-controls .repeat-count { min-width: 38px; text-align: center; font-size: 12px; }
.playlist-item-controls .remove-poem { color: var(--warn-ink); }
.playlist-start { width: 100%; min-height: 46px; border: 0; border-radius: var(--r-sm); background: var(--primary); color: var(--primary-ink); font-weight: 600; }
.playlist-start:hover:not(:disabled) { background: var(--ink-light); }
@media (max-width: 760px) { .articles-body { grid-template-columns: 1fr; } .playlist-panel { position: static; grid-row: 1; } .playlist-items { max-height: 32vh; } }

dialog { color: var(--text); }
dialog::backdrop { background: rgba(1, 19, 27, .72); backdrop-filter: blur(4px); }
/* ─────────── 设置页（家长面 · 浅色）───────────
   与书架、朗读屏共用同一套语义 token，只是这一层把 token 映射成浅色（见文件顶部 light 段）。 */
.settings-view { background: var(--bg); color: var(--text); }
.settings-topbar { display: grid; grid-template-columns: 44px 1fr 44px; align-items: center; gap: var(--sp-3); min-height: 64px; padding: 10px clamp(16px, 3vw, 32px); border-bottom: 1px solid var(--border); background: var(--surface); }
.settings-topbar h1 { margin: 0; text-align: center; font-size: 17px; font-weight: 700; letter-spacing: .1em; }
.settings-topbar .icon-only-button { border-color: transparent; }
.settings-topbar .icon-only-button:hover { border-color: var(--border); background: var(--surface-hover); }
.settings-body { min-height: 0; overflow-y: auto; padding: var(--sp-5) clamp(16px, 3vw, 32px) max(var(--sp-6), env(safe-area-inset-bottom)); }
.settings-section { max-width: 820px; margin: 0 auto var(--sp-6); }
.settings-section-head h2 { margin: 0 0 var(--sp-1); font-family: var(--han); font-size: 24px; font-weight: 600; letter-spacing: .06em; }
.settings-section-head p { margin: 0 0 var(--sp-3); color: var(--text-muted); font-size: 13px; line-height: 1.65; }
.about-list { margin: 0; display: grid; gap: var(--sp-2); }
.about-list > div { display: grid; grid-template-columns: 92px 1fr; gap: var(--sp-3); align-items: baseline; padding: var(--sp-3) var(--sp-4); border: 1px solid var(--border); border-radius: var(--r-sm); background: var(--surface); }
.about-list dt { color: var(--text-muted); font-size: 12px; letter-spacing: .06em; }
.about-list dd { margin: 0; font-size: 13px; line-height: 1.6; }
.eyebrow { color: var(--support); font-size: 11px; letter-spacing: .22em; font-weight: 700; }
.help-dialog h2 { font-family: var(--han); font-size: 30px; margin: 6px 0 5px; letter-spacing: .06em; }
.close-button { width: 37px; height: 37px; display: grid; place-items: center; border: 0; background: var(--surface-sunken); color: var(--text-muted); border-radius: 50%; flex: none; }
.selection-summary { display: flex; justify-content: space-between; align-items: center; gap: 15px; padding: 11px var(--sp-4); border-radius: var(--r-sm); background: var(--surface-sunken); font-size: 13px; font-weight: 700; }
.selection-summary button { border: 0; background: transparent; color: var(--support-ink); font-size: 12px; text-decoration: underline; text-underline-offset: 3px; }
.lesson-list { padding: var(--sp-3) 0 0; }
/* 按作品分组（三字经 / 千字文 / 古诗）。用 details/summary 做折叠，原生可访问、
   不依赖脚本；条目数到数百条时这是家长能找到东西的前提。 */
.collection-section { margin-top: var(--sp-3); border: 1px solid var(--border); border-radius: var(--r-md); background: var(--surface); overflow: hidden; }
.collection-section:first-child { margin-top: 0; }
.collection-heading { display: flex; align-items: baseline; gap: 10px; padding: 13px var(--sp-4); background: var(--surface-sunken); cursor: pointer; list-style: none; }
.collection-heading::-webkit-details-marker { display: none; }
.collection-heading strong { font-family: var(--han); font-size: 21px; font-weight: 600; letter-spacing: .08em; }
.collection-kicker { color: var(--support); font-size: 11px; letter-spacing: .18em; }
.collection-count { margin-left: auto; color: var(--text-muted); font-size: 12px; white-space: nowrap; }
.collection-body { padding: var(--sp-2) 10px 2px; }
.collection-body .lesson-option { border: 0; margin-bottom: 2px; background: transparent; }
.collection-body .lesson-option:hover { background: var(--surface-hover); }
.lesson-tag { align-self: flex-start; margin-top: 4px; padding: 2px 9px; border-radius: var(--r-pill); font-size: 11px; background: var(--support-soft); color: var(--support-ink); }
.lesson-tag[data-category="neutral"] { background: var(--info-soft); color: var(--info-ink); }
.lesson-tag[data-category="cautious"] { background: var(--warn-soft); color: var(--warn-ink); }
.lesson-option { display: flex; align-items: center; justify-content: space-between; gap: 20px; padding: 13px var(--sp-4); margin-bottom: var(--sp-2); border: 1px solid var(--border); border-radius: 14px; background: var(--surface); cursor: pointer; }
.lesson-option:hover { border-color: var(--border-strong); }
.lesson-option-copy { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.lesson-option-copy strong { font-family: var(--han); font-size: 23px; font-weight: 600; letter-spacing: .08em; }
.lesson-option-copy small { color: var(--text-muted); font-size: 12px; line-height: 1.4; }
.lesson-option input { appearance: none; width: 42px; height: 25px; border-radius: var(--r-pill); background: var(--switch-off); position: relative; flex: none; transition: background var(--dur-fast); }
.lesson-option input::after { content: ""; position: absolute; top: 3px; left: 3px; width: 19px; height: 19px; border-radius: 50%; background: var(--white); box-shadow: var(--shadow-1); transition: transform var(--dur-fast); }
.lesson-option input:checked { background: var(--support-ink); }
.lesson-option input:checked::after { transform: translateX(17px); }
.done-button { width: 100%; min-height: 47px; border: 0; border-radius: var(--r-sm); background: var(--primary); color: var(--primary-ink); font-weight: 700; font-size: 14px; }
.done-button:hover { background: var(--ink-light); }
.help-dialog { width: min(450px, calc(100vw - 30px)); border: 0; border-radius: 24px; padding: 25px 28px 28px; background: var(--paper); }
.help-head { display: flex; justify-content: space-between; align-items: start; }
.help-dialog h2 { margin-top: 0; }
.help-dialog ol { padding-left: 23px; line-height: 1.9; font-size: 14px; }
.help-dialog li { padding-left: 5px; }
.help-dialog p { color: #5c7378; font-size: 13px; line-height: 1.6; margin: 16px 0 24px; }
.rotate-prompt { display: none; }

@media (max-width: 900px) and (orientation: landscape) {
  .home-topbar { min-height: 56px; padding: max(8px, env(safe-area-inset-top)) 22px 8px; }
  .home-body { padding: 6px 22px 12px; }
  .home-heading { margin-bottom: var(--sp-3); }
  .shelf-card { border-radius: var(--r-md); }
  .topbar { min-height: 56px; padding: max(8px, env(safe-area-inset-top)) 22px 8px; }
  .brand-mark { width: 34px; height: 34px; font-size: 21px; }
  .brand-copy strong { font-size: 15px; }
  .brand-copy small { display: none; }
  .topbar-right { gap: 9px; }
  .work-label { font-size: 11px; }
  .text-button { padding: 7px 10px; font-size: 11px; }
  .reading-stage { padding: 2px 0; }
  .stage-rail { display: none; }
  .stage-inner { padding: 4px 2px; }
  .focus-toggle { top: 7px; right: 9px; width: 29px; height: 29px; font-size: 18px; }
  .reading-kicker { margin-bottom: 3px; font-size: 10px; }
  .char-cell { border-radius: 12px; }
  .reading-status { margin-top: 3px; font-size: 11px; }
  .controlbar { padding-right: 16px; padding-left: 16px; }
  .control-button { min-width: 44px; }
  .control-button span { font-size: 26px; }
  .play-button { min-width: 132px; }
  .focus-reading .stage-inner { padding-bottom: 12px; }
  .focus-controls-toggle { right: 9px; bottom: max(8px, env(safe-area-inset-bottom)); }
  .focus-reading .controlbar { bottom: max(8px, env(safe-area-inset-bottom)); width: min(calc(100vw - 16px), 430px); }
}

@media (max-height: 480px) and (orientation: landscape) {
  .home-topbar { min-height: 47px; padding-top: 5px; padding-bottom: 5px; }
  .home-heading h1 { font-size: 24px; }
  .home-heading p { font-size: 12px; }
  .topbar { min-height: 47px; padding-top: 5px; padding-bottom: 5px; }
  .reading-stage { padding-top: 5px; padding-bottom: 5px; }
  .stage-inner { padding-top: 7px; padding-bottom: 7px; }
  .reading-row.is-current .char-cell { padding-top: 4px; padding-bottom: 7px; }
  .reading-kicker { margin-bottom: 2px; }
  .reading-status { margin-top: 3px; }
  .controlbar { min-height: 48px; }
}

@media (orientation: portrait) {
  /* 竖屏引导只在朗读屏出现：家长竖屏拿着手机翻书架是正常动作，不该被挡住。 */
  .app-shell[data-view="reader"] .rotate-prompt { position: fixed; z-index: 5; inset: 0; display: flex; align-items: center; justify-content: center; flex-direction: column; gap: 14px; padding: 30px; text-align: center; color: var(--text); background: var(--bg); }
  .rotate-icon { font-size: 72px; line-height: 1; transform: rotate(-28deg); color: var(--sun); }
  .rotate-prompt strong { font-family: var(--han); font-size: 28px; }
  .rotate-prompt span { color: var(--muted); font-size: 14px; }
  .help-dialog { z-index: 6; }
}

/* 减弱动效：压掉「装饰性」过渡与动画。
   但卡拉OK 逐字填充必须豁免 —— 它不是装饰，而是「现在读到哪个字」的唯一指示：
   压到 0.01ms 会退化成整字瞬变（字一下就全金），孩子失去跟读的依据。
   它本身没有位移 / 缩放 / 视差 / 频闪，不属于会引发前庭不适的那类动效，
   保留它才是无障碍与功能两全。注意：减弱动效下 iOS 与部分安卓机型默认开启。 */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { transition-duration: .01ms !important; animation-duration: .01ms !important; }
  .reading-row.is-current .char-cell.is-active .char-han::before,
  .reading-row.is-current .char-cell.is-active .char-pinyin::before {
    animation-duration: var(--char-duration, 420ms) !important;
  }
}
/* 动态目录的加载、空态与可重试错误。 */
.catalog-state { grid-column: 1 / -1; padding: 2rem; text-align: center; }
.catalog-state p { line-height: 1.8; }
