:root {
  --primary: #2f6bff;
  --primary-dark: #1e4fd8;
  --primary-soft: #eaf0ff;
  --success: #12a150;
  --success-soft: #e7f7ee;
  --danger: #e5484d;
  --danger-soft: #fdecec;
  --warn: #f5a524;
  --warn-soft: #fff5e3;
  --ink: #10203a;
  --ink-2: #43536b;
  --ink-3: #8593a8;
  --line: #e6ebf3;
  --bg: #f2f5fa;
  --card: #ffffff;
  --radius: 16px;
  --radius-sm: 10px;
  --shadow: 0 1px 2px rgba(16, 32, 58, 0.05), 0 6px 18px rgba(16, 32, 58, 0.06);
  --nav-h: 62px;
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei",
    "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: 15px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

body { padding-bottom: calc(var(--nav-h) + env(safe-area-inset-bottom, 0px)); }

.app { max-width: 720px; margin: 0 auto; }

/* ---------- 顶部 ---------- */
.topbar {
  position: sticky; top: 0; z-index: 20;
  background: linear-gradient(135deg, #2f6bff 0%, #4d8bff 55%, #6aa6ff 100%);
  color: #fff;
  padding: 18px 16px 16px;
  border-radius: 0 0 20px 20px;
  box-shadow: 0 6px 20px rgba(47, 107, 255, 0.22);
}
.topbar h1 { margin: 0; font-size: 18px; font-weight: 700; letter-spacing: .3px; }
.topbar p { margin: 4px 0 0; font-size: 12.5px; opacity: .9; }
.topbar .badge-mock {
  display: inline-block; margin-top: 8px; font-size: 11.5px;
  background: rgba(255, 255, 255, .2); border: 1px solid rgba(255, 255, 255, .35);
  padding: 2px 9px; border-radius: 999px;
}

/* ---------- 页面容器 ---------- */
.page { padding: 14px 14px 24px; display: none; animation: fade .22s ease; }
.page.active { display: block; }
@keyframes fade { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }

/* ---------- 卡片 ---------- */
.card {
  background: var(--card); border-radius: var(--radius); box-shadow: var(--shadow);
  padding: 14px; margin-bottom: 12px;
}
.card.tight { padding: 12px; }
.card-title {
  font-size: 15px; font-weight: 700; margin: 0 0 10px;
  display: flex; align-items: center; gap: 7px;
}
.card-title .dot { width: 4px; height: 15px; border-radius: 3px; background: var(--primary); }
.muted { color: var(--ink-3); font-size: 12.5px; }
.center { text-align: center; }
.row { display: flex; gap: 10px; align-items: center; }
.row.wrap { flex-wrap: wrap; }
.grow { flex: 1; min-width: 0; }
.hidden { display: none !important; }

/* ---------- 表单 ---------- */
label.field { display: block; margin-bottom: 10px; }
label.field > span { display: block; font-size: 12.5px; color: var(--ink-2); margin-bottom: 5px; font-weight: 600; }
select, input[type="text"], input[type="password"], textarea {
  width: 100%; padding: 10px 12px; border: 1.5px solid var(--line); border-radius: var(--radius-sm);
  font-size: 15px; background: #fbfcfe; color: var(--ink); font-family: inherit;
  transition: border-color .15s, box-shadow .15s;
}
select:focus, input:focus, textarea:focus {
  outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px var(--primary-soft); background: #fff;
}
textarea { resize: vertical; min-height: 64px; }

/* ---------- 按钮 ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  border: none; border-radius: var(--radius-sm); padding: 11px 16px;
  font-size: 15px; font-weight: 600; font-family: inherit;
  background: var(--primary); color: #fff; cursor: pointer;
  transition: transform .1s, filter .15s;
  white-space: nowrap;
}
.btn:active { transform: scale(.975); }
.btn:disabled { opacity: .5; cursor: not-allowed; }
.btn.block { display: flex; width: 100%; }
.btn.ghost { background: var(--primary-soft); color: var(--primary-dark); }
.btn.gray { background: #eef1f6; color: var(--ink-2); }
.btn.danger { background: var(--danger); }
/*
 * 描边红 =「可以删，但不鼓励」。
 *
 * 和 `.btn.danger`（红底实心）是两种语气：实心红在一行按钮里最抢眼，
 * 摆在「生成练习卷」旁边会让用户第一眼看到的是删除。批量删除那颗按钮要的正好相反 ——
 * 找得到，但绝不能被当成主操作（误点的代价不可逆）。
 *
 * 边框用 `box-shadow: inset` 而不是 `border`：border 会把这颗按钮撑高 3px，
 * 和同一行里的「清空」对不齐（操作条是 flex + align-items:center，
 * 差一点点就是肉眼能看出来的那种歪）。box-shadow 不占布局。
 */
.btn.danger-outline {
  background: #fff; color: var(--danger);
  box-shadow: inset 0 0 0 1.5px #f2b9ba;
}
.btn.danger-outline:active { background: var(--danger-soft); }
.btn.sm { padding: 7px 12px; font-size: 13px; border-radius: 9px; }
.btn.lg { padding: 14px 18px; font-size: 16px; }

/* ---------- 上传区 ---------- */
.dropzone {
  border: 2px dashed #c3d2ea; border-radius: var(--radius); background: #f8faff;
  padding: 26px 16px; text-align: center; cursor: pointer; transition: all .18s;
}
.dropzone:active { background: var(--primary-soft); border-color: var(--primary); }
.dropzone .icon { font-size: 34px; line-height: 1; }
.dropzone .t1 { font-weight: 700; margin-top: 8px; font-size: 15px; }
.dropzone .t2 { font-size: 12.5px; color: var(--ink-3); margin-top: 3px; }

.preview-wrap { position: relative; border-radius: var(--radius); overflow: hidden; background: #000; }
.preview-wrap img { display: block; width: 100%; }
.preview-wrap .clear {
  position: absolute; top: 8px; right: 8px; background: rgba(0, 0, 0, .55); color: #fff;
  border: none; border-radius: 999px; width: 30px; height: 30px; font-size: 16px; cursor: pointer;
}

/* ---------- 统计条 ---------- */
.stat-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.stat {
  background: var(--card); border-radius: var(--radius); padding: 12px 8px; text-align: center;
  box-shadow: var(--shadow);
}
.stat .num { font-size: 22px; font-weight: 800; line-height: 1.2; }
.stat .lbl { font-size: 12px; color: var(--ink-3); margin-top: 2px; }
.stat.good .num { color: var(--success); }
.stat.bad .num { color: var(--danger); }
.stat.info .num { color: var(--primary); }

/* ---------- 徽章 ---------- */
.tag {
  display: inline-block; font-size: 11.5px; padding: 2px 8px; border-radius: 999px;
  background: var(--primary-soft); color: var(--primary-dark); font-weight: 600; margin: 2px 4px 2px 0;
}
.tag.green { background: var(--success-soft); color: var(--success); }
.tag.red { background: var(--danger-soft); color: var(--danger); }
.tag.amber { background: var(--warn-soft); color: #b5730a; }
.tag.gray { background: #eef1f6; color: var(--ink-2); }

/* ---------- 题目卡片 ---------- */
.q-card { border: 1.5px solid var(--line); border-radius: var(--radius); padding: 13px; margin-bottom: 11px; background: #fff; }
.q-card.wrong { border-color: #f7c9ca; background: #fffbfb; }
.q-head { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.q-no {
  width: 26px; height: 26px; border-radius: 8px; display: grid; place-items: center;
  font-size: 12.5px; font-weight: 800; background: var(--primary-soft); color: var(--primary-dark); flex: none;
}
.q-no.wrong { background: var(--danger-soft); color: var(--danger); }
.q-no.right { background: var(--success-soft); color: var(--success); }
.q-stem { font-size: 14.5px; line-height: 1.65; margin-bottom: 9px; white-space: pre-wrap; word-break: break-word; }
.answer-line { font-size: 13.5px; margin: 3px 0; }
.answer-line b { color: var(--ink-2); font-weight: 600; }
.answer-line .right-ans { color: var(--success); font-weight: 600; }
/* 学生作答：不用红色删除线 —— 那看起来像「写的全错了」，
   但很多时候只是漏了一问，或者判分本身有争议。
   对错由旁边的判定标签说明，这里保持中性可读。 */
.answer-line .wrong-ans { color: var(--ink-2); }

/*
 * 「点这块看讲解」（错题本卡片）：题干 + 学生作答 + 正确答案 + 知识点整块可点。
 *
 * 为什么不按按钮的样子做（底色块 + 边框）：这一块是**要读的正文** ——
 * 一上按钮底色，整张卡就变成一大块色块，文字反而更难读，喧宾夺主。
 * 但纯文本又完全看不出能点，所以只留最轻的两处暗示：
 *   ① 按下时渗出一点点蓝（:active 的淡底），
 *   ② 右下角一行「看讲解」+ 箭头图标。
 *
 * 箭头用 SVG 而不是「›」字符：那个码位在部分安卓字体里会渲染成方块，
 * 前文所有图标都已经是 SVG，多一个字符型箭头反而是例外。
 *
 * 负的左右外边距 + 等量内边距：让那层按压底色比文字横向宽出 6px、不贴边，
 * 而两者相加为零，所以卡片原本的排版一点没动。
 *
 * 带上 `tabindex` 是必须的：iOS Safari 只给「可聚焦的元素」触发 :active，
 * 没有它这层反馈在手机上根本不出现。
 */
.q-tap { margin: 0 -6px; padding: 0 6px; border-radius: 10px; cursor: pointer; transition: background .15s; }
.q-tap:active { background: rgba(47, 107, 255, .07); }
.q-tap:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.q-tap-hint {
  display: flex; align-items: center; justify-content: flex-end; gap: 2px;
  margin-top: 6px; font-size: 12px; color: var(--ink-3);
}
.q-tap:active .q-tap-hint { color: var(--primary); }

/*
 * 组卷模式下整卡点击都归勾选，这一块不再是讲解入口 ——
 * 「看讲解」留着就是骗人（点下去其实是勾选），所以藏掉。
 * （光标不用改：body.picking 里卡片本来就是 pointer。）
 */
body.picking .q-tap-hint { display: none; }

/*
 * 错题卡右上角的删除键。
 *
 * 为什么画得这么「轻」：它和「举一反三 / 已掌握」两个大按钮在同一张卡上，
 * 却是**低频且不可逆**的动作。做成一枚实心红按钮，视觉重量会压过主操作，
 * 用户顺手点到的概率反而变高。所以只留一个淡灰的 ✕，
 * 手指按下去才变红（:active）—— 颜色是「按下确认」的反馈，不是装饰。
 *
 * 命中区域用 padding 撑到 30px 见方：✕ 本身只有 18px，
 * 手机上那个小十字按不准，而按不准的删除键比没有删除键更糟。
 *
 * 组卷勾选模式下卡片里所有 .btn 都被断掉点击（见上面 body.picking 那条），
 * 这个键不是 .btn，所以在这里显式跟上 —— 选择模式下「只勾选」这条规矩
 * 不能有一个例外，否则用户点一下 ✕ 会同时弹出系统确认框和勾中这张卡。
 */
.q-del {
  flex: none;
  width: 30px; height: 30px; padding: 0;
  display: grid; place-items: center;
  border: none; border-radius: 9px;
  background: transparent;
  color: var(--ink-3, #97a3b6);
  font-size: 18px; line-height: 1;
  cursor: pointer;
  transition: background .15s, color .15s;
}
.q-del:hover { background: #f2f4f8; color: var(--ink-2, #5b6b82); }
.q-del:active { background: var(--danger-soft); color: var(--danger); }
body.picking .q-del { pointer-events: none; opacity: .35; }

/* ---------- 提示条 ---------- */
.alert {
  border-radius: var(--radius); padding: 12px 14px; margin-bottom: 12px; font-size: 13.5px;
  display: flex; gap: 10px; align-items: flex-start; line-height: 1.6;
}
.alert .ico { font-size: 18px; flex: none; line-height: 1.3; }
.alert.warn { background: var(--warn-soft); border: 1.5px solid #fbdcae; color: #8a5a06; }
.alert.danger { background: var(--danger-soft); border: 1.5px solid #f7c9ca; color: #a92a2e; }
.alert.info { background: var(--primary-soft); border: 1.5px solid #cfdeff; color: var(--primary-dark); }
.alert.success { background: var(--success-soft); border: 1.5px solid #bfe8d1; color: #0b7c3c; }
.alert b { font-weight: 800; }

/* ---------- 讲解区 ---------- */
.explain { background: #f8faff; border-radius: var(--radius-sm); padding: 12px; margin-top: 10px; font-size: 13.8px; }
.explain h4 {
  margin: 12px 0 5px; font-size: 13px; color: var(--primary-dark);
  display: flex; align-items: center; gap: 5px;
}
.explain h4:first-child { margin-top: 0; }
.explain ol, .explain ul { margin: 4px 0; padding-left: 20px; }
.explain li { margin-bottom: 4px; }
.explain .encourage {
  margin-top: 10px; padding: 9px 11px; background: var(--success-soft); border-radius: 9px; color: #0b7c3c; font-size: 13px;
}

/* ---------- 加载 ---------- */
.loading { display: flex; flex-direction: column; align-items: center; gap: 10px; padding: 26px 10px; color: var(--ink-3); font-size: 13.5px; }
.spinner {
  width: 30px; height: 30px; border: 3px solid var(--line); border-top-color: var(--primary);
  border-radius: 50%; animation: spin .8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* ---------- 底部导航 ---------- */
.nav {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 30;
  background: rgba(255, 255, 255, .96); backdrop-filter: blur(12px);
  border-top: 1px solid var(--line);
  display: flex; padding-bottom: env(safe-area-inset-bottom, 0px);
}
.nav-inner { max-width: 720px; margin: 0 auto; width: 100%; display: flex; }
.nav button {
  flex: 1; border: none; background: none; padding: 8px 2px 7px; cursor: pointer;
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  font-size: 10.5px; color: var(--ink-3); font-family: inherit; height: var(--nav-h);
  justify-content: center; position: relative;
}
.nav button .ni { font-size: 19px; line-height: 1; }
.nav button.active { color: var(--primary); font-weight: 700; }
.nav button .dot-badge {
  position: absolute; top: 5px; right: 50%; margin-right: -20px;
  background: var(--danger); color: #fff; font-size: 10px; min-width: 16px; height: 16px;
  border-radius: 999px; display: grid; place-items: center; padding: 0 4px; font-weight: 700;
}

/* ---------- 列表项 ---------- */
.list-item {
  display: flex; gap: 11px; align-items: flex-start; padding: 12px;
  border: 1.5px solid var(--line); border-radius: var(--radius); margin-bottom: 10px; background: #fff;
  cursor: pointer; transition: border-color .15s;
}
.list-item:active { border-color: var(--primary); }
.list-item .li-body { flex: 1; min-width: 0; }
.list-item .li-title { font-weight: 700; font-size: 14.5px; margin-bottom: 3px; }
.list-item .li-sub { font-size: 12.5px; color: var(--ink-3); }

.kp-count {
  flex: none; text-align: center; min-width: 54px; padding: 6px 4px; border-radius: var(--radius-sm);
  background: #eef1f6;
}
.kp-count .n { font-size: 19px; font-weight: 800; line-height: 1.1; }
.kp-count .t { font-size: 10.5px; color: var(--ink-3); }
.kp-count.hot { background: var(--danger-soft); }
.kp-count.hot .n { color: var(--danger); }
.kp-count.warm { background: var(--warn-soft); }
.kp-count.warm .n { color: #b5730a; }

/* ---------- 进度条 ---------- */
.bar { height: 7px; background: #eef1f6; border-radius: 999px; overflow: hidden; margin-top: 6px; }
.bar > i { display: block; height: 100%; background: var(--primary); border-radius: 999px; transition: width .4s; }

/* ---------- 柱状图 ---------- */
.chart { display: flex; align-items: flex-end; gap: 10px; height: 130px; padding: 8px 0 0; }
.chart .col { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: flex-end; height: 100%; gap: 4px; }
.chart .col .v { font-size: 11.5px; font-weight: 700; color: var(--ink-2); }
.chart .col .b { width: 100%; max-width: 44px; background: linear-gradient(180deg, #6aa6ff, #2f6bff); border-radius: 7px 7px 3px 3px; min-height: 3px; }
.chart .col .b.wrong { background: linear-gradient(180deg, #ff8a8d, #e5484d); }
.chart .col .l { font-size: 11.5px; color: var(--ink-3); }

/* ---------- 弹层 ---------- */
.modal-mask {
  position: fixed; inset: 0; background: rgba(16, 32, 58, .45); z-index: 50;
  display: flex; align-items: flex-end; justify-content: center;
}
.modal {
  background: #fff; width: 100%; max-width: 720px; border-radius: 20px 20px 0 0;
  max-height: 88vh; overflow-y: auto; padding: 18px 16px calc(24px + env(safe-area-inset-bottom, 0px));
  animation: rise .25s ease;
}
@keyframes rise { from { transform: translateY(30px); opacity: .6; } to { transform: none; opacity: 1; } }
.modal-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 14px; }
.modal-head h3 { margin: 0; font-size: 16.5px; }
.modal-head .x { border: none; background: #eef1f6; width: 30px; height: 30px; border-radius: 999px; font-size: 16px; cursor: pointer; color: var(--ink-2); }

/* ---------- Toast ---------- */
.toast {
  position: fixed; left: 50%; bottom: calc(var(--nav-h) + 22px); transform: translateX(-50%);
  background: rgba(16, 32, 58, .92); color: #fff; padding: 10px 18px; border-radius: 999px;
  font-size: 13.5px; z-index: 99; max-width: 88vw; text-align: center;
  animation: fade .2s ease;
}

/* ---------- 空状态 ---------- */
.empty { text-align: center; padding: 46px 20px; color: var(--ink-3); }
.empty .ei { font-size: 42px; margin-bottom: 10px; }
.empty .et { font-size: 14px; }

/* ---------- 折叠 ---------- */
.collapse-head { display: flex; align-items: center; justify-content: space-between; cursor: pointer; }
.collapse-head .arrow { color: var(--ink-3); font-size: 12px; transition: transform .2s; }
.collapse.open .arrow { transform: rotate(90deg); }

/* ---------- 练习答题 ---------- */
.practice-item { border: 1.5px solid var(--line); border-radius: var(--radius); padding: 13px; margin-bottom: 11px; }
.practice-item .pi-head { display: flex; justify-content: space-between; gap: 8px; margin-bottom: 7px; align-items: center; }
.practice-item .pi-result { margin-top: 9px; font-size: 13.5px; padding: 9px 11px; border-radius: 9px; }
.practice-item .pi-result.right { background: var(--success-soft); color: #0b7c3c; }
.practice-item .pi-result.wrong { background: var(--danger-soft); color: #a92a2e; }

@media (max-width: 360px) {
  .stat .num { font-size: 19px; }
  .nav button { font-size: 10px; }
}


/* ================================================================== */
/* 组卷打印页                                                          */
/* ================================================================== */

/* ---- 知识点勾选行 ---- */
.kp-pick {
  display: flex; align-items: center; gap: 10px;
  padding: 11px 12px; border-radius: var(--radius-sm);
  border: 1.5px solid var(--line); background: #fff;
  margin-bottom: 8px; cursor: pointer; transition: .15s;
}
.kp-pick:active { transform: scale(.995); }
.kp-pick.on { border-color: var(--primary); background: var(--primary-soft); }
.kp-pick input { display: none; }
.kp-pick .kp-body { flex: 1; min-width: 0; }
.kp-pick .kp-name { display: block; font-size: 14.5px; font-weight: 600; color: var(--ink); }
.kp-pick .kp-bar {
  display: block; height: 5px; border-radius: 3px; background: #e9eef7;
  margin-top: 6px; overflow: hidden;
}
.kp-pick .kp-bar i { display: block; height: 100%; background: linear-gradient(90deg,#7aa2ff,#2f6bff); border-radius: 3px; }
.kp-pick .kp-count {
  font-size: 12.5px; color: var(--ink-3); white-space: nowrap;
  background: #fff; padding: 3px 9px; border-radius: 20px; border: 1px solid var(--line);
}
.kp-pick.on .kp-count { background: #fff; border-color: #c7d7ff; color: var(--primary-dark); font-weight: 600; }

/* ---- 卷子 ---- */
.paper {
  background: #fff; border-radius: 12px; padding: 22px 20px 26px;
  box-shadow: var(--shadow); margin-bottom: 20px;
  font-size: 13.5px; line-height: 1.85; color: #000;
}
.paper-head { text-align: center; border-bottom: 2px solid #000; padding-bottom: 12px; margin-bottom: 18px; }
.paper-title { font-size: 20px; font-weight: 800; letter-spacing: .5px; }
.paper-meta {
  display: flex; justify-content: space-between; gap: 10px;
  margin-top: 12px; font-size: 13px; flex-wrap: wrap;
}
.paper-sub { margin-top: 8px; font-size: 12.5px; color: #444; }

.paper-group { margin-bottom: 16px; }
.paper-group-title {
  font-weight: 700; font-size: 13.5px; color: #1a3a7a;
  background: #eef3ff; border-left: 4px solid #2f6bff;
  padding: 5px 10px; border-radius: 4px; margin-bottom: 12px;
}
.paper-cols { display: grid; gap: 14px 20px; }
.paper-cols[data-cols="2"] { grid-template-columns: 1fr 1fr; }
.paper-cols[data-cols="1"] { grid-template-columns: 1fr; }

.pq-item { break-inside: avoid; page-break-inside: avoid; }
.pq-stem { text-align: justify; }
.pq-no { font-weight: 700; margin-right: 3px; white-space: nowrap; display: inline-block; }
.pq-options { margin: 3px 0 0 14px; display: flex; flex-direction: column; }
.pq-opt { display: block; }
.pq-space { height: 58px; border-bottom: 1px dashed #ccc; margin: 4px 0 2px; }
.paper-cols[data-cols="1"] .pq-space { height: 84px; }

.paper-answers { margin-top: 24px; border-top: 2px solid #000; padding-top: 16px; }
.ans-row { display: flex; gap: 8px; font-size: 13px; padding: 3px 0; border-bottom: 1px dotted #e2e2e2; }
.ans-row b { flex: 0 0 62px; }
.ans-text { flex: 1; word-break: break-word; }
.ans-kp { flex: 0 0 auto; color: #7a8aa5; font-size: 11.5px; }
.paper-foot { text-align: center; font-size: 11px; color: #9aa7bb; margin-top: 20px; }

/* ---- 打印：只打印组卷覆盖层里的那张卷子 ---- */
@media print {
  @page { size: A4; margin: 12mm 10mm; }
  html, body { background: #fff !important; }
  .no-print, .topbar, .nav, #toast, .modal-mask, .pick-bar,
  .overlay-head, #paperBuilder { display: none !important; }

  /* 页面上所有内容都藏起来，只留卷子 */
  .page, main { display: none !important; }

  body.overlay-open { overflow: visible !important; }
  #paperOverlay {
    position: static !important;
    display: block !important;
    background: #fff !important;
    overflow: visible !important;
    height: auto !important;
  }
  #paperOverlay .overlay-body { padding: 0 !important; overflow: visible !important; }
  #paperPreview { display: block !important; }

  .paper {
    box-shadow: none !important; border-radius: 0 !important;
    padding: 0 !important; margin: 0 !important;
    font-size: 10.5pt; line-height: 1.7;
  }
  .paper-title { font-size: 15pt; }
  .paper-cols { gap: 10px 14px; }
  .pq-space { height: 42px; }
  .page-break { page-break-before: always; break-before: page; }
  .ans-row { font-size: 9.5pt; }
}

/* ================================================================== */
/* 全屏覆盖层（组卷）                                                   */
/* ================================================================== */
.overlay {
  position: fixed;
  inset: 0;
  z-index: 200;
  background: var(--bg);
  display: flex;
  flex-direction: column;
  animation: fade .18s ease;
}
.overlay.hidden { display: none; }
body.overlay-open { overflow: hidden; }

.overlay-head {
  flex: none;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 14px;
  background: linear-gradient(135deg, #2f6bff, #1e4fd8);
  color: #fff;
  font-size: 16px;
  font-weight: 700;
}
.overlay-head #paperOverlayTitle { flex: 1; }
.icon-btn {
  flex: none;
  width: 34px; height: 34px;
  border-radius: 50%;
  border: 1px solid rgba(255,255,255,.45);
  background: rgba(255,255,255,.18);
  color: #fff; font-size: 16px; line-height: 1;
  display: grid; place-items: center; cursor: pointer;
}
.icon-btn:active { transform: scale(.94); }

.overlay-body {
  flex: 1;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 14px 14px 30px;
}


/* ================================================================== */
/* 顶部齿轮按钮（设置从底部导航挪到了这里）                             */
/* ================================================================== */
.topbar-row { display: flex; align-items: flex-start; gap: 12px; }
.topbar-row > div { flex: 1; min-width: 0; }
.gear-btn {
  flex: none; width: 40px; height: 40px; border-radius: 12px;
  border: 1px solid rgba(255,255,255,.45); background: rgba(255,255,255,.18);
  color: #fff; font-size: 19px; line-height: 1; cursor: pointer;
  display: grid; place-items: center; transition: .15s;
}
.gear-btn:active { transform: scale(.94); background: rgba(255,255,255,.3); }

/* ================================================================== */
/* 「巩固」页的顶部三段切换                                             */
/* ================================================================== */
.seg {
  display: grid; grid-auto-flow: column; gap: 4px;
  background: #e8eef9; border-radius: 12px; padding: 4px; margin-bottom: 12px;
}
.seg button {
  border: 0; background: transparent; color: var(--ink-2);
  font-size: 13.5px; font-weight: 600; padding: 9px 6px; border-radius: 9px;
  cursor: pointer; font-family: inherit; transition: .15s;
}
.seg button.on { background: #fff; color: var(--primary-dark); box-shadow: 0 1px 3px rgba(16,32,58,.12); }
.subpage { display: none; }
.subpage.active { display: block; }

/* ================================================================== */
/* 图库                                                                */
/* ================================================================== */
.chip-row { display: flex; gap: 8px; overflow-x: auto; padding-bottom: 2px; -webkit-overflow-scrolling: touch; }
.chip-row::-webkit-scrollbar { display: none; }
.chip {
  flex: none; border: 1.5px solid var(--line); background: #fff; color: var(--ink-2);
  border-radius: 20px; padding: 6px 13px; font-size: 13px; cursor: pointer;
  font-family: inherit; transition: .15s; white-space: nowrap;
}
.chip.on { border-color: var(--primary); background: var(--primary-soft); color: var(--primary-dark); font-weight: 600; }
.switch-line { display: flex; align-items: center; gap: 7px; font-size: 13px; color: var(--ink-2); cursor: pointer; }
.switch-line input { width: 16px; height: 16px; accent-color: var(--primary); }

.lib-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.lib-card {
  padding: 0; border: 2px solid var(--line); border-radius: 12px; background: #fff;
  overflow: hidden; cursor: pointer; font-family: inherit; text-align: left;
  display: flex; flex-direction: column; transition: .15s;
}
.lib-card:active { transform: scale(.985); }
.lib-card.right { border-color: #b7e4c9; }
.lib-card.wrong { border-color: #f7c9ca; }
.lib-thumb { display: block; background: #f6f8fc; overflow: hidden; }
/* 题目切图有宽有窄（选择题是宽条、解答题接近方形），
   所以不能固定宽高比 + cover —— 那会把宽条裁成中间一条。用自然高度。 */
.lib-thumb img {
  width: 100%; height: auto; max-height: 260px;
  object-fit: contain; object-position: top center; display: block;
}
.lib-meta {
  display: flex; align-items: center; gap: 6px; padding: 6px 8px;
  border-top: 1px solid var(--line); font-size: 11.5px; color: var(--ink-3);
}
.lib-mark { font-weight: 800; font-size: 13px; }
.lib-card.right .lib-mark { color: var(--success); }
.lib-card.wrong .lib-mark { color: var(--danger); }
.lib-sub { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 题库排重的「重复 N 次」标记：靠右并禁止收缩 ——
   左边的学科文字本来就是可截断的，这个标记被截掉用户就不知道有东西被排掉了。 */
.lib-dup { margin-left: auto; flex: none; white-space: nowrap; margin-right: 0; }
.lib-detail-img { border-radius: 10px; overflow: hidden; border: 1px solid var(--line); background: #f6f8fc; }
.lib-detail-img img { width: 100%; display: block; }

.tag.green { background: var(--success-soft); color: var(--success); }
.dropzone .t3 { font-size: 12px; color: var(--ink-3); margin-top: 6px; }


/* ================================================================== */
/* 批改后的标记图：看得见的题框 + 只读的对错标记                          */
/* ================================================================== */
.marked-wrap {
  position: relative;
  display: inline-block;
  max-width: 100%;
  line-height: 0;
  --mark-size: 28px;
}
.marked-wrap > img { display: block; max-width: 100%; height: auto; border-radius: 10px; }
.mark-dot {
  position: absolute;
  min-width: var(--mark-size);
  height: var(--mark-size);
  padding: 0 4px;
  margin: 0;
  border-radius: 8px;
  border: 2px solid #fff;
  color: #fff;
  font-size: calc(var(--mark-size) * 0.5);
  font-weight: 800;
  line-height: 1;
  display: grid;
  place-items: center;
  transform: translate(-50%, -50%);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
  /*
   * 这个胶囊**只是标签，不是按钮**（原先是「点它改判」，见 util.js 里那段）。
   * `pointer-events: none` 让点击穿透到下面的题框热区 ——
   * 用户想点开解析时点在胶囊上，也该弹解析，而不是什么都没发生。
   */
  pointer-events: none;
  -webkit-tap-highlight-color: transparent;
}
.mark-dot.right { background: var(--success); }
.mark-dot.wrong { background: var(--danger); }

/*
 * 每道题的虚线题框。
 *
 * 视觉语言**刻意跟等待期间的 `.scan-box` 对齐**（同样 2px、同样 5px 圆角、
 * 下方那个 animation 也是从它那儿来的），目的是让用户觉得
 * 「等待时圈出这道题的那个框留下来了」，而不是批改后又冒出一套新图形。
 *
 * 但有两处**必须比 scan-box 克制**：
 *
 *   ① 去掉内发光和多层外发光。scan-box 是一张「还没人细看」的图上的
 *      临时装饰；批改后这张图是拿来读的 —— 题干、选项、孩子的手写作答
 *      全在框里，任何发光的叠加都会让底下的铅笔字发灰。
 *   ② 不做半透明填充，只在边缘描一圈点。填充会影响读题，
 *      而虚线本身已经足够说明「这块是一道题」。
 *
 * 颜色按对错分（绿 / 红），和胶囊、和下方的题目卡片是同一套语义。
 */
.q-outline {
  position: absolute;
  box-sizing: border-box; /* 让虚线框和 bbox 严格重合：不加这条，2px 边框会向外撑出去 */
  border: 2px dashed rgba(18, 161, 80, 0.6);
  border-radius: 5px;
  /* ② 的硬要求：框不能吃点击，题框内任意位置都得能弹出解析。
     它在 DOM 里本来就排在热区前面，这一条是第二道保险。 */
  pointer-events: none;
  box-shadow: 0 0 6px rgba(18, 161, 80, 0.14);
  /* 缓慢呼吸一下，帮「这题在哪儿」多抓一眼注意力。幅度很小，
     因为它是背景信息，不该比图上的字更抢眼。 */
  animation: q-outline-breath 2.8s ease-in-out infinite;
}
.q-outline.no {
  border-color: rgba(229, 72, 77, 0.66);
  box-shadow: 0 0 6px rgba(229, 72, 77, 0.16);
}
@keyframes q-outline-breath {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.72; }
}
@media (prefers-reduced-motion: reduce) {
  .q-outline { animation: none; }
}

/* 拍下一张 */
.next-bar { margin: 14px 0 4px; }


/* ================================================================== */
/* 错题本页头：一整行                                                    */
/* ================================================================== */

/*
 * 页头从「2×2 四个控件」收成一行（用户原话：四个位置占地方太大、喧宾夺主）。
 *
 * 内边距比 .card.tight 再小一档（8px 而不是 12px），每多一个像素都是从下面的
 * 错题列表里抢的。整条高度实测 48px 上下，而原来光是两个带标签的 select 就要 60px，
 * 加上第二行按钮整块在 130px 左右。
 *
 * ⚠️ 选择器必须写成 `.card.wrong-bar` 而不是 `.wrong-bar`：
 * 全局的 `.card` / `.card.tight` 也是两个类选择器，写成单个类的话
 * 特异性比它低，padding 会被 `.card.tight` 的 12px 盖掉 ——
 * 实测就是「改了没生效，页头还是 62px」。（已验证过 48px 的效果）
 */
.card.wrong-bar { padding: 8px 10px; }
.wrong-bar-row { display: flex; align-items: center; gap: 8px; }

/*
 * 两个筛选（学科 / 状态）的紧凑样式：无边框 + 淡底 + 自己的小箭头。
 *
 * 为什么不用全局那个 select 样式（白底 + 1.5px 边框 + 10px 内边距）：
 * 那个尺寸是为「一屏一个的设置项」定的，两个并排放在页头里就会顶成两条大框，
 * 正是用户说的「占地方」。
 *
 * 箭头只能自己画：`appearance: none` 之后浏览器原生的小三角也没了，
 * 而**只把边框去掉、留着原生箭头**在 Chrome 下会把箭头和文字贴到一起（很难看）。
 * 背景图是内联 SVG，零外部请求，粗细也跟着这里的 stroke-width 定死。
 */
.mini-select {
  width: auto;                 /* 顶掉全局 select 的 width:100% */
  flex: 0 1 auto;
  min-width: 0;
  max-width: 40%;
  padding: 7px 26px 7px 10px;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--ink-2);
  border: none;
  border-radius: 9px;
  background-color: #f1f4f9;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%235b6b82' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m5.4 9.6 6.6 6.6 6.6-6.6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 8px center;
  background-size: 12px 12px;
  appearance: none;
  -webkit-appearance: none;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* 聚焦时把底色提亮到白 + 一圈淡蓝：这里没有边框可亮，只能靠底色和光晕说明焦点 */
.mini-select:focus { background-color: #fff; box-shadow: 0 0 0 3px var(--primary-soft); }

/* 筛选和组卷之间的竖线：把「看」和「生成」两组动作分开，省一整个标签的宽度 */
.wrong-bar-sep { width: 1px; height: 18px; background: var(--line); margin: 0 2px; flex: none; }

/*
 * 「组卷」展开出来的两个选项。
 * 收起用 .hidden（display:none !important），所以这里写 display:flex 不会打架。
 */
.wrong-bar-opts { display: flex; gap: 8px; margin-top: 8px; }

/*
 * 展开状态：底色变激活色（.btn.gray.on 已在下面统一定义），箭头翻过来。
 *
 * 为什么不只翻箭头：箭头只有 12px，用户低头看按钮文字时根本注意不到它转了；
 * 而底色一变，整条页头的重心就看得出来了。
 */
.wrong-bar-car { display: inline-flex; transition: transform .16s; }
#wPaperToggle.on .wrong-bar-car { transform: rotate(180deg); }


/* ================================================================== */
/* 勾选组卷（错题本 / 题库）                                            */
/* ================================================================== */

/* 底部操作条：浮在底部导航之上 */
.pick-bar {
  position: fixed;
  left: 0; right: 0;
  bottom: calc(var(--nav-h) + env(safe-area-inset-bottom, 0px));
  z-index: 60;
  /*
   * 竖排 + 里面再分行：错题本的勾选条有两行（第一行组卷、第二行清理工具，
   * 理由见 paper-picker.js 里那段「分两行是防误触」）。
   * 没有清理工具的页面（题库）只有一行，视觉上和原来一模一样。
   */
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 8px;
  padding: 10px 14px;
  background: rgba(255, 255, 255, .97);
  backdrop-filter: blur(10px);
  border-top: 1px solid var(--line);
  box-shadow: 0 -6px 18px rgba(16, 32, 58, .1);
}
.pick-bar .pick-row { display: flex; align-items: center; gap: 10px; }
.pick-bar .ps-count {
  font-size: 13.5px; color: var(--ink-2); white-space: nowrap;
}
.pick-bar .ps-count b { color: var(--primary); font-size: 16px; }

/* 选择模式下给内容留出底部空间，别被操作条盖住 */
body.picking main { padding-bottom: 96px; }
/* 带清理工具时操作条多一行（约 40px），留的空间也得跟着长 —— 否则最后一张卡片被压住，用户以为列表就这么多 */
body.picking.pick-tools main { padding-bottom: 148px; }

/* 卡片角上的勾选框 */
.pick-box {
  display: none;
  position: absolute;
  right: 10px; top: 10px;
  width: 24px; height: 24px;
  border-radius: 50%;
  border: 2px solid var(--line);
  background: #fff;
  color: #fff;
  font-size: 14px; font-weight: 800;
  line-height: 1;
  place-items: center;
  z-index: 3;
  pointer-events: none;
}
body.picking .pick-box { display: grid; }
body.picking .q-card,
body.picking .lib-card { position: relative; cursor: pointer; }
body.picking .q-card:active,
body.picking .lib-card:active { transform: scale(.99); }

/* 选中态 */
body.picking .pick-box { border-color: #c3cfdf; }
.q-card.picked .pick-box,
.lib-card.picked .pick-box {
  background: var(--primary); border-color: var(--primary);
}
.q-card.picked { border-color: var(--primary) !important; box-shadow: 0 0 0 2px rgba(47,107,255,.16); }
.lib-card.picked { border-color: var(--primary) !important; box-shadow: 0 0 0 2px rgba(47,107,255,.16); }

/* 选择模式下，卡片内部的按钮不该抢点击（要整卡可点） */
body.picking .q-card .btn { pointer-events: none; opacity: .45; }

/* 已激活的切换按钮 */
.btn.gray.on {
  background: var(--primary-soft); color: var(--primary-dark);
  border-color: #c7d7ff; font-weight: 600;
}


/* 题库缩略图文件丢失时的占位 */
.lib-thumb.thumb-missing {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  min-height: 88px;
  background: #f1f4f9;
  color: var(--ink-3);
}
.lib-thumb.thumb-missing .tm-ico { font-size: 22px; opacity: .6; }
.lib-thumb.thumb-missing .tm-txt { font-size: 11px; }

/* 一行小标签（替代大段说明文字） */
.hint-row {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  margin: 8px 0 0;
}
.hint-row .muted { font-size: 12px; }

/* 正确率用颜色说话 */
.acc { font-weight: 700; }
.acc.good { color: var(--success); }
.acc.mid { color: var(--warn); }
.acc.bad { color: var(--danger); }

/* 顶部标题栏：只留标题 */
.topbar { padding-bottom: 14px; }
.topbar-row { display: flex; align-items: center; gap: 10px; }
.topbar-row h1 { flex: 1; margin: 0; }
.badge-mock {
  flex: none;
  font-size: 11px; font-weight: 700; letter-spacing: .3px;
  padding: 3px 8px; border-radius: 20px;
  background: rgba(255,255,255,.22); color: #fff;
  border: 1px solid rgba(255,255,255,.4);
}
.gear-btn { width: 34px; height: 34px; font-size: 17px; border-radius: 50%; }

/* 图上题框热区：点一下弹答案与解析 */
.q-hit {
  position: absolute;
  margin: 0; padding: 0;
  border: 2px solid transparent;
  border-radius: 6px;
  background: transparent;
  cursor: pointer;
  transition: border-color .12s, background .12s;
  -webkit-tap-highlight-color: transparent;
}
.q-hit.right:hover, .q-hit.right:active { border-color: rgba(18,161,80,.55); background: rgba(18,161,80,.07); }
.q-hit.wrong:hover, .q-hit.wrong:active { border-color: rgba(229,72,77,.55); background: rgba(229,72,77,.07); }

/* 图片下方一行统计 */
.mark-stats {
  display: flex; align-items: center; justify-content: center; gap: 18px;
  margin-top: 10px; padding-top: 10px;
  border-top: 1px solid var(--line);
  font-size: 13px; color: var(--ink-3);
}
.mark-stats b { font-size: 17px; color: var(--ink); margin-right: 2px; }
.mark-stats .good b { color: var(--success); }
.mark-stats .bad b { color: var(--danger); }


/* ================================================================== */
/* 逐步揭示的讲解                                                       */
/* ================================================================== */
.step-bar {
  display: flex; align-items: center; gap: 10px;
  margin-bottom: 12px;
}
.step-count {
  flex: none;
  font-size: 12px; font-weight: 700; color: var(--primary-dark);
  background: var(--primary-soft); padding: 3px 9px; border-radius: 20px;
}
.step-progress {
  flex: 1; height: 5px; border-radius: 3px; background: #e9eef7; overflow: hidden;
}
.step-progress i {
  display: block; height: 100%; border-radius: 3px;
  background: linear-gradient(90deg, #7aa2ff, #2f6bff);
  transition: width .25s ease;
}

.step-block {
  padding: 10px 12px;
  border-radius: 10px;
  margin-bottom: 10px;
  background: #f7f9fd;
  border: 1px solid transparent;
  animation: fade .2s ease;
}
.step-block.now {
  background: #fff;
  border-color: #c7d7ff;
  box-shadow: 0 2px 10px rgba(47, 107, 255, .1);
}
.step-block h4 {
  margin: 0 0 6px;
  font-size: 14px;
  color: var(--primary-dark);
}
.step-block > div { font-size: 13.8px; line-height: 1.75; }
.step-ask {
  margin-top: 8px; padding: 7px 10px;
  border-radius: 8px;
  background: var(--warn-soft);
  color: #8a5a00;
  font-size: 13px;
}
.step-actions { margin-top: 14px; }
.step-done {
  text-align: center;
  padding: 12px;
  border-radius: 10px;
  background: var(--success-soft);
  color: #0b6b36;
  font-size: 13.5px;
}

/* ================================================================== */
/* 知识点总结 + 这类题的通用解法（讲解的第二段）                        */
/* ================================================================== */
/*
 * 为什么**不做成** .step-block 的样子、也不并进上面那条进度条：
 *
 * 上面那些块是「跟着这道题往下走」（先算什么、再算什么），是**过程**；
 * 这一块是「换个数字也会做」的**套路**，性质不一样。长得一样的话，
 * 用户会以为它只是讲解的最后两步，直接点「跳到最后」时一眼扫过就滑走了。
 *
 * 所以给它一套明显不同的外观：左边一条色条、标题字号更小、外面有一圈边界。
 * 两块之间用颜色区分（知识点=靛蓝，通法=暖橙）—— 这不是装饰，是让家长
 * 扫一眼就知道「哪一块讲知识、哪一块讲怎么下手」。颜色之外还有图标和标题，
 * 所以色觉障碍下也分得清（不能只靠颜色，这是这个项目的既有约定）。
 */
.deep-analysis { margin-top: 14px; }
.deep-block {
  margin: 0 0 10px;
  padding: 11px 13px;
  border-radius: 10px;
  border: 1px solid var(--line);
  border-left-width: 3px;
  background: #fff;
  font-size: 13.6px;
  line-height: 1.75;
}
.deep-block:last-child { margin-bottom: 0; }
.deep-block.knowledge { border-left-color: #7c8cf8; background: #fafbff; }
.deep-block.method { border-left-color: #e8a33d; background: #fffcf6; }
.deep-block h5 {
  margin: 0 0 7px;
  font-size: 12.8px;
  display: flex; align-items: center; gap: 5px;
}
.deep-block.knowledge h5 { color: #4a5bd6; }
.deep-block.method h5 { color: #a86a12; }
.deep-identify {
  display: flex; gap: 6px; align-items: flex-start;
  margin-bottom: 8px; padding: 7px 9px;
  border-radius: 8px;
  background: rgba(232, 163, 61, .1);
  color: #7a4d06;
  font-size: 13.2px;
}
.deep-identify .ico { flex: none; margin-top: 2px; }
.deep-steps { margin: 0; padding-left: 19px; }
.deep-steps li { margin-bottom: 4px; }
.deep-steps li:last-child { margin-bottom: 0; }
/*
 * 还在后台补的时候那一行小字。
 *
 * 不用转圈（.loading/.spinner）：转圈看起来像「这一块还没加载完」，
 * 用户会停在那里等它 —— 而上面的讲解其实此刻就能读。一行淡字既不拦人，
 * 也说明了「下面还会长出东西来」，不会让人以为讲解就这么多。
 */
.deep-loading {
  margin-top: 12px;
  display: flex; align-items: center; gap: 5px;
  font-size: 12px; color: var(--ink-3);
}


/* ================================================================== */
/* 原始作答截图（错题详解最下面那一块）                                  */
/* ================================================================== */

/*
 * 这块图的存在理由只有一个：让用户**验证自己当时是怎么写的**。
 * 所以它必须能被点开放大 —— 按弹层宽度显示时，手机上那道题的笔迹小到看不清，
 * 看不清就等于没有。
 *
 * 用 .lib-detail-img 那套观感（题库里题目详情就是这么展示题图的），
 * 不再另起一套边框/圆角：用户提到的参照就是「题库 → 题目」的展示方式。
 */
.q-origin { margin-top: 16px; }
.q-origin-cap {
  display: flex; align-items: center; gap: 5px;
  font-size: 12px; color: var(--ink-3); margin-bottom: 6px;
}
/*
 * 图外面套一层 button（键盘可达），所以按钮自己的默认外观要清干净 ——
 * 不清的话会出现一条浏览器默认边框，图看起来像被框在一个灰盒子里。
 */
.q-origin-btn {
  display: block; width: 100%; padding: 0;
  border: none; background: none; font: inherit; text-align: left;
  cursor: zoom-in;
}
.q-origin-btn:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; border-radius: 10px; }
.q-origin img {
  width: 100%; display: block;
  border-radius: 10px; border: 1px solid var(--line); background: #f6f8fc;
  cursor: zoom-in;
}
/*
 * 兜底：图不是切好的题图，而是**整页作业图**时（老数据、裁图失败，见 wrong.js 的注释）。
 *
 * 必须限高：整页图按宽度铺满会有三四屏那么高，用户想看的「知识点总结 / 通法」
 * 反而被这张图挤到看不见了 —— 那正是这次要改掉的问题。限高 + contain
 * 至少让人一眼看出「这是一整页」，想看细节再点开放大。
 */
.q-origin.page img { max-height: 240px; object-fit: contain; object-position: top center; }
.q-origin-miss {
  border: 1.5px dashed var(--line); border-radius: 10px;
  padding: 18px 12px; text-align: center;
  color: var(--ink-3); font-size: 12.5px;
}

/*
 * 点图放大的那一层。
 *
 * 为什么不复用 openModal：它开的时候做的是 `root.innerHTML = ''` ——
 * 在错题详解上面再开一个会把下面那个弹层直接拆掉（用户会看到「点图放大，结果详解没了」）。
 * 所以放大层自己挂在 body 上，和 .pick-bar 一样不参与弹层栈。
 *
 * 交互用「点图切换」而不是手势缩放：捏合缩放要自己处理 touch 事件、双指距离、
 * 位移边界，写不对就是「放大了但拖不动」。点一下 = 原始尺寸 + 可滚动，
 * 在手机和桌面上都成立，也不需要额外的按钮。
 */
/*
 * z-index 1500：必须**压过 .pwa-bar（900）**。
 *
 * 踩过的坑：安装提示条是 sticky + z-index 900，第一次给这一层写 80 时，
 * 提示条正好盖在右上角 —— 关闭键被压在下面，截图上看到的是提示条自己那颗 ×。
 * 用户点「关闭」结果把安装提示关掉了，图还开着。
 * 1500 在 .pwa-bar(900) 与 .auth-gate(2000) 之间：全屏看图该盖住一切，
 * 但没登录时不该盖住登录门。
 */
.img-viewer {
  position: fixed; inset: 0; z-index: 1500;
  background: rgba(10, 15, 26, .94);
  display: flex; align-items: center; justify-content: center;
  padding: 12px; overflow: auto;
}
.img-viewer img { max-width: 100%; max-height: 100%; object-fit: contain; cursor: zoom-in; }
.img-viewer.zoomed { align-items: flex-start; justify-content: flex-start; }
.img-viewer.zoomed img { max-width: none; max-height: none; cursor: zoom-out; }
/*
 * 关闭键必须是**深底白字**：它浮在图上面，而图里有大片浅色（作业纸），
 * 半透明白底的小圆在那种区域上几乎看不见 —— 实测第一版就是这样，
 * 截图上那颗 × 得盯着找。深色底在任何底色上都立得住。
 */
.img-viewer-x {
  position: fixed; top: 10px; right: 10px; z-index: 1501;
  width: 36px; height: 36px; border-radius: 999px;
  border: 1px solid rgba(255, 255, 255, .38);
  background: rgba(12, 18, 30, .68); color: #fff;
  display: grid; place-items: center; cursor: pointer;
  box-shadow: 0 2px 10px rgba(0, 0, 0, .35);
}
.img-viewer-hint {
  position: fixed; left: 0; right: 0; bottom: 14px; z-index: 1501;
  text-align: center; color: rgba(255, 255, 255, .72); font-size: 12px;
  pointer-events: none;
}


/* ================================================================== */
/* 批改中：把上面的预览图缩成小缩略图                                   */
/* 目的：进度提示 + 「重新拍一张」按钮要和缩略图同屏可见，不用滚动        */
/* ================================================================== */
/* ------------------------------------------------------------------
   批改等待：大图 + 激光扫描，**不显示进度文字**
   ------------------------------------------------------------------
   参考豆包爱学的做法。改之前这里把照片缩成 96px 的缩略图，旁边配一串
   「正在用 TextIn 切边增强…」「任务已提交（task_id=…）」「已等待 12 秒」——
   那些都是**我们的实现细节**，用户既看不懂也不需要看。

   用户等待时真正需要的信息只有一条：「它在看我的卷子」。
   一张大图 + 一条来回扫的激光就够了。

   ⚠️ 第一版把这段挂在**上传卡片**的预览图上，靠 `body.grading` + `:has()` 改样式 ——
      实测「激光不见了」。链条太长：卡片可见性、overflow、CSS 特异性、
      `:has()` 支持度，任何一环出问题就静默失效。
      现在图和激光在同一个容器里（`.scan-stage`），自给自足，不依赖外部状态。
   ------------------------------------------------------------------ */
body.grading .card:has(#gradePreviewWrap) { display: none; }
body.grading #gradeSubmit { display: none; }
/* 原来还有一条 body.grading #gradeAdv —— 那个「补充说明 / 这是谁写的」折叠块
   连同批改页的空态一起删掉了：孩子挪去设置页，补充说明直接没了。 */

.grading-wait { display: block; }

/*
 * 大图舞台。
 *
 * ⚠️ 这里用 `scan-frame` 再套一层，是为了**让题框严格贴住图片**。
 * 第一版直接在 `.scan-stage` 上用 `object-fit: contain` ——
 * 图片被 max-height 限高之后会在容器里居中留边，而框是按容器算百分比的，
 * 结果就是**框整体偏移**，指哪儿哪儿不对。
 * 现在 `.scan-frame` 是 inline-block，尺寸**恰好等于图片**，
 * 框和激光都相对它定位，天然对齐。
 */
.scan-stage { display: flex; justify-content: center; }
.scan-frame {
  position: relative;
  display: inline-block;
  line-height: 0; /* 去掉 img 下面的基线缝隙，否则激光扫到底会露一条黑边 */
  border-radius: 12px;
  overflow: hidden;
  background: #0f1520;
}
.scan-frame img {
  display: block;
  max-width: 100%;
  max-height: 54vh;
  width: auto;
  height: auto;
}

/* 已识别出的题目框：淡蓝描边，出现时轻轻放大一下（参考豆包爱学） */
.scan-boxes { position: absolute; inset: 0; pointer-events: none; }
.scan-box {
  position: absolute;
  border: 2px solid rgba(130, 200, 255, 0.92);
  border-radius: 5px;
  box-shadow:
    0 0 10px rgba(130, 200, 255, 0.45),
    inset 0 0 14px rgba(130, 200, 255, 0.12);
  /* 入场动画。错开时间由 JS 按题数算好后写在 style 上（见 grade.js 里的 gap） */
  animation: box-in 0.4s cubic-bezier(0.2, 0.9, 0.3, 1) both;
}
@keyframes box-in {
  from {
    opacity: 0;
    transform: scale(0.94);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}
@media (prefers-reduced-motion: reduce) {
  .scan-box { animation: none; }
}

/* 框角上的 ✓/✗ —— 爱学批完一道就给一道（参考豆包爱学） */
.scan-mark {
  position: absolute;
  top: -9px;
  left: -9px;
  width: 21px;
  height: 21px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: 13px;
  font-weight: 700;
  font-style: normal; /* 用的是 <i>，得压掉斜体 */
  line-height: 1;
  color: #fff;
  box-shadow: 0 1px 5px rgba(0, 0, 0, 0.35);
  animation: mark-in 0.22s cubic-bezier(0.2, 1.4, 0.4, 1) both;
}
.scan-mark.ok { background: #21b573; }
.scan-mark.no { background: #e5484d; }
@keyframes mark-in {
  from {
    opacity: 0;
    transform: scale(0.4);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}
@media (prefers-reduced-motion: reduce) {
  .scan-mark { animation: none; }
}

/* 激光：一条淡蓝色光带，自上而下反复扫 */
.scan-laser {
  position: absolute;
  left: -4%;
  right: -4%;
  top: -22%;
  height: 15%;
  pointer-events: none;
  background: linear-gradient(
    to bottom,
    rgba(120, 200, 255, 0) 0%,
    rgba(120, 200, 255, 0.12) 42%,
    rgba(220, 245, 255, 0.95) 50%,
    rgba(120, 200, 255, 0.35) 54%,
    rgba(47, 107, 255, 0.1) 70%,
    rgba(47, 107, 255, 0) 100%
  );
  box-shadow: 0 0 14px 2px rgba(130, 200, 255, 0.45);
  animation: laser-sweep 2.1s cubic-bezier(0.45, 0, 0.55, 1) infinite;
  will-change: top, opacity;
}
@keyframes laser-sweep {
  0% {
    top: -22%;
    opacity: 0;
  }
  12% {
    opacity: 1;
  }
  88% {
    opacity: 1;
  }
  100% {
    top: 102%;
    opacity: 0;
  }
}
/* 系统开了「减少动态效果」就不扫，尊重无障碍设置 */
@media (prefers-reduced-motion: reduce) {
  .scan-laser {
    animation: none;
    display: none;
  }
}

/* 等待提示：只剩一行很轻的字，没有转圈、没有步骤列表、没有计时 */
.grading-hint {
  text-align: center;
  padding: 14px 0 2px;
  font-size: 14px;
  color: var(--ink-3, #8a94a6);
  letter-spacing: 0.02em;
  line-height: 1.5;
}

/* ================================================================== */
/* P2 变式追练                                                          */
/* ================================================================== */
.variant-cta { margin-top: 16px; padding-top: 14px; border-top: 1px dashed var(--line); }
.variant-cta.flash { animation: ctaFlash 1.6s ease; }
@keyframes ctaFlash {
  0%, 100% { background: transparent; }
  20%, 60% { background: var(--warn-soft); }
}

.variant-card {
  margin-top: 12px;
  padding: 12px;
  border-radius: 12px;
  border: 1.5px solid #f0d9a8;
  background: #fffdf7;
}
.variant-head { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 8px; }
.variant-stem { font-size: 14px; line-height: 1.75; margin-bottom: 10px; }
.variant-input {
  width: 100%;
  box-sizing: border-box;
  border: 1.5px solid var(--line);
  border-radius: 10px;
  padding: 10px 12px;
  font-size: 14.5px;
  font-family: inherit;
  line-height: 1.6;
  resize: vertical;
  margin-bottom: 10px;
  background: #fff;
}
.variant-input:focus { outline: none; border-color: var(--primary); }

/* ------------------------------------------------------------------
   「修一修」：批改结果页每道题内嵌的编辑区 / 加入错题本那一组动作

   为什么是**内嵌的 details** 而不是弹层：用户核对一道题时必须来回看
   题干、学生作答、图上那一块，弹层会把它们盖住。而这一组的用途是
   「一道一道过」，多两次开关弹层的点击乘以 N 道题就是纯粹的成本。
   ------------------------------------------------------------------ */
.q-actions { align-items: stretch; }
.q-actions .btn { flex: 1 1 128px; }
/* 「手动编辑」按下去之后要能看出它是开着的（不然用户以为点了没反应） */
.q-actions .act-edit.on {
  background: var(--primary-soft);
  border-color: var(--primary);
  color: var(--primary-dark);
}
.q-fix { margin-top: 10px; }
.q-fix > summary {
  cursor: pointer;
  font-size: 12.5px;
  color: #8b95a7;
  list-style: none;
  padding: 6px 0;
}
.q-fix > summary::-webkit-details-marker { display: none; }
.q-fix > summary::before { content: '▸ '; }
.q-fix[open] > summary::before { content: '▾ '; }
.q-edit {
  border: 1.5px dashed var(--line);
  border-radius: 10px;
  padding: 11px 12px;
  background: #fbfcff;
}
.q-edit-label {
  display: block;
  font-size: 12px;
  color: #8b95a7;
  margin: 2px 0 4px;
}
.q-edit-input {
  width: 100%;
  box-sizing: border-box;
  border: 1.5px solid var(--line);
  border-radius: 9px;
  padding: 8px 10px;
  font-size: 13.6px;
  font-family: inherit;
  line-height: 1.65;
  resize: vertical;
  margin-bottom: 9px;
  background: #fff;
}
.q-edit-input:focus { outline: none; border-color: var(--primary); }

/* 手机访问地址：要一眼看清，所以字号给大 */
.lan-url {
  font-family: ui-monospace, Consolas, monospace;
  font-size: 17px;
  font-weight: 700;
  color: var(--primary-dark);
  background: var(--primary-soft);
  border-radius: 10px;
  padding: 12px 14px;
  text-align: center;
  letter-spacing: .3px;
  user-select: all;
  word-break: break-all;
}

/* ================================================================== */
/* 批改进度（真实阶段，不再是编的）                                      */
/* ================================================================== */
.grade-steps { margin-top: 12px; }
.grade-step {
  font-size: 12.5px;
  color: var(--ink-3);
  padding: 3px 0 3px 2px;
  display: flex;
  gap: 6px;
  align-items: flex-start;
}
.grade-step.done { color: var(--success); }
#gradeLoadingText b { font-size: 15px; }

/* 判定可疑的横幅：必须显眼（爱学判定会波动，这是已知问题） */
.suspect-card {
  border: 1.5px solid #f0c36d;
  background: #fffbf0;
  margin-top: 12px;
}
.suspect-card b { color: #8a5a00; }

/* ================================================================== */
/* 首页第二个入口：拍照答疑                                             */
/* ================================================================== */
.entry-qa { cursor: pointer; transition: transform .12s, box-shadow .12s; }
.entry-qa:active { transform: scale(.985); }
.entry-ico { font-size: 22px; line-height: 1; }
.entry-qa b { font-size: 15px; }

/* 答疑结果页 */
.qa-answer-card .q-stem { margin-bottom: 8px; }
.qa-options {
  margin: 8px 0;
  padding-left: 2px;
  font-size: 13.6px;
  line-height: 1.9;
  color: var(--ink-2);
}
.qa-answer {
  display: flex; align-items: center; gap: 10px;
  margin-top: 12px; padding: 12px 14px;
  border-radius: 12px;
  background: linear-gradient(135deg, #eef4ff, #e3edff);
  border: 1.5px solid #c7d7ff;
}
.qa-answer-label {
  flex: none;
  font-size: 12px; font-weight: 700; color: #fff;
  background: var(--primary);
  padding: 3px 10px; border-radius: 20px;
}
.qa-answer-value {
  font-size: 20px; font-weight: 800; color: var(--primary-dark);
  word-break: break-word; line-height: 1.4;
}

/* 和批改结果并排对照 */
.compare-cell {
  flex: 1;
  padding: 10px;
  border-radius: 10px;
  background: #f7f9fd;
  border: 1px solid var(--line);
  text-align: center;
}
.compare-cell.ours { background: #eef4ff; border-color: #c7d7ff; }
.compare-val {
  font-size: 17px; font-weight: 800;
  margin: 5px 0;
  word-break: break-word;
  line-height: 1.4;
}
.compare-cell.ours .compare-val { color: var(--primary-dark); }

/* 题目详情里的「单独解这道题」 */
.qa-cta { margin-top: 14px; padding-top: 14px; border-top: 1px dashed var(--line); }
.qa-cta.is-suspect { border-top-color: #f0c36d; }

/* ---------- 拍照答疑：框选要问的那一道题 ---------- */
/* 蒙层用「一层铺满 + clip-path 挖洞」，不用四条边框拼：
   拼出来的四条边在缩放时转角会露缝，题目文字正好压在那条缝上，
   看着像页面裂了。 */
#qaPreviewBox { position: relative; }

#qaCropBox {
  position: absolute; inset: 0;
  overflow: hidden; border-radius: var(--radius);
  /* 手指在图上时不要滚页面：手机上「想拖框结果页面滑走了」是最恼人的一种失败 */
  touch-action: none;
  -webkit-user-select: none; user-select: none;
  cursor: move;
}
.qa-crop-box {
  position: absolute; left: 0; top: 0; width: 0; height: 0;
  border: 2px solid #fff;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, .35);
  cursor: move;
  /* touch-action 不继承，框上要再写一次 —— 只靠蒙层上那一条，
     手指落在框里时浏览器仍会把手势当成滚动 */
  touch-action: none;
}
/* 蒙层必须铺满整张图，再靠 inset 把框内镂空；
   它的定位跟着 .qa-crop-box（也就是跟着框），所以 inset 的基准就是框本身 */
.qa-crop-rect {
  position: absolute; left: -2px; top: -2px; right: -2px; bottom: -2px;
  background: rgba(12, 20, 36, .55);
  /* 镂空一律直角：inset 带圆角会在四个角留下小月牙，和框的圆角对不齐 */
  clip-path: inset(0px);
}
.qa-crop-h {
  position: absolute; width: 24px; height: 24px;
  /* 把角点朝画面外挪，让把手压在框线上，不然粗把手会把题干第一行盖住 */
  margin: -12px 0 0 -12px;
  border-radius: 50%;
  background: rgba(255, 255, 255, .18);
  border: 3px solid #fff;
  box-shadow: 0 1px 3px rgba(0, 0, 0, .4);
}
.qa-crop-h[data-dir='nw'] { left: 0; top: 0; cursor: nwse-resize; }
.qa-crop-h[data-dir='ne'] { left: 100%; top: 0; cursor: nesw-resize; }
.qa-crop-h[data-dir='sw'] { left: 0; top: 100%; cursor: nesw-resize; }
.qa-crop-h[data-dir='se'] { left: 100%; top: 100%; cursor: nwse-resize; }

/* 提示挂在框正下方，跟着框走。框贴着底边时它会露在图外一小截，
   但 .preview-wrap 是 overflow:hidden，不会撑坏布局。 */
.qa-crop-hint {
  position: absolute; left: 50%; top: 100%;
  transform: translateX(-50%);
  margin-top: 10px;
  display: flex; align-items: center; gap: 8px;
  padding: 5px 8px 5px 11px; border-radius: 999px;
  background: rgba(0, 0, 0, .62); color: #fff;
  font-size: 12px; white-space: nowrap;
  /* 提示压在图上会挡住题干、也会挡住拖框，所以它自己不吃指针事件 */
  pointer-events: none;
}
.qa-crop-reset {
  pointer-events: auto;
  border: none; border-radius: 999px;
  background: rgba(255, 255, 255, .22); color: #fff;
  font-size: 12px; padding: 3px 10px; cursor: pointer;
}
.qa-crop-reset:active { background: rgba(255, 255, 255, .38); }

/* 漏答小问的提示 */
.part-card {
  border: 1.5px solid #f0c36d;
  background: #fffbf0;
  margin-top: 12px;
}
.part-card b { color: #8a5a00; }
.part-warn { border-left: 3px solid #e5a020; }

/* 半对（做过的问都对，但有没做完的问） */
.mark-stats .mid b { color: #d97706; }

/* ---------- 登录门（多租户） ---------- */
.auth-gate {
  position: fixed; inset: 0; z-index: 2000;
  display: flex; align-items: center; justify-content: center;
  padding: 20px;
  background: linear-gradient(160deg, #2f6bff 0%, #1b3fa8 100%);
  overflow-y: auto;
}
.auth-gate.hidden { display: none; }

/* 未登录时把应用藏起来，免得看到半截界面 */
body.auth-locked .app { visibility: hidden; }

.auth-card {
  width: 100%; max-width: 360px;
  background: #fff; border-radius: 18px;
  padding: 26px 22px 22px;
  box-shadow: 0 18px 50px rgba(0, 0, 0, .28);
}
.auth-logo { font-size: 34px; text-align: center; line-height: 1; }
.auth-card h2 {
  margin: 12px 0 6px; font-size: 17.5px; text-align: center; color: var(--ink);
}
.auth-sub {
  margin: 0 0 18px; font-size: 12.5px; line-height: 1.6;
  color: var(--ink-3); text-align: center;
}
.auth-sub b { color: var(--ink-2); }

.auth-field { display: block; margin-bottom: 12px; }
.auth-field > span {
  display: block; font-size: 12.5px; color: var(--ink-2);
  margin-bottom: 5px; font-weight: 500;
}
.auth-field input {
  width: 100%; box-sizing: border-box;
  padding: 11px 13px; font-size: 15px;
  border: 1.5px solid var(--line); border-radius: 11px;
  background: #fbfcfe; color: var(--ink);
  outline: none; transition: border-color .15s, background .15s;
}
.auth-field input:focus { border-color: var(--brand); background: #fff; }
.auth-field em {
  display: block; font-style: normal; font-size: 11.5px;
  color: var(--ink-3); margin-top: 4px;
}

.auth-err {
  min-height: 17px; margin: 2px 0 8px;
  font-size: 12.5px; color: var(--danger);
}
.auth-alt {
  margin-top: 15px; text-align: center;
  font-size: 12.5px; color: var(--ink-3);
}
.auth-alt a { color: var(--brand); text-decoration: none; font-weight: 500; }
.auth-alt a:hover { text-decoration: underline; }

/* 设置页里的账号卡片 */
.account-row {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 0; border-bottom: 1px solid var(--line);
}
.account-row:last-child { border-bottom: 0; }
.account-row .who { flex: 1; min-width: 0; }
.account-row .who b { display: block; font-size: 14px; }
.account-row .who span { font-size: 12px; color: var(--ink-3); }

/* ---------- 「添加到主屏幕」引导条 ---------- */
.pwa-bar {
  position: sticky; top: 0; z-index: 900;
  display: flex; align-items: center; gap: 10px;
  padding: 10px 12px;
  background: #eef4ff;
  border-bottom: 1px solid #d3e0ff;
  font-size: 12.5px;
  color: var(--ink-2);
}
.pwa-bar.hidden { display: none; }
.pwa-bar .ico { font-size: 20px; line-height: 1; flex: none; }
.pwa-bar .pwa-text { flex: 1; min-width: 0; line-height: 1.45; }
.pwa-bar .pwa-text b { display: block; font-size: 13px; color: var(--ink); }
.pwa-bar .pwa-text span { color: var(--ink-3); }
.pwa-bar .pwa-close {
  flex: none; width: 24px; height: 24px; padding: 0;
  border: 0; border-radius: 50%;
  background: rgba(0, 0, 0, .06); color: var(--ink-3);
  font-size: 16px; line-height: 1; cursor: pointer;
}
.pwa-bar .pwa-close:hover { background: rgba(0, 0, 0, .12); }

/* 独立窗口（已添加到主屏幕）时，顶部要留出状态栏高度 */
@media (display-mode: standalone) {
  .topbar { padding-top: env(safe-area-inset-top); }
}

/* ---------- 二维码 ---------- */
.qr-wrap {
  display: flex; justify-content: center;
  padding: 10px; background: #fff;
  border: 1px solid var(--line); border-radius: 12px;
}
.qr-img {
  width: 200px; height: 200px; max-width: 100%;
  /* 二维码靠的是像素级锐利，缩放时别做平滑，否则边缘发灰影响识别 */
  image-rendering: pixelated;
  display: block;
}

/* ---------- 用量面板 ---------- */
.usage-row {
  display: flex; align-items: center; justify-content: space-between;
  padding: 6px 0; font-size: 13px; color: var(--ink-2);
  border-bottom: 1px dashed var(--line);
}
.usage-row:last-child { border-bottom: 0; }
.usage-row b { color: var(--ink); font-variant-numeric: tabular-nums; }
.usage-sep {
  margin: 12px 0 4px; font-size: 11.5px; font-weight: 600;
  color: var(--ink-3); letter-spacing: .3px;
}
#usageCard .field > span { color: var(--ink-3); }
#usageCard .field input { padding: 8px 10px; font-size: 14px; }

/* 证书快过期 / 已过期的提醒条（设置页） */
.warnbar {
  background: #fff8e1;
  border: 1px solid #ffe082;
  color: #7a5b00;
  border-radius: 8px;
  padding: 9px 11px;
  font-size: 12.5px;
  line-height: 1.55;
  margin-bottom: 10px;
}
.warnbar.danger {
  background: #ffebee;
  border-color: #ef9a9a;
  color: #b3261e;
}


/* 半成品结果：判分刚出来、题干还没补时的预览 */
.partial-note {
  display: flex;
  align-items: center;
  gap: 7px;
  margin-top: 9px;
  font-size: 12.5px;
  color: #4a5568;
}
.partial-note .dotpulse {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #2f6bff;
  animation: dotpulse 1.1s ease-in-out infinite;
  flex: none;
}
@keyframes dotpulse {
  0%, 100% { opacity: 0.25; transform: scale(0.8); }
  50%      { opacity: 1;    transform: scale(1.15); }
}

/* ------------------------------------------------------------------
   骨架屏：内容还在路上时占位
   ------------------------------------------------------------------
   为什么不用「转圈」或「暂无」：转圈让人以为在空等，而「暂无」
   让人以为坏了。骨架屏 + 一句直白话（「正在读取题干…」）说的是
   「东西在路上」—— 这正是「先给答案、题干随后到」那套设计需要的语气。
   ------------------------------------------------------------------ */
.skel-line {
  margin: 7px 0;
  line-height: 0;
}
.skel-bar {
  display: inline-block;
  height: 13px;
  border-radius: 7px;
  background: linear-gradient(90deg, #eceff4 0%, #f6f8fb 40%, #eceff4 80%);
  background-size: 220% 100%;
  animation: skel-shimmer 1.5s linear infinite;
}
@keyframes skel-shimmer {
  0%   { background-position: 120% 0; }
  100% { background-position: -120% 0; }
}
.skel-note {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 6px;
  font-size: 12.5px;
  color: #7b8794;
}
@media (prefers-reduced-motion: reduce) {
  .skel-bar { animation: none; }
}

/* 半成品卡片上的「几道题同时在读」进展条 */
.partial-progress {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 8px;
  padding: 7px 10px;
  border-radius: 8px;
  background: #eef4ff;
  color: #234a99;
  font-size: 12.5px;
  min-height: 0;
}
.partial-progress:empty { display: none; }
.partial-progress b { font-weight: 600; }

/* ==================================================================
   学情报告卡片（「学情」页顶部）
   ==================================================================
   这块的排版原则：**先给结论，再给证据**。
   家长打开学情页最想知道的是「我孩子到底什么问题」——
   所以一句话总览要最大最显眼，三条「反复踩的坑」默认展开第一条，
   证据/方法/避免用左标签右内容的行式排版，扫一眼就能读完。
   ================================================================== */
.rep { padding: 14px; }
.rep-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 10px;
}
.rep-title {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 14.5px;
  font-weight: 600;
  color: var(--ink, #1f2733);
}
.rep-tabs { display: flex; gap: 4px; }
.rep-tab {
  border: 0;
  background: #f2f5fa;
  color: #6b7688;
  font-size: 12px;
  padding: 4px 9px;
  border-radius: 20px;
  cursor: pointer;
  font-family: inherit;
}
.rep-tab.on { background: #2f6bff; color: #fff; }

/* 一句话总览 —— 整块里最重要的东西 */
.rep-headline {
  font-size: 15px;
  line-height: 1.65;
  color: #1f2733;
  background: linear-gradient(180deg, #f3f7ff 0%, #eef4ff 100%);
  border-left: 3px solid #2f6bff;
  border-radius: 8px;
  padding: 11px 12px;
}

.rep-patterns { margin-top: 12px; display: flex; flex-direction: column; gap: 8px; }
.rep-pat {
  border: 1px solid var(--line, #e6eaf2);
  border-radius: 10px;
  padding: 10px 11px;
  background: #fff;
}
.rep-pat > summary {
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  list-style: none;
  font-weight: 600;
  font-size: 13.5px;
}
.rep-pat > summary::-webkit-details-marker { display: none; }
.rep-pat-idx {
  flex: none;
  width: 19px;
  height: 19px;
  border-radius: 50%;
  background: #eef4ff;
  color: #2f6bff;
  font-size: 11.5px;
  display: grid;
  place-items: center;
}
.rep-pat-name { flex: 1; }
.rep-pat-arrow { color: #9aa5b5; transition: transform 0.18s; }
.rep-pat[open] .rep-pat-arrow { transform: rotate(180deg); }
.rep-row {
  display: flex;
  gap: 9px;
  margin-top: 9px;
  font-size: 13px;
  line-height: 1.6;
}
.rep-k {
  flex: none;
  width: 2.4em;
  color: #7b8794;
  font-size: 12px;
  padding-top: 1px;
}
.rep-v { flex: 1; color: #333c4a; }

.rep-next {
  display: flex;
  gap: 9px;
  align-items: flex-start;
  margin-top: 12px;
  padding: 10px 11px;
  border-radius: 9px;
  background: #fff8e8;
  border: 1px solid #ffe2a8;
  font-size: 13px;
  line-height: 1.6;
  color: #6b4e00;
}
.rep-next b { display: block; font-size: 12px; opacity: 0.75; margin-bottom: 2px; }

.rep-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px dashed var(--line, #e6eaf2);
  font-size: 12px;
}
.rep-regen {
  border: 0;
  background: transparent;
  color: #2f6bff;
  font-size: 12.5px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-family: inherit;
}

/* 空态 / 待生成 */
.rep-empty { text-align: center; padding: 8px 4px 4px; }
.rep-empty-main { font-size: 14.5px; font-weight: 600; color: #1f2733; }
.rep-empty-sub {
  font-size: 12.8px;
  color: #6b7688;
  line-height: 1.6;
  margin-top: 6px;
  max-width: 30em;
  margin-left: auto;
  margin-right: auto;
}
.rep-empty-stats { font-size: 12.5px; color: #8a94a6; margin-top: 8px; }
.rep-empty .rep-gen { margin-top: 12px; }
.rep-tip { font-size: 11.5px; color: #9aa5b5; margin-top: 7px; }

.rep-loading {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  padding: 6px 2px;
  font-size: 13.5px;
  line-height: 1.6;
}

/* ------------------------------------------------------------------
   学情报告：学科 × 学段 的分组标签
   ------------------------------------------------------------------
   为什么要有这一排：同一个孩子可能既有高一数学、又有八年级数学、还有高一物理。
   不分组的报告只能给「交集结论」，最后写成「计算要仔细」这种废话。
   分组后每组只喂同一学科同一学段的错题，结论才具体。

   视觉上做得比时间窗标签**更重**（有边框、有几行字），
   因为它承担的是「你正在看哪一块」这个信息，不是次要筛选。
   ------------------------------------------------------------------ */
.rep-groups {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 0 0 12px;
}
.rep-group {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 6px 10px;
  border: 1px solid var(--line, #e6eaf2);
  background: #fff;
  border-radius: 9px;
  font-size: 13px;
  font-family: inherit;
  color: #4a5666;
  cursor: pointer;
  transition: all .15s;
}
.rep-group span {
  font-size: 11.5px;
  color: #98a2b3;
}
.rep-group i {
  font-style: normal;
  font-size: 11px;
  background: #f0f3f8;
  color: #7b8794;
  border-radius: 20px;
  padding: 1px 6px;
}
.rep-group.on {
  border-color: #2f6bff;
  background: #f2f6ff;
  color: #2f6bff;
  font-weight: 600;
}
.rep-group.on span { color: #6f92e0; }
.rep-group.on i { background: #dbe7ff; color: #2f6bff; }

/* 生成前告诉用户「这次只分析哪一块」——避免又变成一锅烩 */
.rep-scope {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 12.5px;
  color: #2f6bff;
  background: #f2f6ff;
  border-radius: 20px;
  padding: 4px 11px;
  margin-bottom: 10px;
}

/* ------------------------------------------------------------------
   学情报告：失分结构 / 知识-习惯诊断 / 7天清单 / 验收目标
   ------------------------------------------------------------------
   这一版吸收了两份外部模板里真正有用的部分：

   · 失分结构（分类占比）—— 让「不会 vs 习惯不好」变成可看见的数字。
     颜色按 kind 区分：知识类偏红、习惯类偏黄，扫一眼就知道该往哪使劲。
   · 知识 / 习惯 两句话诊断 —— 家长最想知道的就是这个，而且两件事的
     应对方式完全不同（补知识 vs 改流程）。说错方向等于白使劲。
   · 7 天清单 —— 外部模板里「直接照做」这个点是对的，但它的填充内容
     全是废话（「回归课本：定义、定理、例题重新过一遍」）。我们保留结构，
     内容由模型针对具体错题生成，并禁掉了那类空话。
   · 验收目标 —— 把报告变成下次可对照的东西，否则家长无法判断有没有进步。
   ------------------------------------------------------------------ */
.rep-mix { margin: 12px 0 0; }
.rep-mix-title {
  font-size: 12px;
  color: #7b8794;
  margin-bottom: 7px;
  letter-spacing: .3px;
}
.rep-mix-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 5px;
  font-size: 12.5px;
}
.rep-mix-label { flex: none; width: 5.6em; color: #4a5666; }
.rep-mix-bar {
  flex: 1;
  height: 7px;
  background: #eef1f6;
  border-radius: 5px;
  overflow: hidden;
}
.rep-mix-bar i {
  display: block;
  height: 100%;
  border-radius: 5px;
  transition: width .4s ease;
}
/* 知识类 = 红（要补）、习惯类 = 黄（要改流程） */
.rep-mix-bar i.knowledge { background: linear-gradient(90deg, #ff8a8a, #e5484d); }
.rep-mix-bar i.habit { background: linear-gradient(90deg, #ffd166, #f0a020); }
.rep-mix-num {
  flex: none;
  width: 4.6em;
  text-align: right;
  color: #4a5666;
  font-variant-numeric: tabular-nums;
}
.rep-mix-num b { color: #98a2b3; font-weight: 400; font-size: 11.5px; margin-left: 4px; }
/* 换成丢分口径时数字更长（「12分30%」），给它宽一点，别把条形图挤扁 */
.rep-mix-num.wide { width: 6.2em; }
.rep-mix-num.wide b { color: #e5484d; font-size: 12.5px; margin-left: 0; margin-right: 3px; }
.rep-mix-note {
  margin-top: 9px;
  font-size: 12.5px;
  line-height: 1.6;
  color: #6b4e00;
  background: #fff8e8;
  border: 1px solid #ffe2a8;
  border-radius: 8px;
  padding: 8px 10px;
}

/* 知识 / 习惯 两句话诊断 */
.rep-diag { margin-top: 12px; display: flex; flex-direction: column; gap: 7px; }
.rep-diag-row {
  display: flex;
  gap: 8px;
  align-items: flex-start;
  font-size: 13px;
  line-height: 1.6;
  color: #333c4a;
}
.rep-diag-k {
  flex: none;
  font-size: 11.5px;
  padding: 2px 7px;
  border-radius: 5px;
  margin-top: 1px;
}
.rep-diag-k.know { background: #ffebee; color: #c62828; }
.rep-diag-k.habit { background: #fff8e1; color: #a06a00; }

/* 接下来 7 天 */
.rep-plan {
  margin-top: 12px;
  padding: 11px 12px;
  border-radius: 10px;
  background: #f6f9ff;
  border: 1px solid #dbe7ff;
}
.rep-plan-title {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 13px;
  font-weight: 600;
  color: #2f6bff;
  margin-bottom: 7px;
}
.rep-plan ol { margin: 0; padding-left: 1.35em; }
.rep-plan li { font-size: 13px; line-height: 1.7; color: #333c4a; margin-bottom: 3px; }

/* 验收目标 */
.rep-check {
  display: flex;
  gap: 9px;
  align-items: flex-start;
  margin-top: 12px;
  padding: 10px 11px;
  border-radius: 9px;
  background: #f2fbf6;
  border: 1px solid #b7e4cd;
  font-size: 13px;
  line-height: 1.6;
  color: #14663f;
}
.rep-check b { display: block; font-size: 12px; opacity: .75; margin-bottom: 2px; }

/* ==================================================================
   学情报告 · 看板版
   ==================================================================
   为什么重做排版：第一版是一屏半的长文，用户反馈
   「面对一堆文字没人有耐心读下去」。内容是对的，呈现方式不对 ——
   家长在手机上看，要的是**一眼看到结论和数字**，细节想深究再点开。

   所以这一版的层次是：
     结论（最大）→ 三个数字 → 失分条形图 → 知识/习惯两张卡
     → 反复踩的坑（全部折叠，只露标题）→ 7天清单 → 验收目标
   ================================================================== */

/* 一句话结论：整块里最显眼的 */
.rep-hero {
  display: flex;
  gap: 9px;
  align-items: flex-start;
  padding: 13px 13px;
  border-radius: 11px;
  background: linear-gradient(135deg, #eef4ff 0%, #f6f9ff 100%);
  border-left: 4px solid #2f6bff;
}
.rep-hero-ico { flex: none; color: #2f6bff; margin-top: 1px; }
.rep-hero-txt {
  flex: 1;
  font-size: 16px;
  font-weight: 600;
  line-height: 1.5;
  color: #1f2733;
}

/* 三个数字卡片 */
.rep-stats {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  margin-top: 12px;
}
.rep-stat {
  text-align: center;
  padding: 11px 4px 9px;
  border-radius: 10px;
  background: #f7f9fc;
  border: 1px solid #edf1f7;
}
.rep-stat b {
  display: block;
  font-size: 25px;
  font-weight: 700;
  line-height: 1.1;
  color: #1f2733;
  font-variant-numeric: tabular-nums;
}
.rep-stat span {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  font-size: 11.5px;
  color: #7b8794;
  margin-top: 5px;
}
.rep-stat.habit { background: #fffaf0; border-color: #ffe6b8; }
.rep-stat.habit b { color: #d98800; }
.rep-stat.know { background: #fff5f5; border-color: #ffd9d9; }
.rep-stat.know b { color: #e5484d; }

/* 一句判断：该刷题还是该改流程 */
.rep-verdict {
  margin-top: 9px;
  font-size: 12.5px;
  line-height: 1.6;
  padding: 8px 11px;
  border-radius: 8px;
  background: #fff8e8;
  border: 1px solid #ffe2a8;
  color: #6b4e00;
}
.rep-verdict.know { background: #fff5f5; border-color: #ffd9d9; color: #a32020; }

/* ------------------------------------------------------------------
   分值口径：得分率（按分值加权）/ 题数正确率
   ------------------------------------------------------------------
   为什么要单开一块、还分主次：

   · 用户的原话是「错一道低分值的小题和一个高分值的大题不是一个概念」，
     所以能给得分率时它就是主指标 —— 但**必须和题数口径分开摆**，
     两个百分比混在一行里，家长只会更糊涂。
   · 有分值的题不到一半时**主指标退回题数正确率**（后端已定好 mainMetric），
     得分率降级成一行灰字并带上样本量。样本太小的百分比摆在主位，
     家长会盯住一个不可靠的数 —— 那比不给更糟。
   · 加 `.weak` 是为了让「降级」在视觉上也看得出来（浅底 + 灰字），
     不然它和主口径长得一样，用户分不清哪个能信。
   ------------------------------------------------------------------ */
.rep-score {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 12px;
  padding: 11px 13px;
  border-radius: 10px;
  background: #f4f8ff;
  border: 1px solid #dbe7ff;
}
.rep-score.weak { background: #f8f9fb; border-color: #edf1f7; }
.rep-score-main { flex: none; text-align: center; min-width: 92px; }
.rep-score-main b {
  display: block;
  font-size: 26px;
  font-weight: 700;
  line-height: 1.1;
  color: #2f6bff;
  font-variant-numeric: tabular-nums;
}
.rep-score.weak .rep-score-main b { color: #4a5666; }
.rep-score-main span { display: block; font-size: 11.5px; color: #7b8794; margin-top: 3px; }
.rep-score-side { flex: 1; font-size: 12px; line-height: 1.65; color: #4a5666; }
.rep-score-side .rep-score-sub { display: block; color: #8a94a6; }
.rep-score-side .rep-score-warn { display: block; color: #a2711a; }

/* 小节标题 */
.rep-sec {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 13px;
  font-weight: 600;
  color: #4a5666;
  margin: 16px 0 9px;
}
.rep-sec-tip {
  margin-left: auto;
  font-size: 11.5px;
  font-weight: 400;
  color: #a8b0bd;
}

/* 失分条形图 */
.rep-mix { display: flex; flex-direction: column; gap: 6px; }
.rep-mix-row { display: flex; align-items: center; gap: 8px; font-size: 12.5px; }
/* 宽度按最长的标签（「没做完/跳步」6 个字）留足，否则会换行 */
.rep-mix-label { flex: none; width: 6.6em; color: #4a5666; font-size: 12px; white-space: nowrap; }
.rep-mix-bar {
  flex: 1;
  height: 9px;
  background: #eef1f6;
  border-radius: 6px;
  overflow: hidden;
}
.rep-mix-bar i {
  display: block;
  height: 100%;
  border-radius: 6px;
  transition: width .45s cubic-bezier(.2,.9,.3,1);
}
/* 知识类 = 红（要补）、习惯类 = 黄（要改流程）—— 颜色本身就是信息 */
.rep-mix-bar i.knowledge { background: linear-gradient(90deg, #ff9d9d, #e5484d); }
.rep-mix-bar i.habit { background: linear-gradient(90deg, #ffd97a, #f0a020); }
.rep-mix-num {
  flex: none;
  width: 4.2em;
  text-align: right;
  color: #7b8794;
  font-variant-numeric: tabular-nums;
}
.rep-mix-num b { color: #333c4a; font-weight: 600; margin-right: 4px; }

/* 知识 / 习惯 两张并排卡 */
.rep-two {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
  margin-top: 12px;
}
@media (max-width: 360px) { .rep-two { grid-template-columns: 1fr; } }
.rep-note { border-radius: 10px; padding: 10px 11px; border: 1px solid; }
.rep-note.know { background: #fff7f7; border-color: #ffdede; }
.rep-note.habit { background: #fffcf3; border-color: #ffebc4; }
.rep-note-h {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 12px;
  font-weight: 600;
  margin-bottom: 5px;
}
.rep-note.know .rep-note-h { color: #d33; }
.rep-note.habit .rep-note-h { color: #b8770a; }
.rep-note-b { font-size: 12.5px; line-height: 1.6; color: #444d5a; }

/* 反复踩的坑：全部默认折叠，只露标题 */
.rep-patterns { display: flex; flex-direction: column; gap: 7px; }
.rep-pat {
  border: 1px solid var(--line, #e6eaf2);
  border-radius: 10px;
  background: #fff;
  overflow: hidden;
}
.rep-pat > summary {
  display: flex;
  align-items: center;
  gap: 9px;
  cursor: pointer;
  list-style: none;
  padding: 11px 12px;
  font-weight: 600;
  font-size: 13.5px;
}
.rep-pat > summary::-webkit-details-marker { display: none; }
.rep-pat[open] > summary { border-bottom: 1px solid #f0f3f8; }
.rep-pat-idx {
  flex: none;
  width: 21px;
  height: 21px;
  border-radius: 50%;
  background: #eef4ff;
  color: #2f6bff;
  font-size: 12px;
  display: grid;
  place-items: center;
}
.rep-pat-name { flex: 1; color: #1f2733; }
.rep-pat-arrow { color: #b3bbc7; transition: transform .18s; }
.rep-pat[open] .rep-pat-arrow { transform: rotate(180deg); }
.rep-pat .rep-row { padding: 0 12px; }
.rep-pat .rep-row:first-of-type { padding-top: 10px; }
.rep-pat .rep-row:last-of-type { padding-bottom: 11px; }

/* 7 天清单：带勾选框 */
.rep-plan {
  margin-top: 16px;
  padding: 12px;
  border-radius: 11px;
  background: #f6f9ff;
  border: 1px solid #dbe7ff;
}
.rep-plan-title {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 13.5px;
  font-weight: 600;
  color: #2f6bff;
  margin-bottom: 9px;
}
.rep-plan-list { display: flex; flex-direction: column; gap: 7px; }
.rep-plan-item {
  display: flex;
  gap: 9px;
  align-items: flex-start;
  font-size: 13px;
  line-height: 1.55;
  color: #333c4a;
  cursor: pointer;
}
.rep-cb {
  flex: none;
  width: 17px;
  height: 17px;
  border: 1.8px solid #b7cdf5;
  border-radius: 5px;
  margin-top: 1px;
  background: #fff;
}
.rep-plan-item span { flex: 1; }
.rep-plan-item span b {
  display: inline-block;
  color: #2f6bff;
  font-weight: 600;
  margin-right: 5px;
}

/* 验收目标 */
.rep-check {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  margin-top: 12px;
  padding: 12px;
  border-radius: 11px;
  background: linear-gradient(135deg, #f1fbf6 0%, #f7fdfa 100%);
  border: 1px solid #b7e4cd;
  font-size: 13.5px;
  line-height: 1.55;
  color: #14663f;
  font-weight: 500;
}
.rep-check-ico { flex: none; color: #21b573; margin-top: 1px; }
.rep-check b {
  display: block;
  font-size: 11.5px;
  font-weight: 600;
  opacity: .7;
  margin-bottom: 3px;
}

/* ------------------------------------------------------------------
   等待期间：题框可以点（批完一道就能点开一道）
   ------------------------------------------------------------------
   用户提的：「豆包爱学的逻辑是可以点击这道题让用户先看，但我们不行 ——
   我们要等到全部批改完用户才能看。这样的确慢」。

   行为：
     · 已批完的框（.done）—— 实心描边、可点、点击弹出这道题的答案和解析
     · 还没批完的框       —— 虚线描边、不可点（点了会提示「还在批改」）
   这样用户一眼就能看出「哪几道已经能看了」。
   ------------------------------------------------------------------ */
.scan-boxes { pointer-events: none; }
.scan-box {
  pointer-events: auto;      /* 只有框本身可点，空白区域仍然穿透 */
  cursor: pointer;
  transition: box-shadow .15s;
}
.scan-box:active { box-shadow: 0 0 14px rgba(130, 200, 255, 0.9); }

/* 还没批完：虚线 + 半透明，明确表达「还不能点」 */
.scan-box:not(.done) {
  border-style: dashed;
  opacity: .55;
  cursor: default;
}
/* 已批完：实线加粗一点，表示「能点开看」 */
.scan-box.done { border-width: 2.5px; }

/* ==================================================================
   「学习」首页 —— 底部第一个 tab
   ==================================================================
   版式参考豆包爱学的学习页：上面两张平行大卡（入口），下面按学科分块的错题本。

   为什么入口卡做得这么大：这一页唯一的任务就是让用户**一眼选定一条路**
   （整页作业，还是单题答疑）。做得小了就得读文字，读文字就要停下来想 ——
   而这一步本来不该花时间。颜色区分开了就不用读字。
   ================================================================== */
.entry-cards {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 11px;
  margin-bottom: 20px;
}

.entry-card {
  position: relative;
  text-align: left;
  padding: 15px 14px 13px;
  border: 0;
  border-radius: 15px;
  min-height: 132px;
  display: flex;
  flex-direction: column;
  cursor: pointer;
  overflow: hidden;
  transition: transform .14s, box-shadow .14s;
  -webkit-tap-highlight-color: transparent;
}
.entry-card:active { transform: scale(.975); }

/* 作业批改（蓝）—— 整页，是「主线任务」，所以用更稳的蓝 */
.entry-card.grade {
  background: linear-gradient(150deg, #cfe6ff 0%, #a8d2ff 100%);
  box-shadow: 0 4px 14px rgba(60, 130, 220, .16);
}
/* 题目答疑（绿）—— 单题，是「顺手一问」 */
.entry-card.qa {
  background: linear-gradient(150deg, #cdf3dd 0%, #a5e9c5 100%);
  box-shadow: 0 4px 14px rgba(46, 160, 100, .16);
}

.entry-t1 {
  font-size: 17px;
  font-weight: 700;
  color: #1f2733;
  letter-spacing: .3px;
}
.entry-t2 {
  font-size: 11.5px;
  color: #4a5666;
  opacity: .78;
  margin-top: 4px;
}
/* 图标压在右下角、半透明 —— 当装饰用，不跟标题抢注意力 */
.entry-ico {
  margin-top: auto;
  align-self: flex-end;
  opacity: .5;
  color: #1f2733;
}

/* ---- 错题本区 ---- */
.home-sec {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 2px 11px;
}
.home-sec-title {
  font-size: 16px;
  font-weight: 700;
  color: #1f2733;
  display: flex;
  align-items: center;
  gap: 6px;
}
.home-sec-num {
  font-size: 12px;
  font-weight: 600;
  color: #7b8794;
  background: #eef1f6;
  border-radius: 20px;
  padding: 1px 8px;
}
.home-sec-more {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 3px;
  border: 0;
  background: none;
  font-size: 12.5px;
  color: #2f6bff;
  cursor: pointer;
  padding: 4px 0;
}
.home-sec-more .spark { color: #7aa7ff; }
.home-sec-more.plain { color: #8a94a3; }

/* 学科块：三列。每块一个淡色底 + 学科名 + 题数 + 角落淡图标 */
.subject-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
}
@media (max-width: 340px) { .subject-grid { grid-template-columns: repeat(2, 1fr); } }

.subject-card {
  position: relative;
  text-align: left;
  border: 0;
  border-radius: 13px;
  padding: 12px 11px;
  min-height: 92px;
  cursor: pointer;
  overflow: hidden;
  transition: transform .14s;
  -webkit-tap-highlight-color: transparent;
}
.subject-card:active { transform: scale(.97); }
.subject-card.skel { background: #f2f4f8; animation: pulse 1.4s ease-in-out infinite; }
@keyframes pulse { 0%,100% { opacity: 1 } 50% { opacity: .55 } }

.subject-name { font-size: 14.5px; font-weight: 700; color: #1f2733; }
.subject-count { font-size: 11.5px; color: #5b6675; opacity: .8; margin-top: 3px; }
.subject-ico {
  position: absolute;
  right: 8px;
  bottom: 6px;
  opacity: .34;
  color: #1f2733;
}
/* 有新错题时右上角一个小红点 —— 和底部 tab 的红点语义一致 */
.subject-dot {
  position: absolute;
  top: 9px;
  right: 9px;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #e5484d;
}

/* 学科配色。颜色在这里是**信息**（扫一眼定位），不是装饰 —— 所以固定映射 */
.subject-card.blue   { background: #d8ecff; }
.subject-card.orange { background: #ffecd6; }
.subject-card.purple { background: #e6e2ff; }
.subject-card.cyan   { background: #d6f2f8; }
.subject-card.green  { background: #d8f3e0; }
.subject-card.lime   { background: #e8f5d0; }
.subject-card.red    { background: #ffdfdd; }
.subject-card.brown  { background: #f0e4d8; }
.subject-card.teal   { background: #d3f0ec; }
.subject-card.slate  { background: #e8ebf0; }

.home-empty {
  text-align: center;
  padding: 30px 20px;
  color: #8a94a3;
  font-size: 13px;
  line-height: 1.7;
  background: #f7f9fc;
  border-radius: 13px;
}
.home-empty-ico { color: #b9c2cf; margin-bottom: 8px; }
.home-hint { text-align: center; font-size: 12px; color: #a0a8b4; margin-top: 14px; }

/* ==================================================================
   子页面返回栏（批改 / 答疑 / 错题本 都是「学习」的下级）
   ==================================================================
   底部导航仍然高亮「学习」，所以需要一个明确的「我进到二级页了」的信号，
   否则用户会以为切走了 tab、找不到回去的路。 */
.sub-head {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: -2px 0 12px;
}
.sub-back {
  border: 0;
  background: #eef1f6;
  color: #4a5666;
  width: 32px;
  height: 32px;
  border-radius: 10px;
  display: grid;
  place-items: center;
  cursor: pointer;
  padding: 0;
  -webkit-tap-highlight-color: transparent;
}
.sub-back:active { background: #e2e7ef; }
.sub-title { font-size: 15px; font-weight: 600; color: #4a5666; }

/* ------------------------------------------------------------------
   标记图左上角的「符号 + 题号」胶囊
   ------------------------------------------------------------------
   用户反馈：批改**过程中**画的是 ✓ ✗，批改完就换成序号了，
   对错只剩颜色 —— 而家长扫一眼图想确认的是「哪几道错了」，
   不是「第几题在哪儿」；颜色对色觉障碍的人也不友好。

   所以两个都放：**符号放大、题号缩小**。不做开关 ——
   开关意味着用户得先知道有这个东西、再去设置里找，多两步。

   后来它又被**去掉了点击**（见 util.js 里那段）：想点开解析的人总误触到它，
   判定就被悄悄改了。现在它只是标签，改判在解析弹层里。
   ------------------------------------------------------------------ */
.mark-dot { gap: 0; }
.mark-dot b {
  /* 符号是主角：它回答「这道题对了没有」 */
  font-size: calc(var(--mark-size) * 0.62);
  font-weight: 800;
  line-height: 1;
}
.mark-dot i {
  /* 题号退成配角：它只用来和下面的题目卡片对号 */
  font-size: calc(var(--mark-size) * 0.34);
  font-weight: 600;
  font-style: normal;
  opacity: .82;
  margin-left: 2px;
  line-height: 1;
}
/* 三位数题号（很少见）时别把胶囊撑得太宽 */
.mark-dot i { max-width: 2.6em; overflow: hidden; }

/* ------------------------------------------------------------------
   改判按钮（.tag-toggle）—— 和状态标签同行，但**是动作不是信息**
   ------------------------------------------------------------------
   用户反馈两点，其实是同一件事：
     · 「『这题其实做错了』容易误解」—— 那是**陈述句**，读起来像系统在告知；
     · 「放第二行有点占地方」—— 为一个低频动作单开一整行，视觉重量过大。

   要挪到状态标签那一行，文案就必须先压短（「改为做错」4 个字）。
   样式上它和 .tag 长得像（同样的小圆角、同样的字号），
   但**只有描边、没有底色** —— 前两个是「读的信息」，这一个是「点的动作」，
   混成一样用户就会去点那两个标签。
   ------------------------------------------------------------------ */
.tag-toggle {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 9px;
  border-radius: 999px;
  border: 1px solid #cfd6e0;
  background: #fff;
  color: #4a5666;
  font-size: 12.5px;
  font-weight: 600;
  line-height: 1.5;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background .12s, border-color .12s;
}
.tag-toggle:hover { background: #f4f7fb; border-color: #aab6c6; }
.tag-toggle:active { background: #e9eff7; transform: scale(.97); }
/* 请求中禁用 —— 要看得出来，否则用户会以为没点上 */
.tag-toggle:disabled { opacity: .5; cursor: default; transform: none; }
/* 里面的 SVG 跟着文字色走 */
.tag-toggle .ic { flex: none; }

/* ------------------------------------------------------------------
   错题详情里「逐步讲解」的折叠条
   ------------------------------------------------------------------
   它原来默认展开，占半个屏（进度条 + 问题卡 + 两个按钮）。
   用户反馈：这个页面是"攒错题 / 打印"用的，而"逐步引导孩子自己想"
   发生在**纸质卷子**上 —— 这个功能在错题本里碰不到使用场景。

   不删的理由：删是不可逆的，而它本身有价值（复习模式、孩子自己在 app 里看讲解）。
   收起 = 一行 vs 一大块，成本几乎为零。
   ------------------------------------------------------------------ */
.explain-fold {
  margin: 0 0 10px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: #fafbfd;
}
.explain-fold > summary {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 10px 12px;
  font-size: 13px;
  font-weight: 600;
  color: #4a5666;
  cursor: pointer;
  list-style: none;          /* 干掉默认的小三角，用自绘的 */
  -webkit-tap-highlight-color: transparent;
}
.explain-fold > summary::-webkit-details-marker { display: none; }
/* 自绘箭头：展开时转 90°，和站内其它折叠项一致 */
.explain-fold > summary::after {
  content: '›';
  margin-left: auto;
  font-size: 18px;
  color: #9aa6b6;
  transition: transform .15s;
}
.explain-fold[open] > summary::after { transform: rotate(90deg); }
.explain-fold[open] > summary { border-bottom: 1px solid var(--line); }
.explain-fold-body { padding: 12px; }

/* ------------------------------------------------------------------
   页脚：网站备案号
   ------------------------------------------------------------------
   《互联网信息服务管理办法》要求已备案网站在首页底部标注备案号，
   并链接到工信部备案系统。所以这块**不能删**。

   刻意做得淡、小、居中：它是合规信息，不是给用户看的内容 ——
   但也不能藏（藏起来等于没标）。链接用 target="_blank" + noopener，
   别把主页面交给第三方页面控制（rel 里那个 noreferrer 是配套的）。
   ------------------------------------------------------------------ */
.site-foot {
  text-align: center;
  padding: 16px 12px 4px;
  font-size: 11.5px;
  line-height: 1.6;
  color: #9aa6b6;
}
.site-foot a {
  color: inherit;
  text-decoration: none;
}
.site-foot a:hover { color: #6b7a8d; text-decoration: underline; }
