/* ==========================================================================
   信奥教练助手 — 界面样式
   浅色为主、蓝色强调、左侧会话栏、居中消息流。零外部依赖（不引任何 CDN 字体/库）。
   ========================================================================== */

:root {
  --brand:        #4d6bfe;
  --brand-strong: #3a56e0;
  --bg:           #ffffff;
  --bg-side:      #f7f8fa;
  --bg-hover:     #eceef2;
  --bg-active:    #e4e8f5;
  --bubble-user:  #eff3ff;
  --ink:          #1b1e28;
  --ink-soft:     #4a5163;
  --muted:        #8b93a7;
  --line:         #e6e8ee;
  --code-bg:      #f6f7f9;
  --danger:       #d93025;
  --radius:       14px;
  --sidebar-w:    268px;
  --font: system-ui, -apple-system, "Segoe UI", "PingFang SC", "Hiragino Sans GB",
          "Microsoft YaHei", "Source Han Sans SC", sans-serif;
  --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
}

/* 深色主题 */
html[data-theme="dark"] {
  --bg:          #17181c;
  --bg-side:     #1e1f24;
  --bg-hover:    #2a2c33;
  --bg-active:   #32353e;
  --bubble-user: #232731;
  --ink:         #e8eaef;
  --ink-soft:    #b9bfcc;
  --muted:       #868da0;
  --line:        #2e3038;
  --code-bg:     #101114;
}

* { box-sizing: border-box; }

/* 关键：hidden 属性靠的是浏览器默认样式 [hidden]{display:none}，
   而作者样式表里任何 display 声明（grid/flex/block）都会把它覆盖掉。
   多处元素用了 .hidden = true 来控制显隐（.msg-actions 是 flex、.attachments 是 flex 等），
   没有这条兜底就会出现"该隐藏的藏不住"。 */
[hidden] { display: none !important; }

html, body { height: 100%; }

/* 手机上浏览器地址栏会收起/展开，100vh 会算错导致输入框被顶出屏幕。
   用 dvh（动态视口高度）才是准的；不支持的浏览器自动用上面的 100% 兜底。 */
body { min-height: 100dvh; }

body {
  margin: 0;
  font: 15px/1.7 var(--font);
  color: var(--ink);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  display: flex;
  flex-direction: column;
}

/* ---------- 顶部板块导航 ---------- */
.boards {
  flex: 0 0 auto;
  background: var(--bg-side);
  border-bottom: 1px solid var(--line);
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: thin;
}
.boards::-webkit-scrollbar { height: 6px; }
.boards::-webkit-scrollbar-thumb { background: var(--line); border-radius: 3px; }

.boards-inner {
  display: flex;
  align-items: center;
  gap: 3px;
  padding: 7px 12px;
  white-space: nowrap;
  min-height: 44px;
}

.board-group {
  font-size: .7rem;
  color: var(--muted);
  padding: 0 4px 0 12px;
  letter-spacing: .04em;
  user-select: none;
}
.board-group:first-child { padding-left: 0; }

.board-btn {
  border: 1px solid transparent;
  background: none;
  color: var(--ink-soft);
  padding: 5px 11px;
  border-radius: 8px;
  font-size: .82rem;
  white-space: nowrap;
  line-height: 1.4;
}
.board-btn:hover { background: var(--bg-hover); color: var(--ink); }
.board-btn.is-active {
  background: var(--brand);
  color: #fff;
  font-weight: 600;
  border-color: var(--brand);
}
.board-btn.is-empty { opacity: .45; }   /* 人设未加载的板块 */
.board-btn.is-empty::after { content: "·"; margin-left: 3px; }

button { font: inherit; cursor: pointer; }

:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

/* ---------- 布局 ---------- */
.app { display: flex; flex: 1; min-height: 0; overflow: hidden; }

/* ---------- 侧边栏 ---------- */
.sidebar {
  width: var(--sidebar-w); flex: 0 0 var(--sidebar-w);
  display: flex; flex-direction: column;
  background: var(--bg-side); border-right: 1px solid var(--line);
}

.sidebar-head { padding: 14px 12px 8px; }

.brand { display: flex; align-items: center; gap: 9px; padding: 0 6px 12px; }
.brand-mark {
  width: 28px; height: 28px; border-radius: 8px; display: grid; place-items: center;
  background: linear-gradient(140deg, var(--brand), #7c8cff);
  color: #fff; font-size: .72rem; font-weight: 800; letter-spacing: .02em;
}
.brand-name { font-weight: 650; font-size: .95rem; }

.new-chat {
  width: 100%; display: flex; align-items: center; justify-content: center; gap: 6px;
  padding: 10px 12px; border: 1px solid var(--line); border-radius: 10px;
  background: var(--bg); color: var(--ink); font-weight: 600; font-size: .9rem;
}
.new-chat:hover { background: var(--bg-hover); }

.conv-list { flex: 1; overflow-y: auto; padding: 6px 8px; }
.conv-list::-webkit-scrollbar { width: 8px; }
.conv-list::-webkit-scrollbar-thumb { background: var(--line); border-radius: 4px; }

.conv-item {
  display: flex; align-items: center; gap: 8px;
  padding: 9px 10px; margin-bottom: 2px; border-radius: 9px;
  color: var(--ink-soft); font-size: .875rem; cursor: pointer;
  white-space: nowrap; overflow: hidden;
}
.conv-item:hover { background: var(--bg-hover); }
.conv-item.is-active { background: var(--bg-active); color: var(--ink); font-weight: 600; }
.conv-item .conv-text { flex: 1; overflow: hidden; text-overflow: ellipsis; }
.conv-del {
  opacity: 0; border: 0; background: none; color: var(--muted);
  padding: 2px 4px; border-radius: 6px; line-height: 1;
}
.conv-item:hover .conv-del { opacity: 1; }
.conv-del:hover { color: var(--danger); background: var(--bg); }

.sidebar-foot { padding: 10px 14px 14px; border-top: 1px solid var(--line); }
.field { display: block; margin-bottom: 8px; }
.field > span { display: block; font-size: .75rem; color: var(--muted); margin-bottom: 4px; }
.field select {
  width: 100%; padding: 7px 9px; border-radius: 8px; font: inherit; font-size: .85rem;
  border: 1px solid var(--line); background: var(--bg); color: var(--ink);
}
.status { font-size: .75rem; color: var(--muted); line-height: 1.5; }
.status.is-error { color: var(--danger); }

/* ---------- 主区 ---------- */
.main { flex: 1; display: flex; flex-direction: column; min-width: 0; }

.topbar {
  display: flex; align-items: center; gap: 8px;
  padding: 10px 14px; border-bottom: 1px solid var(--line);
}
.topbar-title {
  flex: 1; font-weight: 600; font-size: .95rem;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.icon-btn {
  border: 1px solid transparent; background: none; color: var(--ink-soft);
  width: 34px; height: 34px; border-radius: 9px; display: grid; place-items: center; font-size: 1rem;
}
.icon-btn:hover { background: var(--bg-hover); }
.menu-toggle { display: none; }

/* ---------- 消息区 ---------- */
.messages {
  flex: 1; overflow-y: auto; scroll-behavior: smooth;
  padding: 26px 20px 8px;
}
.messages::-webkit-scrollbar { width: 8px; }
.messages::-webkit-scrollbar-thumb { background: var(--line); border-radius: 4px; }

.welcome { max-width: 760px; margin: 6vh auto 0; text-align: center; }
.welcome h1 { font-size: clamp(1.5rem, 3.6vw, 2rem); margin: 0 0 10px; font-weight: 700; }
.welcome-sub { color: var(--muted); margin: 0 auto 26px; max-width: 52ch; }
.chips { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 10px; }
.chip {
  text-align: left; padding: 13px 15px; border-radius: 12px;
  border: 1px solid var(--line); background: var(--bg); color: var(--ink-soft);
  font-size: .875rem; line-height: 1.5;
}
.chip:hover { background: var(--bg-hover); color: var(--ink); border-color: #d6dae4; }

.msg { max-width: 820px; margin: 0 auto 22px; display: flex; gap: 12px; }
.msg-avatar {
  flex: 0 0 30px; width: 30px; height: 30px; border-radius: 9px;
  display: grid; place-items: center; font-size: .7rem; font-weight: 700; color: #fff;
  background: linear-gradient(140deg, var(--brand), #7c8cff);
}
.msg.user .msg-avatar { background: #8b93a7; }
.msg-body { min-width: 0; flex: 1; }
.msg-role { font-size: .78rem; color: var(--muted); margin-bottom: 4px; font-weight: 600; }
.msg-content { word-wrap: break-word; overflow-wrap: anywhere; }

.msg.user .msg-content {
  background: var(--bubble-user); border-radius: 12px; padding: 10px 14px; display: inline-block;
  max-width: 100%;
}

/* 思考中（三点动画） */
.typing { display: inline-flex; gap: 5px; align-items: center; padding: 6px 0; }
.typing i { width: 7px; height: 7px; border-radius: 50%; background: var(--muted); animation: blink 1.2s infinite; }
.typing i:nth-child(2) { animation-delay: .18s; }
.typing i:nth-child(3) { animation-delay: .36s; }
@keyframes blink { 0%, 60%, 100% { opacity: .25; } 30% { opacity: 1; } }

/* 光标（流式输出时） */
.cursor::after {
  content: "▍"; color: var(--brand); margin-left: 1px;
  animation: caret 1s steps(2) infinite;
}
@keyframes caret { 0%, 50% { opacity: 1; } 51%, 100% { opacity: 0; } }

/* ---------- 深度思考面板 ---------- */
.thinking {
  margin: 0 0 10px; border: 1px solid var(--line); border-radius: 10px;
  background: var(--code-bg); overflow: hidden;
}
.thinking[hidden] { display: none; }
.thinking-summary {
  cursor: pointer; padding: 8px 12px; font-size: .82rem; color: var(--muted);
  list-style: none; display: flex; align-items: center; gap: 7px; user-select: none;
}
.thinking-summary::-webkit-details-marker { display: none; }
.thinking-summary::before {
  content: "▸"; display: inline-block; transition: transform .15s ease; font-size: .7rem;
}
.thinking[open] .thinking-summary::before { transform: rotate(90deg); }
.thinking-body {
  padding: 0 12px 10px; font-size: .84rem; line-height: 1.7; color: var(--muted);
  white-space: pre-wrap; word-break: break-word; max-height: 260px; overflow-y: auto;
  border-top: 1px solid var(--line); padding-top: 9px; margin-top: 2px;
}

/* ---------- Markdown 渲染 ---------- */
.msg-content p { margin: 0 0 .8em; }
.msg-content p:last-child { margin-bottom: 0; }
.msg-content h1, .msg-content h2, .msg-content h3 { margin: 1.1em 0 .5em; line-height: 1.35; }
.msg-content h1 { font-size: 1.35rem; }
.msg-content h2 { font-size: 1.18rem; }
.msg-content h3 { font-size: 1.05rem; }
.msg-content ul, .msg-content ol { margin: 0 0 .8em; padding-left: 1.4em; }
.msg-content li { margin: .25em 0; }
.msg-content a { color: var(--brand); }
.msg-content blockquote {
  margin: .6em 0; padding: .4em 0 .4em 1em; border-left: 3px solid var(--line); color: var(--ink-soft);
}
.msg-content code {
  font-family: var(--mono); font-size: .87em;
  background: var(--code-bg); padding: .15em .38em; border-radius: 5px;
}
.msg-content hr { border: 0; border-top: 1px solid var(--line); margin: 1.2em 0; }
/* 表格外面包了一层 .table-wrap：
   桌面端由它负责横向滚动（列多时），手机端则由它把表格变成卡片（见下面的媒体查询）。 */
.table-wrap { margin: .6em 0; overflow-x: auto; -webkit-overflow-scrolling: touch; }
.msg-content table { border-collapse: collapse; margin: 0; font-size: .92em; min-width: 100%; }
.msg-content th, .msg-content td { border: 1px solid var(--line); padding: 6px 10px; text-align: left; }
.msg-content th { background: var(--code-bg); font-weight: 650; white-space: nowrap; }

.code-block { margin: .7em 0; border: 1px solid var(--line); border-radius: 10px; overflow: hidden; }
.code-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 6px 10px; background: var(--code-bg); border-bottom: 1px solid var(--line);
  font-size: .76rem; color: var(--muted); font-family: var(--mono);
}
.code-copy { border: 1px solid var(--line); background: var(--bg); color: var(--ink-soft);
  border-radius: 6px; padding: 2px 9px; font-size: .74rem; }
.code-copy:hover { background: var(--bg-hover); }
.code-block pre { margin: 0; padding: 12px 14px; overflow-x: auto; background: var(--code-bg); }
.code-block code { background: none; padding: 0; font-size: .85rem; line-height: 1.65; }

/* ---------- 图片附件 ---------- */
.attachments {
  max-width: 820px; margin: 0 auto 8px;
  display: flex; flex-wrap: wrap; gap: 8px;
}
.attachments[hidden] { display: none; }

.attachment {
  position: relative; width: 72px; height: 72px; border-radius: 10px;
  overflow: hidden; border: 1px solid var(--line); background: var(--code-bg);
}
.attachment img { width: 100%; height: 100%; object-fit: cover; display: block; }
.attachment-remove {
  position: absolute; top: 2px; right: 2px; width: 20px; height: 20px;
  border: 0; border-radius: 50%; background: rgba(20, 24, 36, .72); color: #fff;
  font-size: .78rem; line-height: 1; display: grid; place-items: center; padding: 0;
}
.attachment-remove:hover { background: rgba(20, 24, 36, .92); }
.attachment-busy {
  position: absolute; inset: 0; display: grid; place-items: center;
  background: rgba(255, 255, 255, .6); font-size: .7rem; color: var(--ink-soft);
}

.attach-btn {
  flex: 0 0 34px; width: 34px; height: 34px; border-radius: 10px;
  border: 1px solid var(--line); background: var(--bg); color: var(--ink-soft);
  display: grid; place-items: center;
}
.attach-btn:hover { background: var(--bg-hover); color: var(--ink); }

/* 用户消息里的图片 */
.msg-images { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.msg-images img {
  max-width: min(260px, 100%); max-height: 260px; border-radius: 10px;
  border: 1px solid var(--line); display: block; cursor: zoom-in;
}

/* ---------- 朗读按钮 ---------- */
.msg-actions { display: flex; gap: 6px; margin-top: 8px; }
.speak-btn {
  display: inline-flex; align-items: center; gap: 5px;
  border: 1px solid var(--line); background: var(--bg); color: var(--muted);
  border-radius: 8px; padding: 4px 10px; font-size: .76rem;
}
.speak-btn:hover { background: var(--bg-hover); color: var(--ink); }
.speak-btn.is-speaking { color: var(--brand); border-color: #c3cdf0; background: var(--bubble-user); }

/* ---------- 输入区 ----------
   两行结构（用户要求：段落内容独立一行，不要和拍照/录音挤在一行）：
     第一行：textarea 独占整行（width: 100%）
     第二行：.composer-tools 工具条 —— 左：拍照 / 更多 / 录音，右：发送
   两行都在 .composer-inner 这个圆角容器里，视觉上仍是一个输入区。
   ⚠️ 不要把 .composer-inner 改回单行 flex：手机上 3 个 40px 按钮 + 发送按钮
      会把文字挤成 138px 宽的窄条（改前实测值，用户截图就是这个状态）。 */
.composer { padding: 10px 20px 14px; }
.composer.is-dragging .composer-inner {
  border-color: var(--brand); border-style: dashed;
  background: var(--bubble-user);
}
.composer-inner {
  max-width: 820px; margin: 0 auto;
  display: flex; flex-direction: column; align-items: stretch; gap: 8px;
  padding: 10px 9px 9px; border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--bg); box-shadow: 0 2px 10px -6px rgba(20, 30, 60, .18);
}
.composer-inner:focus-within { border-color: #c3cdf0; }
/* 第一行：段落内容独占整行。
   flex:none 是为了在竖向 flex 里按自身高度走（以前横向一行时才是 flex:1）。 */
.composer-inner textarea {
  flex: none; width: 100%; min-width: 0;
  border: 0; outline: none; resize: none; background: none; color: var(--ink);
  /* 输入框字号比正文小 30%（原来是 font: inherit，跟着 body 的 15px 走）。
     用 0.7em 而不是写死 10.5px：它相对父级算，body 的基准变了会跟着变。
     注意手机端不能这么小 —— 见下面 640px 那条（iOS 聚焦会自动放大页面）。 */
  font-family: inherit;
  font-size: 0.7em;
  line-height: 1.6; max-height: 180px; padding: 5px 0;
}
/* 第二行：工具条。左边三个按钮靠左，发送按钮用 margin-left:auto 推到最右。 */
.composer-tools {
  display: flex; align-items: center; gap: 8px; min-width: 0;
}
.composer-tools .send-btn { margin-left: auto; }
.send-btn {
  flex: 0 0 34px; width: 34px; height: 34px; border-radius: 10px; border: 0;
  background: var(--brand); color: #fff; display: grid; place-items: center;
}
.send-btn:hover:not(:disabled) { background: var(--brand-strong); }
.send-btn:disabled { background: var(--line); color: var(--muted); cursor: not-allowed; }
.disclaimer { max-width: 820px; margin: 8px auto 0; text-align: center; font-size: .74rem; color: var(--muted); }

/* ---------- 登录 / 注册 ---------- */
.auth-card { max-width: 400px; }
.auth-tabs { display: flex; gap: 6px; margin: 12px 0 16px; }
.auth-tab {
  flex: 1; padding: 8px; border-radius: 9px; border: 1px solid var(--line);
  background: var(--bg); color: var(--ink-soft); font-weight: 600; font-size: .88rem;
}
.auth-tab.is-active { background: var(--brand); border-color: var(--brand); color: #fff; }
.auth-field { display: block; margin-bottom: 12px; }
.auth-field > span:first-child { display: block; font-size: .78rem; color: var(--muted); margin-bottom: 4px; }
/* 同样排除勾选框：表单里以后加"记住我"之类的复选框时，
   不排除就会被撑成整行宽（.modal-card input 就是这么坑了试卷识别的排版）。 */
.auth-field input:not([type="checkbox"]):not([type="radio"]) {
  width: 100%; padding: 10px 12px; border: 1px solid var(--line); border-radius: 9px;
  font: inherit; background: var(--bg); color: var(--ink);
}
.code-row { display: flex; gap: 8px; }
.code-row input { flex: 1; }
.btn-code {
  flex: 0 0 auto; padding: 0 14px; border-radius: 9px; border: 1px solid var(--line);
  background: var(--bg); color: var(--ink-soft); font-size: .84rem; white-space: nowrap;
}
.btn-code:hover:not(:disabled) { background: var(--bg-hover); }
.btn-code:disabled { opacity: .55; cursor: not-allowed; }
#auth-submit { width: 100%; margin-top: 6px; }
.modal-hint { color: var(--muted); font-size: .78rem; margin: 10px 0 0; min-height: 1.1em; }

/* ---------- 用量提示 ---------- */
.quota-badge {
  display: inline-block; padding: 1px 7px; border-radius: 20px; font-size: .72rem;
  background: var(--bg-hover); color: var(--muted);
}

/* ---------- 数学公式 ---------- */
/* KaTeX 自带样式已由 katex.min.css 提供，这里只做与本站主题的衔接 */

.msg-content .katex { font-size: 1.02em; }
/* 独立公式（\[ ... \]）：允许横向滚动，避免长公式把消息撑破。
   注意不能只写 overflow-x —— 那会把公式的上下标裁掉。 */
.msg-content .katex-display {
  margin: .7em 0;
  overflow-x: auto;
  overflow-y: hidden;
  padding: 2px 0 4px;
}
.msg-content .katex-display > .katex { white-space: normal; }
/* 公式渲染失败（KaTeX 未加载或语法错误）时退回显示源码 */
.math-fallback {
  font-family: var(--mono); font-size: .88em;
  background: var(--code-bg); color: var(--danger);
  padding: 1px 5px; border-radius: 5px; white-space: pre-wrap;
}
/* 暗色主题下 KaTeX 的默认色是黑的，要跟随主题 */
[data-theme="dark"] .msg-content .katex { color: var(--ink); }

/* ---------- 新版本提示条 ---------- */
.update-banner {
  position: fixed; top: 0; left: 0; right: 0; z-index: 200;
  display: flex; align-items: center; justify-content: center; gap: 12px;
  padding: calc(9px + env(safe-area-inset-top)) 14px 9px;
  background: var(--brand); color: #fff; font-size: .88rem; font-weight: 600;
  box-shadow: 0 2px 12px rgba(0,0,0,.18);
}
.update-banner button {
  padding: 5px 14px; border-radius: 20px; border: 0;
  background: #fff; color: var(--brand); font-weight: 700; font-size: .84rem;
}

/* ==========================================================================
   错题本
   ========================================================================== */

.notebook-entry {
  display: flex; align-items: center; gap: 8px; width: 100%;
  margin-top: 8px; padding: 9px 12px; border-radius: 10px;
  border: 1px solid var(--line); background: var(--bg); color: var(--ink);
  font: inherit; font-size: .88rem; text-align: left;
}
.notebook-entry:hover { background: var(--bg-hover); }
.notebook-count {
  margin-left: auto; min-width: 20px; padding: 1px 7px; border-radius: 20px;
  background: var(--brand); color: #fff; font-size: .72rem; font-weight: 700; text-align: center;
}
.notebook-count:empty { display: none; }

.nb {
  position: fixed; inset: 0; z-index: 60; background: var(--bg-soft, #f7f8fa);
  display: flex; flex-direction: column; overflow: hidden;
}
.nb-head {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 12px 16px; background: var(--bg); border-bottom: 1px solid var(--line);
  padding-top: calc(12px + env(safe-area-inset-top));
  position: relative;   /* 顶部「⋯」菜单以它为定位基准 */
}
/* 标题**永不换行**：右边四个按钮会把宽度吃满，
   不加 nowrap 的话「错题本」会被挤成「错题 / 本」两行（用户报过）。 */
.nb-head strong { font-size: 1.05rem; flex: 0 0 auto; white-space: nowrap; }
/* 按钮留在同一行（不换行），实在放不下就横向滚动，而不是把标题压扁 */
.nb-head-right {
  display: flex; gap: 8px;
  /* ⚠️ 原来是 nowrap + overflow-x: auto（注释写的是"手机上不显示滚动条，能滑就行"）。
     两个导出按钮时刚好放得下；加到三个（Excel / 打印版 / Markdown）之后，
     实测：360px 手机上需要 282px 而只有 224px，**连 414px 的大屏也只有 263px** ——
     第三个按钮被推到屏幕外，而**没有滚动条提示，学生根本不知道能往右滑**。
     改成换行：宁可多占一行，也不能让人看不见按钮。 */
  flex-wrap: wrap; justify-content: flex-end;
}
.nb-head-right::-webkit-scrollbar { display: none; }
.nb-head-right .btn-mini { flex: 0 0 auto; }

.btn-mini {
  padding: 5px 11px; border-radius: 8px; border: 1px solid var(--line);
  background: var(--bg); color: var(--ink); font: inherit; font-size: .82rem;
}
.btn-mini:hover { background: var(--bg-hover); }
.btn-mini.ghost { border-color: transparent; color: var(--muted); }
.btn-mini.danger { color: var(--danger); }
/* ⚠️ .btn-mini.primary 之前**根本没有样式** —— 只有 .ghost 和 .danger。
   于是「📷 拍试卷」这类主操作按钮一直是普通外观，和旁边的次要按钮长得一样，
   用户看不出哪个是主要动作（截图才发现）。补一条，和 .viz-bar button.primary 对齐。 */
.btn-mini.primary {
  background: var(--brand); border-color: var(--brand);
  color: #fff; font-weight: 600;
}
.btn-mini.primary:hover { background: var(--brand-strong); border-color: var(--brand-strong); }
.btn-mini.primary:disabled { opacity: .5; }
/* 禁用态：背诵本卡片上的「📤 分享」在资料**没拆好**时置灰（只能分享整理好的资料）。
   要看得见、但一眼知道点不动 —— 隐藏会让人以为没有分享功能，所以选择置灰 + title 说明。 */
.btn-mini:disabled { opacity: .45; cursor: not-allowed; }
.btn-mini:disabled:hover { background: var(--bg); }

.nb-filter {
  display: flex; gap: 10px; flex-wrap: wrap; align-items: flex-end;
  padding: 12px 16px; background: var(--bg); border-bottom: 1px solid var(--line);
}
.nb-field { display: flex; flex-direction: column; gap: 3px; font-size: .75rem; color: var(--muted); }
.nb-field.grow { flex: 1 1 180px; }
.nb-field select, .nb-field input {
  padding: 6px 9px; border: 1px solid var(--line); border-radius: 8px;
  font: inherit; font-size: .86rem; background: var(--bg); color: var(--ink);
}

.nb-summary { padding: 10px 16px; font-size: .84rem; color: var(--ink-soft); }
.nb-chip {
  display: inline-block; margin: 0 4px 2px 0; padding: 1px 8px; border-radius: 20px;
  background: #e8f0fe; color: var(--brand-strong); font-size: .78rem;
}

.nb-list { flex: 1; overflow-y: auto; padding: 0 16px 30px; -webkit-overflow-scrolling: touch; }
.nb-empty { color: var(--muted); text-align: center; padding: 40px 20px; line-height: 1.9; }

.nb-card {
  background: var(--bg); border: 1px solid var(--line); border-radius: 12px;
  padding: 13px 15px; margin-bottom: 11px;
}
.nb-card.is-mastered { opacity: .62; }
.nb-card-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: .78rem; }
.nb-point {
  /* 考点徽章的颜色由 JS 挑的 .nb-cN 决定（同一个考点永远同色）；
     没有 .nb-cN 的地方（比如拍试卷的预览列表）照旧用主色。 */
  padding: 2px 9px; border-radius: 20px; background: var(--nb-accent, var(--brand));
  color: #fff; font-weight: 600;
}
.nb-diff { color: #e8a33d; letter-spacing: 1px; }
.nb-tag { padding: 1px 8px; border-radius: 20px; background: var(--bg-hover); color: var(--muted); }
.nb-tag.ok { background: #e6f4ea; color: var(--ok, #188038); }
.nb-date { margin-left: auto; color: var(--muted); }
.nb-card h4 { margin: 8px 0 6px; font-size: .96rem; }
.nb-card details { margin: 6px 0; }
.nb-card summary { cursor: pointer; color: var(--muted); font-size: .82rem; }
.nb-card pre {
  white-space: pre-wrap; word-break: break-word; background: var(--code-bg);
  padding: 9px 11px; border-radius: 8px; font-size: .84rem; margin: 6px 0 0;
}
.nb-reason, .nb-note { margin: 6px 0 0; font-size: .87rem; line-height: 1.65; }
.nb-reason b, .nb-note b { color: var(--brand-strong); margin-right: 6px; }
/* 原题照片：题目里有图的时候文字描述不出来，必须能看原图。
   缩略图给足尺寸（130px 看不清数轴上的标注），点开进大图查看器。 */
.nb-imgs { display: flex; gap: 8px; flex-wrap: wrap; margin: 8px 0 0; align-items: flex-start; }
.nb-imgs-cap { flex: 0 0 100%; font-size: .76rem; color: var(--muted); margin-bottom: 2px; }
.nb-img-btn {
  position: relative; width: 180px; height: 180px; padding: 0; overflow: hidden;
  border-radius: 9px; border: 1px solid var(--line); background: var(--code-bg);
  cursor: zoom-in; display: block;
}
.nb-img-btn img { width: 100%; height: 100%; object-fit: cover; display: block; }
.nb-img-btn:hover { border-color: var(--brand); }
.nb-img-ph {
  position: absolute; inset: 0; display: grid; place-items: center;
  font-size: .72rem; color: var(--muted);
}
.nb-img-btn.is-loaded .nb-img-ph { display: none; }
.nb-img-btn.is-failed { cursor: default; border-style: dashed; }
/* 编辑表单里的小预览 */
.mk-img [data-img] { display: block; width: 100%; height: 100%; }
.mk-img [data-img] img { width: 100%; height: 100%; object-fit: cover; display: block; }
.mk-img .nb-img-ph { font-size: .6rem; }

/* 刷题卡最后那块「原题照片」：默认折成一行，点开才看照片。
   为什么单独一套样式、不改公共的 .nb-imgs：拍试卷预览、编辑表单里也用 .nb-imgs，
   那些地方需要"直接摊开"，只有刷题卡要折起来（用户要的是做题时屏幕给题目让路）。 */
.nb-photos { margin-top: 10px; border-top: 1px solid var(--line); padding-top: 8px; }
.nb-photos-sum {
  cursor: pointer; font-size: .8rem; color: var(--muted); padding: 4px 0;
  list-style: none;   /* 去掉默认三角：手机上那个小三角很难点中，整行都可点 */
}
.nb-photos-sum::-webkit-details-marker { display: none; }
.nb-photos-sum::before { content: '▸ '; }
.nb-photos[open] .nb-photos-sum::before { content: '▾ '; }
.nb-photos[open] .nb-photos-sum { color: var(--ink-soft); }
/* 长照片展开后自己滚（不给 max-height 的话一张长图会把整屏顶飞，
   题目和按钮全被推到屏幕外，学生得一路滚下去才能点「做下一题」） */
.nb-photos .nb-imgs { max-height: 46vh; overflow-y: auto; -webkit-overflow-scrolling: touch; }
.nb-photos .nb-img-btn { width: 130px; height: 130px; }
.nb-photos .nb-img-btn img { object-fit: contain; }
/* ⚠️ 这一条**必须自己写**：浏览器对"关着的 <details>"里的内容用的是 UA 样式
   （display:none），而**作者样式优先于 UA 样式** —— 上面那条 .nb-photos .nb-imgs
   的 display:flex 会把它顶掉，结果就是"折起来了，但照片照样占着屏幕"
   （这正是浏览器测试抓到的：折起来时照片区还有 668x130）。
   显式写一条 :not([open]) 比猜某个浏览器版本的行为靠谱。 */
.nb-photos:not([open]) > .nb-imgs { display: none; }

/* 朗读开关：卡片头上一个小按钮。开着时高亮（学生一眼知道"现在会出声"），
   关掉时是灰的 —— 图书馆里做题前看一眼就知道会不会响。 */
.nb-say { flex: 0 0 auto; margin-left: auto; white-space: nowrap; }
.nb-say.is-on { color: var(--brand-strong); border-color: var(--brand); }

/* 原题照片大图查看器：可缩放、可拖动 */
.img-view {
  position: fixed; inset: 0; z-index: 90; background: rgba(12, 15, 24, .94);
  display: flex; flex-direction: column;
}
.img-view[hidden] { display: none !important; }
.img-view-stage {
  position: relative;   /* 裁剪层要相对它定位 */
  flex: 1; min-height: 0; overflow: hidden; display: grid; place-items: center;
  touch-action: none;   /* 手指拖动用来平移，不要触发页面滚动 */
}
.img-view-stage img {
  transform-origin: center center; user-select: none; -webkit-user-drag: none;
  will-change: transform;
}
.img-view-bar {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  padding: 10px 12px calc(12px + env(safe-area-inset-bottom));
  background: rgba(20, 24, 36, .92); flex-wrap: wrap;
}
.img-view-zoom { color: #cfd6e6; font-size: .8rem; min-width: 52px; text-align: center; font-variant-numeric: tabular-nums; }
.img-view-hint {
  text-align: center; color: #cfd6e6; font-size: .76rem;
  padding: 6px 12px 0; background: rgba(20, 24, 36, .92);
}
.img-view-hint[hidden] { display: none !important; }

/* ---------- 图片裁剪 ---------- */
/* 手机拍试卷常有多余的桌面/边角，或者只想留错题那一块。
   框外用一层半透明黑遮住，一眼能看出会裁掉什么。 */
.crop-layer { position: absolute; inset: 0; z-index: 3; touch-action: none; cursor: crosshair; }
.crop-layer[hidden] { display: none !important; }
.crop-box {
  position: absolute; box-sizing: border-box;
  border: 1px solid rgba(255, 255, 255, .9);
  /* 用超大外阴影做"框外变暗"，这样框内完全不受影响 */
  box-shadow: 0 0 0 9999px rgba(8, 10, 18, .62);
  cursor: move;
}
/* 三等分参考线，帮助对齐（试卷边框、题目分隔） */
.crop-box::before, .crop-box::after {
  content: ''; position: absolute; pointer-events: none;
  background: rgba(255, 255, 255, .32);
}
.crop-box::before { left: 33.33%; right: 33.33%; top: 0; bottom: 0; border-left: 1px solid rgba(255,255,255,.32); border-right: 1px solid rgba(255,255,255,.32); background: none; }
.crop-box::after { top: 33.33%; bottom: 33.33%; left: 0; right: 0; border-top: 1px solid rgba(255,255,255,.32); border-bottom: 1px solid rgba(255,255,255,.32); background: none; }
/* 四角手柄：做大一点，手指才抓得住（触摸目标约 40px） */
.crop-h {
  position: absolute; width: 40px; height: 40px; touch-action: none;
  display: grid; place-items: center;
}
.crop-h::after {
  content: ''; width: 18px; height: 18px; border: 3px solid #fff;
  border-radius: 3px; box-shadow: 0 1px 4px rgba(0, 0, 0, .5);
}
/* 四角 */
.crop-h[data-crop="nw"] { left: -20px; top: -20px; cursor: nwse-resize; }
.crop-h[data-crop="ne"] { right: -20px; top: -20px; cursor: nesw-resize; }
.crop-h[data-crop="sw"] { left: -20px; bottom: -20px; cursor: nesw-resize; }
.crop-h[data-crop="se"] { right: -20px; bottom: -20px; cursor: nwse-resize; }
.crop-h[data-crop="nw"]::after { border-right: 0; border-bottom: 0; }
.crop-h[data-crop="ne"]::after { border-left: 0; border-bottom: 0; }
.crop-h[data-crop="sw"]::after { border-right: 0; border-top: 0; }
.crop-h[data-crop="se"]::after { border-left: 0; border-top: 0; }
/* 四边中点：只改一个方向。
   裁试卷边框时常常只需裁掉上下的桌面、或左右的边 ——
   只给四角的话，拖的时候另一个方向也会跟着变，很难对准。 */
.crop-h[data-crop="n"] { left: 50%; top: -20px; margin-left: -20px; cursor: ns-resize; }
.crop-h[data-crop="s"] { left: 50%; bottom: -20px; margin-left: -20px; cursor: ns-resize; }
.crop-h[data-crop="w"] { top: 50%; left: -20px; margin-top: -20px; cursor: ew-resize; }
.crop-h[data-crop="e"] { top: 50%; right: -20px; margin-top: -20px; cursor: ew-resize; }
/* 边中点画成一条短杠，和四角的方角区分开 */
.crop-h[data-crop="n"]::after, .crop-h[data-crop="s"]::after {
  width: 26px; height: 0; border-width: 0 0 4px 0; border-radius: 2px;
}
.crop-h[data-crop="w"]::after, .crop-h[data-crop="e"]::after {
  width: 0; height: 26px; border-width: 0 0 0 4px; border-radius: 2px;
}
.nb-card-foot {
  display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
  margin-top: 11px; padding-top: 10px; border-top: 1px solid var(--line);
}
.nb-meta { margin-left: auto; color: var(--muted); font-size: .76rem; }

/* ---------- 错题本配色（全部从现有变量派生，深色主题自动跟着变） ----------
   用户要「题目多用彩色」，但不能为了彩色牺牲可读性、也不能写死十六进制
   （写死了深色主题下就是一片瞎）。所以：
     · .nb 上定义 --nb-c1~--nb-c6 六个"区分用"色号，值全部是**已有变量**；
       JS 只负责按科目/考点稳定地挑一个号（.nb-cN 类），颜色怎么定在这儿改。
     · 正文一律用 --ink（最高对比），颜色只加在"要区分"的东西上。 */
.nb {
  --nb-c1: var(--brand);
  --nb-c2: var(--danger);
  --nb-c3: var(--brand-strong);
  --nb-c4: var(--ink-soft);
  --nb-c5: var(--muted);
  --nb-c6: var(--ink);
  --nb-accent: var(--brand);
  --nb-math: var(--brand-strong);
  --nb-key: var(--danger);
}
.nb-c1 { --nb-accent: var(--nb-c1); }
.nb-c2 { --nb-accent: var(--nb-c2); }
.nb-c3 { --nb-accent: var(--nb-c3); }
.nb-c4 { --nb-accent: var(--nb-c4); }
.nb-c5 { --nb-accent: var(--nb-c5); }
.nb-c6 { --nb-accent: var(--nb-c6); }

/* ---------- 顶部：一条细栏（收起来）/ 筛选面板（展开） ----------
   做题时筛选必须收起来，把屏幕让给题目；细栏上要能看出"现在在刷什么"。 */
.nb-fbar {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 8px 14px; margin: 0; border: 0; border-bottom: 1px solid var(--line);
  background: var(--bg); color: var(--ink-soft); font: inherit; font-size: .8rem;
  cursor: pointer; text-align: left;
}
.nb-fbar-txt {
  flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--nb-accent, var(--ink-soft)); font-weight: 600;
}
.nb-fbar-hint { color: var(--muted); font-size: .74rem; flex: 0 0 auto; }
.nb-filters {
  display: flex; flex-direction: column; gap: 8px;
  padding: 10px 14px 12px; background: var(--bg-soft, var(--bg-side));
  border-bottom: 1px solid var(--line);
}
.nb-f-head { display: flex; align-items: center; gap: 8px; }
.nb-f-title { flex: 1; font-size: .78rem; color: var(--muted); }
.nb-f-row { display: flex; gap: 10px; flex-wrap: wrap; }
.nb-f-row .nb-field { flex: 1 1 140px; }
.nb-f-topics { display: flex; flex-wrap: wrap; gap: 7px; }
/* 考点 chips：每个考点一个颜色（同一考点永远同色），带题数 —— 手机上点着舒服 */
.nb-topic-chip {
  display: inline-flex; align-items: center; gap: 5px; max-width: 100%;
  font: inherit; font-size: .82rem; padding: 7px 11px; border-radius: 20px;
  border: 1px solid var(--line); background: var(--bg); color: var(--ink);
  cursor: pointer; text-align: left;
}
.nb-topic-chip .nb-topic-chip-t { color: var(--nb-accent, var(--ink)); font-weight: 600; word-break: break-word; }
.nb-topic-chip b { color: var(--muted); font-weight: 600; font-size: .76rem; }
.nb-topic-chip.is-on { border-color: var(--nb-accent, var(--brand)); box-shadow: inset 0 0 0 1px var(--nb-accent, var(--brand)); }
.nb-topic-chip.is-on b { color: var(--nb-accent, var(--brand-strong)); }

/* ---------- 刷题：一题一屏，点一次揭示一层 ----------
   为什么每题独占一屏：用户要的是"一题一题做"。整册一次列出来时，一屏能看见
   四五道题，做的时候会被下一题的题干干扰，也会不小心瞟到下一题的答案。
   整张卡片可点（卡片里的按钮/图片各自拦自己的点击）—— 做题时手上是笔不是鼠标。 */
.nb-drill { flex: 1; min-height: 0; overflow-y: auto; padding: 14px 14px 30px; -webkit-overflow-scrolling: touch; }
.nb-drill-card {
  background: var(--bg); border: 1px solid var(--line); border-radius: 14px;
  /* 科目色：左边一条竖线，一眼知道这是哪一科 */
  border-left: 4px solid var(--nb-accent, var(--brand));
  padding: 14px 15px; cursor: pointer;
}
.nb-drill-progress { font-weight: 700; color: var(--brand-strong); margin-right: 2px; }
.nb-drill-q { margin: 10px 0 0; }
.nb-drill-cap {
  display: inline-block; font-size: .72rem; font-weight: 700; letter-spacing: .08em;
  color: var(--muted); margin-bottom: 5px;
}
/* 题目正文：这是给学生**做**的，字号要比列表预览大得多；
   长题在 .nb-drill 里滚动（pre-wrap 不截断）。 */
.nb-drill-q pre {
  white-space: pre-wrap; word-break: break-word; background: var(--code-bg);
  padding: 14px 14px; border-radius: 10px; margin: 0;
  font-size: 1.12rem; line-height: 1.95; color: var(--ink);
}
/* 题目内部彩色：选项 / 公式 / 重点词。只改颜色不改内容，
   正文仍是 --ink，颜色只用在"区分"上。 */
.nb-opt { display: inline-block; font-weight: 600; }
.nb-opt-a { --nb-accent: var(--brand); }
.nb-opt-b { --nb-accent: var(--danger); }
.nb-opt-c { --nb-accent: var(--brand-strong); }
.nb-opt-d { --nb-accent: var(--ink-soft); }
.nb-opt { color: var(--nb-accent, var(--ink)); }
.nb-math { color: var(--nb-math); font-weight: 600; }
.nb-key { color: var(--nb-key); font-weight: 700; }
/* 答案和解析是两层：答案用来对错，解析用来看"下次怎么做对"。
   背景和左边条区分开（都用主题变量，深色下不会变瞎），正文保持高对比。 */
.nb-drill-layer {
  margin-top: 13px; padding: 11px 13px; border-radius: 10px;
  background: var(--bubble-user); border-left: 3px solid var(--brand);
}
.nb-drill-ana { background: var(--bg-hover); border-left-color: var(--ink-soft); }
.nb-drill-ans .nb-answer { color: var(--ink); font-weight: 700; font-size: 1.02rem; }
.nb-drill-layer .nb-answer, .nb-drill-layer .nb-analysis { margin: 0; white-space: pre-wrap; word-break: break-word; }
.nb-drill-hint { margin: 12px 0 0; font-size: .78rem; color: var(--muted); line-height: 1.7; }
.nb-drill-card .nb-card-foot { margin-top: 12px; }

/* 刷完一组：明确收尾，不默默绕回第 1 题 */
.nb-done { text-align: center; padding: 44px 20px; }
.nb-done-t { font-size: 1.05rem; font-weight: 700; margin: 0 0 8px; }
.nb-done-sub { color: var(--muted); font-size: .86rem; margin: 0 0 16px; line-height: 1.8; }
.nb-done-act { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; }

/* ---------- 错题编辑表单 ---------- */
.mistake-card { max-width: 640px; height: auto; max-height: 92vh; overflow-y: auto; padding: 20px; }
.mistake-card h2 { margin: 0 0 14px; font-size: 1.1rem; }
.mk-row { display: flex; gap: 10px; flex-wrap: wrap; }
.mk-row .mk-field { flex: 1 1 150px; }
.mk-field.narrow { flex: 0 0 130px; }
.mk-field { display: block; margin-bottom: 11px; }
.mk-field > span { display: block; font-size: .77rem; color: var(--muted); margin-bottom: 4px; }
.mk-field input:not([type="checkbox"]):not([type="radio"]),
.mk-field textarea, .mk-field select {
  width: 100%; padding: 9px 11px; border: 1px solid var(--line); border-radius: 9px;
  font: inherit; font-size: .9rem; background: var(--bg); color: var(--ink); resize: vertical;
}
.mk-images { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 11px; }
.mk-img { position: relative; }
.mk-img img { max-width: 106px; max-height: 106px; border-radius: 8px; border: 1px solid var(--line); }
.mk-img button {
  position: absolute; top: -6px; right: -6px; width: 21px; height: 21px; border-radius: 50%;
  border: 0; background: var(--danger); color: #fff; font-size: 14px; line-height: 1; cursor: pointer;
}
.mk-foot { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 6px; }
.mk-msg { color: var(--muted); font-size: .82rem; }

@media (max-width: 640px) {
  .nb-filter { gap: 8px; padding: 10px 12px; }
  .nb-field { flex: 1 1 44%; }
  .nb-field select, .nb-field input { font-size: 16px; }  /* 防 iOS 聚焦缩放 */
  .nb-list { padding: 0 11px 24px; }
  .nb-summary { padding: 9px 12px; }
  .nb-head { padding: 10px 12px calc(10px + env(safe-area-inset-top)); padding-top: calc(10px + env(safe-area-inset-top)); gap: 7px; }
  /* 一行放下「标题 + 四个按钮」：整体缩小，标题保持不换行 */
  .nb-head strong { font-size: .95rem; }
  .nb-head-right { gap: 5px; }
.nb-head-right .btn-mini { padding: 5px 8px; font-size: .74rem; }

  .nb-card-foot .nb-meta { margin-left: 0; flex-basis: 100%; }
  .mistake-card { padding: 16px; }
  .mk-field input, .mk-field textarea, .mk-field select { font-size: 16px; }
}

/* ==========================================================================
   可视化演示（算法动画 / 函数图像）
   ========================================================================== */

.viz {
  border: 1px solid var(--line); border-radius: 12px; background: var(--bg);
  margin: .7em 0; overflow: hidden;
}
.viz-head {
  display: flex; align-items: center; gap: 9px; flex-wrap: wrap;
  padding: 9px 13px; border-bottom: 1px solid var(--line); background: var(--code-bg);
}
.viz-title { font-weight: 650; font-size: .92rem; }
.viz-kind {
  padding: 1px 8px; border-radius: 20px; font-size: .72rem;
  background: #e8f0fe; color: var(--brand-strong);
}
.viz-step-no { margin-left: auto; font-size: .78rem; color: var(--muted); }

.viz-stage { padding: 14px 13px 6px; overflow-x: auto; }
.viz-note {
  min-height: 1.6em; padding: 0 13px 10px; font-size: .87rem; color: var(--ink-soft);
}
.viz-note:empty { display: none; }
.viz-bar {
  display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
  padding: 9px 13px; border-top: 1px solid var(--line); background: var(--code-bg);
}
.viz-bar button {
  padding: 5px 11px; border-radius: 8px; border: 1px solid var(--line);
  background: var(--bg); color: var(--ink); font: inherit; font-size: .84rem;
}
.viz-bar button:hover { background: var(--bg-hover); }
.viz-bar button.primary { background: var(--brand); border-color: var(--brand); color: #fff; font-weight: 600; }
.viz-bar input[type="range"] { flex: 1 1 120px; min-width: 90px; accent-color: var(--brand); }
.viz-bar select {
  padding: 5px 8px; border-radius: 8px; border: 1px solid var(--line);
  background: var(--bg); color: var(--ink); font: inherit; font-size: .82rem;
}
.viz-err { color: var(--danger); font-size: .86rem; margin: 0; padding: 10px 13px; }
.viz-warn { color: var(--muted); font-size: .78rem; margin: 0; padding: 0 13px 8px; }

/* ---------- 数组 / 指针 ---------- */
/* 贴着数组宽度的包裹层：指针与区间条都以它为参照，
   用百分比也不会跑到舞台中间去（用户截图里的错位就是百分比相对了满宽容器）。 */
.viz-arrbox { display: inline-block; max-width: 100%; }
.viz-arr { display: flex; gap: 5px; position: relative; padding-bottom: 4px; }
.viz-cell {
  display: flex; flex-direction: column; align-items: center; min-width: 40px;
  border: 1px solid var(--line); border-radius: 8px; overflow: hidden; background: var(--bg);
  transition: background .18s, border-color .18s;
}
.viz-cell b { padding: 7px 8px 5px; font-size: .92rem; font-weight: 600; }
.viz-cell i {
  font-style: normal; font-size: .66rem; color: var(--muted);
  background: var(--code-bg); width: 100%; text-align: center; padding: 1px 0;
}
.viz-cell.is-mark { border-color: var(--brand); background: #eef2ff; }
.viz-cell.is-done { border-color: #b7dfc6; background: #eefaf1; }
.viz-cell.is-cur, .viz-cell.is-mark b { color: var(--brand-strong); }
/* 空位：插入排序挪位时，被移走的位置画成虚框，避免与"重复值"混淆 */
.viz-cell.is-hole { border-style: dashed; border-color: #c9cfda; background: repeating-linear-gradient(45deg, #fafbfc, #fafbfc 4px, #f0f2f6 4px, #f0f2f6 8px); }
.viz-key {
  display: inline-block; margin-bottom: 7px; padding: 3px 11px; border-radius: 8px;
  background: #fff4e0; color: #9a6414; font-size: .84rem;
}
.viz-key b { font-size: 1.02rem; }

/* ---------- 二分答案的「答案取值」数值轴 ---------- */
/* 必须显式给宽度：.viz-stage 是纵向 flex + 居中，而这个盒子里的元素**全是绝对定位**，
   不给宽度它就会塌成 0 宽 —— 高度 68px 但看不见任何东西，标签还会从左边溢出去。 */
/* 注意：类名不能叫 .viz-range —— 那个名字早被"二分查找的当前区间细条"占用了
   （下面 .viz-ptrs 附近那条 absolute/3px 的），重名会互相覆盖，
   结果就是数值轴被压成 3px 高的一条细线、还定位到了页面顶部（这个坑踩过）。 */
.viz-numaxis {
  position: relative; height: 68px; width: 100%; max-width: 620px;
  margin: 6px auto 2px; box-sizing: border-box; padding: 0 26px;
}
.viz-range-track {
  position: absolute; left: 0; right: 0; top: 30px; height: 6px;
  border-radius: 3px; background: #e6e9ef;
}
.viz-range-win {
  position: absolute; top: 29px; height: 8px; border-radius: 4px;
  background: rgba(26,147,111,.55);
}
.viz-range-dot {
  position: absolute; top: 28px; width: 10px; height: 10px; margin-left: -5px;
  border-radius: 50%; border: 1.5px solid #fff;
}
.viz-range-dot.is-ok { background: #1a936f; }
.viz-range-dot.is-no { background: #e8505b; }
.viz-range-ans {
  position: absolute; top: 46px; transform: translateX(-50%);
  font: 600 10px/1 var(--mono); color: #1a936f;
}
.viz-range-tag {
  position: absolute; top: 2px; transform: translateX(-50%);
  font: 600 11px/1 var(--mono); padding: 2px 4px; border-radius: 4px; white-space: nowrap;
}
.viz-range-tag.is-mid { color: #fff; background: #8e44ad; }
.viz-range-tag.is-lo { color: #1a936f; }
.viz-range-tag.is-hi { color: #e8505b; }
.viz-range-end {
  position: absolute; top: 52px; font: 10px/1 var(--mono); color: var(--muted);
  transform: translateX(-50%);
}
.viz-range-end:first-of-type { transform: none; }
.viz-range-cap { margin: 0 0 8px; font-size: .74rem; color: var(--muted); text-align: center; }
/* 被搬走的石头 / 被锯掉的部分：划掉并变灰 */
.viz-cell.is-removed { border-color: #e8505b; background: #fdecea; opacity: .75; }
.viz-cell.is-removed b { text-decoration: line-through; color: #b3261e; }
.viz-cell.is-cut { border-color: #e8a33d; background: #fff4e0; }
.viz-cell.is-cut::after {
  content: ''; position: absolute; left: -3px; top: 0; bottom: 0;
  border-left: 2px dashed #e8a33d;
}

/* ---------- 代码对照面板 ---------- */
.viz-code { border-top: 1px solid var(--line); background: var(--code-bg); }
.viz-code > summary {
  cursor: pointer; padding: 7px 13px; font-size: .8rem; color: var(--muted);
  list-style: none;
}
.viz-code > summary::-webkit-details-marker { display: none; }
.viz-code > summary::before { content: '▸ '; }
.viz-code[open] > summary::before { content: '▾ '; }
.viz-code-body {
  max-height: 220px; overflow-y: auto; padding: 0 0 8px;
  font-family: var(--mono); font-size: .78rem; line-height: 1.65;
}
.viz-cl { display: flex; gap: 10px; padding: 1px 13px; border-left: 3px solid transparent; }
.viz-cl i { color: #a8b0c0; font-style: normal; min-width: 1.6em; text-align: right; user-select: none; }
.viz-cl b { font-weight: 400; white-space: pre-wrap; word-break: break-word; }
.viz-cl.is-on { background: #fff4e0; border-left-color: #e8a33d; }
.viz-cl.is-on b { font-weight: 650; }
.viz-warn-bad {
  margin: 0 0 .5em; padding: 7px 11px; border-radius: 8px;
  background: #fdecea; color: #b3261e; font-size: .82rem; line-height: 1.55;
}

.viz-ptrs { position: relative; height: 20px; margin-top: 3px; }
.viz-ptr { position: absolute; transform: translateX(-50%); display: flex; flex-direction: column; align-items: center; }
.viz-ptr em { font-style: normal; font-weight: 700; font-size: .76rem; }
.viz-ptr i { width: 0; height: 0; border-left: 5px solid transparent; border-right: 5px solid transparent; border-top: 7px solid; }
/* 开边界（如左闭右开区间的 r = n）：贴在最后一个格子右边，
   表示"在最后一个元素之后"，和"指着某个元素"区分开 */
.viz-ptr.is-open em { font-style: italic; }
.viz-ptr.is-open::before {
  content: ''; position: absolute; left: 50%; top: -3px; width: 0; height: 16px;
  border-left: 2px dashed currentColor; opacity: .55;
}
.viz-range {
  position: absolute; top: -3px; height: 3px; border-radius: 2px;
  background: var(--brand); opacity: .35;
}
.viz-target { font-size: .84rem; color: var(--ink-soft); margin-top: 6px; }

/* ---------- 试卷拍照 ---------- */
.scan-card { max-width: 720px; width: min(94vw, 720px); max-height: 90vh; display: flex; flex-direction: column; }
.scan-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 10px; }
.scan-head h2 { margin: 0; font-size: 1.05rem; }
.scan-body { overflow-y: auto; flex: 1; }
/* 保留照片的选择：这是一条**有后果的选择**（导出的打印版里有没有图），
   所以给它一个明显的卡片样式，不是随便一个小勾选框。 */
.scan-keep {
  display: flex; align-items: flex-start; gap: 9px; cursor: pointer;
  padding: 10px 12px; margin-bottom: 10px; border-radius: 10px;
  border: 1px solid #bcd0ff; background: #f2f6ff;
}
.scan-keep.is-off { border-color: var(--line); background: var(--code-bg); }
.scan-keep input[type="checkbox"] {
  flex: 0 0 auto; width: 18px; height: 18px; margin: 2px 0 0; padding: 0;
  accent-color: var(--brand);
}
.scan-keep-txt { display: block; font-size: .84rem; line-height: 1.5; }
.scan-keep-txt b { display: block; margin-bottom: 2px; }
.scan-keep-txt i { display: block; font-style: normal; font-size: .76rem; color: var(--muted); }
.scan-pick { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 8px; }
.scan-tip { font-size: .76rem; color: var(--muted); line-height: 1.5; flex: 1 1 220px; }
/* 试卷缩略图**占满容器宽度**（手机上和屏幕同宽）。
   小尺寸缩略图根本看不清试卷上的字，也就没法判断方向对不对、
   拍清楚没有 —— 而这两件事正是学生在这一步唯一要做的事。 */
.scan-thumbs { display: flex; flex-direction: column; gap: 10px; margin-bottom: 10px; }
/* 缩略图上的「转正」按钮：学生拍照时手机横着很常见，
   照片躺倒会让模型推理量暴涨（实测一张横图能烧掉几万 token 还没写完正文）。
   与其让模型硬啃，不如让学生点一下转正。 */
.scan-thumb { position: relative; display: block; width: 100%; }
/* 试卷缩略图要比普通图片预览大：学生要在这上面确认方向对不对，
   还要放下「向左转」「向右转」「删除」三个按钮。
   注意要写在 .mk-img img 之后，靠顺序盖过它的 106px 上限。 */
/* 图片本身撑满宽度，高度按比例走（不裁切、不留白），
   长边就等于屏幕宽度 —— 字能看清，方向也能判断。 */
.scan-thumb img {
  width: 100%; height: auto; max-width: none; max-height: none;
  min-width: 0; border-radius: 10px; cursor: zoom-in; display: block;
}
/* 提示"点照片可以调方向" —— 角落的小按钮不容易被注意到 */
.scan-hint { flex: 1 1 100%; font-size: .74rem; color: var(--brand-strong); padding-top: 2px; }
/* 大图查看器里的"调整方向"按钮组 */
.img-view-rotgrp { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; justify-content: center;
  padding-right: 10px; margin-right: 4px; border-right: 1px solid rgba(255, 255, 255, .22); }
.img-view-rotgrp[hidden] { display: none !important; }
.img-view-dim { color: #cfd6e6; font-size: .78rem; white-space: nowrap; font-variant-numeric: tabular-nums; }
/* 左下角：向左 / 向右各转 90°。
   只给一个方向不行 —— 转反了就只能删掉重拍，所以两个方向都要有。 */
.scan-thumb .scan-rotgrp { position: absolute; left: 2px; bottom: 2px; display: flex; gap: 3px; }
.scan-thumb .scan-rot {
  width: 24px; height: 24px; border: 0; border-radius: 50%;
  background: rgba(20, 24, 36, .74); color: #fff;
  font-size: 15px; line-height: 1; cursor: pointer; padding: 0;
}
.scan-thumb .scan-rot:hover { background: var(--brand); }
.scan-thumb .scan-rot:disabled { opacity: .45; cursor: default; }
/* 右下角：删除 */
.scan-thumb .scan-del {
  position: absolute; right: 2px; bottom: 2px; width: 24px; height: 24px;
  border: 0; border-radius: 50%; background: rgba(214, 48, 49, .85); color: #fff;
  font-size: 16px; line-height: 1; cursor: pointer; padding: 0;
}
/* 右上角：尺寸 + 方向。光看缩略图不容易判断横竖，直接写出来 */
.scan-thumb .scan-dim {
  position: absolute; right: 2px; top: 2px; padding: 1px 6px; border-radius: 8px;
  background: rgba(20, 24, 36, .72); color: #fff; font-size: .62rem;
  line-height: 1.5; white-space: nowrap;
}
.scan-thumbs:empty { display: none; }
/* 照片改成整宽后会把「开始识别」推到很下面，学生得划过所有照片才找得到 ——
   所以让这条操作栏吸在底部，滚到哪儿都能点。 */
.scan-actions {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  position: sticky; bottom: 0; z-index: 2; margin-bottom: 12px;
  padding: 9px 0; background: var(--bg);
  border-top: 1px solid var(--line);
}
.scan-result { border-top: 1px solid var(--line); padding-top: 10px; }
.scan-result-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.scan-list { display: flex; flex-direction: column; gap: 8px; max-height: 46vh; overflow-y: auto; padding-right: 2px; }
.scan-item {
  display: flex; gap: 9px; align-items: flex-start; padding: 9px 11px;
  border: 1px solid var(--line); border-radius: 10px; background: var(--bg); cursor: pointer;
}
.scan-item:has(input:not(:checked)) { opacity: .5; }
/* 用 input[type="checkbox"] 提高优先级（0,2,1），稳赢 .modal-card input（0,1,1）。
   语义上也更对：勾选框本来就不该套用文本框的 width/padding/border-radius。 */
.scan-item input[type="checkbox"] {
  margin: 3px 0 0; flex: 0 0 auto; width: 17px; height: 17px; padding: 0;
  border-radius: 3px; accent-color: var(--brand);
}
.scan-item-body { flex: 1; min-width: 0; }
.scan-item-top { margin-bottom: 4px; }
.scan-q { font-size: .86rem; line-height: 1.55; white-space: pre-wrap; word-break: break-word; }
.scan-a { margin-top: 5px; font-size: .82rem; color: var(--ink-soft); }
.scan-a b { color: var(--accent, #1a936f); margin-right: 5px; }
.scan-an { font-size: .82rem; color: var(--ink-soft); line-height: 1.6; white-space: pre-wrap; margin-top: 4px; }
.scan-foot { display: flex; align-items: center; gap: 10px; margin-top: 10px; flex-wrap: wrap; }
/* 错题卡片里"答案/解析"折叠区 */
.nb-fold > summary { cursor: pointer; font-size: .82rem; color: var(--muted); padding: 3px 0; }
.nb-answer { margin: 4px 0 6px; font-size: .88rem; color: #1a936f; font-weight: 600; }
.nb-analysis { margin: 4px 0 6px; font-size: .86rem; line-height: 1.65; color: var(--ink-soft); white-space: pre-wrap; }

/* ---------- 错题本：考点热力图 ---------- */
/* 把整册考点铺成一张图，颜色表示掌握程度。
   注意"未涉及"要显式画成灰的 —— 不能让学生把"没做过"误读成"已掌握"。 */
/* 「错题本」与「考点热力图」是两个**并列视图**，用标签页切换。
   之前是塞在列表上面的折叠块，看起来像父子关系 —— 学生的实际使用是
   "先看哪块薄弱（热力图）→ 再回来看具体题目（列表）"，是两个平行的动作。 */
.nb-tabs {
  display: flex; gap: 4px; padding: 0 14px; margin: 2px 0 0;
  border-bottom: 1px solid var(--line);
}
.nb-tab {
  position: relative; font: inherit; font-size: .88rem; font-weight: 600;
  padding: 9px 14px; border: 0; background: none; color: var(--muted);
  cursor: pointer; border-bottom: 2px solid transparent; margin-bottom: -1px;
  border-radius: 8px 8px 0 0;
}
.nb-tab:hover { color: var(--ink); background: var(--code-bg); }
.nb-tab.is-active { color: var(--brand-strong); border-bottom-color: var(--brand); }
/* 热力图上挂一个小角标，显示"待补考点"的条数 ——
   不切过去也能知道那边有没有东西要看 */
.nb-tab-badge {
  display: inline-block; margin-left: 6px; min-width: 17px; height: 17px;
  border-radius: 9px; background: #e8505b; color: #fff;
  font-size: .64rem; line-height: 17px; text-align: center; padding: 0 5px;
  vertical-align: 1px;
}
.nb-view { flex: 1; min-height: 0; display: flex; flex-direction: column; }
.nb-view[hidden] { display: none !important; }
/* 热力图图例挪到视图内部（不再挂在下拉标题上） */
.nb-heat-legend {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  padding: 8px 13px 0; font-size: .74rem; color: var(--muted);
}
.nb-heat-legend i { width: 10px; height: 10px; border-radius: 3px; display: inline-block; }
.nb-heat-legend i.is-none { background: #eceef2; }
.nb-heat-legend i.is-weak { background: #ffe08a; }
.nb-heat-legend i.is-bad { background: #f08a8a; }
.nb-heat-legend i.is-ok { background: #8fd6a8; }
.nb-heat { padding: 6px 13px 8px; flex: 1; min-height: 0; overflow-y: auto; }
.nb-heat-topic { margin-bottom: 10px; }
.nb-heat-topic h5 { margin: 6px 0 5px; font-size: .78rem; color: var(--muted); font-weight: 600; }
.nb-heat-grid { display: flex; flex-wrap: wrap; gap: 5px; }
.nb-heat-cell {
  position: relative; font: inherit; font-size: .74rem; line-height: 1.25;
  padding: 5px 8px; border-radius: 7px; border: 1px solid transparent;
  cursor: pointer; max-width: 100%; text-align: left; color: var(--ink);
}
.nb-heat-cell span { display: block; }
.nb-heat-cell i {
  position: absolute; top: -5px; right: -5px; min-width: 15px; height: 15px;
  border-radius: 8px; background: var(--ink); color: #fff; font-size: .62rem;
  font-style: normal; line-height: 15px; text-align: center; padding: 0 3px;
}
.nb-heat-cell.is-none { background: #eceef2; color: var(--muted); border-color: #dfe3ea; }
.nb-heat-cell.is-weak { background: #ffe08a; border-color: #f0c65a; }
.nb-heat-cell.is-bad  { background: #f08a8a; border-color: #e05f5f; color: #4a1010; }
.nb-heat-cell.is-ok   { background: #8fd6a8; border-color: #63bf85; }
.nb-heat-cell:hover, .nb-heat-cell:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }
.nb-heat-hint { margin: 0; padding: 0 13px 10px; font-size: .74rem; color: var(--muted); }
/* 手机：格子更紧凑，热力图区域矮一点 */
@media (max-width: 640px) {
  .nb-heat { max-height: 260px; }
  .nb-heat-cell { font-size: .7rem; padding: 4px 6px; }
  .nb-heat-legend { width: 100%; margin-left: 0; }
}

/* ---------- 朗读跟随高亮（整块） ---------- */
/* 念到哪一句，就把那一整块高亮出来。
   整块而不是"每个字/每个片段"：一句话里有粗体、行内代码时，
   按字符包 <mark> 会碎成好几段、深浅不一；整块加类则完整均匀，眼睛有落点。 */
.speak-hl {
  background: #fff0c2;
  border-radius: 6px;
  box-shadow: inset 3px 0 0 #e8a33d;
  transition: background .18s;
}
/* 左右各让出一点内边距，高亮看起来才像"一整条"，而不是贴着字。
   用负外边距抵消，保证不影响整体排版宽度。 */
p.speak-hl, li.speak-hl, blockquote.speak-hl, dd.speak-hl, dt.speak-hl {
  padding: 3px 9px; margin-left: -9px; margin-right: -9px;
}
h1.speak-hl, h2.speak-hl, h3.speak-hl, h4.speak-hl, h5.speak-hl, h6.speak-hl {
  padding: 3px 9px; margin-left: -9px; margin-right: -9px;
}
/* 表格：tr 自己的背景会被单元格盖住，得把颜色下放到单元格 */
tr.speak-hl { background: transparent; box-shadow: none; }
tr.speak-hl > td, tr.speak-hl > th { background: #fff0c2; }
tr.speak-hl > td:first-child, tr.speak-hl > th:first-child { box-shadow: inset 3px 0 0 #e8a33d; }
/* 深色模式下别刺眼 */
@media (prefers-color-scheme: dark) {
  .speak-hl { background: rgba(232,163,61,.28); }
  tr.speak-hl > td, tr.speak-hl > th { background: rgba(232,163,61,.28); }
}

/* ---------- 语音输入 ---------- */
/* 正在收音时按钮变红并轻微呼吸，让学生一眼确认"它在听"（否则会怀疑没生效） */
.mic-btn.is-listening {
  color: #fff; background: #e8505b; border-color: #e8505b;
  animation: mic-pulse 1.4s ease-in-out infinite;
}
@keyframes mic-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(232,80,91,.55); }
  50%      { box-shadow: 0 0 0 7px rgba(232,80,91,0); }
}
/* 用户在系统里开了"减少动态效果"就别闪了，免得难受 */
@media (prefers-reduced-motion: reduce) {
  .mic-btn.is-listening { animation: none; }
}

/* 朗读暂停态：按钮变成"▶ 继续"并换个颜色，一眼区分"暂停"和"正在读" */
.speak-btn.is-paused {
  background: #fff4e0; color: #9a6414; border-color: #e8a33d;
}

/* ---------- 错题本：未分类 ---------- */
/* 灰底虚线，一眼能和真实考点区分开 —— 它是兜底分类，不是一个知识点 */
.nb-point.is-unclassified {
  background: #f0f2f6; color: var(--muted); border: 1px dashed #c9cfda;
}
.nb-chip.is-unclassified { background: #f0f2f6; color: var(--muted); border: 1px dashed #c9cfda; }
.nb-chip.is-unclassified b { color: var(--ink-soft); }

/* ---------- 柱状 ---------- */
.viz-bars { display: flex; align-items: flex-end; gap: 3px; height: 150px; }
.viz-bar-i {
  flex: 1 1 0; min-width: 8px; background: #c9d2e3; border-radius: 4px 4px 0 0;
  display: flex; align-items: flex-start; justify-content: center; position: relative;
  transition: background .18s, height .25s;
}
.viz-bar-i b { font-size: .68rem; color: var(--ink-soft); position: absolute; top: -15px; }
.viz-bar-i.is-cur { background: var(--brand); }
.viz-bar-i.is-mark { background: #e8a33d; }
.viz-bar-i.is-done { background: #7fc79b; }

/* ---------- 表格 ---------- */
.viz-table-wrap { overflow-x: auto; }
.viz-table { border-collapse: collapse; font-size: .84rem; }
.viz-table th, .viz-table td {
  border: 1px solid var(--line); padding: 4px 9px; text-align: center; min-width: 34px;
}
.viz-table th { background: var(--code-bg); color: var(--muted); font-weight: 600; }
.viz-td { transition: background .18s; }
.viz-td.is-mark { background: #eef2ff; }
.viz-td.is-cur { background: var(--brand); color: #fff; font-weight: 700; }

/* ---------- 二叉树 ---------- */
.viz-tree { width: 100%; max-width: 640px; height: auto; display: block; margin: 0 auto; }
.viz-node circle { fill: var(--bg); stroke: #c9cfda; stroke-width: 2; transition: fill .18s, stroke .18s; }
.viz-node text { font-size: 13px; fill: var(--ink); text-anchor: middle; font-weight: 600; }
.viz-node.is-done circle { fill: #eefaf1; stroke: #7fc79b; }
.viz-node.is-cur circle { fill: var(--brand); stroke: var(--brand); }
.viz-node.is-cur text { fill: #fff; }
.viz-order { text-align: center; font-size: .84rem; color: var(--ink-soft); margin-top: 6px; }

/* ---------- 函数图像 ---------- */
.viz-plot { width: 100%; max-width: 640px; height: auto; display: block; margin: 0 auto; }
/* ---------- 仿真场景（物理/化学/生物/地理） ---------- */
.viz-scene { width: 100%; max-width: 640px; height: auto; display: block; margin: 0 auto; touch-action: pan-y; }
.viz-lbl { font-size: 12px; fill: var(--ink-soft); }
.viz-legend { text-align: center; font-size: .82rem; margin-top: 4px; }

@media (max-width: 640px) {
  .viz-stage { padding: 11px 9px 4px; }
  .viz-cell { min-width: 32px; }
  .viz-cell b { padding: 6px 6px 4px; font-size: .85rem; }
  .viz-bar button { padding: 6px 10px; font-size: 16px; }   /* 触摸目标 + 防 iOS 缩放 */
  .viz-bars { height: 110px; }
  .viz-table { font-size: .78rem; }
  .viz-table th, .viz-table td { padding: 3px 6px; min-width: 28px; }
}

/* ---------- 弹窗 ---------- */
/* 弹窗必须盖在**任何面板**之上。
   错题本是全屏面板（z-index 60），而弹窗原来是 50 —— 于是从错题本里
   打开的弹窗（拍试卷、编辑错题）全都藏在面板后面，表现就是"点了没反应"。
   这里取 70：高于错题本(60)，低于顶部更新提示条(200)。 */
.modal { position: fixed; inset: 0; background: rgba(15, 18, 28, .45); display: grid; place-items: center; padding: 20px; z-index: 70; }
.modal[hidden] { display: none; }
.modal-card { width: 100%; max-width: 380px; background: var(--bg); border-radius: var(--radius); padding: 24px; }
.modal-card h2 { margin: 0 0 8px; font-size: 1.1rem; }
.modal-card p { color: var(--muted); font-size: .87rem; margin: 0 0 14px; }
/* 只给**文本框类**输入框铺满宽度。
   踩过的坑：不加 :not() 时，这条会命中弹窗里的**勾选框** ——
   .scan-card 同时也是 .modal-card，于是试卷识别结果里的复选框被撑成
   864px 宽、把内容区挤成 0 宽，表现就是"题目、答案、解析全都不见了"，
   只剩一个巨大的空白框。优先级又正好和 .scan-item input 相同（都是 0,1,1），
   靠后的这条赢 —— 从代码上完全看不出来。 */
.modal-card input:not([type="checkbox"]):not([type="radio"]) {
  width: 100%; padding: 10px 12px; border: 1px solid var(--line); border-radius: 9px;
  font: inherit; background: var(--bg); color: var(--ink);
}
.modal-actions { margin-top: 14px; }.btn { padding: 10px 16px; border-radius: 9px; border: 1px solid transparent; font-weight: 600; }
.btn-primary { background: var(--brand); color: #fff; width: 100%; }
.btn-primary:hover { background: var(--brand-strong); }
.modal-error { color: var(--danger); font-size: .82rem; margin: 10px 0 0; min-height: 1.2em; }

/* ---------- 响应式（手机版） ---------- */

/* 拍照按钮：只在触摸设备上显示。
   桌面端已经有"＋"选文件按钮，多一个功能重复的按钮只会让界面变乱；
   而 capture 属性在桌面浏览器本来也会被忽略。 */
.camera-btn { display: none; }
@media (pointer: coarse), (max-width: 860px) {
  .camera-btn { display: grid; }
}

@media (max-width: 860px) {
  /* 侧边栏变成抽屉 */
  .sidebar {
    position: fixed; inset: 0 auto 0 0; z-index: 40; transform: translateX(-100%);
    transition: transform .2s ease; box-shadow: 0 0 40px rgba(0,0,0,.18);
    padding-top: env(safe-area-inset-top);
  }
  .sidebar.is-open { transform: none; }
  .menu-toggle { display: grid; }

  /* 顶部板块导航：手机上横向滑动，加吸附让手指一拨就停在一项上 */
  .boards { scroll-snap-type: x proximity; -webkit-overflow-scrolling: touch; }
  .boards-inner { padding: 6px 10px calc(6px + env(safe-area-inset-bottom, 0px)); }
  .boards-inner { padding-bottom: 6px; }
  .board-btn { scroll-snap-align: center; padding: 8px 13px; font-size: .86rem; }

  /* 输入区：固定在底部、避开 iPhone 底部横条，触摸目标放大到 44px */
  .messages { padding: 16px 13px 6px; overscroll-behavior: contain; }
  .composer { padding: 8px 12px calc(10px + env(safe-area-inset-bottom)); }
  /* 两行：textarea 在上、工具条在下，行间距 7px（在要求的 6~10px 之间）；
     iPhone 底部安全区仍由上面 .composer 的 padding 负责，没有改动。 */
  .composer-inner { padding: 8px 9px 7px; border-radius: 16px; gap: 7px; }
  .composer-tools { gap: 6px; }
  .attach-btn, .send-btn { width: 40px; height: 40px; flex-basis: 40px; }
  .msg { gap: 9px; margin-bottom: 18px; }
  .msg-avatar { flex-basis: 26px; width: 26px; height: 26px; border-radius: 8px; font-size: .66rem; }
  .msg-content { font-size: .94rem; }
  .msg-images img { max-width: min(200px, 100%); max-height: 200px; }
  .welcome { margin-top: 3vh; }
  .chips { grid-template-columns: 1fr; }
  .chip { padding: 12px 14px; }

  /* iOS 上 font-size 小于 16px 的输入框会在聚焦时自动放大页面 —— 这条是必须的 */
  .composer-inner textarea,
  .auth-field input,
  .modal-card input:not([type="checkbox"]):not([type="radio"]) { font-size: 16px; }

  /* 代码块在手机上横向滚动，不换行破坏缩进 */
  .code-block pre { font-size: .8rem; }
}

/* ---------- 手机上的排版收紧 ----------
   手机上屏幕窄、行数多，桌面那套间距（段间距 .8em、行高 1.75）读起来会很散，
   一屏显示不了几行。这里把行高、段间距、标题与列表的留白整体收紧一档。 */
@media (max-width: 640px) {
  .msg-content { line-height: 1.6; }
  .msg-content p { margin: 0 0 .62em; }
  .msg-content h1, .msg-content h2, .msg-content h3 { margin: .85em 0 .38em; }
  .msg-content h1 { font-size: 1.2rem; }
  .msg-content h2 { font-size: 1.08rem; }
  .msg-content h3 { font-size: .99rem; }
  .msg-content ul, .msg-content ol { margin: 0 0 .62em; padding-left: 1.15em; }
  .msg-content li { margin: .16em 0; }
  .msg-content blockquote { margin: .55em 0; padding: 2px 0 2px 10px; }
  .msg-content hr { margin: .9em 0; }
  .msg-content code { font-size: .86em; padding: 1px 3px; }
  .msg { margin-bottom: 15px; gap: 8px; }
  .messages { padding: 14px 11px 4px; }

  /* ---------- 表格变卡片 ----------
     375px 宽的屏幕上，四五列的表格横着放不下：挤成一行看不清，横向滚动又难用。
     这里把每一行变成一张卡片，单元格用 ::before 把列名（data-label）显示出来。
     实测数据（390px 宽）：每张卡片 189px、单元格 30px 高，太占屏幕 ——
     所以行高、内边距、分隔线都收紧一档，目标压到 120px 以内。 */
  .table-wrap { overflow: visible; margin: .5em 0; }
  .table-wrap table,
  .table-wrap thead,
  .table-wrap tbody,
  .table-wrap tr,
  .table-wrap th,
  .table-wrap td { display: block; width: 100%; }
  .table-wrap thead { display: none; }          /* 列名改由每个单元格自己带 */
  .table-wrap tr {
    border: 1px solid var(--line); border-radius: 9px;
    padding: 5px 10px; margin-bottom: 6px; background: var(--code-bg);
  }
  .table-wrap tr:last-child { margin-bottom: 0; }
  .table-wrap td {
    border: 0; padding: 1px 0; display: flex; align-items: baseline; gap: 8px;
    text-align: left; line-height: 1.38;
  }
  /* 去掉虚分隔线：紧凑之后靠行距和加粗的列名就足以区分，
     再画线只会让卡片显得更碎更花。 */
  .table-wrap td::before {
    content: attr(data-label);
    flex: 0 0 auto; max-width: 40%;
    color: var(--muted); font-size: .78em; font-weight: 600; line-height: 1.38;
  }
  /* 第一列通常是"行标题"（如题型、板块），去掉列名让它当卡片标题更清爽 */
  .table-wrap td:first-child {
    font-weight: 650; padding-bottom: 2px;
  }
  .table-wrap td:first-child::before { display: none; }
  .table-wrap td[data-label=""]::before { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
  .messages { scroll-behavior: auto; }
}

/* ---------- 错题本顶栏：窄屏再收一档 ---------- */
/* 单独放在顶层（不嵌套在 640px 里），可读性更好。
   注意：调试时我一度以为"这条没生效是因为嵌套"，其实不是 ——
   真正的原因是 **Edge headless 的窗口宽度有下限（约 518px）**，
   --window-size=320 被夹到了 518，于是 max-width:400px 压根不匹配。
   量移动端布局必须用 iframe 锁宽（iframe 的视口就是它自己的宽度）。 */
@media (max-width: 400px) {
  .nb-head { gap: 6px; padding-left: 10px; padding-right: 10px; }
  .nb-head strong { font-size: .9rem; }
  .nb-head-right { gap: 4px; }
  .nb-head-right .btn-mini { padding: 5px 6px; font-size: .68rem; }
}

/* ==================== 背诵本 ==================== */
/* 和错题本共用 .nb 一套外壳（面板、筛选、列表），只加了卡片样式。
   卡片刻意做得比错题卡"素"：背诵本里全是正文，装饰多了反而看不清字。 */

.rc-card {
  background: var(--bg); border: 1px solid var(--line); border-radius: 12px;
  /* 比原来收紧一点：375 宽下一屏要能看到 ≥1.5 条材料 */
  padding: 11px 12px 9px; margin-bottom: 8px;
}
.rc-card-head {
  display: flex; align-items: center; gap: 7px; flex-wrap: wrap; margin-bottom: 2px;
}
.rc-card-head strong { font-size: .94rem; }
/* ★ 标题必须能收缩：flex: 1 1 0 + min-width: 0 才有省略号。
   没有 min-width: 0 的话，长标题会把 badge / 科目标签顶出卡片
   （这是 flex 省略号最常见的坑，之前踩过"标题撑破"）。 */
.rc-card-title {
  flex: 1 1 0; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rc-tag {
  font-size: .68rem; padding: 1px 7px; border-radius: 20px;
  background: var(--bg-hover); color: var(--muted); white-space: nowrap;
}

/* ── 材料类型标记：句子 / 作文 / 朗读 / 其他 / 未拆分 ──
   ★ 字号 12px（手机上看得清；用户要求 ≥12px）；小圆角标签，不抢标题。
   ★ flex: 0 0 auto —— 标签不参与压缩，压缩的是标题（标题有省略号）。
   ★ 深色主题单独配色：硬编码浅色底在暗色下会刺眼、对比度也不够。 */
.mat-badge {
  display: inline-block; flex: 0 0 auto;
  font-size: 12px; line-height: 18px; font-weight: 600;
  padding: 1px 8px; border-radius: 20px;
  white-space: nowrap;
}
.mat-badge.is-sentence { background: #e8f1ff; color: #1d4ed8; }
.mat-badge.is-essay    { background: #f5e9ff; color: #7e22ce; }
.mat-badge.is-read     { background: #e3f9ea; color: #15803d; }
.mat-badge.is-other,
.mat-badge.is-unsplit  { background: #f1f2f5; color: #5f6675; }
html[data-theme="dark"] .mat-badge.is-sentence { background: #1e2a4a; color: #9db7ff; }
html[data-theme="dark"] .mat-badge.is-essay    { background: #2e2140; color: #d8b4fe; }
html[data-theme="dark"] .mat-badge.is-read     { background: #12301f; color: #86efac; }
html[data-theme="dark"] .mat-badge.is-other,
html[data-theme="dark"] .mat-badge.is-unsplit  { background: #2a2c33; color: #a1a8b8; }
.rc-note {
  margin: 1px 0 4px; font-size: .72rem; color: var(--muted);
  /* 备注只占一行：完整内容点开卡片能看到 */
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* 正文预览：**最多两行**小字（用户要求），完整正文点卡片看。
   行数用 -webkit-line-clamp 收；max-height 给不支持 clamp 的浏览器兜底。 */
.rc-preview {
  margin: 3px 0 6px; font-size: .8rem; line-height: 1.55; color: var(--ink-soft);
  word-break: break-word;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  overflow: hidden; max-height: 3.1em;
}
.rc-card-foot { display: flex; gap: 8px; flex-wrap: nowrap; align-items: center; margin-top: 5px; }
/* 卡片上的动作按钮：手机可点（≥44px）。第一行只有「🔊 朗读」+「⋮ 更多」。 */
.rc-card-foot > .btn-mini {
  min-height: 44px; display: inline-flex; align-items: center; justify-content: center;
  padding: 0 14px; font-size: .84rem; box-sizing: border-box;
}
.rc-card-foot > [data-act="speak"] { flex: 1 1 auto; }
.rc-card-foot > [data-act="more"] { flex: 0 0 auto; min-width: 92px; color: var(--ink-soft); }
.rc-count { margin-left: 6px; font-size: .72rem; color: var(--muted); font-weight: 400; }

/* 缩略图右上角的删除按钮（错题本用不上，但这里是新加的） */
.scan-thumb { position: relative; }
.scan-thumb-x {
  position: absolute; top: 4px; right: 4px; width: 26px; height: 26px;
  border-radius: 50%; border: 0; background: rgba(16, 18, 28, .72); color: #fff;
  font-size: 14px; line-height: 1; cursor: pointer; display: grid; place-items: center;
}
.scan-thumb-x:hover { background: rgba(16, 18, 28, .9); }

/* 背诵本的正文输入框：要能看清一大段文字，所以比普通输入框高、行高大 */
#rc-f-content, #rc-e-content {
  font-size: 16px;      /* 手机端保持 16px，否则 iOS 聚焦会放大页面 */
  line-height: 1.8;
  font-family: inherit;
}
@media (min-width: 641px) {
  /* 桌面端可以小一点（手机端不能小，见上） */
  #rc-f-content, #rc-e-content { font-size: 14px; }
}

/* 背诵本面板的列表区域：长内容也要能滚 */
#rc-list { padding: 0 11px 24px; }

/* ---------- 背诵本：看全文 ---------- */
/* 内容要**像读书一样**好读：行高给足、字号比列表大、纯文本可选中。
   这是"以文本形式观看"的核心 —— 不是把预览放大，而是换一种阅读排版。 */
.rc-view-card { width: min(94vw, 760px); }
.rc-view-meta {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin-bottom: 10px; font-size: .76rem; color: var(--muted);
}
.rc-view-note { color: var(--muted); }
.rc-view-len { margin-left: auto; color: var(--muted); }
.rc-view-body {
  white-space: pre-wrap;      /* 原文的换行必须保留（古诗一行一句、单词表一词一行） */
  word-break: break-word;
  font-size: 16px;            /* 手机端 16px：既好读，也避开 iOS 聚焦放大 */
  line-height: 1.95;          /* 背诵材料要慢慢读，行距给足 */
  color: var(--ink);
  background: var(--bg-soft, #fafbfc);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 14px 15px;
  max-height: 58vh;
  overflow: auto;
  user-select: text;          /* 允许选中复制 */
  -webkit-user-select: text;
}
.rc-view-foot { position: sticky; bottom: 0; background: var(--bg); padding-top: 10px; }
/* 「全文共 N 字，点开看 →」这行是给长内容的提示，短内容不显示 */
.rc-more { margin: -4px 0 8px; font-size: .74rem; color: var(--brand, #4a6cf7); }
.rc-card { cursor: pointer; }
.rc-card-foot button { cursor: pointer; }

/* ---------- 卡片「⋮ 更多」菜单（方案 A） ----------
   卡片内展开，不浮到 body：手机上不会被 .nb-list 的 overflow 裁掉，
   也不会挡住别的卡片。两列 × 三行，每一项 ≥44px。
   `[hidden]` 必须带 !important —— 这里 display:grid，否则 hidden 会被它废掉。 */
.rc-menu {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 6px; margin: 7px 0 1px; padding: 6px;
  background: var(--bg-hover);
  border: 1px solid var(--line); border-radius: 10px;
}
.rc-menu[hidden] { display: none !important; }
.rc-menu-item {
  min-height: 44px; display: flex; align-items: center; justify-content: flex-start;
  padding: 7px 10px; border: 1px solid var(--line); border-radius: 8px;
  background: var(--bg); color: var(--ink); font: inherit; font-size: .8rem;
  text-align: left; cursor: pointer; box-sizing: border-box;
}
.rc-menu-item:hover { background: var(--bg-hover); }
.rc-menu-item.is-primary { color: var(--brand-strong, #3452c9); font-weight: 600; }
.rc-menu-item.is-danger { color: var(--danger); }
.rc-menu-item:disabled { opacity: .45; cursor: not-allowed; }
.rc-menu-item:disabled:hover { background: var(--bg); }

/* ---------- 「背英语句子」：极简顶部 ----------
   用户要求：
     · 科目下拉**删掉**（这一页只可能是当前英语板块的材料）
     · 搜索收成一行小框（不再占一整行高）
     · 灰色批次提示不再常驻（按钮上已经有条数）
   只在本模式生效：面板由 applyMode() 打上 .is-pair；背诵本（中文）保持原样。
   ⚠️ 科目 / 搜索的类名由 recitations.js 的 markFilterFields() 现加，
      不改 index.html（微信会缓存 HTML）。 */
#recite.is-pair .nb-filter {
  padding: 7px 11px 3px; gap: 0;
  align-items: center; background: transparent; border-bottom: 0;
}
/* 第一项是科目字段：直接藏掉（类名 + :first-child 双保险） */
#recite.is-pair .rc-subject-field,
#recite.is-pair .nb-filter > .nb-field:first-child { display: none !important; }
#recite.is-pair .rc-search-field {
  flex: 1 1 100%; width: 100%; display: block; position: relative;
}
#recite.is-pair .rc-search-field > span { display: none !important; }
#recite.is-pair .rc-search-field::before {
  content: "🔍"; position: absolute; left: 10px; top: 50%;
  transform: translateY(-50%); font-size: 13px; opacity: .55;
  pointer-events: none; line-height: 1;
}
#recite.is-pair .rc-search-field input[type="search"] {
  width: 100%; height: 34px; box-sizing: border-box;
  padding: 3px 12px 3px 30px; border-radius: 17px;
  border: 1px solid var(--line); background: var(--bg);
  font-size: .82rem; color: var(--ink);
}
/* 列表顶部的摘要（"共 N 条…科目：…"）在背英语句子这一页不显示：
   条数已经在侧边栏角标上，科目只有一个，留着只占一行。 */
#recite.is-pair .nb-summary { display: none !important; }
/* 长内容提示"全文共 N 字，点开看 →"也收掉：标题行已经能点开全文 */
#recite.is-pair .rc-more { display: none !important; }
/* 照片缩略图小一号，给正文和动作留高度 */
#recite.is-pair .rc-thumb { width: 48px; height: 48px; }
#recite.is-pair .rc-thumb-more { height: 48px; }
#recite.is-pair .rc-thumbs { margin: 2px 0 6px; }
/* 进度条（🎯 …）紧凑一点，不再是一个大胶囊 */
#recite.is-pair .rc-train-line { margin: 1px 0 2px; }
#recite.is-pair .rc-train { padding: 2px 7px; font-size: .72rem; gap: 5px; }
/* 分享条：发送收进卡片菜单后，这里只剩「待接收」；
   没有待接收时由 JS 直接 hidden，不占一行。 */
#recite.is-pair .rc-share-bar { margin: 0 11px 6px; }
#recite.is-pair .rc-share-bar #rc-share-open { display: none !important; }
/* 手机上搜索框字号 ≥16px（否则 iOS 聚焦会放大整页） */
@media (max-width: 640px) {
  #recite.is-pair .rc-search-field input[type="search"] { font-size: 16px; height: 36px; }
}

/* ---------- 顶部「⋯」工具菜单 ----------
   pair 模式只留「拍照录入 + ⋯ + 关闭」一行：375 宽下原来 6 个按钮会折成
   两三行。手写 / 背诵训练 / Excel / 打印版原按钮保留在 DOM（菜单项 .click()
   触发它们），只是不可见 —— 功能没有删。 */
#recite.is-pair .nb-head-right > #rc-add,
#recite.is-pair .nb-head-right > #rd-entry,
#recite.is-pair .nb-head-right > #rc-export-csv,
#recite.is-pair .nb-head-right > #rc-export-md { display: none !important; }
#recite:not(.is-pair) #rc-tools-more { display: none !important; }
/* 顶栏按钮手机可点：一行只留 3 个，给足 44px 高度 */
#recite.is-pair .nb-head-right .btn-mini {
  min-height: 44px; display: inline-flex; align-items: center; justify-content: center;
  padding-top: 0; padding-bottom: 0;
}
#recite.is-pair #rc-tools-more { min-width: 44px; padding: 0 9px; font-size: .9rem; line-height: 1; }
.rc-tools-menu {
  position: absolute; top: calc(100% - 2px); right: 10px; z-index: 80;
  width: 172px; padding: 6px; display: flex; flex-direction: column; gap: 4px;
  background: var(--bg); border: 1px solid var(--line); border-radius: 10px;
  box-shadow: 0 10px 28px rgba(15, 23, 42, .2);
}
.rc-tools-menu[hidden] { display: none !important; }
.rc-tools-menu > button {
  min-height: 44px; display: flex; align-items: center; padding: 7px 10px;
  border: 0; border-radius: 8px; background: transparent; color: var(--ink);
  font: inherit; font-size: .86rem; text-align: left; cursor: pointer;
}
.rc-tools-menu > button:hover { background: var(--bg-hover); }
@media (min-width: 641px) {
  .rc-view-body { font-size: 15px; }
}

/* ---------- 背诵本看全文：头部与底部的两处修正 ---------- */
/* ① 标题可能很长（如「7.（1）①三味书屋读书②南京求学③回国教书」），
      要截断，不能把「关闭」按钮挤到第二行。 */
.rc-view-card .scan-head h2 {
  flex: 1 1 auto; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rc-view-card .scan-head .btn-mini { flex: 0 0 auto; }
/* ② .btn-primary 本身是 width:100%（表单按钮设计的），
      放在这一排里会把「编辑」撑成整行、把另外两个挤到上面 —— 这里改回自适应。 */
.rc-view-foot { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.rc-view-foot .btn { width: auto; flex: 0 1 auto; }
.rc-view-foot .btn-primary { width: auto; }

/* ---------- 背诵本：模式选择 / 照片 ---------- */
/* 两种做法二选一。给足点击区域（手机上是手指点的），
   选中态要一眼看得出来 —— 选错了会白等一次抄录。 */
.rc-mode { display: flex; flex-direction: column; gap: 8px; margin: 12px 0; }
.rc-mode-opt {
  display: flex; gap: 10px; align-items: flex-start; cursor: pointer;
  padding: 11px 12px; border: 1px solid var(--line); border-radius: 10px;
  background: var(--bg);
}
.rc-mode-opt:has(input:checked) { border-color: var(--brand); background: var(--bg-hover); }
.rc-mode-opt input { flex: 0 0 auto; width: 18px; height: 18px; margin-top: 2px; }
.rc-mode-opt > span { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.rc-mode-opt b { font-size: .92rem; }
.rc-mode-opt em {
  font-style: normal; font-size: .76rem; color: var(--muted); line-height: 1.5;
}
.rc-thumb-tip { margin: 6px 0 0; font-size: .76rem; color: var(--muted); }

/* 缩略图上的序号（30 张时很好用：知道哪张还没删） */
.scan-thumb-i {
  position: absolute; left: 4px; top: 4px; min-width: 20px; height: 20px;
  border-radius: 10px; background: rgba(16, 18, 28, .72); color: #fff;
  font-size: 11px; line-height: 20px; text-align: center; padding: 0 5px;
}

/* 卡片上的照片缩略图（最多 3 张 + “+N”） */
.rc-thumbs { display: flex; gap: 6px; margin: 0 0 8px; align-items: center; }
.rc-thumb {
  display: block; width: 62px; height: 62px; border-radius: 8px;
  border: 1px solid var(--line); overflow: hidden; background: var(--bg-hover);
}
.rc-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.rc-img-ph { font-size: .6rem; color: var(--muted); display: grid; place-items: center; height: 100%; }
.rc-thumb-more {
  font-size: .72rem; color: var(--muted); padding: 0 6px; height: 62px;
  display: grid; place-items: center;
}
.rc-tag-photo { background: #e8f0ff; color: #3b5bdb; }

/* 看全文时的照片：铺开显示，一列排（手机上一行两张太挤） */
.rc-view-imgs { display: flex; flex-direction: column; gap: 8px; margin-bottom: 10px; }
.rc-vimg {
  display: block; border: 1px solid var(--line); border-radius: 10px;
  overflow: hidden; background: var(--bg-hover); min-height: 40px;
}
.rc-vimg img { width: 100%; height: auto; display: block; }
/* 编辑框里的图片数量提示 */
#rc-e-imgs { margin: 6px 0 0; font-size: .76rem; color: var(--muted); }

/* ---------- 填空题的填空线 ---------- */
/* 题目里「−0.8 ___ −3/4」的那个空：渲染成**一条实线**，而不是三个下划线字符。
   下划线字符在手机上会连成一片看不清，也容易被当成题号的下划线。
   宽度给 3.4em（差不多三个汉字的空），和正文基线对齐。 */
.blank {
  display: inline-block;
  min-width: 3.4em;
  border-bottom: 1.5px solid currentColor;
  vertical-align: -0.12em;
  margin: 0 .18em;
}
/* 表格里空间紧，稍微窄一点，但别窄到看不出是填空 */
td .blank, th .blank { min-width: 2.6em; }
/* 「准备中」状态：服务器合成首句要 4-5 秒，这期间必须让学生看到反馈，
   否则他会以为点坏了、再点一次 —— 而那次点击是"停止"。 */
.speak-btn.is-preparing,
.speak-btn.is-speaking.is-preparing {
  background: #e8eef5; border-color: #c2cfdd; color: #5a6b80;
  cursor: progress; animation: none;
}
/* ---------- 登录方式切换（验证码登录 / 密码登录）----------
   放在手机号下面，紧挨着要看的内容。
   默认选中「验证码登录」：家长不用记密码，收到短信就能进。
   「密码登录」保留为备用 —— 短信没配置、欠费、或运营商延迟时，
   只有验证码一种方式的话所有人都进不来。 */
.login-modes {
  display: flex; gap: 6px; margin: 2px 0 12px;
  background: var(--bg-hover, #eef2f7); padding: 3px; border-radius: 9px;
}
.login-mode {
  flex: 1; padding: 7px 0; border: 0; border-radius: 7px; cursor: pointer;
  background: transparent; color: var(--muted, #6c7c92);
  font-family: inherit; font-size: .84rem; transition: background .15s, color .15s;
}
.login-mode.is-active {
  background: #fff; color: var(--ink, #1c2634); font-weight: 600;
  box-shadow: 0 1px 3px rgba(28, 38, 52, .12);
}
/* ---------- 忘记密码 / 返回登录 ---------- */
.auth-links { text-align: center; margin-top: 10px; }
.auth-link {
  border: 0; background: none; cursor: pointer; padding: 4px 8px;
  color: var(--muted, #6c7c92); font-family: inherit; font-size: .82rem;
  text-decoration: underline; text-underline-offset: 3px;
}
.auth-link:hover { color: var(--brand, #2f6fb2); }
/* ---------- 重置密码流程的两处补丁 ----------
   ① 页签藏起来时（重置密码），标题下面就没有 .auth-tabs 那 12px 上边距了，
      标题会和「手机号」挤在一起（截图里验证过）。这里补回来。
   ② 提示文案里不要用 ** 这种 markdown 记号 —— 那是纯文本，
      页面上会把星号原样显示出来。 */
.auth-tabs[hidden] { display: none !important; }
.auth-tabs[hidden] + #auth-form { margin-top: 16px; }
.auth-card h2#auth-title { margin-bottom: 0; }
/* ---------- 侧边栏底部版本号 ----------
   小、灰、不抢眼 —— 它的作用是"确认装的是哪一版"，不是给人看的设计元素。 */
.version {
  margin-top: 6px; font-size: .72rem; line-height: 1.5;
  color: var(--muted, #8a97a8); word-break: break-all;
}
/* ---------- 侧边栏底部：当前是谁 + 退出 ---------- */
.who {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--line, #e3e8f0);
  font-size: .78rem; color: var(--ink-soft, #46566b);
}
.who-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.logout-btn {
  flex: 0 0 auto; padding: 3px 10px; border-radius: 7px; cursor: pointer;
  border: 1px solid var(--line, #dde3ec); background: #fff;
  color: var(--muted, #6c7c92); font-family: inherit; font-size: .76rem;
}
.logout-btn:hover { color: #c0392b; border-color: #e8b4ad; }
.logout-btn:disabled { opacity: .6; cursor: default; }
/* ---------- 顶栏朗读开关 ----------
   和主题切换并排，所以沿用 .icon-btn 的尺寸；
   只补"开着/正在读"的视觉差异 —— 不然学生分不清现在是开还是关。 */
.speak-toggle.is-speaking { color: #2f6fb2; }
.speak-toggle.is-speaking.is-paused { color: #d68910; }
/* 正在朗读时轻微脉动，让人一眼看到"它还在念" */
@keyframes dsh-speak-pulse { 0%,100% { opacity: 1; } 50% { opacity: .45; } }
.speak-toggle.is-speaking:not(.is-paused) { animation: dsh-speak-pulse 1.6s ease-in-out infinite; }
/* 尊重系统的"减少动态效果"设置 */
@media (prefers-reduced-motion: reduce) {
  .speak-toggle.is-speaking:not(.is-paused) { animation: none; }
}
/* ---------- 「从这里开始朗读」底部动作条 ----------
 *
 * ⚠️ 它原来是"浮在选区旁边的小胶囊"，改过两版都是同一个毛病：**被盖住**。
 *    原因不是 z-index —— 是**安卓/iOS 选中文字后自己弹的系统菜单**
 *    （复制/全选/分享），它也在选区上方，两个东西永远在抢同一块地方。
 *    而且系统菜单是操作系统的浮层，z-index 再大也压不过。
 *
 *    所以改成**固定在屏幕底部**的横条：那个位置系统菜单永远不去。
 *    代价是离选区远一点，但"点得到"比"离得近"重要得多。
 */
.speak-from-btn {
  position: fixed;
  left: 10px; right: 10px; bottom: 10px;
  /* 用最大整数：这个站点里 update-banner 是 200、img-view 90、modal 70，
     "够大就行"的数值迟早会被某一层压住。 */
  z-index: 2147483647;
  padding: 13px 16px;
  border: 0; border-radius: 12px; cursor: pointer;
  background: #2f6fb2; color: #fff;
  font-family: inherit; font-size: .95rem; font-weight: 600;
  text-align: center;
  /* 阴影往**上**投，提示"我在内容之上" */
  box-shadow: 0 -2px 18px rgba(28, 38, 52, .3);
  user-select: none; -webkit-user-select: none;
  /* 底部安全区：全面屏手机别被手势条压住 */
  bottom: calc(10px + env(safe-area-inset-bottom, 0px));
}
.speak-from-btn:active { transform: scale(.985); }
.speak-from-btn[hidden] { display: none !important; }

/* ---------- 轻提示（右下角浮一条） ----------
   导出的回退路径必须给可见反馈：
   学生点完什么都没看见，只会以为按钮坏了（家长就是这么反馈的）。 */
.nb-toast {
  position: fixed;
  left: 50%; bottom: 24px;
  transform: translate(-50%, 14px);
  max-width: min(92vw, 460px);
  padding: 11px 16px;
  border-radius: 11px;
  background: rgba(24, 32, 44, .94);
  color: #fff;
  font-size: .86rem; line-height: 1.6;
  white-space: pre-wrap;
  box-shadow: 0 8px 26px rgba(12, 18, 28, .3);
  z-index: 2147483600;          /* 比浮动按钮低一档，但高过所有面板 */
  opacity: 0; pointer-events: none;
  transition: opacity .18s ease, transform .18s ease;
}
.nb-toast.is-on { opacity: 1; transform: translate(-50%, 0); }
@media (prefers-reduced-motion: reduce) {
  .nb-toast { transition: none; }
}

/* ---------- 导出范围（按考点选）----------
   学生打开热力图，看到某个考点是红的，想做的就是"只把这一摞题导出来重做"。
   整册导出混着已掌握的和还没学的，打印出来没有重点。 */
.exp-card { max-width: 420px; display: flex; flex-direction: column; max-height: 86vh; }
.exp-tools {
  display: flex; align-items: center; gap: 8px;
  padding: 10px 0; border-bottom: 1px solid var(--line);
  flex-wrap: wrap;
}
.exp-count { margin-left: auto; color: var(--muted); font-size: .8rem; white-space: nowrap; }
.exp-list {
  flex: 1; min-height: 0;              /* 关键：不写的话 flex 子项不会滚动，只能把弹窗撑爆 */
  overflow-y: auto; -webkit-overflow-scrolling: touch;
  padding: 6px 2px;
}
.exp-item {
  display: flex; align-items: center; gap: 10px;
  padding: 11px 10px; border-radius: 9px; cursor: pointer;
  font-size: .9rem; line-height: 1.4;
}
.exp-item:active { background: var(--bg-soft, #f4f6f9); }
.exp-item input { flex: 0 0 auto; width: 18px; height: 18px; }
.exp-item .exp-name { flex: 1; min-width: 0; word-break: break-word; }
.exp-item .exp-n { flex: 0 0 auto; color: var(--muted); font-size: .78rem; }
.exp-item.is-unclassified .exp-name { color: var(--muted); }
.exp-empty { padding: 20px 6px; color: var(--muted); font-size: .87rem; text-align: center; }

/* ---------- 拍试卷：逐题裁出来的原题图 ----------
   裁图是"试卷原样"：图形、表格、学生自己的作答、卷面上的批改全在。
   文字抄录做不到这一点（题干里的图用文字永远描述不准），
   所以确认页上必须让学生**看到裁图** —— 裁歪了、裁少了只有看图才知道。 */
.scan-crop {
  display: block; width: 100%; height: auto;
  margin: 6px 0 8px; border: 1px solid var(--line); border-radius: 8px;
  background: #fff;
}
.scan-no {
  display: inline-block; margin-right: 6px;
  font-size: .76rem; font-weight: 600; color: var(--brand);
  background: var(--bg-soft, #eef2f8); border-radius: 5px; padding: 1px 7px;
}
.scan-nocrop {
  margin: 6px 0 8px; padding: 8px 10px;
  font-size: .78rem; color: var(--muted); line-height: 1.6;
  background: var(--bg-soft, #f5f7fa); border-radius: 8px;
}

/* ---------- 导出排版选择（错题集 / 重做卷）---------- */
.exp-layout {
  padding: 10px 0 2px; border-top: 1px solid var(--line);
  display: flex; flex-direction: column; gap: 6px;
}
.exp-layout[hidden] { display: none; }
.exp-layout-cap { font-size: .78rem; color: var(--muted); margin-bottom: 2px; }
.exp-radio {
  display: flex; align-items: flex-start; gap: 9px;
  padding: 8px 10px; border-radius: 9px; cursor: pointer; font-size: .88rem;
}
.exp-radio:active { background: var(--bg-soft, #f4f6f9); }
.exp-radio input { flex: 0 0 auto; width: 17px; height: 17px; margin-top: 2px; }
.exp-radio span { flex: 1; min-width: 0; line-height: 1.45; }
.exp-radio em { display: block; font-style: normal; font-size: .76rem; color: var(--muted); margin-top: 1px; }

/* ---------- 拍试卷：跨页题 ----------
   一道题横跨两页时（短文在一页、题目在下一页），
   要把两段裁图**都**显示出来 —— 学生确认时才看得出是不是完整。 */
.scan-multi {
  display: inline-block; margin-right: 6px;
  font-size: .76rem; font-weight: 600; color: #a15c00;
  background: #fdf3e3; border-radius: 5px; padding: 1px 7px;
}
.scan-dup {
  margin: 6px 0 8px; padding: 8px 10px;
  font-size: .78rem; color: #8a5a00; line-height: 1.6;
  background: #fdf7e8; border: 1px solid #f0e0bc; border-radius: 8px;
}
/* 跨页题的每一段之间留条缝，看得出是两部分拼起来的一道题 */
.scan-crop.is-part + .scan-crop.is-part { margin-top: 4px; }
.scan-crop.is-part { border-style: dashed; }

/* ---------- 背诵训练 ----------
   演练要学生**盯着字背**，所以占满整屏、字号偏大、行距宽松。
   点击区域是整块正文（不是按钮）—— 他正在背，手在屏幕上，
   让他去找按钮就是打断。 */
/* ⚠️ z-index 必须**高于背诵本**（.nb 是 60）。
   背诵训练现在是背诵本的**子功能**（入口就在背诵本面板里，Excel 前面）——
   同层级的话 DOM 靠后的 .nb 会盖住它，点「🎯 背诵训练」看起来像没反应。 */
.rd {
  position: fixed; inset: 0; z-index: 65; display: flex; flex-direction: column;
  background: var(--bg, #fff);
}
.rd[hidden] { display: none !important; }
.rd-head {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 10px 14px; padding-top: calc(10px + env(safe-area-inset-top));
  border-bottom: 1px solid var(--line);
}
/* 顶栏材料名：**永远只占一行**，长了用省略号（用户原话"顶部标题只显示一行，
   不要显示多行"）。三件套缺一不可 —— 这个坑很容易踩：
     · flex-basis: 0  换行算法才肯把它和两边的按钮放在同一行
                      （basis 是内容宽度时，它"装不下"→ 整行换行 → 顶栏变两三行）
     · min-width: 0   flex 子项默认的自动最小尺寸 = 内容宽度，不写这个**永远不会收缩**，
                      省略号也就永远不出现（这是最隐蔽的一条）
     · nowrap + ellipsis  单行、超出省略
   ⚠️ .rd-head 的 flex-wrap: wrap 要留着 —— 窄屏下"返回/关闭/第 N 段"自己换行是正常的，
      用户说的只是**材料名**不许换行。 */
.rd-h-title {
  flex: 1 1 0; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rd-h-pos { margin-left: auto; color: var(--muted); font-size: .82rem; white-space: nowrap; }
.rd-list { flex: 1; min-height: 0; overflow-y: auto; padding: 14px; }
.rd-lead { color: var(--muted); font-size: .84rem; line-height: 1.7; margin: 0 0 12px; }
.rd-item {
  display: block; width: 100%; text-align: left; margin-bottom: 9px;
  padding: 12px 14px; border: 1px solid var(--line); border-radius: 11px;
  background: var(--bg); color: var(--ink); font: inherit; cursor: pointer;
}
.rd-item:hover { border-color: var(--brand); }
/* ⚠️ .rd-item 是 <button>。它中间一度改成过 <div> —— 因为行内放过
   「拆句子 / 按作文拆 / ▶ 背诵」三个按钮，而 HTML 不允许 <button> 里再放
   <button>（浏览器会把内层按钮提出去，事件和 data-id 全错位）。
   用户 2026-10-16 截图纠正：那三个按钮属于「背英语句子」那一页的材料卡片，
   不是训练面板里这份清单。按钮撤掉后外层改回了 <button>：
     · HTML 重新合法（行内再没有按钮）
     · Tab 能到、回车/空格原生激活 —— 不用补 tabindex + role="button"
   下面这条规则本来就把 button 的 UA 样式盖全了（display/width/font/color/
   background/border/padding/text-align/cursor），所以两种标签看起来一样。 */
/* ★ 标题 + 类型标记排一行：标题 flex: 1 1 0 + min-width: 0（省略号），
   标记 flex: 0 0 auto（不被压缩）。缺了 min-width: 0 会把标记顶出卡片。 */
.rd-item-t {
  display: flex; align-items: center; gap: 8px;
  font-weight: 600; margin-bottom: 5px;
}
.rd-item-title {
  flex: 1 1 0; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rd-item-m { display: flex; gap: 10px; color: var(--muted); font-size: .78rem; }
.rd-item-src { margin-left: auto; }
/* （.rd-item-acts 的规则已删：它只服务于上面说的那三个按钮。
   留着死规则会让人以为行内还有按钮。按钮现在在背诵本卡片上，
   样式走 .rc-card-foot / .btn-mini。） */
.rd-empty { color: var(--muted); font-size: .87rem; line-height: 1.8; padding: 18px 4px; }

.rd-drill { flex: 1; min-height: 0; display: flex; flex-direction: column; }
.rd-drill[hidden] { display: none !important; }
.rd-drill-head { padding: 12px 16px 0; }
.rd-seg-title { font-weight: 600; font-size: .95rem; line-height: 1.5; }
.rd-done-count { color: var(--accent, #1a936f); font-size: .78rem; margin-top: 3px; }

/* ★ 点击推进的区域：整块都算，而且**不允许滚动** */
.rd-stage {
  /* min-height 是"句子钉住之后"给分析区留的保底高度：
     - 给太大（84px）：300+ 字符的句子会差十几像素放不下，钉住区只好自己滚；
     - 给太小：句子一长，分析区被挤成 0 高，看起来像"句法/句型没加载出来"。
     56px ≈ 1.5 行分析文字，刚好能看出"这里还有内容、要往下滚"。
     用户的选择是**句子优先**（句子完整显示，分析往下滚才看得到），所以宁可压分析区。 */
  flex: 1; min-height: 56px; overflow-y: auto;
  padding: 20px 18px; cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
/* ---------- 背诵训练：句子钉在顶部，只有分析区滚动 ----------
   用户报的问题（按句子拆 / 按作文拆）：句子很长时，句法/句型一出现，
   内容突然变高，把**句子本身顶出了屏幕** —— 学生连要背的句子都看不到。

   修法：把句子移出滚动区（.rd-stage），作为兄弟节点放在它上面。
   为什么不给句子 position: sticky —— 粘在滚动区里仍然参与滚动计算，
   而且分析会从句子底下穿过去（还要额外背景 + 层叠）；移出滚动区最稳：
   stage 怎么滚，这块都不动。

   ⚠️ 用户明确要求**句子完整显示、绝不截断**：
      所以这里**没有** max-height + overflow: hidden。只有当句子长到
      连屏幕都放不下时，这一块才自己上下滚（overflow-y: auto ——
      文字一个字都不少，只是要滚一下），其余情况它就是个固定的头。 */
.rd-pinned {
  flex: 0 1 auto; min-height: 0;
  overflow-y: auto; -webkit-overflow-scrolling: touch;
  padding: 16px 18px 0;
  cursor: pointer;   /* 和 stage 一样：点句子这里也能推进（点屏幕任意处） */
  -webkit-tap-highlight-color: transparent;
}
.rd-pinned[hidden] { display: none; }
.rd-text {
  font-size: 1.12rem; line-height: 2.15; letter-spacing: .02em;
  white-space: pre-wrap; word-break: break-word;
}
/* 重点词：未揭示 = 下划线留白（虚线，看得出是"要填的地方"） */
.rd-key.is-off {
  border-bottom: 2px solid var(--brand);
  color: transparent;
  margin: 0 1px;
}
.rd-blank { color: transparent; }
/* 已揭示 = 红字 + 下划线 */
.rd-key.is-on {
  color: #d0342c; font-weight: 700;
  border-bottom: 2px solid #d0342c;
  margin: 0 1px;
}
/* ★ 英文整句：**不用红色**。
   用户报过"英语有错误、红色预警" —— 其实是英文整句被当成重点词标成红色，
   学生（和用户自己）看到满屏红以为在报错。
   英文整句是"要背出来的内容"，不是"要填的空"，所以用正文色 + 细下划线。 */
.rd-key.is-on.rd-key-sent {
  color: var(--ink, #1a1f2b); font-weight: 500;
  border-bottom: 1px solid rgba(0, 0, 0, .18);
}
/* 英文整句未揭示：一整条灰底虚线占位 ——
   看得出"这里有一句话要背"，但整句**一起**藏着（不是只藏几个词） */
.rd-key.is-off.rd-key-sent {
  border-bottom: 1px solid var(--line, #dcdfe6);
  background: repeating-linear-gradient(90deg,
    var(--bg-soft, #f2f4f7) 0 6px, transparent 6px 10px);
  border-radius: 3px;
}
@keyframes rd-pop { from { transform: scale(1.35); } to { transform: scale(1); } }
.rd-key.is-pop { display: inline-block; animation: rd-pop .3s ease; }
@media (prefers-reduced-motion: reduce) { .rd-key.is-pop { animation: none; } }

.rd-hidden { padding: 30px 0; text-align: center; }
.rd-hidden-t { font-weight: 600; font-size: 1rem; margin-bottom: 10px; }
.rd-hidden-h { color: var(--muted); font-size: .84rem; line-height: 1.8; }
.rd-hint {
  padding: 9px 16px; color: var(--muted); font-size: .8rem; text-align: center;
  border-top: 1px solid var(--line);
}
.rd-actions {
  display: flex; gap: 8px; flex-wrap: wrap; justify-content: center;
  padding: 10px 14px; padding-bottom: calc(10px + env(safe-area-inset-bottom));
  border-top: 1px solid var(--line); background: var(--bg-soft, #f7f8fa);
}

/* 提示条（复用错题本的 toast 样式思路，独立一份免得互相影响） */
.rd-toast {
  position: fixed; left: 50%; bottom: 24px; transform: translate(-50%, 14px);
  max-width: min(92vw, 460px); padding: 11px 16px; border-radius: 11px;
  background: rgba(24, 32, 44, .94); color: #fff;
  font-size: .86rem; line-height: 1.6; white-space: pre-wrap;
  z-index: 2147483600; opacity: 0; pointer-events: none;
  transition: opacity .18s ease, transform .18s ease;
}
.rd-toast.is-on { opacity: 1; transform: translate(-50%, 0); }
@media (prefers-reduced-motion: reduce) { .rd-toast { transition: none; } }

/* ---------- 背诵训练：表格段 ----------
   知识梳理、对比表用表格比一行行文字清楚得多（用户要求）。
   手机窄屏放不下多列表格 —— 用 data-label 把每行变成一张卡片
   （和对话页表格是同一套做法，见 markdown.js 的说明）。 */
.rd-table { width: 100%; border-collapse: collapse; font-size: .95rem; line-height: 1.75; }
.rd-table th, .rd-table td {
  border: 1px solid var(--line); padding: 8px 10px; text-align: left; vertical-align: top;
  word-break: break-word;
}
.rd-table thead td { background: var(--bg-soft, #f7f8fa); font-weight: 600; }
/* 正在跟读的单元格 */
.rd-table td.is-reading { background: #fff6d8; }

/* ---------- 跟读：文字和声音同步往下读 ---------- */
.rd-unit { transition: background-color .18s ease; border-radius: 3px; }
.rd-unit.is-reading {
  background: #fff6d8;
  box-shadow: 0 0 0 2px #fff6d8;
}
@media (prefers-reduced-motion: reduce) { .rd-unit { transition: none; } }

/* 手机：多列表格变成"一行一张卡片" */
@media (max-width: 560px) {
  .rd-table thead { display: none; }
  .rd-table, .rd-table tbody, .rd-table tr, .rd-table td { display: block; width: 100%; }
  .rd-table tr {
    border: 1px solid var(--line); border-radius: 10px;
    margin-bottom: 10px; padding: 6px 4px;
  }
  .rd-table td { border: none; border-bottom: 1px dashed var(--line); padding: 6px 8px; }
  .rd-table td:last-child { border-bottom: none; }
  .rd-table td::before {
    content: attr(data-label) "：";
    display: block; font-size: .74rem; color: var(--muted); margin-bottom: 2px;
  }
  .rd-table td:not([data-label])::before { content: none; }
}

/* 跟读开关（"读重点"勾选框） */
.rd-chk {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: .8rem; color: var(--muted); cursor: pointer; padding: 0 4px;
}
.rd-chk input { width: 16px; height: 16px; margin: 0; }

/* 内容不完整的提示（照片被裁切 / 有看不清的字）
   用户报过"最后一段文字丢了" —— 其实是照片右下角没拍到。
   这个提示就是为了让那种情况一眼看得出来，而不是让人怀疑系统抄漏了。 */
.rd-warn {
  margin-top: 6px; padding: 7px 10px; border-radius: 8px;
  font-size: .78rem; line-height: 1.6;
  color: #8a5a00; background: #fdf7e8; border: 1px solid #f0e0bc;
}
.rd-warn[hidden] { display: none; }

/* 材料的抄录备注（「哪块没拍全」这类）—— 显示在演练区最上面 */
.rd-mat-note {
  margin: 10px 14px 0; padding: 8px 11px; border-radius: 8px;
  font-size: .78rem; line-height: 1.65;
  color: #6b5a2e; background: #fbf7ea; border: 1px solid #efe6cd;
}
.rd-mat-note[hidden] { display: none; }

/* ---------- 背诵训练：思维导图 ----------
   只对"有总分 / 并列 / 递进结构"的段落显示（用户要求）。
   用缩进 + ├└ 画树形，不用 SVG 连线和绝对定位 ——
   手机上文字长度不一，绝对定位很容易错位，而缩进永远不会错。 */
.rd-map { padding: 4px 0; }
.rd-map-root {
  display: inline-block; padding: 7px 14px; border-radius: 9px;
  background: var(--brand); color: #fff; font-weight: 600; font-size: 1rem;
}
.rd-map-kind {
  display: inline-block; margin-left: 8px; padding: 2px 8px; border-radius: 6px;
  font-size: .72rem; color: #a15c00; background: #fdf3e3; vertical-align: middle;
}
.rd-map-tree { margin-top: 14px; }
.rd-map-node {
  position: relative; padding: 5px 0 5px calc(var(--d, 1) * 18px);
  line-height: 1.6; transition: opacity .18s ease;
}
.rd-map-line {
  position: absolute; left: calc((var(--d, 1) - 1) * 18px);
  color: var(--muted, #8b93a1); font-family: monospace;
}
.rd-map-label { font-weight: 600; }
.rd-map-detail { color: var(--muted, #8b93a1); font-size: .84rem; margin-left: 6px; }
/* 未展开的项：**只显示一个占位**，不泄露内容 */
.rd-map-node.is-off { opacity: .28; }
.rd-map-node.is-off .rd-map-label {
  background: var(--bg-soft, #f2f4f7); border-radius: 4px; color: transparent;
  -webkit-user-select: none; user-select: none;
}
.rd-map-node.is-on .rd-map-label { color: var(--brand); }
@media (prefers-reduced-motion: reduce) { .rd-map-node { transition: none; } }

/* ---------- 背诵本列表：背诵次数 ----------
   用户要求"在材料目录里记录已经背诵的次数"。
   单位是**整段材料**（背出一整段算一次），所以显示的是累计段次。
   没拆过段的材料不显示 —— 不是"0 次"，而是"还没开始背"。 */
.rc-train-line { margin: 6px 0 0; }
.rc-train {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 3px 9px; border-radius: 7px; font-size: .78rem;
  color: var(--muted, #8b93a1); background: var(--bg-soft, #f2f4f7);
}
.rc-train.is-done { color: #1a7f5a; background: #e8f6ef; }
.rc-train b { font-size: .92em; }
.rc-train em { font-style: normal; opacity: .75; }

/* ---------- 背诵训练：英语语法分析 ----------
   用户要求"英语每句话都要做词性分析或从句分析"。
   显示在这一句的下方，**这一句揭示出来了才出现** ——
   先自己回忆，回忆出来了再看语法（顺序反了就成了"抄答案"）。 */
.rd-gram {
  display: block; margin: 6px 0 2px; padding: 7px 9px;
  border-left: 3px solid var(--brand); border-radius: 0 7px 7px 0;
  background: var(--bg-soft, #f7f8fa);
  font-size: .8rem; line-height: 1.75;
}
.rd-gram-pos { display: flex; flex-wrap: wrap; gap: 4px 8px; }
.rd-gram-w {
  display: inline-flex; align-items: baseline; gap: 3px;
  padding: 1px 5px; border-radius: 5px; background: #fff;
  border: 1px solid var(--line);
  /* ⚠️ 单项不能宽过屏幕 —— 像 `from the UK 介词短语 表语` 这种长短语，
     不加这两行就会被右边切掉（截图里见过）。
     min-width:0 是 flex 子项能收缩的前提，光有 max-width 不生效。 */
  max-width: 100%; min-width: 0;
  overflow-wrap: anywhere;
}
.rd-gram-w b { overflow-wrap: anywhere; }
.rd-gram-w b { font-weight: 600; color: var(--ink); }
.rd-gram-w i { font-style: normal; font-size: .92em; }
/* 词性用紫色、句子成分用绿色 —— 两者要一眼分得开 */
.rd-gram-t { color: #8e44ad; }
.rd-gram-r { color: #1a936f; }
.rd-gram-clause {
  margin-top: 5px; color: #a15c00; font-size: .94em;
}
.rd-gram-clause::before { content: "句型　"; color: var(--muted, #8b93a1); font-size: .9em; }

/* ---------- 语法分析：**按词性着色**（用户要求"不同的词性用不同的颜色"）----------
   配色原则：同族同色系、高频的给最醒目的颜色。
   名词最多给蓝色（最稳），动词给红色（句子骨架，要最先看见），
   形容词绿、副词橙；代词/介词/连词/冠词/数词给低饱和度的色，不抢眼。
   色相拉开、明度接近 —— 满屏彩色里还能一眼看出"这句话里动词在哪"。 ---------- */
.rd-gram-w.c-n    { border-color: #bcd7f5; background: #eef6ff; }  /* 名词：蓝 */
.rd-gram-w.c-n b  { color: #1663c7; }
.rd-gram-w.c-v    { border-color: #f5c2c2; background: #fff0f0; }  /* 动词：红 */
.rd-gram-w.c-v b  { color: #c92a2a; }
.rd-gram-w.c-adj  { border-color: #bfe5cd; background: #eefaf2; }  /* 形容词：绿 */
.rd-gram-w.c-adj b{ color: #1a7f4b; }
.rd-gram-w.c-adv  { border-color: #f7d9a8; background: #fff8ec; }  /* 副词：橙 */
.rd-gram-w.c-adv b{ color: #b06f00; }
.rd-gram-w.c-pron { border-color: #dcc7f0; background: #f8f2ff; }  /* 代词：紫 */
.rd-gram-w.c-pron b { color: #7040a8; }
.rd-gram-w.c-prep { border-color: #b9d9dc; background: #eef9fa; }  /* 介词：青 */
.rd-gram-w.c-prep b { color: #0f6f78; }
.rd-gram-w.c-mod  { border-color: #f0c8dd; background: #fff2f8; }  /* 情态/助动：玫红 */
.rd-gram-w.c-mod b{ color: #a83271; }
.rd-gram-w.c-q    { border-color: #c9cbe0; background: #f3f4fb; }  /* 疑问词：靛 */
.rd-gram-w.c-q b  { color: #3f4593; }
.rd-gram-w.c-inf  { border-color: #e0d3b8; background: #faf6ec; }  /* 非谓语：棕 */
.rd-gram-w.c-inf b{ color: #7a5c1e; }
.rd-gram-w.c-conj { border-color: #d8d8d8; background: #f7f7f7; }  /* 连词：灰 */
.rd-gram-w.c-conj b { color: #555; }
.rd-gram-w.c-art  { border-color: #e2e2e2; background: #fafafa; }  /* 冠词：浅灰 */
.rd-gram-w.c-art b{ color: #777; }
.rd-gram-w.c-num  { border-color: #cfd8c0; background: #f7faef; }  /* 数词：橄榄 */
.rd-gram-w.c-num b{ color: #556b2f; }
.rd-gram-w.c-x b  { color: #444; }

/* ---------- 跟读：单词一个一个展示出来 ----------
   念到的词加 .is-spoken。未念到的是正常黑色，念过的**沉下去**（变淡）+ 底下一条进度线，
   这样"读到哪儿"一眼看得见，又不会像全彩那样晃眼。 */
.rd-w { transition: color .12s, opacity .12s; }
.rd-w.is-spoken {
  color: var(--muted, #8b93a1);
  border-bottom: 2px solid var(--brand);
}

/* ---------- 背句子（初中/高中英语）：四行版式 ----------
   用户要求的版式：
     第 1 行 中文（一直可见，这是提示）
     第 2 行 英语（点击后出现；跟读时逐词出现）
     第 3 行 音标（每个词一个格子，和上面的英语词一一对齐）
     第 4 行 主谓宾 / 宾语从句等语法分析
   ★ 音标**按音节分段着色**（"按音标分段记忆，分段用不同颜色"）——
     长单词按音节记拼写比整块硬记容易得多。 ---------- */
.sp-pair { padding: 4px 0; }
.sp-zh {
  font-size: 1.08rem; line-height: 1.7; color: var(--ink, #1a1f2b);
  margin-bottom: 10px;
}
.sp-en-wrap { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 2px 10px; }
.sp-en-wrap.is-off { display: block; }
.sp-w { display: inline-flex; flex-direction: column; align-items: center; gap: 1px; }
.sp-en { font-size: 1.06rem; font-weight: 500; line-height: 1.35; }
.sp-ph {
  display: inline-flex; gap: 1px; font-size: .74rem; line-height: 1.3;
  font-family: "Segoe UI", "Lucida Sans Unicode", system-ui, sans-serif;
}
.sp-ph.is-none { color: var(--muted, #8b93a1); }
/* 音节：5 色循环。相邻音节必然不同色 —— 一眼能数出几个音节 */
.sp-sy { display: inline-block; padding: 0 2px; border-radius: 3px; }
/* ★ **单词本身也按音节切开着色**（用户要求"单词也按音节分、和音标同色"）：
      in | tro | duce        ← 拼写分段
     ˌɪn | trə | djuːs       ← 音标同色，一一对应
   上下同色 → 一眼看出哪段拼写对应哪段音。 */
.sp-en.is-syl { display: inline-flex; gap: 0; }
.sp-en.is-syl .sp-sy { font-weight: 500; }
.sp-sy.sy0 { color: #1663c7; background: #eaf3ff; }
.sp-sy.sy1 { color: #c92a2a; background: #ffefef; }
.sp-sy.sy2 { color: #1a7f4b; background: #eafaf1; }
.sp-sy.sy3 { color: #b06f00; background: #fff6e6; }
.sp-sy.sy4 { color: #7040a8; background: #f6efff; }
/* 跟读：念到的词沉下去、给一条进度线（和普通英语句一样的观感） */
.sp-w { transition: opacity .12s; }
.sp-w.is-spoken .sp-en { color: var(--muted, #8b93a1); }
.sp-w.is-spoken .sp-ph { opacity: .55; }
.sp-w.is-spoken { border-bottom: 2px solid var(--brand); }
/* 第 4 行：语法 */
.sp-gram {
  margin-top: 12px; padding: 7px 9px;
  border-left: 3px solid var(--brand); border-radius: 0 7px 7px 0;
  background: var(--bg-soft, #f7f8fa); font-size: .8rem; line-height: 1.75;
}
.sp-gram-pos { display: flex; flex-wrap: wrap; gap: 4px 8px; }
.sp-gram-clause { margin-top: 5px; color: #a15c00; font-size: .94em; }
.sp-gram-clause::before { content: "句型　"; color: var(--muted, #8b93a1); font-size: .9em; }

/* ---------------- 点单词弹卡片（构词 / 形近词 / 记忆提示） ---------------- */

/* 有"构词分解/形近词/提示"的词，给一点点可点的暗示 ——
   虚线下划线，不显眼但看得出能点。 */
.sp-w.sp-tap {
  cursor: pointer;
  border-bottom: 1px dashed rgba(37, 99, 235, 0.45);
}
.sp-w.sp-tap:active { background: rgba(37, 99, 235, 0.08); }

.sp-cards {
  position: fixed;
  inset: 0;
  /* ⚠️ 必须盖住背诵面板（.rd 是 65）和背诵本（.nb 是 60）—— 见 check-zindex.mjs */
  z-index: 90;
}
.sp-card-mask {
  position: absolute;
  inset: 0;
  background: rgba(15, 23, 42, 0.42);
}
.sp-card {
  position: absolute;
  left: 50%;
  bottom: 12%;
  transform: translateX(-50%);
  width: min(88vw, 420px);
  max-height: 60vh;
  overflow-y: auto;
  background: #fff;
  border-radius: 14px;
  box-shadow: 0 12px 40px rgba(15, 23, 42, 0.28);
  padding: 14px 16px 16px;
  box-sizing: border-box;
}
.sp-card-x {
  position: absolute;
  top: 6px;
  right: 8px;
  width: 30px;
  height: 30px;
  border: 0;
  background: transparent;
  color: #94a3b8;
  font-size: 15px;
  line-height: 1;
  border-radius: 8px;
  cursor: pointer;
}
.sp-card-x:active { background: #f1f5f9; }

.sp-card-h {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 8px;
  padding-right: 28px;
  margin-bottom: 10px;
}
.sp-card-h b { font-size: 19px; color: #0f172a; letter-spacing: 0.2px; }
.sp-card-ph { font-size: 13px; color: #64748b; }

.sp-card-row {
  display: flex;
  gap: 8px;
  align-items: flex-start;
  padding: 7px 0;
  border-top: 1px solid #f1f5f9;
}
.sp-card-row:first-of-type { border-top: 0; }
.sp-card-k {
  flex: 0 0 auto;
  font-size: 12px;
  color: #2563eb;
  background: rgba(37, 99, 235, 0.09);
  border-radius: 6px;
  padding: 2px 7px;
  line-height: 1.5;
}
.sp-card-v {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 14px;
  color: #334155;
  line-height: 1.65;
  word-break: break-word;
}

/* ---------------- 热力图的**分科标题**（合并板块专用） ---------------- */

/* 「初中科学」= 物理 + 化学 + 生物 + 地理。33 个大考点平铺一屏拉不完，
   而且物理的考点和生物的混在一起看没意义 —— 学生想知道的是
   "我物理哪里弱"。所以按科分组，每科一个小标题 + 该科的待补数。
   ⚠️ 没有 subject 的板块不生成这些元素，原来长什么样还是什么样。 */
.nb-heat-subject {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin: 16px 0 8px;
  padding-bottom: 5px;
  border-bottom: 1px solid #e2e8f0;
}
.nb-heat-subject:first-child { margin-top: 4px; }
.nb-heat-subject h4 {
  margin: 0;
  font-size: 14px;
  font-weight: 600;
  color: #0f172a;
  letter-spacing: 0.3px;
}
.nb-heat-subject-hint {
  font-size: 12px;
  color: #64748b;
}

/* ---------------- 藏着的关键词（背诵：点1次出一个关键词） ---------------- */

/* 没揭示的关键词用**等长下划线**占位：看得出这个词有几个音节、多长
   （这本身就是很强的提示），但看不到是什么词。
   ⚠️ 用文字颜色而不是背景色 —— 下划线是 `＿` 字符，不是 border。 */
.sp-w.is-off .sp-sy.sy-off {
  color: #c8d0dc;
}
/* 藏着的词不能点（点了也没内容，还会误触弹卡片） */
.sp-w.is-off { pointer-events: none; }
/* 藏着的词不给"可点"的下划虚线 */
.sp-w.is-off.sp-tap { border-bottom: none; }

/* ---------------- 选择性背诵（熟练级别 1-9） ---------------- */

/* 顶部「只背 N 级以下」的下拉。故意做得小 —— 它是**筛选条件**，
   不是主操作，不该抢「跟读 / 下一段 / 我背出来了」的注意力。 */
.rd-level-sel {
  font: inherit;
  font-size: .78rem;
  padding: 1px 4px;
  border: 1px solid var(--line, #dde3ec);
  border-radius: 6px;
  background: #fff;
  color: inherit;
  cursor: pointer;
}

/* 句子列表：40 句一次列出来，能看级别、能改、点一下跳过去 */
.rd-list-card {
  width: min(680px, 94vw);
  max-height: 84vh;
  display: flex;
  flex-direction: column;
}
.rd-list-head {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--line, #dde3ec);
}
.rd-list-head h2 { margin: 0; font-size: 1rem; }
.rd-list-sum { flex: 1; font-size: .78rem; color: var(--muted, #6c7c92); }
.rd-list-body { overflow-y: auto; padding-top: 6px; }

.rd-li {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 7px 9px;
  border-radius: 8px;
  cursor: pointer;
  border-left: 3px solid transparent;
}
.rd-li + .rd-li { margin-top: 2px; }
.rd-li:hover { background: rgba(37, 99, 235, .06); }

/* 还没背下来的（级别 ≤ 筛选值）—— 这是学生要背的那几句，让它显眼 */
.rd-li.is-need { border-left-color: #2563eb; background: rgba(37, 99, 235, .045); }
/* 已经会的 —— 淡一点，一眼能看出"这些不用背了" */
.rd-li.is-done { opacity: .52; }
/* 当前正在背的那一句 */
.rd-li.is-cur { background: rgba(37, 99, 235, .13); font-weight: 600; }

.rd-li-i {
  flex: 0 0 2.1em;
  text-align: right;
  font-size: .76rem;
  color: var(--muted, #6c7c92);
  font-variant-numeric: tabular-nums;
}
.rd-li-t {
  flex: 1;
  font-size: .84rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rd-li-lv {
  flex: 0 0 auto;
  font: inherit;
  font-size: .78rem;
  padding: 1px 4px;
  border: 1px solid var(--line, #dde3ec);
  border-radius: 6px;
  background: #fff;
  cursor: pointer;
}
/* ── 语法填空 / 变形练习（用户要求：挖空填词，答案红色）── */
.rd-text.sp-cloze {
  padding: 18px 16px;
}
.rd-cloze-line {
  font-size: 22px;
  line-height: 2.1;
  color: #111827;
  word-break: break-word;
}
.rd-cloze {
  display: inline-block;
  margin: 0 4px;
  vertical-align: baseline;
}
/* 空位：红色下划线，够宽让学生看得出这里要填 */
.rd-cloze-blank {
  display: inline-block;
  min-width: 68px;
  height: 1.1em;
  border-bottom: 2px solid #ef4444;
  vertical-align: -0.15em;
}
/* 变形练习括号里的原形：(be) (have) */
.rd-cloze-hint {
  font-style: normal;
  font-size: 0.78em;
  color: #6b7280;
  margin-right: 2px;
}
/* ★ 答案：红色加粗（用户要求） */
.rd-cloze.is-ans {
  color: #dc2626;
  font-weight: 700;
  padding: 0 2px;
}
.rd-cloze-tags {
  margin-top: 12px;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.rd-cloze-tag {
  font-size: 12px;
  color: #b45309;
  background: #fef3c7;
  border: 1px solid #fde68a;
  border-radius: 4px;
  padding: 1px 7px;
  white-space: nowrap;
}
.rd-cloze-tip {
  margin-top: 10px;
  font-size: 13px;
  color: #6b7280;
}

/* ══════════════════════════════════════════════════════════════
   背作文（英语）—— 独立模块的样式（ed-* 前缀）
   ⚠️ 和「背句子」（rd-*）**不共用任何类名** —— 用户明确要求两个模块独立开发，
      共用类名的话，改一边的样式会莫名其妙影响另一边。
   ══════════════════════════════════════════════════════════════ */
.ed {
  position: fixed;
  inset: 0;
  /* ★★ 必须**高于**背诵训练面板（.rd 是 65）。
     我第一版写的 60 —— 转交到作文时 .rd 的列表还在（只藏了演练区），
     它的层级更高，就把整个作文面板盖住了。表现是
     「点作文 → 秒退回列表页」（用户报的原话），而作文面板其实**已经打开了**，
     只是被压在下面看不见。又低于 .modal(70)，弹窗还能盖在上面。 */
  z-index: 68;
  background: #f6f7f9;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.ed[hidden] { display: none !important; }

.ed-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 12px;
  background: #fff;
  border-bottom: 1px solid #e5e7eb;
  flex: 0 0 auto;
}
/* 作文面板的标题：**永远只占一行**，长了省略号（用户原话"返回右边，标题占了
   太多行，只能有一行"）。
   ⚠️ 这条和背诵训练的 .rd-h-title 是**两条独立规则** —— 用户截图里的顶栏是
      作文面板（顶栏显示 4 / 46 句、按钮是上一句/循环朗读/下一句），
      所以真正被撑成多行的是这一条，跟 .rd-h-title 无关。
   三件套缺一不可（和 .rd-h-title 同一个坑）：
     · flex-basis: 0  收缩/换行算法才肯把它和两边的按钮放在同一行
     · min-width: 0   flex 子项默认不收缩（自动最小尺寸=内容宽度），
                      不写这个**永远不会收缩**，省略号永远不出现
     · nowrap + ellipsis  单行、超出省略 */
.ed-h-title {
  flex: 1 1 0; min-width: 0;
  font-size: 15px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ed-h-pos { font-size: 12px; color: #6b7280; white-space: nowrap; }

/* ── ① 开背前一屏：全文中文翻译 + 重点归纳 ── */
.ed-preview {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: 14px 16px 90px;
  -webkit-overflow-scrolling: touch;
}
.ed-preview[hidden] { display: none; }
.ed-lead { font-size: 13px; color: #6b7280; line-height: 1.7; margin: 0 0 12px; }
.ed-sub { font-size: 14px; margin: 18px 0 8px; color: #111827; }
.ed-zh-list { margin: 0; padding-left: 22px; }
.ed-zh-list li { font-size: 15px; line-height: 1.9; margin-bottom: 6px; }
.ed-points { margin: 0; padding-left: 20px; }
.ed-points li {
  font-size: 14px;
  line-height: 1.8;
  margin-bottom: 8px;
  color: #374151;
}
.ed-points .ed-none { color: #9ca3af; list-style: none; margin-left: -20px; }
.ed-foot {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  display: flex;
  gap: 8px;
  padding: 10px 16px;
  background: #fff;
  border-top: 1px solid #e5e7eb;
}
.ed-foot .btn-primary { flex: 1; }

/* ── ② 逐句堆积 ── */
.ed-drill {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.ed-drill[hidden] { display: none; }

/* 英文堆积区：越背越多，自动滚到底 */
.ed-stage {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: 14px 16px 8px;
  -webkit-overflow-scrolling: touch;
}
.ed-en {
  font-size: 17px;
  line-height: 1.75;
  margin: 0 0 10px;
  color: #111827;
  font-family: Georgia, "Times New Roman", serif;
}
.ed-en.is-new { animation: ed-in 0.32s ease-out; }
@keyframes ed-in {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}

/* 抽查：只给中文，点开核对英文 */
.ed-quiz-zh { font-size: 18px; line-height: 1.7; color: #111827; margin: 0 0 12px; font-weight: 600; }
.ed-quiz-hint { font-size: 13px; color: #9ca3af; margin: 0; }

/* 中文提示：**永远只有一行** —— 下一句要背的英文的中文翻译 */
.ed-zh {
  flex: 0 0 auto;
  padding: 12px 16px;
  background: #eef2ff;
  border-top: 1px solid #c7d2fe;
  border-bottom: 1px solid #c7d2fe;
  min-height: 54px;
  display: flex;
  align-items: center;
  gap: 8px;
}
.ed-zh-label {
  flex: 0 0 auto;
  font-size: 11px;
  color: #4338ca;
  border: 1px solid #a5b4fc;
  border-radius: 4px;
  padding: 1px 5px;
}
.ed-zh-text { font-size: 16px; line-height: 1.6; color: #1e1b4b; }
.ed-zh-done { font-size: 14px; color: #4338ca; }

.ed-actions {
  flex: 0 0 auto;
  display: flex;
  gap: 8px;
  padding: 8px 12px;
}
.ed-actions .btn-mini { flex: 1; }
.ed-actions .btn-mini.is-on { background: #4338ca; color: #fff; border-color: #4338ca; }
.ed-tip {
  flex: 0 0 auto;
  margin: 0;
  padding: 0 12px 12px;
  font-size: 11px;
  color: #9ca3af;
  line-height: 1.5;
}

/* 列表里的类型标记统一走 .mat-badge（见上文「材料类型标记」）——
   原来的 .rd-item-kind（10px、只有两种颜色）已撤掉：
   字号小于 12px 手机上看不清，而且它区分不了 朗读 / 其他 / 未拆分。 */

/* ---------- 材料列表：分类筛选 + 搜索标题（用户要求） ----------
   「句子 / 作文 / 朗读 / 其他」按**实际内容**分类（判据见 recite-drill.js 的
   materialType —— 不能按 kind，线上 99 份 kind=text 里大多是句子/作文素材）。
   这一条**粘在列表顶部**：材料多的时候滚到下面还能换分类、改搜索词。 */
.rd-filter {
  position: sticky;
  top: 0;
  z-index: 3;
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 0 0 10px;
  padding: 8px 0 10px;
  background: var(--bg, #fff);
  border-bottom: 1px solid var(--line);
}
.rd-filter[hidden] { display: none !important; }
.rd-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.rd-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 5px 10px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--bg);
  color: var(--ink);
  font: inherit;
  font-size: .82rem;
  line-height: 1.2;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.rd-chip:active { transform: scale(.97); }
/* 选中的那个要一眼看出来（用户要求"选中的高亮"） */
.rd-chip.is-on {
  background: var(--brand, #2f6fed);
  border-color: var(--brand, #2f6fed);
  color: #fff;
  font-weight: 600;
}
/* 每类后面的数量：小一号、弱一点，但**必须是真实条数** */
.rd-chip em { font-style: normal; opacity: .72; font-size: .9em; }
.rd-search { position: relative; display: flex; align-items: center; }
.rd-search-in {
  flex: 1;
  min-width: 0;
  padding: 8px 32px 8px 11px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--bg);
  color: var(--ink);
  font: inherit;
  font-size: .9rem;
  line-height: 1.3;
  -webkit-appearance: none;
  appearance: none;
}
.rd-search-in:focus { outline: 2px solid var(--brand, #2f6fed); outline-offset: -1px; }
.rd-search-x {
  position: absolute;
  right: 6px;
  width: 24px;
  height: 24px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--bg-soft, #eef1f5);
  color: var(--muted);
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.rd-search-x[hidden] { display: none !important; }
/* ── 背作文：当前句红色 / 逐词高亮 / 句子下方的语法分析 ──
   用户要求：正在读的那句用红色、单词跟着朗读进度显示、读完红色变黑，
   句子下方出现主谓宾等语法分析。 */
.ed-sent { margin: 0 0 14px; }
.ed-sent .ed-en {
  font-size: 17px;
  line-height: 1.85;
  margin: 0;
  color: #111827;
  font-family: Georgia, "Times New Roman", serif;
}
/* 正在读的那一句：整句红色 */
.ed-sent.is-now .ed-en { color: #dc2626; font-weight: 600; }
/* 每个词一个格子，才谈得上"跟着朗读逐个亮" */
.ed-w {
  font-style: normal;
  padding: 0 1px;
  border-radius: 3px;
  transition: background .12s linear, color .12s linear;
}
/* 已经念到的词：底色点亮 */
.ed-sent .ed-w.is-spoken { background: #fef08a; }
/* 正在读的时候，念过的词在红句里保持红字（底色区分就够了） */
.ed-sent.is-now .ed-w.is-spoken { color: #b91c1c; }

/* 句子下方的语法分析 */
.ed-ana {
  margin: 6px 0 0;
  padding: 6px 8px;
  background: #f3f4f6;
  border-left: 3px solid #9ca3af;
  border-radius: 0 4px 4px 0;
}
.ed-gram { margin: 0 0 4px; font-size: 12px; line-height: 1.6; color: #4b5563; }
.ed-posline { margin: 0; display: flex; flex-wrap: wrap; gap: 4px 8px; }
.ed-pos { font-size: 11px; line-height: 1.5; white-space: nowrap; }
.ed-pos b { color: #111827; font-family: Georgia, serif; }
.ed-pos em { font-style: normal; color: #7c3aed; margin-left: 3px; }
.ed-pos u { text-decoration: none; color: #b45309; margin-left: 3px; }
.ed-pos u::before { content: "·"; color: #d1d5db; margin-right: 3px; }
/* ── 背作文：句型栏（用户要求"每句下面单独一栏，标出可套用的写作模板"）── */
.ed-pattern {
  margin: 0 0 6px;
  font-size: 12.5px;
  line-height: 1.6;
  color: #92400e;
  background: #fffbeb;
  border-left: 3px solid #f59e0b;
  border-radius: 0 4px 4px 0;
  padding: 5px 8px;
}
.ed-pat-tag {
  display: inline-block;
  font-size: 10px;
  line-height: 1;
  padding: 2px 5px;
  margin-right: 6px;
  border-radius: 3px;
  background: #f59e0b;
  color: #fff;
  vertical-align: 1px;
}
.ed-pattern b { color: #7c2d12; }
.ed-pat-use {
  display: block;
  margin-top: 3px;
  font-size: 12px;
  color: #6b7280;
  font-family: Georgia, "Times New Roman", serif;
}

/* ══════════════ 朗读（英语）模式 ══════════════
   用户 2026-10-17 第三轮需求（挖空回忆）：
     阶段 A（进入一句）：整句英文全显示 + 中文翻译 + 自动朗读整句 + 当前词跟着**真实朗读进度**高亮
     阶段 B（点一下进入）：整句仍然显示，但**被标记的词英文藏起来、只显示中文**；
                          每点一下出现**下一个标记词**的英文 + 只读这个词；
                          没被标记的词一直显示英文、不发音、不用点
     阶段 C（标记词全出完）：朗读整句（第 2 遍）→ 再点一下下一句
   样式和「背作文」那套分开（edr-*），互相不干扰。 */
/* 一句英文：**整句所有词一开始就都在**（不是逐个出现），词间距留大一点方便高亮 */
.edr-sent {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 8px 10px;
  padding: 8px 2px 2px;
}
.edr-word {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  line-height: 1.2;
  padding: 1px 3px;
  border-radius: 5px;
  transition: background .1s linear, color .1s linear;
}
.edr-word .edr-w {
  font-size: 22px;
  font-weight: 400;
  color: #111827;
  font-family: Georgia, "Times New Roman", serif;
  transition: color .1s linear;
}
/* 实词：音标 + 中文小字（"词下方小字"——需求原文） */
.edr-word.is-content .edr-w { color: #1e1b4b; font-weight: 600; }
.edr-word .edr-p {
  font-style: normal;
  font-size: 11px;
  color: #9ca3af;
  margin-top: 1px;
}
.edr-word .edr-zh {
  text-decoration: none;
  font-size: 12px;
  color: #b45309;
  margin-top: 1px;
}
/* ── 挖空回忆（阶段 B）：标记词英文藏起来，只显示中文提示 ──
   视觉：一块"浅黄底 + 橙色虚线框的空位" + 下方中文（如「兔子」）——
   一眼能看出"这里有个要回忆的词，中文在这儿、英文空着"。 */
.edr-word.is-blank {
  background: #fffbeb;
  border: 1px dashed #f59e0b;
  border-radius: 5px;
  padding: 2px 6px;
}
/* 空位本身：只有一条下划线占位（英文一个字母都不出现在 DOM 里） */
.edr-blank {
  display: inline-block;
  height: 1.05em;
  border-bottom: 2px solid #d97706;
  border-radius: 1px;
  vertical-align: middle;
}
.edr-word.is-blank .edr-zh { color: #b45309; }
/* ── 构词分解（用户 2026-10-18 第四轮；6+ 字母标记词，点出英文后才显示）──
   一行「音标 ｜ 拼写音节」+ 彩色小块（前缀/词根/后缀/复合词各一色）+ 文字说明。
   ⚠️ 颜色**全用现有 CSS 变量**（不硬编码十六进制）—— 深色主题（html[data-theme="dark"]）
      换的是同一批变量，所以小块在两种主题下都看得见。
   ⚠️ 颜色不超过 4 种，且只染小块（不把整段文字染色）。 */
.edr-word .edr-form {
  font-style: normal;
  font-size: 11px;
  line-height: 1.5;
  color: var(--muted);
  margin-top: 1px;
  white-space: nowrap;
}
.edr-parts {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 3px;
  margin-top: 3px;
}
.edr-part {
  display: inline-block;
  font-size: 11px;
  line-height: 1.5;
  padding: 1px 6px;
  border-radius: 4px;
  border: 1px solid var(--line);
  border-bottom-width: 2px;
  background: var(--bg-active);
  color: var(--ink-soft);
}
/* 4 种块各一色 —— 前缀 / 词根 / 后缀 / 复合词 */
.edr-part.is-prefix { color: var(--brand); border-bottom-color: var(--brand); }
.edr-part.is-root { color: var(--danger); border-bottom-color: var(--danger); }
.edr-part.is-suffix { color: var(--brand-strong); border-bottom-color: var(--brand-strong); }
.edr-part.is-word { color: var(--ink-soft); border-bottom-color: var(--ink-soft); }
.edr-word .edr-build {
  text-decoration: none;
  font-size: 11px;
  line-height: 1.5;
  color: var(--ink-soft);
  margin-top: 3px;
  text-align: center;
  max-width: 14em;
}
/* ── 当前词高亮（阶段 A 的卡拉OK / 阶段 B 刚出现的标记词，同一套视觉）── */
.edr-word.is-cur {
  background: #fde68a;
  box-shadow: 0 0 0 2px #fde68a;
}
.edr-word.is-cur .edr-w { color: #b91c1c; }
/* 阶段 A 卡拉OK：这次朗读已经念过的词灰掉（朗读结束后整句回到正常色） */
.edr-word.is-spoken .edr-w { color: #9ca3af; }
/* 整句出完后的分析块（句型 / 语法 / 成分） */
.edr-ana {
  flex: 1 1 100%;
  margin: 14px 0 0;
  padding: 6px 8px;
  background: #f3f4f6;
  border-left: 3px solid #9ca3af;
  border-radius: 0 4px 4px 0;
}
.edr-pattern { margin: 0 0 4px; font-size: 12.5px; line-height: 1.6; color: #92400e; }
.edr-tag {
  display: inline-block;
  font-size: 10px;
  line-height: 1;
  padding: 2px 5px;
  margin-right: 6px;
  border-radius: 3px;
  background: #f59e0b;
  color: #fff;
  vertical-align: 1px;
}
.edr-gram { margin: 0 0 4px; font-size: 12px; line-height: 1.6; color: #4b5563; }
.edr-pos { margin: 0; display: flex; flex-wrap: wrap; gap: 4px 8px; }
.edr-zh-label {
  display: inline-block;
  font-size: 11px;
  color: #6b7280;
  background: #eef2ff;
  border-radius: 3px;
  padding: 1px 6px;
  margin-right: 6px;
}
/* 中文翻译那一栏用大一点的字（它和英文**同时**出现，是这一句的译文） */
.ed-zh .edr-zh-text { font-size: 17px; line-height: 1.6; color: #1e1b4b; }
/* ── 照片编辑（拍照后裁剪 / 旋转）──
   缩略图上三个按钮：删 ✕ / 转 ↻ / 裁 ✂ */
.scan-thumb-tools {
  position: absolute;
  left: 4px;
  bottom: 4px;
  display: flex;
  gap: 4px;
}
.scan-thumb-t {
  width: 26px;
  height: 26px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: rgba(17, 24, 39, .72);
  color: #fff;
  font-size: 14px;
  line-height: 26px;
  text-align: center;
  cursor: pointer;
}
.scan-thumb-t:active { background: rgba(17, 24, 39, .92); }

/* 裁剪界面：整屏浮层，上面一条操作栏，下面图片 + 可拖动的框 */
.pe-crop {
  position: fixed;
  inset: 0;
  z-index: 90;               /* 高于所有面板（.modal 是 70），裁剪必须是当前唯一焦点 */
  background: #0b0f19;
  display: flex;
  flex-direction: column;
}
.pe-crop[hidden] { display: none !important; }
.pe-crop-bar {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 12px;
  padding-top: calc(10px + env(safe-area-inset-top));
  background: #111827;
}
.pe-crop-tip { flex: 1; font-size: 12px; color: #9ca3af; text-align: center; }
.pe-crop-stage {
  flex: 1 1 auto;
  position: relative;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  touch-action: none;        /* 拖框时不要跟着页面滚 */
}
.pe-crop-img { max-width: 100%; max-height: 100%; display: block; }
.pe-crop-box {
  position: absolute;
  border: 2px solid #22d3ee;
  box-shadow: 0 0 0 9999px rgba(0, 0, 0, .55);   /* 框外压暗，一眼看出保留哪块 */
  box-sizing: border-box;
  cursor: move;
}
.pe-h {
  position: absolute;
  width: 30px;
  height: 30px;
  background: transparent;
}
.pe-h[data-h="nw"] { left: -15px; top: -15px; cursor: nwse-resize; }
.pe-h[data-h="ne"] { right: -15px; top: -15px; cursor: nesw-resize; }
.pe-h[data-h="sw"] { left: -15px; bottom: -15px; cursor: nesw-resize; }
.pe-h[data-h="se"] { right: -15px; bottom: -15px; cursor: nwse-resize; }
.pe-h::after {
  content: "";
  position: absolute;
  left: 50%; top: 50%;
  width: 12px; height: 12px;
  margin: -6px 0 0 -6px;
  border: 2px solid #22d3ee;
  border-radius: 50%;
  background: #0b0f19;
}
/* 裁剪框的"外框"：尺寸 = 图片的实际显示尺寸，裁剪框坐标就是相对它的。
   ⚠️ 必须让它**贴合图片**（宽度自适应、不拉伸），否则竖图左右留白会把框算偏。 */
.pe-crop-frame {
  position: relative;
  line-height: 0;            /* 去掉 img 下面的基线空隙 */
  /* ⚠️ 这里**刻意不写 max-width/max-height** —— 尺寸由 JS 显式设成图片的显示尺寸。
     写成 max-* 的话 frame 会撑满整屏，图片在它里面又居中一次，裁剪框就错位了
     （真浏览器测试量出来 frame 等于整个 stage，一眼看穿）。 */
  flex: 0 0 auto;
}
.pe-crop-frame .pe-crop-img {
  display: block;
  width: 100%;
  height: 100%;
  /* frame 已经就是图片的显示尺寸了，fill 和 contain 等价；
     用 fill 避免再出现一层亚像素留白 */
  object-fit: fill;
}
/* ── 批量抄录的进度条 ──
   （由 JS 现建，写在 HTML 里会被微信缓存住） */
.rc-bar { margin: 8px 0 2px; }
.rc-bar[hidden] { display: none !important; }
.rc-bar-track {
  height: 8px;
  border-radius: 4px;
  background: var(--line, #e5e7eb);
  overflow: hidden;
}
.rc-bar-fill {
  display: block;
  height: 100%;
  width: 0;
  border-radius: 4px;
  background: linear-gradient(90deg, #4d6bfe, #7c3aed);
  transition: width .3s ease;
}
.rc-bar-text {
  margin-top: 4px;
  font-size: 12px;
  color: var(--muted, #6b7280);
  line-height: 1.5;
}

/* ── 批量拆句的工具条（列表上方）──
   同样由 JS 现建：写在 index.html 里会被微信缓存住，学生刷新前看不到新按钮。
   注意 [hidden] 的兜底：这里用了 display:flex，所以下面必须有一条 !important 的
   [hidden] 规则，否则 `hidden = true` 会被这条 flex 规则悄悄废掉
   （chat.css 顶部虽然有一条全局 [hidden]，但同优先级下后写的赢 —— 显式再写一条最稳）。 */
.rc-batch {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin: 4px 11px 8px;
}
.rc-batch[hidden] { display: none !important; }
/* ★ 下拉 + 「⚡ 批量拆」必须**同一排**（用户要求）。
   select 用 flex-basis:0 + width:0 才能真的缩下去；否则它的内容宽度
   会把按钮顶到第二行（原来就是这样：select 一行、按钮一行）。 */
.rc-batch select {
  flex: 1 1 0;
  width: 0;
  min-width: 0;
  height: 44px;
  box-sizing: border-box;
  font: inherit;
  font-size: 13px;
  padding: 0 8px;
  border-radius: 10px;
  border: 1px solid var(--line, #e5e7eb);
  background: var(--bg, #fff);
  color: var(--ink, #111827);
  max-width: 100%;
}
#rc-batch-go {
  flex: 0 0 auto;
  min-height: 44px;
  padding: 0 12px;
  font-size: .8rem;
  white-space: nowrap;
}
/* 灰色提示只在真的有话要说时才占一行；正常状态是空的（按钮上已有条数）。 */
.rc-batch-info {
  flex: 1 1 100%;
  font-size: 11px;
  color: var(--muted, #6b7280);
  line-height: 1.5;
}
.rc-batch-info:empty { display: none; }

/* ── 侧边栏「朗读设置」折叠区 ──
   用户原话「不要长期展开，占用太多页面了」：
   讲解语速 / 英语听力语速 / 音色 + 对照校准，设一次就不动了，
   常驻展开会把小屏侧边栏撑长，把账号信息和入口挤出屏幕。
   默认折叠，点标题展开。 */
.sidebar-settings {
  margin: 2px 0 8px;
  border-radius: 8px;
  background: var(--bg-soft, #f7f8fa);
}
.sidebar-settings > summary {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 9px 10px;
  font-size: .84rem;
  font-weight: 600;
  color: var(--ink-soft, #4b5563);
  cursor: pointer;
  list-style: none;          /* 去掉默认三角，自己用 ::after 画 */
  border-radius: 8px;
}
.sidebar-settings > summary::-webkit-details-marker { display: none; }
.sidebar-settings > summary::after {
  content: "▸";
  margin-left: auto;
  font-size: .8rem;
  color: var(--muted, #9ca3af);
  transition: transform .15s ease;
}
.sidebar-settings[open] > summary::after { transform: rotate(90deg); }
.sidebar-settings[open] > summary { border-bottom: 1px solid var(--line, #e5e7eb); border-radius: 8px 8px 0 0; }
/* 折叠起来时把内边距收紧，展开后给内容让位 */
.sidebar-settings > .field,
.sidebar-settings > .btn-mini { margin-left: 10px; margin-right: 10px; }
.sidebar-settings > .field:first-of-type { margin-top: 8px; }
.sidebar-settings > .btn-mini { margin-bottom: 10px; }
/* 标题右边显示当前档位（折叠时也能一眼看到设定，不用展开） */
.sidebar-settings-hint {
  margin-left: 6px;
  font-size: .75rem;
  font-weight: 400;
  color: var(--muted, #9ca3af);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ==========================================================================
   学习积分（2026-10-19）
   · 顶栏一个「⭐ 1234」小胶囊：只占一个按钮宽度，手机上不新增一行。
   · 点开是浮层明细：总计/今日/本周/排名 + 最近 10 条 + 排行榜。
   · 颜色全用现有变量（--brand / --line / --bg / --ink / --muted），深色主题自动适配。
   ========================================================================== */

.points-badge {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  height: 28px;
  padding: 0 9px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--bg-hover);
  color: var(--ink);
  font: inherit;
  font-size: .8rem;
  font-weight: 600;
  line-height: 1;
  white-space: nowrap;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.points-badge[hidden] { display: none !important; }
.points-badge:hover { border-color: var(--brand); color: var(--brand); }
.points-badge:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }
.points-badge-star { font-size: .82rem; line-height: 1; }
.points-badge-num { font-variant-numeric: tabular-nums; }
.points-badge-unit { font-size: .7rem; font-weight: 500; color: var(--muted); }

.points-panel {
  position: fixed;
  /* 顶栏在板块 tab 下面，底边大约 100px；弹层从这里往下展开，
     不盖住徽章本身（点开以后还看得见它）。 */
  top: 104px;
  right: 12px;
  z-index: 80;
  width: 320px;
  max-width: calc(100vw - 16px);
  max-height: min(72vh, 560px);
  overflow-y: auto;
  padding: 14px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--bg);
  color: var(--ink);
  box-shadow: 0 14px 36px rgba(15, 23, 42, .18);
}
.points-panel[hidden] { display: none !important; }

.points-head { display: flex; align-items: center; gap: 8px; }
.points-title { flex: 1 1 auto; min-width: 0; font-size: .95rem; }
.points-close {
  flex: 0 0 auto;
  width: 26px; height: 26px;
  border: 1px solid transparent;
  border-radius: 8px;
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: .9rem;
  line-height: 1;
  cursor: pointer;
}
.points-close:hover { background: var(--bg-hover); color: var(--ink); }

.points-total {
  display: flex;
  align-items: baseline;
  gap: 5px;
  margin: 10px 0 8px;
}
.points-total-star { font-size: 1.1rem; }
.points-total-num {
  font-size: 1.55rem;
  font-weight: 700;
  line-height: 1;
  color: var(--brand);
  font-variant-numeric: tabular-nums;
}
.points-total-unit { font-size: .78rem; color: var(--muted); }

.points-stats {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 6px;
}
.points-stat {
  padding: 7px 6px;
  border-radius: 10px;
  background: var(--bg-hover);
  text-align: center;
  min-width: 0;
}
.points-stat-num {
  display: block;
  font-size: .95rem;
  font-weight: 700;
  line-height: 1.25;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.points-stat-label { display: block; margin-top: 1px; font-size: .7rem; color: var(--muted); }

.points-cap {
  margin-top: 6px;
  font-size: .72rem;
  color: var(--muted);
  text-align: right;
}

.points-recent-title {
  margin: 12px 0 2px;
  font-size: .8rem;
  font-weight: 600;
  color: var(--ink-soft, #46566b);
}
.points-recent { margin: 0; padding: 0; list-style: none; }
.points-item {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 6px 0;
  border-top: 1px solid var(--line);
  font-size: .78rem;
  min-width: 0;
}
.points-item:first-child { border-top: 0; }
/* flex 省略号三件套：flex:1 1 0 + min-width:0 + overflow 三连。
   少了 min-width:0，长模块名会把右边的 +1 / 时间顶出弹层。 */
.points-item-main {
  flex: 1 1 0;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--ink-soft, #46566b);
}
.points-item-time {
  flex: 0 0 auto;
  font-size: .68rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.points-item-delta {
  flex: 0 0 auto;
  min-width: 24px;
  text-align: right;
  font-weight: 700;
  color: var(--brand);
  font-variant-numeric: tabular-nums;
}
.points-empty, .points-tip {
  padding: 10px 0;
  font-size: .78rem;
  color: var(--muted);
}

.points-rank-btn {
  width: 100%;
  margin-top: 10px;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: 9px;
  background: var(--bg);
  color: var(--ink-soft, #46566b);
  font: inherit;
  font-size: .8rem;
  cursor: pointer;
}
.points-rank-btn:hover { background: var(--bg-hover); color: var(--ink); }

.points-rank { margin-top: 6px; }
.points-rank[hidden] { display: none !important; }
.points-rank-row {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 6px 5px;
  border-top: 1px solid var(--line);
  font-size: .78rem;
  border-radius: 7px;
  min-width: 0;
}
.points-rank-row.is-me { background: rgba(77, 107, 254, .1); border-top-color: transparent; }
.points-rank-no {
  flex: 0 0 auto;
  width: 30px;
  text-align: right;
  color: var(--muted);
  font-size: .72rem;
  font-variant-numeric: tabular-nums;
}
.points-rank-name {
  flex: 1 1 0;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.points-rank-total {
  flex: 0 0 auto;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.points-rank-me {
  margin-top: 6px;
  padding-top: 7px;
  border-top: 1px dashed var(--line);
  font-size: .75rem;
  color: var(--ink-soft, #46566b);
}

/* 手机：徽章再小一点；弹层左右几乎占满，但不改 topbar 的其它布局 */
@media (max-width: 640px) {
  .points-badge {
    height: 26px;
    padding: 0 7px;
    font-size: .76rem;
    gap: 3px;
  }
  .points-panel {
    top: 100px;
    left: 8px;
    right: 8px;
    width: auto;
    max-width: none;
    max-height: 76vh;
    padding: 12px;
  }
}
