/* ═══════════════════════════════════════════════════════════════
   视觉基调：教学文件
   ───────────────────────────────────────────────────────────────
   不是"应用"，是一份排版讲究的教学材料——纯白纸、近黑墨、发丝线做全部
   结构、一个主色。

   四条硬规矩（改这个文件前先读）：
     1. 零圆角。所有边角都是直角，一个 border-radius 都不写。
     2. 零阴影。层次靠 1px 发丝线，不靠投影——投影是"卡片感"，
        发丝线是"表格感"，后者才像教学材料。
     3. 颜色只有下面这几个 token，不许在下面写死颜色值。
     4. 零 emoji。图标一律用 Lucide 的 <i data-lucide="..."></i>。
     5. 状态必须上色。老师扫页面找的就是"哪些要动手"，纯文字状态等于没有。

   之前那版是暖纸底 + 墨绿 + 宋体 + 纸纹格线 + 五套色系 + 50 处圆角，
   信息密度高、决定太多，观感"vibe coded"。这版做的全是减法。
   ═══════════════════════════════════════════════════════════════ */

:root {
  /* 纸与墨 */
  --paper: #ffffff;
  --paper-2: #f7f8f8;      /* 极浅底：表头、嵌套块 */
  --ink: #16181c;          /* 正文/标题 */
  --ink-2: #5c636d;        /* 次要说明 */
  --ink-3: #949aa3;        /* 元信息、时间戳 */
  --rule: #e2e5e8;         /* 发丝线 */
  --rule-strong: #c7ccd2;  /* 需要强调的分隔 */

  /* ── 颜色为什么是这几个 ──────────────────────────────────────
     中国课堂里颜色本来就有约定，直接用它，不另发明：
       红  是老师的笔——批改、朱批。看到红就是"这里要老师动手"。
       蓝黑是学生的笔——作业本上写字的墨水。是"写下内容"的颜色。
     所以主色取墨蓝而不是红：红必须留给"需要处理/错了"，主色若也是红，
     满屏按钮就把真正要处理的东西淹掉了。两者各管各的，不打架。

     绿只当"确认"用（已通过/一致/已签），不做主题色。
     ——上一版拿黑板绿当主题色是从旧配色沿用的，那是惯性不是理由。 */

  /* 主色：墨蓝（写下动作——主按钮、当前模式、链接、焦点） */
  --accent: #1d4e89;
  --accent-2: #163c69;
  --accent-wash: #eef2f8;

  /* 朱红：老师要处理 / 错了（待处理、卡住、方向错、不符、未签） */
  --alert: #b3261e;
  --alert-wash: #fdf1f0;

  /* 淡绿：确认过了（已通过、一致、已签）——只做状态，不做主题 */
  --confirm: #1f6b45;
  --confirm-wash: #eef5f1;

  /* 字号阶梯：1.20 比例，只有这七档 */
  --t-xs: 11px;
  --t-sm: 12px;
  --t-base: 14px;
  --t-md: 16px;
  --t-lg: 19px;
  --t-xl: 23px;
  --t-2xl: 28px;

  /* ── 间距：不只是刻度，是"关系有多近" ──────────────────────────
     光有七档数值不叫系统——得规定哪一档表示哪种关系，否则还是随手挑。
     由近及远，越相关的东西挨得越紧：

       --gap-inline  同一件事的组成部分（图标↔文字、标签↔值）
       --gap-tight   一组之内的相邻行（列表里一条和下一条）
       --gap-item    一个小节内部：标题↔内容、说明↔控件
       --gap-group   同一面板里两个**不同用途**的区块之间
       --gap-block   面板与面板之间
       --gap-page    页面级留白

     判断标准只有一条：**两样东西的间距，必须小于它们各自到别处的间距。**
     现在的毛病正是反过来——难点地图和各组列表贴成 0px，而关系更远的
     操作区反而只隔 12px，读的人分不出哪些是一伙的。 */
  --s-1: 4px;
  --s-2: 8px;
  --s-3: 12px;
  --s-4: 16px;
  --s-5: 24px;
  --s-6: 32px;
  --s-7: 48px;

  --gap-inline: var(--s-2);
  --gap-tight:  var(--s-3);
  --gap-item:   var(--s-4);
  --gap-group:  var(--s-6);
  --gap-block:  var(--s-7);
  --gap-page:   var(--s-7);

  --font: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei",
          -apple-system, BlinkMacSystemFont, "Helvetica Neue", sans-serif;
  --font-num: "SF Mono", "JetBrains Mono", ui-monospace, Menlo, monospace;
}

*, *::before, *::after { box-sizing: border-box; border-radius: 0 !important; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  font-family: var(--font);
  font-size: var(--t-base);
  line-height: 1.7;
  color: var(--ink);
  background: var(--paper);
  -webkit-font-smoothing: antialiased;
}

/* Lucide 图标：跟着文字走，不喧宾夺主 */
[data-lucide] { width: 1em; height: 1em; stroke-width: 1.75; vertical-align: -0.14em; }
.svg-icon { width: 1em; height: 1em; stroke-width: 1.75; vertical-align: -0.14em; flex: none; }

/* ── 页头 ─────────────────────────────────────────────────── */
header {
  padding: var(--s-3) var(--s-5);
  background: var(--paper);
  color: var(--ink);
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s-4);
  border-bottom: 1px solid var(--rule-strong);
  position: sticky; top: 0; z-index: 20;
}
header h1 {
  font-size: var(--t-md); margin: 0; font-weight: 600; letter-spacing: .02em;
  white-space: nowrap;
}
header .badge {
  font-size: var(--t-xs); color: var(--ink-2);
  border: 1px solid var(--rule); padding: 2px var(--s-2);
  white-space: nowrap; font-weight: 400;
}

.wrap { max-width: 1080px; margin: 0 auto; padding: var(--s-6) var(--s-5) var(--s-7); }

/* ── 首页 ─────────────────────────────────────────────────── */
.landing { max-width: 560px; }
/* 选择器曾经写的是 .landing a .sub，而 HTML 里的类名是 .role——
   两者从没匹配上，所以"姓名"和"这个角色能干什么"被挤成了一行。 */
.landing a {
  display: block;
  padding: var(--gap-item) var(--gap-item);
  text-decoration: none; color: var(--ink);
  border-bottom: 1px solid var(--rule);
  font-size: var(--t-md);
}
.landing a:first-of-type { border-top: 1px solid var(--rule); }
.landing a:hover { background: var(--accent-wash); color: var(--accent); }
.landing a .role {
  display: block; margin-top: var(--gap-inline);
  color: var(--ink-3); font-size: var(--t-sm); line-height: 1.6;
}

/* ── 面板：靠线分区，不靠卡片 ──────────────────────────────── */
.panel, .board {
  background: var(--paper);
  border: 1px solid var(--rule);
  margin-bottom: var(--s-6);
}
.panel-head, .board-head {
  padding: var(--s-3) var(--s-4);
  border-bottom: 1px solid var(--rule);
  background: var(--paper-2);
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s-3);
  font-weight: 600; font-size: var(--t-md);
}
.panel-head h2, .board-head h2 { margin: 0; font-size: var(--t-md); font-weight: 600; }
.panel-body, .board-body { padding: var(--s-4); }
.panel-body > :first-child, .board-body > :first-child { margin-top: 0; }
.panel-body > :last-child, .board-body > :last-child { margin-bottom: 0; }

.toolbar {
  display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap;
  margin-bottom: var(--s-3);
}
.spacer { flex: 1 1 auto; }

/* ── 锚点导航 ─────────────────────────────────────────────── */
/* 锚点导航贴在页头下面。**不要写死 top 值**——页头高度随内容变（手机上
   模式切换按钮会换行），写死 49px 会让导航和页头之间露出一条缝、或者被
   面板盖住。改用一个变量，由页头的实际高度决定。 */
.anchor-nav {
  display: flex; gap: var(--s-4);
  padding: var(--s-3) var(--s-5);
  border-bottom: 1px solid var(--rule);
  font-size: var(--t-sm);
  background: var(--paper);
  overflow-x: auto; scrollbar-width: none;
  position: sticky; top: var(--header-h, 52px); z-index: 19;
}
.anchor-nav::-webkit-scrollbar { display: none; }
.anchor-nav a { white-space: nowrap; }
.anchor-nav a { color: var(--ink-2); text-decoration: none; }
.anchor-nav a:hover { color: var(--accent); }

/* ── 按钮：只有两种 ───────────────────────────────────────── */
.btn, .btn-ghost, button {
  font-family: inherit; font-size: var(--t-base); line-height: 1.4;
  padding: var(--s-2) var(--s-4);
  border: 1px solid var(--accent); background: var(--accent); color: #fff;
  cursor: pointer; white-space: nowrap;
  display: inline-flex; align-items: center; gap: var(--s-2);
  transition: background .12s, border-color .12s, color .12s;
}
.btn:hover, button:hover { background: var(--accent-2); border-color: var(--accent-2); }
.btn-ghost {
  background: var(--paper); color: var(--ink); border-color: var(--rule-strong);
}
.btn-ghost:hover { background: var(--paper-2); color: var(--accent); border-color: var(--accent); }
button:disabled, .btn:disabled, .btn-ghost:disabled { opacity: .4; cursor: default; }
button:disabled:hover { background: var(--accent); border-color: var(--accent); }
.btn-ghost:disabled:hover { background: var(--paper); color: var(--ink); border-color: var(--rule-strong); }

/* ── 表单：原生控件，统一外观 ──────────────────────────────────
   下拉刻意保留原生 <select>：师生都在手机上用，原生会拉起系统选择器，
   大、好按、无障碍免费。自绘下拉在手机上是降级。这里只统一外观。 */
input, select, textarea {
  font-family: inherit; font-size: var(--t-md); line-height: 1.5;
  color: var(--ink); background: var(--paper);
  border: 1px solid var(--rule-strong);
  padding: var(--s-2) var(--s-3);
  width: 100%;
}
/* 焦点态不用 box-shadow（硬规矩第 2 条），改成加粗边框；
   同时 padding 减 1px，免得整个控件在聚焦瞬间跳一下。 */
input:focus, select:focus, textarea:focus {
  outline: none; border: 2px solid var(--accent); padding: calc(var(--s-3) - 1px);
}
textarea { resize: vertical; }

select {
  appearance: none; -webkit-appearance: none;
  padding-right: var(--s-6);
  background-image:
    linear-gradient(45deg, transparent 50%, var(--ink-2) 50%),
    linear-gradient(135deg, var(--ink-2) 50%, transparent 50%);
  background-position: right 18px center, right 13px center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}

.field { display: block; margin-bottom: var(--s-3); }
.field > span {
  display: block; font-size: var(--t-sm); color: var(--ink-2);
  margin-bottom: var(--s-1);
}

/* ── 学生页：题干 ─────────────────────────────────────────── */
/* align-items:start——grid 默认 stretch 会把两栏拉成等高，
   题干短的时候「题目」面板底下就空出一大片。 */
.layout { display: grid; gap: var(--gap-block); align-items: start; }
@media (min-width: 900px) { .layout { grid-template-columns: minmax(0,1fr) minmax(0,1fr); } }

#stem { font-size: var(--t-md); line-height: 1.8; }
#stem p { margin: 0 0 var(--s-3); }
#figures img, .t-stem-img { max-width: 100%; display: block; margin-top: var(--s-3); border: 1px solid var(--rule); }

/* ── 对话流 ───────────────────────────────────────────────── */
.chat-panel { display: flex; flex-direction: column; }
#transcript, .group-chat-log { overflow-y: auto; padding-right: var(--s-1); }
#transcript { max-height: 56vh; }
.group-chat-log { max-height: 46vh; }

.msg, .gc-msg { margin-bottom: var(--s-4); }
.msg .who, .gc-msg .who {
  font-size: var(--t-xs); color: var(--ink-3); margin-bottom: var(--s-1);
  letter-spacing: .04em;
}
.msg .bubble, .gc-msg .bubble {
  padding: var(--s-3);
  border: 1px solid var(--rule);
  line-height: 1.75;
}
.msg.student .bubble, .gc-msg.student .bubble {
  background: var(--accent-wash); border-color: var(--accent-wash);
}
.msg.tutor .bubble, .gc-msg.student_agent .bubble { background: var(--paper); }
.gc-msg.host .bubble { background: var(--paper-2); }
.gc-msg.teacher .bubble, .msg.tutor.teacher-reply .bubble {
  background: var(--paper); border-left: 3px solid var(--accent);
}
.gc-msg.system, .msg-note {
  text-align: center; font-size: var(--t-sm); color: var(--ink-3);
  padding: var(--s-2) 0;
}
.msg .meta {
  font-size: var(--t-xs); color: var(--ink-3); margin-top: var(--s-1);
  font-family: var(--font-num);
}
.sent-photo { max-width: 220px; display: block; margin-top: var(--s-2); border: 1px solid var(--rule); }

.composer, .group-chat-composer { display: flex; gap: var(--s-2); margin-top: var(--s-3); }
.composer input { flex: 1; }
.group-chat-composer { flex-direction: column; }
.group-chat-actions { display: flex; gap: var(--s-2); justify-content: flex-end; }
/* 拍照那个是 <label>（里面藏着 file input），不是 <button>——
   按钮样式只选了 button/.btn/.btn-ghost，label 拿不到边框，
   于是它跟旁边的麦克风按钮长得不一样。这里补齐。 */
.photo-btn {
  flex: none; width: 44px; height: 100%; min-height: 40px;
  display: inline-flex; align-items: center; justify-content: center;
  padding: var(--s-2); cursor: pointer;
  border: 1px solid var(--rule-strong);
  background: var(--paper); color: var(--ink-2);
  transition: background .12s, border-color .12s, color .12s;
}
.photo-btn:hover { background: var(--paper-2); color: var(--accent); border-color: var(--accent); }

.starter-prompts { display: flex; gap: var(--s-2); flex-wrap: wrap; margin-bottom: var(--s-3); }
.spinner { color: var(--ink-3); font-size: var(--t-sm); }

/* ── 整页自判 ─────────────────────────────────────────────── */
.selfcheck-bar {
  border: 1px solid var(--rule);
  padding: var(--s-4); margin-top: var(--s-5);
}
.sc-item { padding: var(--s-3) 0; border-bottom: 1px solid var(--rule); }
.sc-item:last-child { border-bottom: none; }
/* 自判结果的四种状态必须一眼可分（硬规矩第 5 条）。原来一律 ink-2 灰，
   学生得逐条读文字才知道哪几题要回头看。
   注意"通过"用 confirm 绿而不是主色——绿在这套配色里只表示"确认"。 */
.sc-status { font-size: var(--t-sm); color: var(--ink-2); }
.sc-status.pass     { color: var(--confirm); }
.sc-status.stuck    { color: var(--alert); font-weight: 600; }
.sc-status.mismatch { color: var(--alert); }
.sc-status.unclear  { color: var(--ink-3); }

/* ── 表格化的行：花名册/复盘/工单/各组 ─────────────────────── */
.roster-grid, .progress-list, .ticket-cols { display: grid; gap: var(--s-3); }
@media (min-width: 720px) {
  .roster-grid { grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); }
  .ticket-cols { grid-template-columns: 1fr 1fr; gap: var(--s-5); }
}
.roster-card, .qc-card, .report-row, .class-row, .guide-row, .ladder-row,
.profile-row, .faq-save-row {
  padding: var(--s-3) 0;
  border-bottom: 1px solid var(--rule);
  display: flex; align-items: center; gap: var(--s-3); flex-wrap: wrap;
}
.roster-card { display: block; border: 1px solid var(--rule); padding: var(--s-3); }
.roster-card .name { font-weight: 600; }
.roster-card .sid, .group-stat { font-size: var(--t-sm); color: var(--ink-3); }
.group-stat { margin-left: auto; font-family: var(--font-num); }
.report-row .count, .prog-chip { font-family: var(--font-num); font-size: var(--t-sm); }

.ticket-col-title {
  font-size: var(--t-sm); color: var(--ink-2); font-weight: 600;
  padding-bottom: var(--s-2); border-bottom: 1px solid var(--rule-strong);
  margin-bottom: var(--s-3);
}
.t-head { display: flex; align-items: baseline; gap: var(--s-2); font-weight: 600; }
.t-q { margin: var(--s-2) 0; }
.t-ctx, .t-stem { font-size: var(--t-sm); color: var(--ink-2); }
.t-stem-body { border-left: 2px solid var(--rule); padding-left: var(--s-3); margin-top: var(--s-2); }
.ticket-reply-form { margin-top: var(--s-3); }
.ticket-reply-actions { display: flex; gap: var(--s-2); margin-top: var(--s-2); }

/* ── 标签/状态 ────────────────────────────────────────────── */
.tag, .badge, .chip, .prog-chip, .unready-chip, .action-tag, .flag, .cat {
  display: inline-flex; align-items: center; gap: var(--s-1);
  font-size: var(--t-xs); padding: 1px var(--s-2);
  border: 1px solid var(--rule-strong); color: var(--ink-2);
  background: var(--paper); white-space: nowrap;
}
.chip-row { display: flex; flex-wrap: wrap; gap: var(--s-2); margin: var(--s-2) 0; }
.chip { cursor: pointer; padding: var(--s-1) var(--s-3); font-size: var(--t-sm); }
.chip:hover { border-color: var(--accent); color: var(--accent); }
.chip-on, .chip.active {
  background: var(--accent); border-color: var(--accent); color: #fff; font-weight: 500;
}
/* 状态色统一在文件末尾定义（见「状态色」一节），这里不重复 */

/* ── 四模式切换 ───────────────────────────────────────────── */
.mode-switch { display: flex; border: 1px solid var(--rule-strong); }
.mode-switch-btn {
  background: var(--paper); color: var(--ink-2); border: none;
  padding: var(--s-2) var(--s-4); font-size: var(--t-sm);
}
.mode-switch-btn + .mode-switch-btn { border-left: 1px solid var(--rule-strong); }
.mode-switch-btn:hover { background: var(--paper-2); color: var(--accent); }
.mode-switch-btn.active { background: var(--accent); color: #fff; font-weight: 500; }
.mode-switch-btn.active:hover { background: var(--accent-2); }
body[data-mode="class"] .homework-only { display: none !important; }
body[data-mode="homework"] .class-only { display: none !important; }

/* ── 课堂横幅 ─────────────────────────────────────────────── */
.class-banner, .stu-class-banner {
  border: 1px solid var(--rule); border-left: 3px solid var(--accent);
  background: var(--paper-2);
  padding: var(--s-3) var(--s-4); margin-bottom: var(--s-4);
}
.class-banner div, .stu-class-banner b { display: block; }
.stu-class-banner b { font-size: var(--t-md); }
.class-phase { font-size: var(--t-sm); color: var(--accent); font-weight: 600; }
.class-actions { display: flex; gap: var(--s-2); flex-wrap: wrap; margin-top: var(--s-3); }
.group-chat { border: 1px solid var(--rule); padding: var(--s-4); margin-bottom: var(--s-4); }
.group-chat-head { padding-bottom: var(--s-2); border-bottom: 1px solid var(--rule); }
.class-visit-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s-2); margin: var(--s-4) 0 var(--s-2);
  padding-top: var(--s-3); border-top: 1px solid var(--rule-strong);
}

/* ── 浮层 ─────────────────────────────────────────────────── */
.overlay {
  position: fixed; inset: 0; background: rgba(22, 24, 28, .45);
  display: flex; align-items: center; justify-content: center;
  padding: var(--s-4); z-index: 50;
}
.overlay-card {
  background: var(--paper); border: 1px solid var(--rule-strong);
  padding: var(--s-5); max-width: 520px; width: 100%;
}
.overlay-actions { display: flex; gap: var(--s-2); justify-content: flex-end; margin-top: var(--s-4); }

/* ── 富文本 ───────────────────────────────────────────────── */
.rich-p { margin: 0 0 var(--s-2); }
.rich-p:last-child { margin-bottom: 0; }
.rich-list { margin: 0 0 var(--s-2); padding-left: 1.3em; }
.rich-list li { margin-bottom: var(--s-1); }
strong, b { font-weight: 600; }
code { font-family: var(--font-num); font-size: .92em; background: var(--paper-2); padding: 1px 4px; }

/* ── 通用 ─────────────────────────────────────────────────── */
.hint, .sub, .report-empty, .empty, .note, .meta {
  color: var(--ink-2); font-size: var(--t-sm); line-height: 1.7;
}
.report-empty, .empty { color: var(--ink-3); padding: var(--s-3) 0; }
h2, h3 { font-weight: 600; letter-spacing: .01em; }
h3 { font-size: var(--t-base); margin: var(--s-4) 0 var(--s-2); }
a { color: var(--accent); }
hr { border: none; border-top: 1px solid var(--rule); margin: var(--s-4) 0; }

/* ── 手机（师生的主要使用方式）───────────────────────────────
   输入类控件字号必须 ≥16px，否则 iOS 聚焦时会自动放大页面。 */
@media (max-width: 860px) {
  .wrap { padding: var(--s-4) var(--s-4) var(--s-7); }
  header { padding: var(--s-3) var(--s-4); }
  .anchor-nav { padding: var(--s-2) var(--s-4); gap: var(--s-3); overflow-x: auto; flex-wrap: nowrap; }
  .anchor-nav a { white-space: nowrap; }
  .layout { grid-template-columns: 1fr; }
  input, select, textarea { font-size: 16px; }
  #transcript { max-height: 50vh; }
  .panel-body, .board-body { padding: var(--s-3); }
  header h1 { font-size: var(--t-base); }
}

/* ── 收尾：把上一版残留的观感统一掉 ───────────────────────────── */

/* 表单控件跟着主色，不要系统蓝 */
input[type="checkbox"], input[type="radio"] {
  width: 15px; height: 15px; accent-color: var(--accent);
  vertical-align: -2px; margin-right: var(--s-2);
}
label:has(> input[type="checkbox"]) { display: inline-flex; align-items: center; }

/* 工单条目：给标题一点层次，别让姓名/题号/状态挤成一条 */
.t-head { justify-content: space-between; }
.t-head .badge { margin-left: auto; }
#tickets .board-body > div > div { margin-bottom: var(--s-5); }

/* 录音按钮别撑成大方块 */
.rec-btn { padding: var(--s-2) var(--s-3); }

/* 手机上锚点导航自己横向滚，但**不许把文档撑宽**——
   overflow-x:auto 的元素若父级没夹住，整页会跟着横向滚。 */
body, .wrap { overflow-x: hidden; }
.anchor-nav { max-width: 100%; }

/* KaTeX：公式过长时在自己的容器里横向滚，**不许顶宽页面**。
   踩过的坑：只写 .katex-display 不够——撑宽页面的是行内公式渲染出的
   隐藏 <math>（MathML 那份），它不受 .katex 的样式管，实测在手机上
   把 390px 的页面顶到 440px。所以直接把 MathML 那份藏掉（KaTeX 渲染的
   HTML 那份才是给人看的，MathML 只为无障碍/复制），并给 .katex 加滚动。 */
.katex-display { overflow-x: auto; overflow-y: hidden; padding: 2px 0; }
.katex { max-width: 100%; }
.katex-html { overflow-x: auto; overflow-y: hidden; }
.katex .katex-mathml {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap;
}

/* 页头右侧那条说明在窄屏会顶宽页头——让它可以换行、并让出空间 */
header .badge { white-space: normal; text-align: right; line-height: 1.4; }
@media (max-width: 520px) { header .badge { display: none; } }

/* 工单回复那一行：录音 / 勾选 / 回复 三块等高对齐，不要各长各的 */
.ticket-reply-actions { align-items: stretch; }
.ticket-reply-actions > * { display: flex; align-items: center; }
.ticket-reply-actions label { flex: 1; gap: var(--s-2); font-size: var(--t-sm); color: var(--ink-2); }
.ticket-reply-actions .rec-btn { justify-content: center; min-width: 88px; }

/* 输入行三块等高 */
.composer { align-items: stretch; }
.composer input { min-height: 40px; }

/* ── 状态色：一眼看出哪些要动手 ─────────────────────────────────
   工单看板上「待处理」和「已回复」原来长得一模一样，老师得逐条读文字
   才知道哪些要处理——状态是这个页面最该被扫到的信息，必须上色。 */
.st-todo {
  color: var(--alert); border-color: var(--alert); background: var(--alert-wash);
  font-weight: 600;
}
.st-done { color: var(--ink-3); border-color: var(--rule); background: var(--paper); }

/* 分栏标题：待处理那一列的标题用红并加粗左边线，扫一眼就知道该看哪边 */
.st-todo-title {
  color: var(--alert); border-bottom-color: var(--alert);
}
.st-done-title { color: var(--ink-3); }

/* 已回复的卡片怎么"退下去"：见文末 .ticket-card 一节——用左边线变灰 +
   浅底，不用透明度。两种手段叠着会让文字发糊，反而更难读。 */

/* 其余状态词沿用同一套语义 */
.ok, .sign-yes { color: var(--confirm); border-color: var(--confirm); background: var(--confirm-wash); }
.stuck, .mismatch, .flag, .sign-no, .pending {
  color: var(--alert); border-color: var(--alert); background: var(--alert-wash);
}

/* 花名册的进度小标：已通过=确认绿，卡住=朱红，进行中=中性 */
.prog-chip.done  { color: var(--confirm); border-color: var(--confirm); background: var(--confirm-wash); }
.prog-chip.stuck { color: var(--alert); border-color: var(--alert); background: var(--alert-wash); }
.prog-chip.going { color: var(--ink-2); }

/* 卡点复盘的人次：数字本身就是"要不要管"的信号，给它颜色 */
.report-row .count { color: var(--alert); font-weight: 600; }
.report-row .head { display: flex; justify-content: space-between; gap: var(--s-3); width: 100%; }

/* 「还没开始」是中性事实，不是问题，别用红 */
.roster-card .current { color: var(--ink-3); font-size: var(--t-sm); }

/* ── 间距系统的落地 ─────────────────────────────────────────────
   用 flex + gap 把"区块之间"的距离交给容器统一管，不再让每个子元素
   各自写 margin-bottom——那正是间距失控的根源（谁都写一点，加起来没规律）。 */

/* 面板内容：直接子块之间是"不同用途的区块"，用 group 档 */
.panel-body, .board-body { display: flex; flex-direction: column; gap: var(--gap-group); }
.panel-body > *, .board-body > * { margin: 0; }

/* 课堂面板：横幅 / 地图 / 各组 / 操作，四个不同用途的区块 */
#class-live { display: flex; flex-direction: column; gap: var(--gap-group); }
#class-idle { display: flex; flex-direction: column; gap: var(--gap-item); }

/* 一个区块内部：标题和它的内容属于同一件事，贴紧 */
#class-map, #class-asking, #class-groups { display: flex; flex-direction: column; gap: var(--gap-tight); }
#class-map .hint:first-child { margin-bottom: calc(var(--gap-tight) * -1 + var(--s-1)); }

/* 列表里相邻的行：同一组，用 tight；行内部的元素用 inline */
.class-row, .report-row, .guide-row, .ladder-row, .profile-row {
  padding: var(--gap-tight) 0; gap: var(--gap-inline);
}
.class-row:first-child, .report-row:first-child { padding-top: 0; }
.class-row:last-child, .report-row:last-child { border-bottom: none; padding-bottom: 0; }

/* 操作区自成一块，和上面的内容拉开到 group 档，并加一条线划界 */
.class-actions, .ticket-reply-actions {
  gap: var(--gap-inline); padding-top: var(--gap-item);
  border-top: 1px solid var(--rule);
}

/* 说明文字紧跟它解释的东西，不要浮在中间 */
.panel-body > .hint:first-child, .board-body > .hint:first-child { margin-bottom: 0; }

/* 工单：一张卡片内部各部分是同一件事，用 item 档；卡片之间用 group 档 */
.ticket-card { display: flex; flex-direction: column; gap: var(--gap-inline); }
#ticket-board .ticket-cols > div { display: flex; flex-direction: column; gap: var(--gap-group); }
.ticket-col-title { margin-bottom: 0; }

/* 对话：一条消息里"谁说的"和"说了什么"是一件事，条与条之间才拉开 */
.msg, .gc-msg { display: flex; flex-direction: column; gap: var(--s-1); margin-bottom: var(--gap-item); }

/* 面板之间 */
.panel, .board { margin-bottom: var(--gap-block); }
.wrap { padding: var(--gap-group) var(--s-5) var(--gap-page); }

/* 交给容器 gap 之后，子元素自己就不能再带 margin——两者会叠加。
   实测：横幅的 margin-bottom:16px 撞上容器的 32px gap，变成 48px，
   比别的区块间距都大，读起来像"横幅和下面无关"。 */
#class-live > *, #class-idle > *,
.panel-body > *, .board-body > *,
#class-map > *, #class-groups > *, #class-asking > * { margin-bottom: 0; }

.class-banner, .stu-class-banner, .group-chat { margin-bottom: 0; }
.toolbar { margin-bottom: 0; }

/* ── 工单卡片：每一条是一张独立的卡 ───────────────────────────────
   原来只给了 flex 和透明度，没有容器——一列里几条工单糊在一起，
   读的人要靠空行猜哪句属于哪条。 */
.ticket-card {
  border: 1px solid var(--rule);
  border-left: 3px solid var(--alert);      /* 待处理：左侧一道红，扫一眼就找得到 */
  padding: var(--gap-item);
  background: var(--paper);
}
.ticket-card.answered {
  border-left-color: var(--rule-strong);     /* 已回复：左侧退成灰 */
  background: var(--paper-2);
}

/* 卡内层级：问题是主，元信息是次 */
.ticket-card .t-q { font-size: var(--t-md); line-height: 1.75; }

/* 老师自己的回复：和学生的问题区分开，用主色的左边线 + 浅底 */
.ticket-card .t-ctx {
  border-left: 2px solid var(--accent);
  background: var(--accent-wash);
  padding: var(--s-2) var(--s-3);
  color: var(--ink);
}

/* ── 可点的东西必须看起来可点 ─────────────────────────────────
   <summary> 默认继承正文样式，「查看题干」看上去就是一句话。
   给它颜色、指针、以及一个会转的三角。 */
summary {
  cursor: pointer; list-style: none;
  color: var(--accent); font-size: var(--t-sm);
  display: inline-flex; align-items: center; gap: var(--s-1);
  padding: var(--s-1) 0;
  user-select: none;
}
summary::-webkit-details-marker { display: none; }
summary::before {
  content: ""; width: 0; height: 0;
  border-left: 4px solid currentColor;
  border-top: 3.5px solid transparent;
  border-bottom: 3.5px solid transparent;
  transition: transform .15s;
}
details[open] > summary::before { transform: rotate(90deg); }
summary:hover { color: var(--accent-2); text-decoration: underline; }
details[open] > summary { margin-bottom: var(--s-2); }

/* ── 触摸设备：把点击区撑到手指够得着 ─────────────────────────────
   实测生产站点（iPhone 13 视口）量出来的：锚点导航 20px、复选框 15px、
   <summary> 28px。Apple 建议 44pt、Material 建议 48dp——20px 是"看得见
   但按不准"，对手机上的初一学生尤其糟。

   用 pointer:coarse 而不是宽度断点：判据是"用手指还是用鼠标"，跟屏幕
   多宽无关。桌面端的信息密度完全不受影响。 */
@media (pointer: coarse) {
  .anchor-nav a {
    padding-top: var(--s-3); padding-bottom: var(--s-3);
    display: inline-block;
  }
  summary { padding: var(--s-3) 0; }
  /* 原生复选框在手机上只有 15px 高，且不受 padding 影响，只能直接放大 */
  input[type="checkbox"] { width: 24px; height: 24px; }
  /* 复选框旁边的文字也要能点——手指按标签比按那个小方块容易得多 */
  label:has(> input[type="checkbox"]) {
    display: inline-flex; align-items: center; min-height: 44px;
  }
  /* min-height 对行内元素无效。**这一条踩过**：只写 min-height:44px，
     实测量出来还是 20px，因为 .selfcheck-upload-btn 是 display:inline。 */
  .selfcheck-upload-btn, .photo-btn {
    display: inline-flex; align-items: center; min-height: 44px;
  }
  .landing a { padding-top: var(--s-5); padding-bottom: var(--s-5); }
}
