/* ═══════════════════════════════════════════════════════════════
   app.css —— 深慧宝 · 全站样式
   原则：《平台建设制度》§〇1「安全 · 稳定 · 丝滑，体验美好」
   一、颜色语义只有一个来源：掌握度 p（§7.2 颜色单向链）——
       所以这里不提供任何"手动改色"的样式钩子，考点颜色一律由 JS 按 KPS.stateOf 赋值。
   二、响应式：桌面端左栏导航，窄屏折叠为顶部横向导航；所有栅格自动降为单列。

   ── 2026-09-22 配色收口（用户原话）──────────────────────────────────
     「关于页面的颜色，请阅读给你的制度，一要美雅，二必须护眼。
       **非阅读区一律绿色（60% 深度）**，阅读区的颜色或绿或蓝，颜色适当。」
   ⇒ 一句话的落法：**界面件归绿、内容件分绿蓝**。
     · 非阅读区 = 导航 / 页头 / 卡片头 / 表头 / 按钮 / 胶囊 / 步骤条 / 进度条 /
       页脚 / 标签 —— 一律绿族，标准档取**绿 60% 深度**（`--g60`），
       深浅只表达"主次与强弱"，不再借第二种色相说话。
     · 阅读区 = 正文 / 题干 / 解析 / 提示条 / 答疑 / 预览 / 结果卡 —— 可绿可蓝：
       知识类阅读走绿（`--g00` 底），**测评·检测·答题类阅读走蓝**（`--b00` 底），
       于是"我在学习"和"我在被测评"两件事，颜色上就先分开了。
     · 蓝族从此**不再用于任何版面件**（导航、按钮底、卡片头…），这是本次收口的硬线，
       `tools/audit-ui.mjs` 的 ⑨ 会逐条拦（含自证用例，见该文件）。

   ── 护眼是**区间**判据，不是"越淡越好"（§1.4）───────────────────
     面板不用纯白（#fbfdfc 一档）、正文不用纯黑（#16283a）、正文 ≥14px、行高 ≥1.75、
     实底按钮上的白字对比度 ≥4.5:1（8 档实算，见 audit-ui ⑦c）。
     纯白配纯黑，瞳孔要在最大收缩与最大舒张之间反复切换 —— 那是"看久了眼睛累"的
     主因，跟字号大小无关。所以低对比不是妥协，是设计要求；但低过头同样伤眼，
     所以下面每一档的用途都写明了，别把浅底当深底用。
   ═══════════════════════════════════════════════════════════════ */

:root {
  /* ══ 一、绿族 · 五档深度（非阅读区唯一用色）══
     深度按 0 / 20 / 40 / 60 / 80 排：**越深越"重"，越重越"主"**。
     · 0%  = 大面积浅底（阅读区底、表格 hover、次行动底）
     · 20% = 次级面（卡片头、标签、徽章底）
     · 40% = 描边 / 分隔 / 已完成的浅标（**不承白字**，对比只有 1.9:1）
     · 60% = **非阅读区标准档**：按钮实底、当前步骤、选中胶囊（白字 4.99:1 ✓）
     · 80% = 主行动 / 深底 / 页头（白字 9.0:1 ✓）
     40% 那一档刻意留着但"不许承白字"：它是描边档，不是填充档 ——
     把浅色当深色用，是这一类页面最常见的"看着不对、说不出哪不对"。 */
  --g00: #eef6f1;
  --g20: #d9eae1;
  /* 25% 档（用户令 2026-09-23 第二轮：「导航栏底色的绿色调到25%」）——
     五档语（0/20/40/60/80）里原本没有 25 这一档，新增一个：
     取 --g20 与 --g40 之间 **25% 位**的 sRGB 插值 = (202,226,215)。
     为什么不就近取 --g20 / --g40 了事：用户给的就是 25%，
     四舍五入到 20 或 40 都等于替他改口径。
     它**只服务导航栏底色**，不进按钮底色的白名单（audit-ui ⑥ 仍只认那五档）。 */
  --g25: #cae2d7;
  --g40: #9ecbb7;
  --g60: #2f7d61;   /* ← 非阅读区标准档（用户令：60% 深度） */
  --g80: #1b5443;
  --g60d: #276a52;  /* 按下态：深一档 */
  --g80d: #143f33;
  --gline: #c9e3d5; /* 绿族描边 */

  /* 兼容名：全站既有引用（86 处）一字不改，只把"老名字指向哪里"改掉 ——
     每多改一处引用，就多一个改坏的机会；而语义名的好处正是"指哪儿改一处"。 */
  --green-1: var(--g00);
  --green-2: var(--g60);
  --green-3: var(--g80);
  --green-2d: var(--g60d);
  --green-3d: var(--g80d);
  --green-line: var(--gline);
  --green-deep: var(--g80);
  --brand: var(--g80);        --brand-d: var(--g80d);   --brand-soft: var(--g00);
  --learn: var(--g60);        --learn-d: var(--g60d);   --learn-soft: var(--g00);
  --ok: var(--g60);           --ok-d: var(--g60d);      --ok-soft: var(--g00);
  --teal: var(--g80);         --teal-soft: var(--g00);

  /* ══ 二、蓝族 · 阅读区专用（阅读底 + 阅读区内的链接与强调）══
     用户令：「阅读区的颜色或绿或蓝」。蓝族在本次收口后**只出现在阅读/答题语境**：
     测评与检测的正文底、答题选项、答疑气泡、预览块、三值对照结果卡、行内链接。
     它一旦出现在导航、按钮底、卡片头上，就是"非阅读区混进了蓝" ⇒ audit-ui ⑨ 报红。 */
  --b00: #e9f0f7;   /* 阅读底 · 蓝 */
  --b60: #1d6ba4;   /* 阅读区强调 */
  --b80: #12456e;   /* 阅读区内链接 / 深字（对 --b00 底 8.3:1） */
  --b60d: #175a8b;
  --b80d: #0d3556;
  --bline: #cbdef0; /* 蓝族描边 */
  --blue-1: var(--b00);
  --blue-2: var(--b60);
  --blue-3: var(--b80);
  --blue-2d: var(--b60d);
  --blue-3d: var(--b80d);
  --blue-line: var(--bline);
  --blue-mid: var(--b60);

  /* ══ 三、中性表面（承载阅读，护眼档）══
     底色带一点绿（--bg / --panel-2），与绿族同族；卡片面（--panel）停在近白一档：
     它是**读的地方**，近白最不干扰字；纯白才是刺眼的那个。 */
  --bg: #f1f6f3;
  --panel: #fbfdfc;
  --panel-2: #f3f8f5;
  --line: #dbe7e0;
  --line-2: #e8f1ec;
  --ink: #16283a;   /* 正文：不纯黑 */
  --ink-2: #3f5b74;
  --ink-3: #5a7391;
  /* ↑ --ink-3 是元信息色（11.5px 的小字），原先 #6e849c 对面板只有 3.7:1 ——
     小字对比不足时，眼睛要"用力看清"，比大字更累。收到 #5a7391 后
     对面板 4.76:1、对页面底 4.54:1，都过了 4.5 线，同时仍与 --ink-2 拉开一档。 */

  /* 提醒色：**不是按钮底色**（用户令：非阅读区一律绿、按钮不用橙）。
     橙色只留给提示条 / 标签 / 警示数字这类"文字语义"。 */
  --warn: #b3601c;
  --warn-d: #96500f;
  --warn-soft: #fbf1e7;
  --warn-deep: #8a4a10;

  /* 撤销破坏色：全站**唯一**保留的红，且只描边、不填色块。
     删除 / 作废 / 退回必须一眼可辨 —— 这是安全要求，绿族表达不了"这一步不可逆"。
     所以它是有意保留的例外，不是漏改（§1.1 注）。 */
  --danger: #a83a3a;
  --danger-soft: #f9eded;
  --danger-line: #e3c2c2;

  /* ══ 三·五、军衔徽记四族四色（用户令 2026-09-22 第七轮 · §1.5 具名例外）══
     用户原话：「军衔前面的**牌/章/星**，要用不同颜色加于区分，**将星定为中国红的五角星**，
     其他由你安排。」
     ⇒ 这是对"非阅读区一律绿"的**具名例外**（谁点的、原话，见 docs/BNJH-UI-2026-001 §1.5）。
       为什么非破不可：阶梯表左边那一个小方块用"绿的深浅"表达级别高低是够的（12px 的面积，
       深浅可辨），但用户要的是**三种形制可辨**（牌 / 章 / 星），形制只能靠"形状 + 色相"
       一起说 —— 单靠深浅，在 15px 上分不出"一杠"与"一颗星"。
     四族顺序按"兵 → 尉 → 校 → 将"冷到暖：银灰 → 靛蓝 → 金 → 中国红。
     与掌握度七色**不冲突**：这四色只出现在**军衔徽记**这一处（形状是折杠/横杠/菱形/五角星），
     而掌握度七色是"单个考点掌握到什么程度"的色块 —— 形状不同、位置不同，不会串味。 */
  --rk-chev: #8792a2;  /* 兵/士 · 折杠（银） */
  --rk-bar: #2f6fa8;   /* 尉官 · 牌（靛蓝，一杠=中尉 · 两杠=上尉） */
  --rk-badge: #b8860b; /* 校官 · 章（金，菱形） */
  --rk-star: #e60012;  /* 将官 · 星（**中国红五角星**，用户点名） */

  /* ══ 三·六、板块框色六色（用户令 2026-09-23）══
     用户原话：「**所有板块都有框，但框之间的颜色必须不同。**」

     ⇒ 它是对"边框只用一种灰绿描边"的修改：同一父级下相邻的两个板块，框色不得相同。
     取值有两条约束，都不是随手挑的：
       · **只作装饰，不表语义** —— 框色不表示掌握度、不表示对错、不表示状态。
         这是它敢用绿/蓝/金/紫/青/赭六种色相的前提：掌握度七色（赤橙黄绿青蓝紫）
         是"一个考点学到什么程度"的专用语，框色若也按分数染色，学生会把"框是蓝的"
         读成"这科还行"。所以**任何地方都不许按数据去选框色** —— 只按"第几个"轮转。
       · **1px 细线上的可辨性** —— 取中低饱和、中明度的六色：相邻两色在近白卡面上
         一眼能分出，又都不到"抢眼"的程度（对比约 1.9–2.4:1，属装饰性描边档，
         与 --g40 那种"不许承白字"的浅描边不同：它承担的是"分块"，不是"强调"）。
     轮转规则见本文件末尾「板块框色轮转」段。 */
  --fr1: #8fb8a4;   /* 松绿 */
  --fr2: #93b0d4;   /* 靛蓝 */
  --fr3: #c9ab7c;   /* 赭金 */
  --fr4: #a99ac9;   /* 紫藤 */
  --fr5: #8cbdb9;   /* 青瓷 */
  --fr6: #cf9d9d;   /* 绛粉 */

  /* ══ 四、页面宽度（§2.1 · 三档）══
     页面不得自己写死宽度：外框用 --w-page，正文区用 --w-content，长文用 --w-read。 */
  --w-page: 1440px;
  --w-content: 1200px;
  --w-read: 720px;
  --w-side: 236px;
  /* 通栏蓝条的高度 —— 侧栏的 `top` 与高度都要减掉它（见 .side）。
     这里写的 74px 只是**兜底**：`shell()` 渲染完会实测一次覆盖掉。
     为什么要有兜底：哪怕 JS 还没跑完，也不能让侧栏先按 `top:0` 闪一下、
     再跳下来 74px —— 那种"进场闪动"比数字不准更显眼。 */
  --h-tb: 74px;

  --r: 12px;
  --r-sm: 8px;
  --sh: 0 1px 2px rgba(16, 24, 40, .04), 0 1px 3px rgba(16, 24, 40, .06);
  --sh-lg: 0 8px 24px rgba(16, 24, 40, .10);
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  background: var(--bg); color: var(--ink);
  /* 护眼三件套：正文不小于 14px、行高不低于 1.75、字色不用纯黑（--ink）。
     行高 1.65 → 1.75 不是为了"好看"：中文没有词间空格，行距不够时换行处
     视线容易串到下一行，读三行就要重读一次。 */
  font: 14px/1.75 -apple-system, "PingFang SC", "Microsoft YaHei", "Segoe UI", system-ui, sans-serif;
  -webkit-font-smoothing: antialiased;
}
a { color: var(--b80); text-decoration: none; }          /* 链接属"读的东西" ⇒ 阅读区蓝 */
a:hover { text-decoration: underline; }
h1, h2, h3, h4 { margin: 0; font-weight: 650; line-height: 1.35; }
h1 { font-size: 20px; } h2 { font-size: 16px; } h3 { font-size: 15px; } h4 { font-size: 14px; }
code, .mono { font-family: ui-monospace, "SF Mono", Consolas, monospace; font-size: .92em; }
hr { border: 0; border-top: 1px solid var(--line); margin: 14px 0; }

/* ── 布局（宽度只走令牌，见 BNJH-UI-2026-001 §2.1 / W1）──
   三层：`.shell`（纵向：顶栏 + 外框）→ `.app`（横向：侧栏 + 正文）→ `.main`。
   `.shell` 是 2026-09-22 第七轮才加的：蓝条要"从最左开始"，就不能再待在 `.main` 里
   —— `.main` 有 24px 左内边距、右缘也到不了窗口边，条永远两头不靠。详见 §2.3 W7。 */
.shell { min-height: 100vh; display: flex; flex-direction: column; }
.app { display: flex; flex: 1 1 auto; min-height: 0; width: 100%; max-width: var(--w-page); margin: 0 auto; }
.side {
  width: var(--w-side); flex: 0 0 var(--w-side);
  /* ── 底色 25% + 绿 40% 边框（用户令 2026-09-23 第二轮）─────────────────
     用户原话：「**导航栏底色的绿色调到25%，框框调为绿色，绿色深度40%。**」
     · 上一轮是「底色较深（绿色40%），加蓝框」；这一轮底色退回 25%、蓝框换绿框。
       底色取新档 `--g25`（= g20↔g40 的 25% 位插值 #cae2d7），框取 `--g40`。
     · 框改绿之后，`.side` **不再引用任何蓝族令牌** ⇒ 上一轮登记在
       tools/audit-ui.mjs 里的具名例外「.side（导航栏蓝框）」**随之撤销**。
       例外的坏法从来不是"没生效"，而是**外溢** —— 留一条已经用不上的例外，
       就等于给"这一块可以用蓝"永久留了后门。
     · 底色变浅，压在它上面的小字对比**全部上升**（--g80 纯色 4.88→6.42:1；
       叠 .78 后 3.28→3.97:1；叠 .72 后 2.96→3.49:1）—— 这三处不必再动。
     · 真正要补救的是**当前格**：`--g20` 压在 `--g25` 上只剩 **1.093:1**，
       等于隐形。故把 `.nav a.on` / `.nav a.sub` 的常态底挪到 `--g40`（1.316:1），
       hover 挪到 `--g00`（1.242:1）—— 两处都在下面各自留了话。
       注：`--g40` 在绿族里的原本职务是**描边档**（"不许承白字"）；这里承的是
       `--g80`(#1b5443) 与 `--b80`(#12456e) 两种深字，实测 4.88:1 / 5.56:1，都过 4.5。 */
  background: var(--g25);
  border: 2px solid var(--g40);
  padding: 16px 12px;
  /* 顶栏常驻（sticky），侧栏就贴在它下沿 —— 所以 top 与高度都要减去顶栏那一截，
     否则侧栏底部会被顶栏顶出视口 74px，多出一条永远滚不到的空白。
     `--h-tb` 由 `shell()` 渲染后**实测写入**，不写死：顶栏在窄屏会换行变高，
     写死 74px 就会露馅。写的是 CSS 变量而不是元素内联样式，样式仍全在 CSS 里。 */
  position: sticky; top: var(--h-tb); height: calc(100vh - var(--h-tb));
  /* 2026-10-25：**不动这一层**。侧栏仍整条可滚、导航一项不少（这是上一轮用
     「4 个导航项被卷进滚动条」换来的教训）。
     印章改用 `position:sticky;bottom:0` 钉在滚动视口下沿 —— 见 `.seal-wrap`。 */
  overflow-y: auto;
}
.brand { display: flex; align-items: center; gap: 9px; padding: 4px 8px 16px; }
.brand i {
  width: 30px; height: 30px; flex: 0 0 30px; border-radius: 9px;
  background: linear-gradient(135deg, var(--g60), var(--g80));
  color: #fff; display: grid; place-items: center; font-style: normal; font-weight: 700; font-size: 13px;
}
.brand b { font-size: 14.5px; letter-spacing: .2px; color: var(--g80); }
/* 侧栏底色由 --g00 升到 --g40 之后（用户令 2026-09-23），这里两行小字要跟着加深：
   `--ink-3`(#5a7391) 对 --g40(#9ecbb7) 只有 **2.6:1** —— 11px 的字在这条线上
   是"要用力看"的程度。改 `--g80`(#1b5443) 后是 **7.1:1**；再叠 .78 的不透明度
   弱化成元信息（合成后约 4.9:1），既退到"次要"又仍过 4.5 线。
   这一处不改，用户那句"较深"就会以"小字看不清"为代价 —— 那不算改对了。 */
.brand span { display: block; font-size: 11px; color: var(--g80); opacity: .78; font-weight: 400; }
.nav { display: flex; flex-direction: column; gap: 2px; }
.nav a {
  /* ★ 2026-10-25 用户令：「所有角色的导航栏都按这个格式设置！」（参照＝学生学习台左栏）
     ⇒ 原来只给学生的 16px / 10px 12px，这一轮**升为全站唯一一档**：
       九个工作台的左栏从此同一字号、同一行距，一条一列、左对齐。
     为什么不再保留 13.5px：十几个台各写一档，结果是"学生这一栏宽、教师那栏窄"，
     同一个平台里换一个身份就换一套密度 —— 用户点名的就是这个不一致。
     `--g80` 对 --g40 底 4.88:1、对 --g25 底 6.42:1，放大字号只升对比、不降。 */
  display: flex; align-items: center; gap: 9px; padding: 10px 12px; border-radius: var(--r-sm);
  color: var(--g80); font-size: 16px; line-height: 1.75;
  /* 未读小数（.nav-unread）要贴在**这一格**的右上角，故这一格必须自成定位基线。
     只加 position 不改任何尺寸，既有布局一格不动。 */
  position: relative;
}
/* ── 未读条数（用户令 2026-10-22：「『师生交流』的右上角要标注未读信息条数」）──
   配色守本工程既有的色法（app.css 顶部那段）：**橙只做文字语义、红只描边不填色块**。
   所以不用"红底白字"那种通用未读牌，而是 `--warn-soft` 底 + `--warn-d` 字 + 橙描边：
   它是"警示数字"，正是那条规矩允许橙出现的地方；压在绿族左栏上也照样跳得出来。
   right:6px / top:5px 是让药丸**骑在词尾右上角**（不压字）；学生台字号 16px 时
   那一格 padding 10/12px，6/5 仍在圆角内，不会顶出格子的边界。 */
.nav a .nav-unread {
  position: absolute; top: 5px; right: 6px;
  min-width: 16px; height: 16px; padding: 0 4px;
  border-radius: 8px; border: 1px solid var(--warn);
  background: var(--warn-soft); color: var(--warn-d);
  font-size: 11px; line-height: 14px; font-weight: 700; font-style: normal;
  text-align: center; letter-spacing: 0;
}
.nav a .nav-unread[hidden] { display: none; }
.nav a:hover { background: var(--g00); text-decoration: none; }
/* ── 当前格与悬停：**成对挪动，不许只改一边**（用户令 2026-09-23 第二轮的连带）──
   侧栏底由 `--g40` 退回 `--g25` 之后，原来那一对取值（当前格 = hover = `--g20`）
   压在 `--g25` 上只剩 **1.093:1** —— 数值上"改过了"，屏幕上等于没画。
   这一点光看样式表看不出来：`--g20` 是个正当的档位，文件里没哪一行写错。
   故本档取**深侧**：当前格 = `--g40`（1.316:1）、hover = `--g00`（1.242:1），
   两者分居底色两侧 —— hover 是"抬起来"，当前格是"沉下去"，不会被认错。
   ★ 老令（用户 2026-09-22 第三轮「把胶囊的背景也与上下的蓝条条一致！」）仍然有效：
   它由 `audit-student` 拿**实际计算色**比对 `.nav a.sub` 与 `.nav a.on:not(.sub)`，
   不手抄常量 —— 所以下面 `.nav a.sub` 的常态底必须与这里**同时**改成 `--g40`；
   只改一边，当场报红「二级胶囊与一级项不同面」。那条判据不是形式主义：
   它证的是"这两个面真是同一个面"，而那正是用户当初要的东西。 */
.nav a.on { background: var(--g40); color: var(--g80); font-weight: 600; }
.nav .grp { font-size: 12.5px; color: var(--g80); opacity: .72; padding: 14px 10px 5px; letter-spacing: .5px; }

/* ══ 「辅助功能」折叠组（★ 2026-11-06 用户令）══════════════════════════════════
   用户原话：「辅助功能（包括：弱项检测、试卷评判、查找、我的天赋、选科与志、平台说明）
   ——此功能非点击处于收起状态」。
   ── 为什么是 `<details>` 而不是 div + JS 开合 ──
     收起态**就是它的默认**（没有 `open` 属性即收起），不需要脚本参与：
     脚本挂了最多是"点开不了"，不会出现"一块空白面板"这种坏得莫名其妙的状态；
     而且它天生可键盘（Tab 到 summary、回车开合）、**浏览器页内查找会自动展开**要找到的那一条。
     本工程 login.html / redeem.html 的邀请码区已用过同一招并记过档，这里沿用，不引第二套开合件。
   ── 三个取值与 `.nav a` 严格同档（16px / 10px 12px / --r-sm）──
     它不是"另一种导航项"，它就是导航项，只是下面挂着一个抽屉。字号或内距差一档，
     这一列就会出现"一行比别的行胖"的错位感 —— 那正是 2026-10-25 那次统一要治的病。 */
.nav .navfold { margin: 0; }
.nav .navfold > summary {
  display: flex; align-items: center; justify-content: space-between; gap: 9px;
  padding: 10px 12px; border-radius: var(--r-sm);
  color: var(--g80); font-size: 16px; line-height: 1.75;
  cursor: pointer; position: relative;
  list-style: none;                    /* 去掉 <summary> 的默认三角（下面再兜 WebKit 一家） */
  -webkit-user-select: none; user-select: none;
}
.nav .navfold > summary::-webkit-details-marker { display: none; }
.nav .navfold > summary::marker { content: ''; }
.nav .navfold > summary:hover { background: var(--g00); }
.nav .navfold > summary:focus-visible { outline: 2px solid var(--g60); outline-offset: 1px; }
/* 三角只做"这里能展开"的提示，跟着开合转 90°（▸ → ▾）。不用图标字体、不引 SVG：
   它是这一列里唯一的方向符，纯字符就够，也不占额外请求。 */
.nav .nf-ar { flex: 0 0 auto; font-size: 11px; line-height: 1; opacity: .62;
  transition: transform .16s ease; }
.nav .navfold[open] .nf-ar { transform: rotate(90deg); }
/* 站在抽屉里某一格时，母格加粗一档（**不再铺一次 --g40 底**）：
   否则上下两行同时沉底，看起来像"同时站在两个地方"。
   这个类由 student.js 的 route() 挂 —— 它才知道当前站在哪一格。 */
.nav .navfold.has-on > summary { font-weight: 600; }
/* 抽屉体内缩进 10px：让六个子项左缘比母格退一格，读起来是"里面"。
   子项本身仍是 `.nav a.sub`（16px / 10-12 / 透明底 / 当前格 --g40），一个字没改 ——
   与教师台那套二级项同一个面，不需要第二套样式。 */
.nav .navfold-body { padding: 2px 0 4px 10px; }
@media print { .nav .navfold { display: none; } }   /* 抽屉是操作件，不进打印稿 */

/* ── 「查找」这类动作格（★ 2026-11-06）：形制与 `.nav a.sub` 完全同档 ──────────
   它长得像导航项、但点下去是**唤出一层浮窗**，不改地址、不改所在页。
   与 `<a>` 的差别只有两处：`<button>` 自带的边框/底色/字体要显式清掉（不写会出来一枚灰按钮），
   以及文字对齐要显式写 left（`<button>` 默认居中）。 */
.nav .nav-act {
  display: flex; align-items: center; gap: 9px; width: 100%;
  margin: 0; padding: 10px 12px; border-radius: var(--r-sm);
  background: transparent; border: 0;
  color: var(--g80); font-family: inherit; font-size: 16px; line-height: 1.75;
  text-align: left; cursor: pointer; position: relative;
}
.nav .nav-act:hover { background: var(--g00); }
.nav .nav-act:focus-visible { outline: 2px solid var(--g60); outline-offset: 1px; }
/* ── 学生台左栏字号放大的**来历**（规则已并入上面 `.nav a`，这里只留痕）──
   2026-10-06 用户令「导航栏的文字，适当放大」：13.5px → 16px，组标题 11 → 12.5px，
   行内边距同步 8/10 → 10/12px。当时**只给学生台**（`.shell[data-role="S"]`），
   理由是"其余三台的左栏与它们页面的信息密度是配套的"。
   ★ 2026-10-25 用户令「所有角色的导航栏都按这个格式设置！」⇒ 这两条特化规则已删，
     取值升进 `.nav a` / `.nav .grp` 本身。留着它们只会变成"同一份取值写两遍"，
     改一处忘一处就又不一致了（本工程记过的坑）。
   二级胶囊 `.nav a.sub` 见下面那一段：这一轮同样拍平成一级样式。 */
.main { flex: 1 1 auto; min-width: 0; width: 100%; max-width: var(--w-content); padding: 20px 24px 48px; }
/* ── 宽版页：**已取消**（2026-09-22 第六轮撤销，留这段是为了不再犯）──
   上一轮为「师生交流」开过 `.app.wide`：让正文按 `66.7vw` 突破 `--w-content`。
   用户否掉了它：「师生交流的页面，**不符合页面的制度规定，必须修正**。」
   教训：**不能用"破制度"去换"这一页更大"**。破一条宽度制度，代价是这一页与
   其他页不再是同一个系统（侧栏位置会随页漂移、正文宽度有了第二个来源）；
   而用户真正嫌的其实是**页内留白** —— 往来容器过宽、气泡只占其中一角，看着空。
   那是**页内**的事，用页内的办法解决（见 `stu-talk.js`：1:2 改 2:1、气泡撑满容器），
   页宽本身照旧走 `--w-content`。规范 §2.3 的 W6 同步撤除 —— 制度里不留死条文。 */
/* ── 顶栏 = 蓝底横条（用户令 2026-09-22 第六轮）──
   「每一页的最上方，都要有这个内容，**蓝底的横条，蓝色深度 80%**。」
   这一句与更早的"非阅读区一律绿"（也是用户亲口定的）**直接冲突**。
   按"后令覆盖前令"执行，并且**不装作没冲突**：
     · 取蓝族 80% 档 `--b80`（＝"蓝色深度 80%"）；
     · 这是**用户点名的具名例外**，已登记进 `tools/audit-ui.mjs` 的 `NAMED_EXCEPTION`。
       为什么登记在"具名例外"而不是 `READING_SEL`：顶栏**不是读的东西**，它是界面件，
       只是被点名破例。塞进阅读区表里，下一个人会以为"这里本来就是阅读区"，
       然后照着这个误解把别处也改成蓝 —— 表撒了谎，代码就守不住。
     · 栏内文字：`#fff` on `--b80` = **9.99:1**；标签用 16% 白叠在深蓝上（≈ 中蓝），白字仍有 7:1 上下。
       注：注释里**不写六位色值** —— audit-ui ③ 是全文扫描的，写在注释里也算"越权色值"，
       报红之后人还得去分辨"这是代码还是注释"。少写一个色值，少一次假红。
     · 按钮仍是绿族 —— "界面件一律绿"没有被这一条取消，被点名的只有这块底色。 */
.topbar {
  /* 通栏横条：**从窗口最左到最右**，无圆角、无外边距（用户令 2026-09-22 第七轮
     「蓝色横条要从最左开始」）。改前它挂在 `.main` 里，左缘被侧栏 + 24px 内边距
     顶到 260px（1440）／492.5px（1920），右端还差 39／275px 到不了窗口边 —— 两头不靠。
     拆成两层之后：**底色**归 `.topbar`（通栏），**排版**归 `.topbar-in`（对齐外框）。
     好处是以后改条的形状碰不到字的位置，改字的位置也碰不到条。 */
  position: sticky; top: 0; z-index: 20;
  background: var(--b80);
}
.topbar-in {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  flex-wrap: wrap;
  max-width: var(--w-page); margin: 0 auto;
  /* 左内边距 = 侧栏宽 + 正文左内边距 ⇒ 标题左缘正好压在**正文列**的左缘上，
     与改前**一像素不差**（1440 下 260、1920 下 492.5）。
     用户要挪的是"横条"，不是条里的字；把字一起挪过去，等于顺手改了没让改的东西。
     用 token 相加而不是写死 260px：外框在宽屏居中时左侧有留白（1920 下 232.5），
     写死的话文字就跟外框脱开了。 */
  padding: 12px 24px 12px calc(var(--w-side) + 24px);
}
.topbar h1 { color: #fff; }
.topbar .who { font-size: 13px; color: rgba(255, 255, 255, .86); }
.topbar .tag { background: rgba(255, 255, 255, .16); color: #fff; border-color: transparent; }
.topbar .tag.gray { background: rgba(255, 255, 255, .1); color: rgba(255, 255, 255, .84); }
/* ══════════ 前进 / 后退（顶栏右上角，2026-10-21 用户令）══════════
   用户原话：「每页都要设置"前进/后退"键。后退能连续退几步合适？」
   行为与步数在 app.js 的 NAV 模块里（连退上限 12 步、退到本轮起点即停）。
   这里只管长相：白描边 + 半透明白底，落在蓝条上不抢标题的视线；
   禁用态**不是隐藏**而是变淡 —— 学生要看得见"这个键在那、只是现在没得退"，
   直接拿掉会让他以为每页的键长得不一样。 */
.topbar-right { display: flex; align-items: center; gap: 12px; flex: 0 0 auto; }
.navbtn { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; }
.navbtn .nb {
  width: 34px; height: 34px; display: grid; place-items: center; padding: 0;
  border-radius: 9px; border: 1px solid rgba(255, 255, 255, .38);
  background: rgba(255, 255, 255, .12); color: #fff; cursor: pointer;
  transition: background .12s, border-color .12s, opacity .12s;
}
.navbtn .nb:hover:not(:disabled) { background: rgba(255, 255, 255, .24); border-color: rgba(255, 255, 255, .62); }
.navbtn .nb:active:not(:disabled) { background: rgba(255, 255, 255, .34); }
.navbtn .nb:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
.navbtn .nb:disabled { opacity: .3; cursor: not-allowed; }
.navbtn .nb svg { display: block; }
.navbtn .nb-base { margin-left: 2px; }
/* 学生台页头是"大一倍"的那一版（40px 台名），按钮跟着放大一档，
   否则 34px 的键在 148px 高的蓝条里像两颗米粒。窄屏回落。 */
.shell[data-role="S"] .navbtn .nb { width: 42px; height: 42px; border-radius: 11px; }
@media (max-width: 720px) {
  .shell[data-role="S"] .navbtn .nb { width: 34px; height: 34px; border-radius: 9px; }
  .topbar-right { gap: 8px; }
}
/* ── 学生台页头加高一倍、字号放大一倍（用户令 2026-10-06）─────────────────────
   原话：「学生学习台，蓝色页台要大一倍，学生学习台的字要大一倍，
           林一，初三1班，中士大一倍」。
   三条改的是**同一个东西的三处尺寸**（条的高度 / 台名字号 / 身份行字号），
   所以写在一条里，免得后来人只看见其中一处、把另外两处当成"没改"。
   · 高度怎么"大一倍"：不写死高度，靠上下内边距 12px→24px 撑；
     字号翻倍后自然更高，实测 74px → 约 148px，正好一倍。
   · 只对**学生台**生效（`.shell[data-role="S"]`）—— 教师台/管理台的顶栏
     是"工作台"的头，学生台是"学生学习平台"的头，两者的阅读重量不是一回事。
   · 只借既有令牌与白字，不新增色值（`#fff` 那句原样沿用）。
   · 窄屏不跟着翻倍：40px 的台名在手机上会顶掉半个屏，那里回落成半档。 */
/* ── 学生台页头「台名顶格」（用户令 2026-10-06：「学生学习平台，左边顶格」）────
   撤掉两处左留白，字才可能"顶格"：
     ① 侧栏偏移 `calc(var(--w-side) + 24px)` = 236+24 = **260px**
        （原意是让台名压在正文列左缘上；但学生台页头已放大一倍，40px 的台名
          再去贴正文列，左侧就空出小半个屏 —— 用户看图点掉的正是这一块）；
     ② `max-width: var(--w-page)` + `margin: 0 auto` 的宽屏居中
        （1920 下外框左侧另有 (1920−1440)/2 = **240px** 留白，与 ① 叠加近 500px）。
   条本身仍是通栏（老令「蓝色横条要从最左开始」不动），只把**条里的字**顶到左缘。
   左内边距取 24px，与上/下/右同值：顶格 ≠ 贴死在屏幕边上，四边同宽才是"格"。 */
.shell[data-role="S"] .topbar-in { max-width: none; padding: 24px; }
.shell[data-role="S"] .topbar h1 { font-size: 40px; }
/* ── 第二行**适当缩小**（用户令 2026-10-06：「第二行：林一等文字，适当缩小」）──────
   上一轮是"三处一起翻倍"（台名 40 / 身份行 26 / 军阶徽 23）—— 用户看过图之后
   对第一行没有意见，只把**第二行**收回来一档：26 → **20px**。
   为什么落在 20 而不是回退到翻倍前的 13px：页头是"台名 + 身份"两级，
   台名 40 而身份行 13 时，第二行会小到像页脚注记、看不出它是"这个人"的说明；
   20px 正好是台名的**一半**（40 : 20），两级一眼分得开，又不抢第一行。
   只改字号、不动位置 —— 第二行仍压在正文列左缘那条竖线上（见下面那道 `.who` 规则）。 */
.shell[data-role="S"] .topbar .who { font-size: 20px; line-height: 1.4; }
/* ── 学生台页头**第二行**保持原位（用户令 2026-10-06：
      「学生学习平台，左边顶格，第二行的林一等文字保持原位置」）──────────────
   第一行「学生学习平台」已顶格到左缘 24px；第二行**不跟着走** ——
   它原来的位置是**压在正文列左缘**上的那条竖线（1440 下 260px = 侧栏 236 + 正文内边距 24），
   跟着一起顶格就会与下方内容错开整整一栏，读起来像整块歪了。
   算式 = 承自 `.topbar-in` 的 24px + 侧栏一栏宽 + 外框在宽屏的居中留白。
   留白必须与 `.app { max-width: var(--w-page); margin: 0 auto }` **永远同值**，
   故由 student.js 量 **`.app` 自身的真实左缘**写进 `--tb-shift`（与"外框居中"由构造同源）。
   ★ 为什么不手算（这两条都踩过）：
     · `100vw` 把竖向滚动条也算进去，而 auto 居中不算 ⇒ 1920 窗口下第二行比正文列
       **右偏 7px**（实测 who 485 vs 正文列 478；1280 / 1440 下两者本来就相等）；
     · 改 `clientWidth` 量也不行 —— 量得太早（首屏还没填满、滚动条还没出现）会偏大。
     改成量 `.app` 左缘后，1280 / 1440 / 1600 / 1920 四档实测**差 0px**。
   JS 未跑（或抛异常）时 `--tb-shift` 取 0 —— 此时 ≤1440 的窗口仍与正文列严格对齐，
   不会因为脚本没跑就歪掉。 */
.shell[data-role="S"] .topbar .who {
  padding-left: calc(var(--w-side) + var(--tb-shift, 0px));
}
/* ── 两类小标签收一档（用户令 2026-10-08：「『初三1班』字号适当小一点」、
      「『中士』胶囊小一点」）──────────────────────────────────────────────
   17 → **15px**：身份行是 20px，"班级 / 军衔"是它的说明件，压到四分之三档
   才一眼分得开主次（徽标比正文大反而像按钮；比正文小太多又会掉成页脚注记）。
   内边距同步收：2/11 → 1/9 —— 字缩了而内边距不收，会显成一个空框。 */
.shell[data-role="S"] .topbar .tag { font-size: 15px; padding: 1px 9px; vertical-align: 2px; }
/* ── 军衔学位的字色 = 该军衔级别的族色（用户令 2026-10-06：
      「军衔学位的文字颜色对应的军衔级别的颜色」）────────────────────────────
   四族色取**既有军衔令牌**（§3.5 `--rk-*`），与成长图徽记、十一级阶梯表**同一套**：
     兵/士 银 `--rk-chev` · 尉 靛蓝 `--rk-bar` · 校 金 `--rk-badge` · 将 中国红 `--rk-star`。
   不另造色值 —— 页头「中士」三个字与成长图里那枚折杠徽记必须是同一个颜色，
   学生才能一眼把页头与阶梯表对上（两处各取一套色，迟早各说各的）。
   为什么要垫一道白底：顶栏是深蓝 `--b80`，靛蓝的"尉"档(#2f6fa8)压在同色系深蓝上
   等于看不见。垫白之后四族色全落在白纸上，谁是哪一族一目了然。
   默认给兵/士档（银）＝ lv1–3；实际档位由 JS 按 `/api/student/growth` 的**级数**覆盖。 */
.shell[data-role="S"] .topbar .tag#tb-rank {
  background: #fff; border-color: transparent; color: var(--rk-chev); font-weight: 700;
}
/* ── 军衔胶囊 = **徽记 + 名字**（用户令 2026-10-08：「在中士前面加上相应的『杠』，
       其他级别的类推」）────────────────────────────────────────────────────
   内容由 student.js 回填：`#tb-rank` 里先塞 app.js 的 `insigniaSvg(lv)`，再跟军衔名。
   同一枚图形在成长图阶梯表里也有 —— **两处是同一个函数画的**，不是各画一份
   （2026-10-08 为这条把图形搬进了外壳层，见 app.js 那段 ★）。
   这里只管"在胶囊里怎么摆"：横排居中、徽记与字之间留 3px。 */
.shell[data-role="S"] .topbar .tag#tb-rank {
  display: inline-flex; align-items: center; gap: 3px;
  padding: 1px 8px 1px 6px;   /* 左边小一点：徽记自身已占掉一段视觉边距 */
  vertical-align: 1px;        /* flex 盒不参与文字基线对齐，与班级标签（2px）手动对齐到同一行 */
}
/* 徽记在胶囊里比阶梯表那枚**小一档**（15 → 13px），并**吃掉** `.rk-ins` 的右外边距 5px：
   那 5px 是给表格行留的；照搬进胶囊，胶囊会平白宽出一圈、还比旁边的班级标签显大。 */
.shell[data-role="S"] .topbar .tag#tb-rank .rk-ins {
  width: 13px; height: 13px; flex: 0 0 13px; margin-right: 0; vertical-align: 0;
}
/* ── 学生台页头：**学校名**入行 · **军衔右移并收小**（用户令 2026-10-17）────────────
   用户原话：「在姓名的后面、年级的前面加上学校的名称。军衔向右移，并且稍调小一点点。」
   （身份行的**内容与次序**——姓名 · 学校 · 年级 · 军衔——在 app.js 里定，这里只管摆位。）

   ① 军衔"向右移"的落点由**同一轮的第二条令**定死：「军衔设置在班级的右边约 20mm 的地方」。
      怎么落：`.who` 由块级行内流改成 flex（`gap` 补回行内流里那一点空白），
      军衔那一枚给 `margin-left: calc(20mm - 9px)` ⇒ **班级右缘到军衔左缘正好 20mm**
      （减 9px 是因为 `gap: 9px` 已经贡献了那一段；不减就变成 20mm + 9px）。
      为什么非改 flex 不可：行内流里"两件之间隔多远"只能靠空格个数，量不出、也给不了毫米。
      为什么单位写 mm 而不是 px：这个距离是**用户量出来给的**，写同一个单位，
      后来人一眼能与原话对上，不必先做换算（20mm ≡ 75.59px @96dpi）。
   ② 左组位置必须不动的那条硬理由：`.who` 的 `padding-left`（侧栏一栏宽 + 外框留白）
      原样保留 ⇒ 正文列左缘那条对齐线不漂。audit-student 量的是 `.who` 的**首个非空
      文本节点**（姓名）的左缘 = 正文列左缘；flex 下姓名仍是第一个盒、左缘不变，
      所以那条判据继续成立（不要为了"排版好看"把姓名也包进 span，那会把它测空）。
   ③ 左组那一格的 `flex: 1 1 auto; min-width: 0`：军衔不再顶右端之后，它不是为对齐留的，
      而是让身份行这一格**占满并允许收缩** —— 窄屏上 `padding-left` 是固定的一大截，
      不给收缩余地就会把整行顶出屏幕。留着它，也免得以后要右对齐时回头改结构。
   ④ 军衔同时**收小一档**：15 → 13px（与它内部的 11px 徽记成比例），内边距同步收 ——
      字缩了而内边距不收，会显成一个空壳（本文件前面记过这条）。 */
.shell[data-role="S"] .topbar-in > div:first-child { flex: 1 1 auto; min-width: 0; }
.shell[data-role="S"] .topbar .who {
  display: flex; align-items: center; gap: 9px; flex-wrap: wrap;
}
/* 与「年级班级」相隔 20mm（见上面 ①）。窄屏照旧按 20mm —— 一行摆不下时 flex-wrap 会
   把军衔换到下一行，不会溢出（不另设窄屏例外，免得同一条规则有两个说法）。 */
.shell[data-role="S"] .topbar .tag#tb-rank {
  margin-left: calc(20mm - 9px); font-size: 13px; padding: 1px 7px 1px 5px;
}
.shell[data-role="S"] .topbar .tag#tb-rank .rk-ins {
  width: 11px; height: 11px; flex: 0 0 11px;
}
/* ── 学生台：左栏与内容页**等长**（用户令 2026-10-06：
      「导航栏的长度与内容页的长度保持相同」）────────────────────────────────
   改前的病：`.side` 走的是通用那条 `height: calc(100vh − --h-tb)` —— 它的长度跟的是
   **视口**，不是内容 ⇒ 内容比一屏矮时绿框在正文脚下多拖出一大截；比一屏高时又短一截
   （`#guide` 实测：正文底 1295、绿框底 1105，差 **190px** 还挂在半空）。两栏底边不对齐，
   看着像左栏"没画完"。
   改法不写死像素 —— 让它**跟着内容走**，三行各管一件事：
     ① `.app { align-self: flex-start }`：`.shell` 是 `min-height: 100vh` 的纵向 flex，
        外框默认被拉伸到一屏高（哪怕内容只有半屏）。这一行让它回到**自己的内容高度**
        （= 两栏谁高听谁的）。页面底色在 `body` 上，外框不再撑满也不会露白。
     ② `.side { position: static; height: auto }`：撤掉"视口高 + 常驻"两件套，
        恢复成普通栅格项 —— 行内 `align-items` 默认 stretch，左栏就**拉到与外框同高**，
        而外框高 = 内容高 ⇒ **两栏底边逐像素齐平**。
     ③ `.nav { position: sticky }`：①② 换来等长，代价是左栏不再钉在视口上。
        长页（如知识树）滚到底时，导航会跟着滚出屏幕 —— 那就等于没有了。
        所以把**导航那一段**单独钉住（钉在顶栏下沿），绿框照样等长、导航也照样常驻：
        两者不是二选一，一个在框上、一个在框里。
   ★ 只对 `.shell[data-role="S"]` 生效，且限桌面断点（≥901px）：窄屏（≤900px）那边
     左栏早已改成"通栏一条、height:auto、position:static"，此处不与之争。 */
@media (min-width: 901px) {
  .shell[data-role="S"] .app { align-self: flex-start; }
  .shell[data-role="S"] .side { position: static; height: auto; overflow: visible; }
  .shell[data-role="S"] .side .nav { position: sticky; top: calc(var(--h-tb) + 14px); }
}
@media(max-width: 640px) {
  .shell[data-role="S"] .topbar-in { padding: 16px 14px; }
  .shell[data-role="S"] .topbar h1 { font-size: 24px; }
  /* 窄屏不留一栏：236px 的缩进会把整行姓名推出屏外 */
  .shell[data-role="S"] .topbar .who { font-size: 16px; padding-left: 0; }
  .shell[data-role="S"] .topbar .tag { font-size: 14px; padding: 1px 10px; vertical-align: 1px; }
  /* 窄屏再把徽记收到 12px：14px 的字配 13px 的徽记，徽记会与字几乎等高、看着抢戏 */
  .shell[data-role="S"] .topbar .tag#tb-rank { padding: 1px 8px 1px 6px; }
  .shell[data-role="S"] .topbar .tag#tb-rank .rk-ins { width: 12px; height: 12px; flex: 0 0 12px; }
}
/* 「轮次·阶段」常驻标注（用户令 2026-09-24）的样式，随 2026-09-26 用户令一并撤除 ——
   顶栏那枚胶囊 `#loop-chip` 已从 `student.js` 删除（连同它的挂载与刷新调用）。
   曾经在这块里修的两处，一并留个去处，免得后来人翻旧账：
     · 只借 `.topbar .tag` 的配色、不新增色值（见 docs/BNJH-UI-2026-001 §1.3 按域登记）；
     · 2026-09-24 删掉过一条 `.loop-chip::before { content:'闭环 · ' }` —— 伪元素
       `textContent` 取不到、读屏也读不到，而"闭环"是这条标注的身份词 ⇒ 挪进了 JS 文本。
   不留空壳规则：留着的选择器不报错也不生效，后来人会以为"只是没渲染出来"。 */
/* 标签属"界面件" ⇒ 绿族；唯一例外是 .red / .amber 两档警示语义（文字色，不填色块） */
.tag {
  display: inline-block; padding: 1px 8px; border-radius: 999px; background: var(--g00);
  color: var(--g80); font-size: 11.5px; font-weight: 600; vertical-align: 1px;
}
.tag.t2 { background: var(--g20); color: var(--g80); }
.tag.gray { background: var(--panel-2); color: var(--ink-3); }
.tag.red { background: var(--danger-soft); color: var(--danger); }
.tag.amber { background: var(--warn-soft); color: var(--warn); }
.tag.green { background: var(--g00); color: var(--g60); }

/* ── 卡片 / 栅格 ── */
.grid { display: grid; gap: 14px; }
.g2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.g3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.g4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.g-2-1 { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); }
.g-1-2 { grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); }
.card {
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--r);
  padding: 16px; box-shadow: var(--sh); min-width: 0;
}
.card.pad0 { padding: 0; overflow: hidden; }
/* 卡片头 = 非阅读区：只给标题一行绿，不给它铺底 —— 铺了就成了"第二个卡片" */
.card > header {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  flex-wrap: wrap; margin-bottom: 12px; padding-bottom: 8px; border-bottom: 1px solid var(--gline);
}
.card > header h2 { color: var(--g80); }
.card > header p { margin: 2px 0 0; font-size: 12px; color: var(--ink-3); }
.kpi { background: var(--panel); border: 1px solid var(--line); border-left: 3px solid var(--g60); border-radius: var(--r); padding: 14px 16px; box-shadow: var(--sh); }
.kpi .lbl { font-size: 12.5px; color: var(--ink-3); }
.kpi .val { font-size: 26px; font-weight: 700; letter-spacing: -.5px; margin: 2px 0 0; }
.kpi .hint { font-size: 11.5px; color: var(--ink-3); }

/* ── 表格（表头是界面件 ⇒ 绿；表体是读的东西 ⇒ 中性面）── */
.tbl-wrap { overflow-x: auto; }
table.tbl { width: 100%; border-collapse: collapse; font-size: 13px; }
table.tbl th, table.tbl td { padding: 9px 10px; text-align: left; border-bottom: 1px solid var(--line-2); vertical-align: top; }
table.tbl th { font-weight: 600; color: var(--g80); font-size: 12px; background: var(--g00); white-space: nowrap; }
table.tbl tbody tr:hover { background: var(--g00); }
table.tbl td.num { text-align: right; font-variant-numeric: tabular-nums; }
/* ★ 表头也必须跟着数字右对齐（2026-10-26 用户令「名称/项目与统计数字要对齐」）。
   原来只写了 `td.num`：数字在列右缘、表头字在列左缘，列一宽，人读表时
   目光要横着挪一大截才能把"输入 token"和它下面的数对上 —— 这是版式错，不是观感问题。
   一起带上 tabular-nums：数字等宽之后，个位、十位、小数点才能上下成一条线。 */
table.tbl th.num { text-align: right; font-variant-numeric: tabular-nums; }
/* 合计行：会计账的合计要能用一道线把它和明细分开，且不能与正文同色（否则会被读成又一行明细）。 */
table.tbl tfoot td, table.tbl tfoot th {
  background: var(--g00); font-weight: 700; color: var(--g80);
  border-top: 1px solid var(--g40); border-bottom: 0;
}
table.tbl tfoot td.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
table.tbl td.nowrap, table.tbl th.nowrap { white-space: nowrap; }

/* ── 表单 ── */
.form { display: grid; gap: 12px; }
.form .row { display: grid; grid-template-columns: 96px minmax(0, 1fr); gap: 10px; align-items: start; }
.form .row > label { font-size: 13px; color: var(--ink-2); padding-top: 8px; }
.form .row > .fld { min-width: 0; }
/* ★ `date`/`time` 也是表单输入框，**必须一起列进来**（2026-10-16 用户令"有效期至的格子要与其左边的一致"）。
   原先这一行只有 text/password/number/search —— 于是全站 9 个 `<input type="date">`
   （发码 1 + 租户编辑 4 + 直营学生 4）全都落回浏览器原生的瘦小方框：
   实测 112×21px、2px 灰边、直角、13.33px 字，而同排的 text/number 是 210×41px、1px 淡边、8px 圆角、13px 字。
   不是"date 不好看"，是**它根本没吃到这条规则**。补在这里 = 一处定义、九处一起正。
   （`time` 现在没有页面在用，但同属"选择器类输入框"，一并登记，免得下次再漏一遍。） */
input[type=text], input[type=password], input[type=number], input[type=search],
input[type=date], input[type=time], select, textarea {
  width: 100%; padding: 8px 10px; border: 1px solid var(--line); border-radius: var(--r-sm);
  background: #fff; color: var(--ink); font: inherit; font-size: 13px;
}
/* ★ 上面补了 `date/time` 之后还差 2px：Chromium 给日期输入框的**内建编辑区**
   （`::-webkit-datetime-edit` 及其 fields-wrapper）自带一层默认 padding/margin，
   于是同样的 padding/border/line-height 下它仍比 text/number 高 2px
   （实测量：40.75px vs 42.75px）。把内建那层归零，两边**逐像素相同**。
   为什么用伪元素而不是"把 padding 调小 1px 去凑"：凑出来的高度会在换字体、
   改 padding、或浏览器换默认值时再次错开；归零是**去掉多出来的那 2px**，根上是稳的。
   （实测候选对照见 tools/_probe-date-height-exp.mjs：归零 diff=0；调 padding diff=0 但方式是补偿。） */
input[type=date]::-webkit-datetime-edit,
input[type=date]::-webkit-datetime-edit-fields-wrapper,
input[type=time]::-webkit-datetime-edit,
input[type=time]::-webkit-datetime-edit-fields-wrapper { padding: 0; margin: 0; }
input:focus, select:focus, textarea:focus { outline: 2px solid var(--g40); border-color: var(--g60); }
textarea { min-height: 72px; resize: vertical; }
.fld .hint { font-size: 11.5px; color: var(--ink-3); margin-top: 4px; }
.fld .err { font-size: 12px; color: var(--danger); margin-top: 4px; }

/* ── 功能键（BNJH-UI-2026-001 §1.1 · 绿族深浅 × 两档尺寸）──
   按钮的**族**只有一族（绿 = 界面件），**深浅**才是区分语义的那根轴：
     次行动   (浅底·描边)  → .btn         · 绿 0% 底 + 绿 80% 字
     提醒求助 (浅底)       → .btn.warn    · 绿 0% 底 + 绿 60% 字（橙色早已从按钮上退场）
     学习行动 (标准档实底) → .btn.act     · 绿 60% 实底 + 白字（4.99:1）
     主行动   (深档实底)   → .btn.pri     · 绿 80% 实底 + 白字（9.0:1）
     完成确认 (标准档实底) → .btn.ok      · 绿 60% 实底      ★与 .act 同色，靠**位置**区分：
                                            推进类动作在左、收尾确认在右（§1.2 R1/R3）
     完成强调 (深档实底)   → .btn.teal    · 绿 80% 实底（旧别名 → 同"完成"语义）
     行内链接 (无底·绿字)  → .btn.link
     撤销破坏 (唯一例外)   → .btn.danger  · 白底 + 红描边 + 红字，**不填色块**
   底色合法取值被 tools/audit-ui.mjs 的 ⑥ 逐个拦：绿族五档 + 中性面板色，
   出现蓝族底 = "非阅读区混进蓝" ⇒ ⑨ 同时报红。 */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  padding: 8px 14px; border-radius: var(--r-sm); border: 1px solid var(--gline);
  background: var(--panel); color: var(--g80); font: inherit; font-size: 13px; font-weight: 550;
  cursor: pointer; transition: .12s;
}
.btn:hover { border-color: var(--g60); background: var(--g00); }
.btn:disabled { opacity: .5; cursor: not-allowed; }              /* R4：禁用只降透明度 */
.btn.pri { background: var(--g80); border-color: var(--g80); color: #fff; }     /* 主行动 · 绿80 */
.btn.pri:hover { background: var(--g80d); border-color: var(--g80d); }
.btn.act { background: var(--g60); border-color: var(--g60); color: #fff; }     /* 学习行动 · 绿60 */
.btn.act:hover { background: var(--g60d); border-color: var(--g60d); }
.btn.ok { background: var(--g60); border-color: var(--g60); color: #fff; }      /* 完成确认 · 绿60 */
.btn.ok:hover { background: var(--g60d); border-color: var(--g60d); }
.btn.teal { background: var(--g80); border-color: var(--g80); color: #fff; }    /* 旧别名 · 绿80 */
.btn.teal:hover { background: var(--g80d); border-color: var(--g80d); }
.btn.warn { background: var(--g00); border-color: var(--gline); color: var(--g60); } /* 提醒求助 · 绿0 浅底 */
.btn.warn:hover { background: var(--g20); border-color: var(--g60); }
.btn.danger { background: var(--panel); color: var(--danger); border-color: var(--danger-line); } /* 撤销破坏：只描边不填色 */
.btn.danger:hover { background: var(--panel-2); border-color: var(--danger); }
.btn.sm { padding: 4px 9px; font-size: 12px; border-radius: 6px; }
.btn.link { border: 0; background: none; color: var(--g80); padding: 2px 4px; }
.btn.link:hover { background: none; text-decoration: underline; }
.btn-row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
/* 阅读型行宽（§2.1 --w-read）：长文超过这个宽度就开始丢行 */
.read { max-width: var(--w-read); }
/* 但**注解块不吃这条行宽**（用户令 2026-09-22 第九轮：「注解右边还有三分之一的空位，
   把注解向右拉满」）：`.read` 的 720px 是给"成段的正文"防丢行的，而 `.note` 是表下的
   口径解说/提示条 —— 它跟着卡片走。实测 `note rule read` 只有 720 / 1111 = 62.6% 宽，
   右边空掉 622px（截图量得 35.8%），一句"三分之一"就是这个数。
   注：页头那些 `<p class="read">` 的说明**不动** —— 那是真正的长文段落，要行宽。 */
.note.read { max-width: none; }

/* ── 阅读区（用户令：阅读区的颜色或绿或蓝）──
   两种阅读底都给到，**按语义选**：
     .read-block      绿底 —— 学到的东西（知识点、规则说明、军衔口径、复习提示）
     .read-block.blue 蓝底 —— 被测的东西（题干、解析、答题与结果）
   铺底只铺"读的那一块"，不铺整张卡片 —— 大面积上色会盖掉色标的层次。 */
.read-block { background: var(--g00); border: 1px solid var(--gline); border-radius: var(--r-sm); padding: 12px 14px; }
.read-block.blue { background: var(--b00); border-color: var(--bline); }

/* ── 提示条 ──
   提示条是"要读的一段话"，不是标签，所以字号跟着正文走（13.5px），
   不跟着 11.5px 的那一档元信息走 —— 该读的字不该比注解还小。 */
.note { border-radius: var(--r-sm); padding: 10px 12px; font-size: 13.5px; border: 1px solid; display: block; }
/* ⚠ 默认必须是 block，**不能是 flex**（这条是踩着坑改回来的）：flex 容器会把段内
   **每一个行内 `<b>` 都 blockify 成独立的一列**，于是"正文里带加粗"的提示条
   会被切成好几栏 —— 截图里一眼可见的参差。需要"图标｜正文"横排的显式加
   `.note.row`；需要"标题/正文"竖排的写行内 `display:flex;flex-direction:column`。
   block 之后，行内加粗就还是行内加粗。 */
.note.row { display: flex; gap: 8px; align-items: flex-start; }
.note b { font-weight: 650; }
.note.info { background: var(--b00); border-color: var(--bline); color: var(--b80); }   /* 阅读区蓝 */
.note.warn { background: var(--warn-soft); border-color: #f6e2bd; color: var(--warn-d); }
.note.danger { background: var(--danger-soft); border-color: var(--danger-line); color: var(--danger); }
.note.ok { background: var(--g00); border-color: var(--gline); color: var(--g80); }     /* 阅读区绿 */
.note.rule { background: var(--panel-2); border-color: var(--line); color: var(--ink-2); }

/* ── 考点格（知识树）──
   格子的**左色条与分值色**由 JS 按七级色标赋值（赤橙黄绿青蓝紫，见 kp-shared.js）；
   格子本身保持中性近白，不然底色会跟色标抢"这一格是什么颜色"的解释权。 */
.kp-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 8px; }
.kp {
  display: block; color: inherit; text-decoration: none;
  border: 1px solid var(--line); border-left-width: 4px; border-radius: var(--r-sm);
  padding: 8px 10px; background: var(--panel); cursor: pointer; transition: .12s;
}
.kp:hover { box-shadow: var(--sh-lg); transform: translateY(-1px); text-decoration: none; }
/* 卡里原本三行：考点名 / 节名 + 掌握度值 / 状态 + 题数。
   用户令 2026-09-22 晚：「上面有章和节了，所以按钮里就不要"节"了，这样可以将按钮高度减一行。」
   删掉重复的那一行之后，**分值并到标题行**（`.kp-hd`，左右两端、按 baseline 对齐）——
   少一行，而分值从"挤在节名旁边"变成"跟标题平起平坐"，反而更醒目。
   类名用 `.kp-hd` 而不是 `.hd`：`.hd` 只有两个字母，且附近已有 `.hd-act`，
   将来谁写个通用 `.hd` 就会连坐。 */
.kp .kp-hd { display: flex; align-items: baseline; justify-content: space-between; gap: 6px; }
/* 考点名取**与左式 .tg-kpname 同一个色**（左式那颗字是 JS 按掌握度色标内联 fill 的）；
   这里用 `--kpc`（由 app.js 的 kpGrid 在 .kp 上内联赋值，与左条、分值同源同一个数）
   取同一个值 —— 不写第二次色标，也就不会有"两处色标走散"这回事。
   `inherit` 只是兜底：万一哪个调用点没传，名字仍是中性深字，不会变成透明。 */
.kp .t { font-size: 13px; font-weight: 600; min-width: 0; color: var(--kpc, inherit); }
.kp .kp-hd .v { font-size: 12px; flex: none; }
.kp .m { font-size: 11.5px; color: var(--ink-3); display: flex; justify-content: space-between; gap: 6px; margin-top: 3px; }
.kp .v { font-variant-numeric: tabular-nums; font-weight: 600; }
.kp.dashed { border-style: dashed; background: var(--panel-2); }
.kp.sel { outline: 2px solid var(--g60); outline-offset: 1px; }

/* ── 答题（阅读区 ⇒ 可用蓝）── */
.stem { font-size: 15px; line-height: 1.8; margin-bottom: 12px; }
.opt {
  display: flex; gap: 10px; align-items: flex-start; padding: 10px 12px; border: 1px solid var(--line);
  border-radius: var(--r-sm); margin-bottom: 8px; cursor: pointer; background: var(--panel); transition: .12s;
}
.opt:hover { border-color: var(--bline); background: var(--panel-2); }
.opt .k {
  width: 22px; height: 22px; flex: 0 0 22px; border-radius: 50%; background: var(--line-2); color: var(--ink-2);
  display: grid; place-items: center; font-size: 12px; font-weight: 700;
}
.opt.choose { border-color: var(--b60); background: var(--b00); }
.opt.choose .k { background: var(--b80); color: #fff; }
.opt.right { border-color: var(--gline); background: var(--g00); }
.opt.right .k { background: var(--g60); color: #fff; }
.opt.wrongpick { border-color: var(--danger-line); background: var(--danger-soft); }
.opt.wrongpick .k { background: var(--danger); color: #fff; }

/* ── 答疑对话（三层递进，阅读区 ⇒ 可用蓝）── */
.coach { display: grid; gap: 10px; }
.bubble { border-radius: var(--r); padding: 11px 13px; font-size: 13.5px; line-height: 1.75; border: 1px solid; }
.bubble .lyr { font-size: 11px; font-weight: 700; letter-spacing: .4px; opacity: .8; display: block; margin-bottom: 4px; }
.bubble.s1 { background: var(--b00); border-color: var(--bline); color: var(--b80); }
.bubble.s2 { background: var(--warn-soft); border-color: #f6e2bd; color: var(--warn-d); }
.bubble.s3 { background: var(--panel-2); border-color: var(--line); color: var(--ink); }
.bubble.me { background: var(--panel); border-color: var(--line); color: var(--ink-2); }
.bubble strong { font-weight: 700; }

/* ── 色标图例（界面件：只在标题上给绿，色块本身仍是七级色标）── */
.legend { display: flex; flex-wrap: wrap; gap: 4px 14px; font-size: 11.5px; color: var(--ink-3); }
.legend i { width: 9px; height: 9px; border-radius: 2px; display: inline-block; margin-right: 5px; }

/* ── 进度条 ── */
/* `.bar` 之前**没有 display** —— 它在 .meter / .paperbar / .th / .ch-row 里都用得对，
   因为那些容器都是 flex，子项会被 blockify，`height` 照样生效。
   2026-09-26 四级色阶的说明块（.lvl-c 是普通块级）第一次把它放进**非 flex 容器**，
   于是它退回 inline：`height` / `overflow` 全部失效，细带渲染成一团红色块（截图里一眼看见）。
   补上 display: block 之后，`.bar` 无论落在什么容器里都是同一条细带 ——
   共用件不该靠"使用者恰好是 flex"才能正确显示。 */
.bar { display: block; height: 7px; border-radius: 999px; background: var(--line-2); overflow: hidden; }
.bar > span { display: block; height: 100%; border-radius: 999px; }

/* ── 图表（手绘 SVG，不引外部库）── */
.chart svg { display: block; width: 100%; height: auto; }
.chart .ax { stroke: var(--line); stroke-width: 1; }
.chart .lb { fill: var(--ink-3); font-size: 9px; }

/* ── 空态 ── */
.empty { text-align: center; color: var(--ink-3); font-size: 13px; padding: 26px 10px; }
.empty b { display: block; color: var(--ink-2); font-size: 14px; margin-bottom: 4px; }

/* ── 提醒窗口（吐司 / 询问 / 索文）的**样式与位置**不在此文件 ──────────────
   用户令 2026-10-23：「所有因要提醒用户弹出来的窗口，位置安排在：左右中线右移 150mm，
   上下中线上移 150mm，窗口为红框，浅红底，蓝字。」
   实现见 assets/ui-notice.js（自包含：自带 CSS + 定位 + 三个 API）。
   为什么不在 app.css：首页 index.html 是自包含页、不引 app.css，却也有一处"退出确认"；
   样式若留在这里，首页就得另写一份，两份迟早分叉。**改样式去 ui-notice.js，改这里无效。** */


/* ── 模态 ── */
.modal { position: fixed; inset: 0; background: rgba(18, 22, 30, .45); display: grid; place-items: center; z-index: 80; padding: 16px; }
.modal .box {
  background: var(--panel); border-radius: var(--r); box-shadow: var(--sh-lg); width: min(760px, 100%);
  max-height: 90vh; overflow: auto; padding: 18px;
}
.modal .box > header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 14px; }

/* ── 「知识树选点器」（用户令 2026-10-09：点「考点」进知识树，在树上点一个考点带回「布置作业」）──
   它不是新开的一页，而是**一层弹窗里的树** —— 跨台跳页必然 403（页面守卫按角色放行），
   且"一跳一回"会丢掉半填好的布置表单（理由详见 assets/kp-picker.js 头注释）。
   考点行**直接沿用学生台知识树那一款**（.clv-sec / .clv-h / .clv-kp / .nm / .tag / .vv）：
   同一棵树、同一款行，老师在这里点的与学生在那棵树里看到的才不是两套系统。
   唯一的差别：那边是 <a> 走深链，这边必须是 <button> 就地选中 ⇒ 补按钮复位（宽度/对齐/字体/手型）。 */
.kpp-sub { margin: 3px 0 0; font-size: 12px; color: var(--ink-3); }
.kpp-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-bottom: 10px; }
/* nowrap + flex:0 0 auto：不加这两条，"学段"/"科目"会被搜索框挤成**竖排两行**
   （flex 子项默认可收缩，中文两字被压到不足两字宽就换行）。 */
.kpp-f { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--ink-2);
  white-space: nowrap; flex: 0 0 auto; }
.kpp-bar input[type="search"] { flex: 1 1 180px; min-width: 0; }
.kpp-n { font-size: 12px; color: var(--ink-3); white-space: nowrap; }
.kpp-n b { color: var(--ink); }
.kpp-terms { margin-bottom: 12px; }
/* 树体自己滚，卡头与工具条固定 —— 否则选到下半学期时，学期页签与搜索框会被滚出视野。 */
.kpp-body { max-height: 52vh; overflow: auto; padding-right: 2px; }
.kpp-ch { display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap;
  margin: 12px 0 7px; padding-bottom: 5px; border-bottom: 1px solid var(--line); }
.kpp-ch:first-child { margin-top: 0; }
.kpp-ch b { font-size: 13.5px; }
.kpp-body .clv-sec { margin-bottom: 8px; }
.kpp-body .clv-kp { width: 100%; text-align: left; font: inherit; cursor: pointer; }
.kpp-body .clv-kp .kpp-id { margin-left: auto; }
.kpp-foot { margin-top: 12px; padding-top: 10px; border-top: 1px solid var(--line);
  font-size: 11.5px; line-height: 1.7; color: var(--ink-3); }
/* 布置作业表单里那一格：按钮 + 选中回显。未选 = 灰字（并写明"可以空着"），
   选中 = 名 + ID + 路径 —— 老师要看得见自己选的到底是哪一个考点。 */
.kp-pick-out { margin-top: 6px; font-size: 12.5px; line-height: 1.7; color: var(--ink-3); }
.kp-pick-out.on { color: var(--ink); }
.kp-pick-out b { color: var(--g80); }
.kp-pick-out .mono { font-size: 12px; color: var(--ink-3); }
.kp-pick-out .kpp-path { display: block; font-size: 11.5px; color: var(--ink-3); }
/* 考点选点器 · 多选态（备课第 1 项 2026-10-13）：点过的点亮，一眼看出选了哪几个。
   只在选点器弹窗内生效（.kp-pick 作用域）—— 别把学生台知识树上的 .on 一并点了。 */
.kp-pick .clv-kp.on { border-color: var(--g80); background: var(--learn-soft); box-shadow: inset 0 0 0 1px var(--g80); }
.kp-pick .clv-kp.on::before { content: '✓ '; color: var(--g80); font-weight: 700; }
.kpp-act { display: flex; align-items: center; gap: 10px; padding: 10px 2px 2px; }
/* 备课七步：每步一个圆号，横向排开就是一张"流程单" */
.pstep .pno { flex: 0 0 auto; width: 26px; height: 26px; border-radius: 8px; background: var(--learn-soft);
  color: var(--g80); font-weight: 700; font-size: 14px; display: flex; align-items: center; justify-content: center; margin-top: 2px; }
.pstep > header h2 { margin: 0; }

/* ── 门户（横幅是界面件 ⇒ 全绿渐变，不再掺蓝）── */
.hero { background: linear-gradient(135deg, var(--g60), var(--g80)); color: #fff; border-radius: var(--r); padding: 30px 26px; }
.hero h1 { font-size: 24px; color: #fff; }
.hero p { color: #dcefe6; margin: 8px 0 0; max-width: 720px; }
.hero .chips { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 14px; }
.hero .chips span { background: rgba(255, 255, 255, .16); border: 1px solid rgba(255, 255, 255, .25); padding: 3px 10px; border-radius: 999px; font-size: 12px; }
.door {
  display: block; background: var(--panel); border: 1px solid var(--line); border-radius: var(--r);
  padding: 14px; box-shadow: var(--sh); color: inherit;
}
.door:hover { text-decoration: none; border-color: var(--g60); box-shadow: var(--sh-lg); transform: translateY(-1px); }
.door b { display: block; font-size: 14px; margin-bottom: 3px; color: var(--g80); }
.door p { margin: 0; font-size: 12.3px; color: var(--ink-3); }
.door .tag { margin-bottom: 7px; }

/* ── 登录页 ── */
.login-wrap { min-height: 100vh; display: grid; place-items: center; padding: 20px;
  background: radial-gradient(1000px 500px at 15% -10%, var(--g20) 0%, transparent 60%), var(--bg); }
.login-card { width: min(410px, 100%); background: var(--panel); border: 1px solid var(--gline); border-radius: 16px; padding: 26px; box-shadow: var(--sh-lg); }
.login-card h1 { font-size: 19px; color: var(--g80); }
.login-card .sub { color: var(--ink-3); font-size: 12.5px; margin: 5px 0 18px; }
.acct-list { display: grid; gap: 6px; margin-top: 6px; }
.acct-list button {
  display: flex; justify-content: space-between; gap: 8px; width: 100%; text-align: left;
  padding: 7px 10px; border: 1px solid var(--line); border-radius: var(--r-sm); background: var(--panel-2);
  font: inherit; font-size: 12.5px; cursor: pointer;
}
.acct-list button:hover { border-color: var(--g60); background: var(--panel); }
.acct-list button span { color: var(--ink-3); }
/* ── 本校账号折叠块（2026-10-16 用户令）────────────────────────────────
   演示账号之外的账号（超管新建的师生）收在这里、按角色分组。
   为什么要收：一个学校几百个账号若平铺，登录页会变成一份通讯录，
   把"登录"这件事淹掉；而名单又必须反映真实账号（不能写死）—— 折叠是两者的交点。 */
.acct-more { margin-top: 6px; }
.acct-more > summary { cursor: pointer; font-size: 12.5px; color: var(--g60); padding: 5px 2px; }
.acct-more > summary:hover { color: var(--g50); }
.acct-more-body { display: grid; gap: 10px; margin-top: 4px; max-height: 300px; overflow: auto; }
.acct-grp-h { font-size: 11.5px; color: var(--ink-3); margin-bottom: 4px; letter-spacing: .4px; }

/* ══════════════ 响应式 ══════════════ */
@media (max-width: 1200px) {
  .g4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .g3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 900px) {
  .app { flex-direction: column; }
  /* ── 窄屏第二行不该再左缩一栏（补 2026-10-06 上一轮的漏）──────────────
     上一轮给第二行加的缩进是 `calc(--w-side + --tb-shift)`，缩进的**依据**是
     "桌面端左侧有一栏、正文列压在这一栏之后"。而 ≤900px 本段里 `.side` 已经
     变成通栏一条、跑到正文**上面**去了 —— 左侧根本没有那一栏，缩进就没有依据。
     上一轮只把 ≤640 的零头补了，641–900 这一段漏了：实测 800px 视口下
     第二行左缘 260px、而正文列左缘 14px —— 姓名被推出屏幕近半行。
     归零后与正文列同一条竖线（顶栏内边距 24px = 正文内边距 24px，天然同值）。 */
  .shell[data-role="S"] .topbar .who { padding-left: 0; }
  .side {
    width: 100%; flex: none; height: auto; position: static; padding: 10px 12px;
    /* 窄屏侧栏变成通栏一条 —— 框也要把这一整条**框住**，不能左边有边、右边没有。
       原写法是 `border-right: 0` + 1px 的 `--gline` 下边：那是为"侧栏底是 --g00"配的，
       现在底是 --g25、框是绿 40%，留一个豁口就不叫框了。
       用户令 2026-09-23 第二轮「框框调为绿色，绿色深度40%」—— 框在哪个断点都得是框。 */
    border: 2px solid var(--g40);
  }
  .brand { padding-bottom: 10px; }
  /* 左栏导航在窄屏改为横向滚动 —— 而不是简单堆成很高的一列 */
  .nav { flex-direction: row; overflow-x: auto; gap: 6px; padding-bottom: 4px; }
  .nav .grp { display: none; }
  .nav a { white-space: nowrap; padding: 7px 11px; }
  /* 窄屏是横向滚动条，二级胶囊不再"向右缩进"（横排里缩进会变成一段空白），
     但保留浅绿底 + 蓝字，好让学生仍能一眼分出"这是台里的哪一格"。 */
  .nav a.sub { align-self: center; margin: 0; padding: 5px 11px; }
  .main { padding: 14px 14px 40px; }
  /* 窄屏侧栏已改成横向条、不再占左侧 ⇒ 顶栏文字的左内边距跟 .main 走 14px。
     不覆盖的话那 260px 会原样留着，375px 的手机屏上文字只剩 115px 宽，全挤成竖条。 */
  .topbar-in { padding: 10px 14px; }
  .g2, .g3, .g4, .g-2-1, .g-1-2 { grid-template-columns: minmax(0, 1fr); }
  .form .row { grid-template-columns: minmax(0, 1fr); gap: 4px; }
  .form .row > label { padding-top: 0; }
  .hero { padding: 22px 18px; }
  .hero h1 { font-size: 20px; }
}
@media (max-width: 620px) {
  .kpi .val { font-size: 22px; }
  .kp-grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
  .card { padding: 13px; }
  /* 窄屏下 #toast 的贴边规则也移到了 ui-notice.js（与本段同一条口径，见该文件）。 */
}

/* 打印：只留正文（报告导出用） */
@media print {
  .side, .topbar .btn-row, #toast, .no-print { display: none !important; }
  body { background: #fff; }
  .app { display: block; }
  .card { box-shadow: none; break-inside: avoid; }
}

/* ═══════════════════════════════════════════════════════════════════════
   学生台 · 平台通用组件
   色只取 BNJH-UI-2026-001 §1.1 的语义令牌；宽只取 §2.1 的三档。
   这些组件（胶囊 / 闭环步骤条 / 徽章 / 微课卡 / 军衔卡）不是学生台私有 ——
   闭环、激励与军衔是平台级模式，教师台、家长台以后照用同一套，不另起一套皮。
   ═══════════════════════════════════════════════════════════════════════ */

/* ── 胶囊选择键：学段（初中/高中）· 科目（界面件 ⇒ 绿族深浅）── */
.pill-group { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.pill-group > .cap, .pill-group > .pill-lbl { font-size: 12px; color: var(--ink-3); flex: 0 0 auto; letter-spacing: .3px; }
.pills { display: flex; flex-wrap: wrap; gap: 6px; }
.pill {
  display: inline-flex; align-items: center; gap: 7px; padding: 6px 14px; border-radius: 999px;
  border: 1px solid var(--gline); background: var(--panel); color: var(--g80);
  font: inherit; font-size: 13px; font-weight: 550; cursor: pointer; transition: .14s; white-space: nowrap;
}
.pill:hover { border-color: var(--g60); background: var(--g00); color: var(--g80); }
.pill.on { background: var(--g80); border-color: var(--g80); color: #fff; }
.pill.on.learn { background: var(--g60); border-color: var(--g60); }
.pill .n { font-size: 11.5px; font-weight: 500; opacity: .72; }
.pill.lg { padding: 9px 22px; font-size: 14px; }

/* ── 闭环步骤条：五步，只有"已完成 / 当前 / 未开始"三态 ──
   三态靠**绿的深浅**区分：完成 = 绿 0% 底 + 绿 60% 字，当前 = 绿 80% 实底 + 白字，
   未开始 = 中性。整条只用一族色，眼睛扫一遍就知道走到哪一步。 */
.steps { display: flex; flex-wrap: wrap; gap: 2px 0; align-items: center; }
.step { display: flex; align-items: center; gap: 8px; padding: 7px 12px 7px 0; }
.step .no {
  width: 24px; height: 24px; flex: 0 0 24px; border-radius: 50%; display: grid; place-items: center;
  font-size: 12px; font-weight: 700; background: var(--line-2); color: var(--ink-3);
}
.step .tx { font-size: 11.5px; color: var(--ink-3); line-height: 1.3; }
.step .tx b { display: block; font-size: 13px; font-weight: 600; color: var(--ink-3); }
.step .arw { color: var(--ink-3); opacity: .45; margin-right: 8px; }
.step.done .no { background: var(--g00); color: var(--g60); }
.step.done .tx b { color: var(--ink-2); }
.step.on .no { background: var(--g80); color: #fff; }
.step.on .tx b { color: var(--g80); }
.step.on .tx { color: var(--ink-2); }
/* 当前步的 note 单独一行（非当前步不渲染 note —— 见 student.js 的 S.stepsHtml 注释） */
.step .tx .nt { display: block; color: var(--ink-3); margin-top: 1px; }
/* 「你正在看这一格」——虚线框，与"该做的那一步"（.on 实心）分得开。
   有了它，点哪一格哪一格有回应，步骤条才和下面的内容真正连成一件事。 */
.step.viewing { outline: 1px dashed var(--g40); outline-offset: 3px; border-radius: 6px; }

/* ── 首页「现在做」条：一句人话 + 一个主按钮（一屏一主行动）──
   学生清早打开平台，第一屏只该回答三件事：我在哪一环、现在做什么、点哪里。
   这一条就是那个答案，压在最显眼处，别的一概不与它争。 */
.nowdo {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  margin-top: 12px; padding: 12px 14px; border-radius: var(--r-sm);
  background: var(--g00); border: 1px solid var(--gline);
}
.nowdo .k { font-size: 11.5px; letter-spacing: .5px; color: var(--g60); font-weight: 700; }
.nowdo .v { font-size: 15px; font-weight: 700; color: var(--g80); margin-top: 2px; }
.nowdo .s { font-size: 12.5px; color: var(--ink-2); margin-top: 3px; }
.nowdo .btn { flex: 0 0 auto; }

/* ── 激励带：一行，整条可点（进「我的成长」）──
   为什么不做成三张并排的 note 卡：本周题量 / 连续天数 / 徽章本来就是同一件事的三个侧面
   （"我在往前走"），拆成三块并排读起来像三份报表；连成一条才像一句激励。 */
.streakline {
  display: flex; align-items: center; gap: 18px; flex-wrap: wrap;
  padding: 10px 14px; border-radius: var(--r-sm);
  background: var(--panel); border: 1px solid var(--line);
  font-size: 13px; color: var(--ink-2); text-decoration: none;
  transition: border-color .12s, background .12s;
}
.streakline:hover { background: var(--g00); border-color: var(--gline); text-decoration: none; }
.streakline b { color: var(--ink); font-size: 14px; }
.streakline .go { margin-left: auto; color: var(--g60); font-weight: 600; font-size: 12.5px; }

/* ── 页尾折叠块（平台原则：想了解再点开，别占学生的第一屏）── */
.fold { margin-top: 14px; border: 1px solid var(--line); border-radius: var(--r-sm); background: var(--panel); }
.fold > summary {
  cursor: pointer; padding: 10px 14px; font-size: 13px; font-weight: 600; color: var(--ink-2);
  list-style: none; display: flex; align-items: center; gap: 8px;
}
.fold > summary::-webkit-details-marker { display: none; }
.fold > summary::before { content: '▸'; color: var(--g60); }
.fold[open] > summary::before { content: '▾'; }
.fold > summary:hover { color: var(--g80); }
.fold-body {
  padding: 10px 14px 12px; display: flex; flex-direction: column; gap: 7px;
  font-size: 12.8px; line-height: 1.75; color: var(--ink-2); border-top: 1px solid var(--line-2);
}
.fold-body b { color: var(--ink); }

/* 表格格内的小字（掌握度下面那行「距目标 65」）——
   与主数值同格，省掉一整列"进度"（它和掌握度说的是同一件事）。 */
.tbl .sub { display: block; font-size: 11.5px; color: var(--ink-3); font-weight: 400; }

/* ── 激励 · 徽章墙 —— **样式已随 DOM 一并删除**（2026-09-22 第十一轮）──
   用户令「成长图下的徽章墙的内容删掉」，.badges / .badge / .badge.get / .badge.locked
   这七行是一条以它为用户的东西，DOM 一走它们就是死代码。
   删样式不只删 DOM：留着的话，下一次有人给别的卡片起名 badge 会**白捡**一套
   圆角 + 居中 + 置灰的样式，然后在别处看见一个莫名其妙的样子 —— 查半天查不到出处。
   ⚠ 注意别误伤：`.badge` 是**类名**，已无使用者；而 stu-loop.js 里的
   `data-badge="..."` 是**属性**（作答状态标签），与这里无关，仍在使用，未动。
   徽章功能本身全在：判定与落库在 lib/growth.js，接口 /api/student/growth 照旧返回，
   toast / 概要首页 / 闭环小结三处照旧显示（见 stu-grow.js 同处留痕）。 */

/* ── 进度计量条（军衔 / 成长值共用）：两端写档位名，中间才是条 ── */
.meter { display: flex; align-items: center; gap: 10px; }
.meter .bar { flex: 1 1 auto; }
.meter .ed { font-size: 12.5px; color: var(--ink-3); white-space: nowrap; }

/* ── 微课卡（封面是界面件 ⇒ 绿）── */
.mc { display: flex; gap: 12px; align-items: flex-start; padding: 12px; border: 1px solid var(--line); border-radius: var(--r-sm); background: var(--panel); }
.mc .cover { width: 74px; height: 52px; flex: 0 0 74px; border-radius: 8px; display: grid; place-items: center; background: var(--g00); color: var(--g60); font-size: 20px; }
.mc .tx { min-width: 0; flex: 1 1 auto; }
.mc .tx b { font-size: 13.5px; display: block; }
.mc .tx p { margin: 3px 0 0; font-size: 12.3px; color: var(--ink-3); }
.mc .btn { flex: 0 0 auto; align-self: center; }
.mc.done { background: var(--panel-2); }
.mc.done .cover { background: var(--line-2); color: var(--ink-3); }

/* ── 题号目录（第 3 步左栏）──
   原先这是 8 个全宽按钮，看上去像 8 个要按的动作 —— 其实它只是个目录：
   点一下换一道题。列表就该长成列表的样子，动作只留"提交作答"一个。
   为什么第 3 步要有目录：一人一案的题是**按薄弱顺序**排的，学生得能看见
   "我在第 3 道、后面还有几道"，才不至于做到一半不知道还剩多少。 */
.qlist { display: grid; gap: 4px; max-height: 420px; overflow: auto; }
.qitem {
  display: flex; align-items: center; gap: 9px; padding: 7px 10px;
  border: 1px solid transparent; border-radius: var(--r-sm);
  font-size: 12.8px; color: var(--ink-2); cursor: pointer; transition: .12s;
}
.qitem .no {
  width: 20px; height: 20px; flex: 0 0 20px; border-radius: 50%; display: grid; place-items: center;
  background: var(--line-2); color: var(--ink-3); font-size: 11px; font-weight: 700;
}
.qitem .tt { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.qitem .lv { flex: 0 0 auto; font-size: 11px; color: var(--ink-3); }
.qitem:hover { background: var(--panel-2); border-color: var(--line); }
.qitem:focus-visible { outline: 2px solid var(--g60); outline-offset: 1px; }
.qitem.on { background: var(--g00); border-color: var(--gline); color: var(--g80); font-weight: 600; }
.qitem.on .no { background: var(--g60); color: #fff; }

/* 错题行上的"错过 N 次"：那是要先啃的骨头，给它一点分量（不是警告色 —— 这不是错误提示） */
.tbl .sub b { color: var(--g60); font-weight: 700; }
.tbl tr.hot { background: var(--g00); }
.tbl tr.hot > td:first-child { box-shadow: inset 3px 0 0 var(--g60); }

.btn-row.center { justify-content: center; }

/* ═══════════════════════════════════════════════════════════════════════
   知识树（用户令 2026-09-22）：
     「知识树只显示学期，里边的章节处于收合状态，只有光标靠近才放大，
       点击时才呈现本章节的考点，未被点击的都处在合闭状态。
       继续点击其他章节，原先打开的章节自动收合。」
   ⇒ 结构：学期块（可收放）→ 章名行（**默认收合**）→ 章体内是节 + 考点格。
   ⇒ 交互：章名行**光标靠近即放大**（hover 反馈），**点击才展开**，且**单开**（手风琴）。
      放大用 transform: scale 而不是改字号 —— 改字号会让整行重新排版、
      左右文字跟着抖（"光标一靠近字就跳"），scale 只动这一行，别处一动不动。
   ═══════════════════════════════════════════════════════════════════════ */
/* ── 学期分 3 纵行摆（用户 2026-09-22 晚：「分成 3 纵行，左边纵行：初一/高一；
      中间纵行：初二/高二；右边纵行：初三/高三。上学期在上。」）──
   一个年级一根柱子，列内上→下。**分列规则不在这里** —— 是接口给的 termCols
   （规则在 lib/catalog.js，唯一一处），前端不自己配对。
   为什么不用"自动填充的瀑布流"：那样列数会随学期数量漂移（今天 6 册 = 3 列，
   明天加一册"衔接册"就变 4 列，"左中右三个年级"这句话当场废掉），而且上下册会被拆散 ——
   "上"与"下"本来就是一年的两半，拆开就看不出这层关系。
   行高（章名行的 padding）压到 6px 是为了让"六册一屏"真的成立：压的是行内上下留白，
   不是字号 —— 字一变小，长章名就该换行了。
   转成纵列后单列更窄，章行里的"已过 a/b"会自动折到第二行（见 .ch-row 的 flex-wrap）：
   这是**有意的**，宁可多占一点行高，也不把长章名裁成"第十四章整式的乘法与因…"。 */
.term-block { margin-bottom: 0; }
.term-cols {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  /* 三条行轨道：「年级标签」那一行 / 「上学期」那一行 / 「下学期」那一行。
     第一轨是 2026-09-26 新增的（列头的 初一/初二/初三 标签，见 .tc-cap）——
     .term-col 相应从"自动跨 2 行"改成显式 `grid-row: 2 / span 2`，
     这样它的 subgrid 仍然借的是下面那两条轨道，**齐平的语义一个字没变**。 */
  grid-template-rows: auto auto auto; gap: 8px 12px; align-items: start;
}
/* 每列用 subgrid 借用父网格的两条行轨道 ⇒ **三列的上册齐平、下册也齐平**。
   为什么非要齐平：不齐的时候，"上学期在上"只在每一列内部成立，整页看着是
   六块长短不一的卡片摞了三摞，扫视时找不到那条分界线；齐平之后
   "上册一行、下册一行"才是一眼能看见的事实 —— 也是用户这句话的字面意思。
   不支持 subgrid 的浏览器会忽略这一行声明，退化成"每列各自堆叠"：
   版式差一点，但六册、次序、链接一个不少，属可接受的降级。 */
/* 三纵列各一种**浅**底色（用户令 2026-09-23：「知识树3纵列用不同颜色，但不能深色。」）
   三列读起来是"三个年级"，不是"三块内容"—— 底色只负责把纵向的分界画出来。
   为什么三种都取最浅一档、而且明度几乎相同：用户点名"不能深色"，更要紧的是**主次**：
   列里装的是带色的考点块，列底一深，人先看到列、后看到考点，层级就反了。
   所以只在浅档里挑**色相**（绿 / 蓝 / 米），明度对齐 —— 一眼分得出是哪一列，
   又不会让任何一列显得比别的列"重"。列左再压一条同族的 3px 描边，把分界落在一条线上。 */
.term-col {
  display: grid; grid-template-rows: subgrid; grid-row: 2 / span 2; gap: 8px; min-width: 0;
  padding: 8px 9px 9px; border-radius: var(--r-sm); border-left: 3px solid var(--gline);
}
/* 列头的年级标签（2026-09-26）：把"一列 = 一个年级"从版式里写出来。
   它落在第 1 条行轨道上（列占第 2–3 条），所以**不参与** subgrid 的齐平计算。
   ⚠ 它在 DOM 里排在三个 .term-col **之后** —— 这是刻意的：
     下面三条 nth-child(1..3) 的列底色按"第几个孩子"选色，标签若插在最前，
     `nth-child(1)` 当场变成标签、三列全部落到 4/5/6 上，底色一起错位。
     显式写了 grid-row/grid-column，DOM 顺序与视觉顺序解耦，不受影响。 */
.tc-cap { grid-row: 1; font-size: 12px; font-weight: 600; color: var(--g80); padding: 0 2px 2px; letter-spacing: .5px; }
.tc-cap span { font-weight: 400; font-size: 11px; color: var(--ink-3); margin-left: 6px; }
.term-col:nth-child(1) { background: var(--g00); border-left-color: var(--g40); }
.term-col:nth-child(2) { background: var(--b00); border-left-color: var(--b60); }
.term-col:nth-child(3) { background: var(--warn-soft); border-left-color: #f6e2bd; }
.term-block > .th {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  /* 底色 `--g00` → `--panel`：上一次它是压在"近白"的整页上，取最浅的绿就够分。
     本轮三纵列各自有了浅色列底（见 .term-col），学期条若还是 --g00，
     落在第 1 列（同样是 --g00）上就跟没铺一样 —— 学期条必须比**任何一列**的底更实。
     取近白面板色，三列通用，且与列里的章行 / 考点块同一族。 */
  padding: 7px 11px; background: var(--panel); border: 1px solid var(--gline);
  /* 左侧 6px 掌握度色条（2026-09-26）：原来这里只有一颗 9px 小色点，
     而它下面是 5px 色条的章行 —— 越往上反而越弱，层级就反了。
     现在四级统一：色条自下而上变粗（节 3 · 考点 4 · 章 5 · 册 6），色一律由 JS 按该级 grpStat.color 赋。
     色值仍只有掌握度一个来源（§7.2 单向链）—— 这里只提供位置与宽度，不提供颜色。 */
  border-left-width: 6px; flex-wrap: wrap;
  border-radius: var(--r-sm); cursor: pointer; font-size: 13px; font-weight: 600; color: var(--g80);
}
.term-block > .th:hover { background: var(--g20); }
.term-block > .th .nm { display: flex; align-items: center; gap: 6px; min-width: 0; }
/* ★ 2026-10-07 用户令：「学期用胶囊。」
   学期名 = 绿底白字胶囊，与**左式 SVG 里那枚学期胶囊**同色同形
   （左式 `.tg-tpill { fill:#00A63E }` + `.tg-ttext { fill:#fff; font-weight:800 }`，rx 16）。
   两式并排摆着，同一个"初一上"在左边是绿胶囊、在右边是黑字，学生第一眼就会问
   "这两个是不是同一件事" —— 样式不一致，等于自己制造一遍这个疑问。
   底色写 #00A63E 而不是引令牌：左式那枚也是写死的同一个值（SVG 属性里给不了 CSS 变量
   之外的东西，而它是 fill 属性），两处必须**是同一个数**才对得上；
   所以在两处注释里互相点名，谁改一处就必须改另一处。
   ⚠ 别把它下面的 `.tag`（档名签，走掌握度色）也吞进来：那一枚是**数据**，
     胶囊是**册名**，两者不同源，所以胶囊只包学期名这一层（.nm > .tp）。 */
.term-block > .th .nm .tp {
  display: inline-block; background: #00A63E; color: #fff; font-weight: 800;
  font-size: 13px; line-height: 1.5; padding: 2px 11px; border-radius: 999px;
  letter-spacing: .5px;
}
.term-block > .th .st { font-weight: 500; font-size: 12px; color: var(--ink-3); }
/* ★ 2026-10-19：册头里的两个概述入口（本册 / 本学年）。
   为什么不给整条 .th 换动作：它现在承担折叠（aria-expanded 跟它走），
   换掉就少了一个收起长清单的办法。所以做两枚**小字入口**，折叠原样保留。
   样式取"可点的说明字"——与 .caret 同一族的克制程度：
   不抢胶囊与档名签的视觉重量，但要看得出能点（下划线 + hover 变色 + 手型）。 */
.term-block > .th .th-jy {
  font-weight: 600; font-size: 11.5px; color: var(--g60);
  text-decoration: underline; text-underline-offset: 2px;
  cursor: pointer; padding: 1px 2px; border-radius: 4px;
}
.term-block > .th .th-jy:hover { color: var(--g80); background: var(--g00); }
.term-block > .th .th-jy:focus-visible { outline: 2px solid var(--g40); outline-offset: 1px; }
.term-block > .th .caret { color: var(--g60); }
/* 册头那条细带：与章行/节头同一条 .bar（全站唯一的进度条定义），只改粗细。
   6px 是四级里最粗的一档，与左侧色条一起把这一册的"已成色"摆在最高处。 */
.term-block > .th .bar { flex: 0 0 100%; height: 6px; margin-top: 5px; }
.term-body { padding: 8px 0 0 2px; display: grid; gap: 7px; }
.term-block.closed .term-body { display: none; }

/* 章名行：默认收合的那一行。光标靠近放大到 1.02 —— 够看出"这一行是活的"，
   又不至于让邻行跟着晃。用 CSS 过渡而不是 JS 每帧算，光标扫过才"丝滑"。 */
.ch-block { border-radius: var(--r-sm); }
.ch-row {
  /* flex-wrap：3 纵列之后单列只剩约 1/3 宽，"章名 + 标签"占满第一行，
     "已过 a/b"与均分自动折到第二行。不裁字、不缩字号 —— 折行是这块版式的代价，
     而且折的位置稳定（长章名都折在同一个地方），扫一眼就能竖着比。 */
  display: flex; flex-wrap: wrap; align-items: center; gap: 9px; row-gap: 2px; padding: 6px 12px;
  /* 左侧 5px = 掌握度色条（2026-09-26 起由 JS 按 ch.color 内联赋值）。
     在此之前章行的左条是灰的：整章的颜色只体现在那颗小 tag 上，
     29 行章名行看上去一模一样 —— 这正是"格子+文字、不美"的一半原因。
     色条宽度四级递进：节 3 · 考点 4 · 章 5 · 册 6，所以"越往上越粗"本身就在说层级。 */
  border: 1px solid var(--line); border-left: 5px solid var(--line); border-radius: var(--r-sm);
  background: var(--panel); cursor: pointer; font-size: 13px;
  /* 章名行是 <a>（真链接），但**不许继承全局 a 的阅读区蓝** ——
     知识树是界面件，按用户令一律绿族/中性；整行染蓝等于把"非阅读区一律绿"这句话破在这里。
     同理要压掉 a:hover 的下划线：一行带下划线像正文引用，不像可以点开的章节。 */
  color: inherit; text-decoration: none;
  transition: transform .13s ease-out, box-shadow .13s ease-out, border-color .13s ease-out, background .13s ease-out;
  transform-origin: left center;
}
.ch-row:hover, .ch-row:focus-visible {
  transform: scale(1.02); box-shadow: var(--sh-lg); text-decoration: none;
  /* ⚠ 这里**不再**写 border-left-color —— 见下面 .open 那条的说明。
     hover 的"手感"由 transform / 阴影 / 底色承担，够用了。 */
  border-color: var(--g60); background: var(--g00); outline: none;
}
/* 展开态：底 + 三边描边表示"这一章展着"，**左条必须留给掌握度**。
   原来这条写的是 `border-left-color: var(--g80)` —— 展开的那一章反而丢掉掌握度色，
   一屏 29 章里唯独最该看清的那一章没了颜色（它还把内联色盖掉？没有：
   内联 style 优先于样式表，所以真实效果是"既不绿也不见色差"，更隐蔽）。
   展开与否是**位置**信息，掌握度是**数据**信息；两者不允许互相覆盖 —— 这是 §7.2 颜色单向链的落点。 */
.ch-block.open > .ch-row { background: var(--g00); border-color: var(--g60); }
/* 章行那条细带：flex 子项占满整行 ⇒ 自动折到章名下面一行（与 .mt/.avg 的折行同一个机制）。
   它是"已过 ÷ 已教"的形状，与旁边写的"已过 a/b"同源同数，不另立口径。 */
.ch-block .bar { flex: 0 0 100%; height: 5px; margin-top: 4px; }
.ch-row .cv { flex: 0 0 12px; color: var(--g60); font-size: 11px; }
/* ★ 2026-10-07 用户令：「表格式的章、节、考点的文字颜色要与图形式的章、节、考点的
   文字颜色一致。」⇒ 章名取**与左式 .tg-chname 同一个红**（原 #DC2626）。
   为什么要照着左式抄而不是"各按各的好看"：两式摆在同一个屏幕上讲同一棵树，
   同一级的名字在两个半幅里颜色不同，读的人会以为那是两种状态（"右边红是不是没掌握？"）。
   颜色对齐之后，"这一级用什么色"这件事**只有一个答案**，在哪个半幅看都一样。
   ★★ 2026-10-07 二次用户令（同日）：「表格式知识树的章的文字的红色，适当调浅
      （现在的红色太红，不好看）。」⇒ 层级红由 #DC2626（red-600，纯赤、偏重）
      调浅一档为 #E25555（明度抬高、彩度略收，白底上不再扎眼）。
      为什么是"调浅"而不是"换色相"：章=红这条层级语义不动，色相仍是正红，
      只降压 —— 所以它不会与掌握度的赤（kp-shared.js 的 #dc2626）混淆。
      ⚠ 同日条令：左式 .tg-chname 必须同值改（见下文那条），否则半幅红浅、半幅红深，
        又回到"同一级两个颜色"的老毛病。
   ⚠ 这是**层级色**（章=红 / 节=蓝），不是掌握度七色标 —— 掌控度色只出现在
     章行左条、节头左条、考点格左条与分值上（§7.2 单向链），两者别混着读。 */
.ch-row .nm { font-weight: 600; min-width: 0; color: #E25555; }
.ch-row .mt { font-size: 11.5px; color: var(--ink-3); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ch-row .avg { margin-left: auto; font-size: 11.5px; color: var(--ink-3); font-variant-numeric: tabular-nums; }
.ch-body { padding: 9px 0 2px 12px; }
.ch-body[hidden] { display: none; }
.sec-group { margin-bottom: 10px; }
/* 节头（2026-09-26 升格）：从"一行 12px 灰字"变成一条**带色条的小标题行**。
   为什么原先不行：服务端一直在给每一节算 grpStat（含颜色、已过数），而屏幕上一个字都没用 ——
   本工程记过的「接口有、屏幕无」。于是四级色阶里唯独"节"这一级是断的：
   考点有色、节没色、章有色、册有色，链子中间少一环，用户说的"逐级变化"就不成立。
   现在它承载三件事：① 该节的颜色（3px 左条）② 档名 ③ 已过 a/b 与"最弱"那一项。
   底色取 --panel-2（极浅），比章行（--panel）略深一点 —— 它是"一段的开头"，不是"一行数据"，
   深浅只表达这层主次，不表达掌握度。 */
.sec-group > .sh {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px; row-gap: 3px;
  font-size: 12px; color: var(--ink-3); letter-spacing: .3px;
  margin: 0 0 6px 2px; padding: 4px 9px;
  border-left: 3px solid var(--gline); border-radius: var(--r-sm); background: var(--panel-2);
}
/* 节名取**与左式 .tg-secname 同一个蓝**（#3B82F6）—— 用户令 2026-10-07 要求两式同级同色。
   原来这里是 var(--ink)（中性深字），于是同一个"21.1 二次函数"在左边是蓝、在右边是黑。
   注意别把 `.sh` 那条左色条也一起染蓝：那一条是**掌握度**（JS 内联 sec.color），
   与名字的层级蓝是两件事，本规则只落在 `.nm` 上。 */
.sec-group > .sh .nm { font-size: 13px; font-weight: 600; color: #3B82F6; }
/* 整节全过 → 节名转绿，与左式 `.tg-secname.ok { fill:#15803D }` 同一个值、同一个判据。
   （两式同级同色是用户令 2026-10-07；只对齐"常态色"而对不上"全过态"，
   等于半幅绿半幅蓝 —— 那正好是用户一眼会看见的那种不一致。） */
.sec-group > .sh.ok .nm { color: #15803D; }
.sec-group > .sh .st { font-size: 11.5px; color: var(--ink-3); font-variant-numeric: tabular-nums; }
/* "最弱 <考点名> · N 分"：靠右。为什么这一句只出现在节头而不在章行/册头：
   章行只有 1/3 栏宽，再塞一截就把章名挤成一行半；册头把"最弱章"写在 title 里。
   节头是唯一"横向有富余、又离那一个考点最近"的一级 —— 想补哪一块，看到的就是哪一块。 */
.sec-group > .sh .wk { font-size: 11.5px; color: var(--ink-3); margin-left: auto; font-variant-numeric: tabular-nums; }
.sec-group .bar { flex: 0 0 100%; height: 4px; margin-top: 2px; }

/* ── 章内「测这一章」那一条（用户令 2026-09-25）──
   为什么它自己占一行、而不是塞进章名行里：章名行是整行可点的 <a>（真链接）——
   往里再放一颗按钮就成了"链接里套按钮"，点下去到底算点了几件事说不清。
   所以它落在章体顶部，只改两样与颜色无关的事：横排对齐、给一句小字。
   （按钮本身用已登记的 `.btn.sm` 中性款，不新立颜色、不新增色值。） */
.ch-acts { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 0 0 9px 2px; }
.ch-acts .dt { font-size: 12px; color: var(--ink-3); }

/* 这里曾有 `.deeplink / .deeplink > .cap / .deeplink .dl`（展示"这一页的地址"的那条分享条，
   用户令 2026-09-22）。2026-09-23 用户令「这个页面的链接有何用？莫名其妙。删了。」
   ⇒ 样式随 HTML 一并删除。**不留空壳规则**：留着的选择器不报错、也不生效，
   下次有人翻到这里会以为"功能还在，只是没渲染出来"，然后再把它接回去 —— 那就白删了。
   这条注释是唯一的痕迹，与 stu-tree.js 里那段注释互相对应。 */

/* ── 逐级联动选择器（学情检测 · 段①「选检测范围」）──
   用户 2026-09-22：范围要"逐级选"（年级 → 学期 → 章 → 节），不摆一张平铺清单。
   一行摆满 4 格，靠 `flex:1 1 140px` 自己在窄屏折行 —— 不写断点，
   因为"几级"是数据决定的（选「学段」一层不摆，选「节」摆四层），写死断点必然对不上。 */
.cascade { display: flex; align-items: flex-end; gap: 10px; flex-wrap: wrap; }
.cascade > .csd { display: flex; flex-direction: column; gap: 4px; min-width: 0; flex: 1 1 150px; }
.cascade > .csd > label { font-size: 12px; color: var(--ink-3); letter-spacing: .3px; }
.cascade > .csd > select { width: 100%; }

/* ═══════════════════════════════════════════════════════════════════════
   学情检测四段式（范围 → 自评 → 校准 6 题 → 检测 ≤20 题）
   依据：用户令 2026-09-22 +《平台建设制度》§7.3（题数随范围走）/ §7.4（三个起点的分工）。
   它属于**答题/测评语境 = 阅读区**，所以这一段整体走蓝族：
   进行中 = 蓝族最深，已完成 = 绿族浅档（"做完"是界面件语义，归绿），未到 = 中性。
   ═══════════════════════════════════════════════════════════════════════ */

/* ── 段进度条：卡在第几段要一眼看得出 ── */
.stagebar { display: flex; align-items: center; gap: 0; flex-wrap: wrap; margin: 4px 0 14px; }
.stagebar .sg { display: flex; align-items: center; gap: 8px; padding: 5px 0; min-width: 0; }
.stagebar .sg .dot {
  width: 24px; height: 24px; flex: 0 0 24px; border-radius: 50%; display: grid; place-items: center;
  font-size: 12px; font-weight: 700; background: var(--line-2); color: var(--ink-3);
}
.stagebar .sg .tx { display: flex; flex-direction: column; line-height: 1.3; min-width: 0; }
.stagebar .sg .tx b { font-size: 12.5px; font-weight: 600; color: var(--ink-3); }
.stagebar .sg .tx i { font-style: normal; font-size: 11px; color: var(--ink-3); opacity: .8; }
.stagebar .link { flex: 1 1 20px; min-width: 14px; height: 1px; background: var(--line); margin: 0 10px; }
.stagebar .sg.done .dot { background: var(--g00); color: var(--g60); }
.stagebar .sg.done .tx b { color: var(--ink-2); }
.stagebar .sg.on .dot { background: var(--b80); color: #fff; }
.stagebar .sg.on .tx b { color: var(--b80); }

/* ── 段①范围清单：一行一个范围，右侧摆"考点数 → 题数 → 分钟数" ── */
.scope-list { display: grid; gap: 6px; margin-bottom: 12px; }
.scope-item {
  border: 1px solid var(--line); border-left: 4px solid var(--line); border-radius: var(--r-sm);
  padding: 9px 12px; background: var(--panel); cursor: pointer; transition: .12s;
}
.scope-item:hover { border-color: var(--bline); background: var(--panel-2); }
.scope-item.sel { border-color: var(--bline); border-left-color: var(--b80); background: var(--b00); }
.scope-item.off { opacity: .62; }
.scope-item.off.sel { opacity: .9; }
.scope-item .nm { font-size: 13.5px; font-weight: 600; }
.scope-item .mt {
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px 12px;
  margin-top: 3px; font-size: 11.5px; color: var(--ink-3);
}
.scope-item .mt b { color: var(--ink-2); font-weight: 600; }

/* ── 「还有哪些内容没检测过」清单（2026-09-24 用户令）────────────────────────
   条目**复用上面的 .scope-item** —— "这块从选范围移植过来"在样式上的意思就是
   "同一套壳"，所以这里只补三件本清单特有的事：序号列、横排、hover 时标题放大。
   颜色一律走既有变量，不新增色值（§1.3 登记闸：新色值要登记，能复用就别新开）。 */
.pend-hint { margin: 2px 0 10px; font-size: 12.5px; color: var(--ink-3); }
.pend-list { display: flex; flex-direction: column; gap: 6px; }
.pend-item { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
/* 序号是学生嘴里说的那个数（"第 3 个"）—— 它必须与清单次序严格同源：
   服务端给 p.no，前端**不自己数**（前端 index+1 与服务端排序漂开时，
   屏幕上就会出现"编号 3 的其实是第 4 条"）。 */
.pend-item .no { flex: 0 0 24px; text-align: right; font-size: 12px; color: var(--ink-3); }
.pend-item .nm {
  flex: 0 1 auto; min-width: 0; display: inline-block;
  transition: transform .12s ease; transform-origin: left center;
}
/* 光标指到标题时标题放大（用户令原话：「光标指到未检测标题时，标题放大」）。
   用 transform 而**不用 font-size**：font-size 会把行高撑开、让下面的行跟着跳，
   而"放大一个标题"不该动别人家的位置。 */
.pend-item:hover .nm, .pend-item:focus-visible .nm { transform: scale(1.09); color: var(--b80); }
.pend-item .mt { margin-top: 0; margin-left: auto; justify-content: flex-end; text-align: right; }
.pend-item .mt .st { padding: 1px 7px; border-radius: 999px; border: 1px solid var(--line); }
.pend-item .mt .st.part { border-color: var(--bline); color: var(--ink-2); }
.scope-item .mt .arrow { color: var(--ink-3); opacity: .5; }
.scope-item .mt .tm { margin-left: auto; color: var(--ink-2); }
.scope-item .mt .tag { margin-left: 0; }

/* ── 段①"圈选即算"的预览块 —— 不点开卷子也知道要考多久 ── */
.preview { border: 1px solid var(--bline); border-radius: var(--r); background: var(--b00); padding: 12px 14px; }
.preview .pv-h { font-size: 12px; font-weight: 600; color: var(--b80); letter-spacing: .2px; }
.preview .pv-b { display: grid; gap: 4px; margin-top: 8px; font-size: 12.8px; color: var(--ink-2); }
.preview .pv-b > div { display: flex; gap: 9px; align-items: baseline; }
.preview .pv-b .k { flex: 0 0 52px; color: var(--ink-3); font-size: 11.5px; }
.preview .pv-b b { color: var(--b80); }

/* ── 段②自评：移动条 ──
   轨道用蓝族浅→中做渐变，拇指走最深档：拖到哪一段，颜色自己就把"把握"
   这件事说清楚了，不必再读文字。 */
.self-wrap { display: grid; gap: 4px; margin: 6px 0 10px; }
.self-num { display: flex; align-items: baseline; gap: 6px; }
.self-num b { font-size: 40px; font-weight: 700; letter-spacing: -1px; line-height: 1.1; }
.self-num span { font-size: 14px; color: var(--ink-3); }
.self-num em { font-style: normal; font-size: 12.5px; color: var(--ink-2); margin-left: 6px; }
.rng { -webkit-appearance: none; appearance: none; width: 100%; height: 34px; background: transparent; cursor: pointer; }
.rng::-webkit-slider-runnable-track {
  height: 8px; border-radius: 999px; border: 1px solid var(--bline);
  background: linear-gradient(90deg, var(--b00), var(--b60));
}
.rng::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none; width: 22px; height: 22px; margin-top: -6px;
  border-radius: 50%; background: var(--panel); border: 3px solid var(--b80); box-shadow: var(--sh);
}
.rng::-moz-range-track { height: 8px; border-radius: 999px; border: 1px solid var(--bline); background: var(--b00); }
.rng::-moz-range-progress { height: 8px; border-radius: 999px; background: var(--b60); }
.rng::-moz-range-thumb { width: 22px; height: 22px; border-radius: 50%; background: var(--panel); border: 3px solid var(--b80); }
.rng:focus-visible { outline: 2px solid var(--bline); outline-offset: 4px; border-radius: 999px; }
.rng-ends { display: flex; justify-content: space-between; font-size: 11.5px; color: var(--ink-3); }

/* ── 段③④卷面：进度 + 暂存提示 ── */
.paperbar { display: flex; align-items: center; gap: 12px; margin: 2px 0 10px; }
.paperbar .bar { flex: 1 1 auto; }
.paperbar .save { flex: 0 0 auto; font-size: 11.5px; color: var(--ink-3); }

/* ── 三值对照：自评 → 校准 → 整卷实测（报告的核心那一行）── */
.compare3 { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr) auto minmax(0, 1fr); gap: 8px; align-items: stretch; margin-top: 10px; }
.compare3 .c3 {
  border: 1px solid var(--line); border-radius: var(--r-sm); background: var(--panel);
  padding: 10px 12px; display: flex; flex-direction: column; gap: 2px; min-width: 0;
}
.compare3 .c3 .k { font-size: 11.5px; color: var(--ink-3); }
.compare3 .c3 b { font-size: 24px; font-weight: 700; letter-spacing: -.5px; color: var(--ink-2); line-height: 1.2; }
.compare3 .c3 i { font-style: normal; font-size: 11px; color: var(--ink-3); }
.compare3 .c3.hi { border-color: var(--bline); background: var(--b00); }
.compare3 .c3.hi b { color: var(--b80); }
.compare3 .op { display: grid; place-items: center; font-size: 11.5px; color: var(--ink-3); padding: 0 2px; }

/* ── 行内小提示（`.hint` 原先只在 .fld / .kpi 里定义过，裸用是没样式的）── */
.btn-row .hint, .self-wrap + .btn-row .hint { font-size: 11.5px; color: var(--ink-3); }

/* ═══════════════════════════════════════════════════════════════════════
   成长激励 · 军衔（用户令 2026-09-22：「成长激励里，有军衔设计」）
   口径与出处见 lib/growth.js 文件头的 RANKS 段：十一级、分母只算已教、
   可升可降、不排名、不兑换。
   颜色：军衔是**界面件**（卡 + 阶梯表），一律绿族；级别高低用**绿的深浅**表达
   （depth 0/20/40/60/80）。刻意**不用**掌握度七色 —— 那七个色是"单个考点掌握到
   什么程度"的专用语，混用之后学生看见红就分不清是"考点没达标"还是"军衔低"。
   ═══════════════════════════════════════════════════════════════════════ */
/* 〔2026-09-22 第十轮〕军衔**概要卡**的专属样式（.rank-card / .rank-hero / .medal /
   .rank-main，以及 9px 的那条 .rk-dot）随那张卡一并删除 —— 用户令
   「@image#2 —— 这个不要了，删了。位置给全科雷达图、掌握度地图、学习画像。」
   删样式而不只删 DOM：留一堆没人用的类名，下一个人会以为"这里本来有块内容"，
   或者更糟 —— 照着它再做一张概要卡回来。摘要行现在的样式见本段后面的 .rk-line。
   顺带一个旧账：这条 9px 的 .rk-dot 与下面"阶梯格子里 5px 的浅点"同名，
   后者排在文件后面、同特异性，实际生效的一直是后者 —— 这条本来就已经是死代码。 */
.rk-ladder td { font-size: 12.5px; }
/* 底色**不在这里定**：五族行底色（见文件末尾「十一级阶梯 · 五族行底色」段）会把每行都铺上，
   "当前档"的高亮由那一段**之后**的 `tbody tr.cur > td` 给出。
   原先这里写 `background: var(--g00)`，在族色落地之后恰好与"士"族（第 2–3 级）同色 ——
   学生正好在第 2 级时，就完全看不出"我在哪一级"，而样式表里没有一处写错。
   这就是"两个来源争同一个属性"的典型下场：单看每条都对，合起来是错的。 */
.rk-ladder tbody tr.cur > td { font-weight: 600; }
.rk-ladder tr.reached { opacity: .8; }
.rk-ladder tr.dim > td { color: var(--ink-3); }

/* ── 军衔徽记（用户令 2026-09-22 第七轮：「军衔前面的牌/章/星，要用不同颜色加于区分，
      将星定为中国红的五角星，其他由你安排。」）──
   四族：折杠（兵/士）· 牌（尉官）· 章（校官）· 星（将官）。
   形状由 stu-grow.js 的内联 SVG 画（`insigniaSvg`），颜色**全部从这里取** ——
   SVG 里写的是 `style="fill:var(--rk-star)"`，所以改色只改这一处，
   不会出现"CSS 改了、JS 里还有一份写死的色"这种两处打架。
   宽高固定 15px：与 12.5px 的行高同基线，不会把行撑高。 */
.rk-ins { width: 15px; height: 15px; flex: 0 0 15px; vertical-align: -3px; margin-right: 5px; }

/* ── 台阶上的"科目矩阵"（用户令 2026-09-22 第九轮）──
   「晋级阶梯里的『科目』的右边排各科的科目名，也就是科目名上移一行。」
   ⇒ 九门科目**各占一列**，"科目名"从格子里的标签**上移一行**变成列头（排「科目」二字右边）；
     名字上移之后，格子里不再重复写名字，只标"这一科在这一档"：
       当前档 → 该科掌握率（绿胶囊，与旧标签同色系，账还看得见）
       已过档 → 一个浅点（走过去了，不喧哗）
       未到档 → 空
   旧排法九门全挤在"列兵"那一行，谁高谁低横着看不出来；成列之后一眼能比。 */
.rk-ladder th.rk-corner { text-align: right; padding-right: 8px; }  /* 「科目」二字贴住右边的九列 */
.rk-sh { text-align: center; border-left: 1px solid var(--line); }
.rk-cell { text-align: center; border-left: 1px solid var(--line); padding: 3px 2px; }
.rk-now {
  display: inline-block; padding: 1px 6px; border-radius: 999px;
  border: 1px solid var(--gline); background: var(--g00); color: var(--g80);
  font-size: 11.5px; font-weight: 600; line-height: 1.5; white-space: nowrap;
}
/* 2026-10-07：它是**链接**了（点这一科的百分数 = 进这一科的闯关进阶）——
   所以补上链接该有的三件：不画下划线、手型光标、悬停/键盘焦点有反馈。
   不改成按钮的原因写在 stu-grow.js 的 cell() 上：真链接才能被复制、被深链还原。
   悬停只加深边框与字色，**不换色相** —— 这一格的颜色是掌握率色标的语汇，
   鼠标一停就变个颜色，学生会以为"我点了之后掌握率变了"。 */
a.rk-now { text-decoration: none; cursor: pointer; transition: border-color .12s, box-shadow .12s; }
a.rk-now:hover, a.rk-now:focus-visible {
  border-color: var(--g40); box-shadow: 0 1px 6px rgba(0, 0, 0, .10);
}
a.rk-now:focus-visible { outline: 2px solid var(--g60); outline-offset: 1px; }
.rk-ladder tr.cur .rk-now { background: var(--panel); border-color: var(--g40); }
.rk-dot { display: inline-block; width: 5px; height: 5px; border-radius: 50%; background: var(--g40); }
/* ⚠ 同上一版：dim 行说的是"我的总军衔还没到这一档"，格子里的记号说的是"这一科到了没到" ——
   两件事。所以 dim 行照旧压灰整行，但**不额外**去压格子里的记号。 */

/* ══ 成长图 · 第十轮三件套（全科雷达 / 掌握度地图 / 学习画像）═════════════════
   用户令 2026-09-22：「参考截图，增设图中的项目：全科雷达图、掌握度地图、学习画像。」
                     「学生成长图和家长观察台里要有一个科目的雷达图，没有开的科目，就定为 0。」
                     「@image#2 —— 这个不要了，删了。位置给全科雷达图、掌握度地图、学习画像。」
   三块的关系：雷达看"哪一科厚、哪一科薄"（结果），地图看"走到哪一章了"（进度），
   画像看"是怎么学的"（过程）—— 三件事各说一件，互不重复，所以并排摆不显得挤。 */

/* ── 阶梯卡头的"摘要行"：原来「我的军衔」概要卡的那几个数迁到这儿 ──
   选择器写成 `.card > header p.rk-line` 而不是 `.rk-line`：卡片头有一条
   `.card > header p { font-size:12px; color:var(--ink-3) }` 的通用规则（特异性 0-1-2），
   只写类名（0-1-0）压不过它 —— 这是本文件里踩过不止一次的坑，写在这儿免得再踩。 */
.card > header p.rk-line { margin-top: 6px; font-size: 12.6px; color: var(--ink-2); line-height: 1.8; }
.rk-line b { color: var(--g80); }
.card > header p.rk-warn { margin-top: 5px; font-size: 12px; color: var(--warn); }
/* 军衔深度的小色块：级别高低用**绿的深浅**说（与徽记同族，不引第五套色） */
.rk-pip { display: inline-block; width: 10px; height: 10px; border-radius: 3px; margin-right: 5px; vertical-align: -1px; }
/* 卡头右侧那一列：状态签 + 一行行动提示（2026-10-07 起；此前这里是一颗按钮） */
.hd-act { display: flex; flex-direction: column; align-items: flex-end; gap: 8px; }
/* 行动提示（用户令 2026-10-07：原按钮「去闭环把掌握率抬上去」改为「点击百分数，闯关进阶！」）
   为什么从 `.btn` 降成一行字而不是只换文案：那句话指的是**表里科目百分数**那一格，
   不是这颗按钮自己 —— 按钮留在原地，就成了"写着 A、点了去 B"。
   它仍属"界面件"（非阅读区），所以底色与字色一律走**绿族**（与旁边的 tag 同族），
   不引第五套色（BNJH-UI-2026-001 §1.3）。 */
.hd-hint {
  max-width: 216px; text-align: right; font-size: 12px; font-weight: 650; line-height: 1.6;
  color: var(--g80); background: var(--g00); border: 1px solid var(--gline);
  border-radius: 999px; padding: 4px 11px;
}
.rk-num { font-size: 13px; font-weight: 600; color: var(--ink-3); margin-left: 3px; }

/* ── 掌握度地图：一屏一科 · 构成环 + 七档计数（用户令 2026-09-22 第十二轮）──
   用户原话：「成长图——掌握度地图，你要参考截图，只是显示统计数字（掌握了多少），
   不能像知识树一样点击打开。」
   ⇒ 上一版的 `.mp / .mp-b`（"每一章一块"的可点卡片墙）**整段删除**，
     连带 .mp-cells / .mp-sub / .mp-none 一起删 —— 只删 DOM 不删类名，
     下一个人会照着重做一版卡片墙回来。
   本段一个色值都不写字面量：色点与环的颜色由 JS 内联注入，源头是 kp-shared 的 BANDS
   （七色标唯一定义地）。唯一写死的是选中态的**图表蓝** #1d4ed8 ——
   它属于"界面件"的强调色（与 .chart text.rl tspan.rl-v 同一条），不是七色标里的任何一档。 */
.km-tabs { display: flex; flex-wrap: wrap; gap: 6px; margin: 2px 0 12px; }
.km-tab {
  display: inline-flex; align-items: baseline; gap: 5px; cursor: pointer;
  border: 1px solid var(--line); background: var(--panel); border-radius: 999px;
  padding: 4px 11px; font: inherit; color: var(--ink-2); transition: .12s;
}
.km-tab > b { font-size: 12.5px; font-weight: 600; color: var(--ink); }
.km-tab > span { font-size: 11.5px; color: var(--ink-3); }
.km-tab:hover { border-color: var(--g40); }
.km-tab.on { border-color: #1d4ed8; box-shadow: 0 0 0 2px rgba(29, 78, 216, .10); }
.km-tab.on > b { color: #1d4ed8; }
/* ★ 2026-10-07 用户令：「掌握度地图，显示6个，固定的有：语文、数学、英语、物理、历史，
   第六个是可变的。」⇒ 第六格（服务端给 `x.varies`）在两处留记号：
     ① 标签自身描边改**虚线** —— 虚线在本工程已固定表示"这一格不是常规项"
        （`.kp.dashed` / `.km-b.none` / `.tg-kpc.off` 都是这条），这里照用，不新造语义。
     ② 缀一枚极小的「可变」字样，让它**不必悬停**就知道这一格会随学情换人
        （title 里的解释只能靠光标停上去才看得见，不等于"看得出"）。
   颜色一律走中性令牌（`--ink-3` / `--line-2`），**不引任何彩色**：
   界面件按本工程令"非阅读区不染蓝"，而这一枚只是注解，不该与选中态（#1d4ed8）争注意力。 */
.km-tab.varies { border-style: dashed; }
.km-var {
  font-size: 10.5px; line-height: 1.4; padding: 0 5px; border-radius: 999px;
  border: 1px dashed var(--line-2); color: var(--ink-3); font-style: normal;
}
/* 第六格上的小箭头：这一格点得开（不是钉死的）。
   只用中性令牌，**不引彩色** —— 它只是"这里能点"的提示，
   不该与选中态（#1d4ed8）争注意力。 */
.km-caret { font-style: normal; font-size: 10px; line-height: 1; color: var(--ink-3); }

/* ── 选科面板（用户令 2026-10-08 · 掌握度地图第六格）────────────────────────
   用户原话：「语文、数学、英语、物理、历史——这5科，常显示（即5个轮），
     其他科则是一个轮的位置，点选到什么科，就显示什么科的掌握度地图。」
   ⇒ 第六格是**一个轮位**给所有未固定科共用：点开它弹出一条选科面板，
     面板里每一科都是一颗 `<button>`（不是 `<a>` —— 第十二轮那条
     "不能像知识树一样点击打开"的红线原样有效：这里只切"看哪一科"，不打开任何东西）。
   颜色只走既有令牌 + 复用 `.km-tab.on` 那支图表蓝（选中态全站同一条），
   不新引一种颜色 —— 多一种色，读的人就以为它多一层含义。 */
.km-pick {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  margin: -4px 0 12px; padding: 8px 10px;
  border: 1px dashed var(--line-2); border-radius: var(--r-sm); background: var(--panel-2);
}
/* `hidden` 属性会被上面的 display:flex 顶掉，必须显式让位 */
.km-pick[hidden] { display: none; }
.km-pick-h { font-size: 11.5px; color: var(--ink-3); }
.km-pick-b {
  display: inline-flex; align-items: baseline; gap: 6px; cursor: pointer;
  border: 1px solid var(--line); background: var(--panel); border-radius: 999px;
  padding: 3px 10px; font: inherit; color: var(--ink-2); transition: .12s;
}
.km-pick-b > b { font-size: 12px; font-weight: 600; color: var(--ink); }
.km-pick-b > span { font-size: 11px; color: var(--ink-3); font-variant-numeric: tabular-nums; }
.km-pick-b:hover { border-color: var(--g40); }
.km-pick-b.on { border-color: #1d4ed8; box-shadow: 0 0 0 2px rgba(29, 78, 216, .10); }
.km-pick-b.on > b { color: #1d4ed8; }

.km-body { display: flex; gap: 18px; align-items: center; flex-wrap: wrap; }
.km-left { display: flex; flex-direction: column; align-items: center; gap: 6px; }
.km-sum { font-size: 11.5px; color: var(--ink-3); text-align: center; line-height: 1.7; }
.km-sum b { color: var(--g80); }

/* 七档计数：一行一档，**数字右对齐**（tabular-nums）——
   七个数竖着比大小，数字不右对齐就比不出来。 */
.km-bands { flex: 1 1 300px; min-width: 0; display: grid; gap: 3px; align-content: center; }
.km-b {
  display: grid; grid-template-columns: 9px 1fr auto 34px; align-items: center; gap: 8px;
  padding: 3px 6px; border-radius: var(--r-sm); font-size: 12.5px;
}
.km-b > i { width: 9px; height: 9px; border-radius: 50%; }
.km-b > b { font-weight: 600; color: var(--ink); }
.km-b > .r { font-size: 11.5px; color: var(--ink-3); font-variant-numeric: tabular-nums; }
.km-b > .n { text-align: right; font-weight: 700; color: var(--ink); font-variant-numeric: tabular-nums; }
/* 计数为 0 的档压淡：**不是"不好"**，是"这一档现在没有人"。
   压淡而不是隐藏 —— 七档的位置固定，藏掉一行，读的人就以为色标只有六档。 */
.km-b.zero { opacity: .45; }
.km-b.zero > .n { font-weight: 600; color: var(--ink-3); }
/* "已教未测"那一行：灰点 + 虚线框，明确它不是七档里的任何一档 */
.km-b.none { border: 1px dashed var(--line); }
.km-b.none > i { background: var(--line-2); }
.km-b.none > b, .km-b.none > .n { color: var(--ink-3); font-weight: 600; }

/* ══ 九科九图：掌握度地图（用户令 2026-10-09）═════════════════════════════════
   「掌握度地图改为9科9个图，同时显示，不必点击科目而选科显示。」
   ⇒ 九格一屏（3 列 × 3 行），**同时铺开、没有"切"这个动作**。
   伴随作废的是"一科一屏"那一套：`.km-tabs`（轮位行）与 `.km-pick`（选科面板）
   在本页已无宿主 —— 样式留着不删，因为**成绩表的科目页签复用了 `.km-tabs`**
   （见 stu-grow.js 的 `<div class="km-tabs sc-tabs">`），删了会把成绩表一起打坏。
   `minmax(0, 1fr)` 的 0 与 `.km-cell` 的 `min-width: 0` 都是防溢出用的：
   九宫格最容易出的毛病就是某一格里长数字把整行顶宽。
   九格内**一个可点元素都没有** —— 第十二轮"只报数、不点开"的红线，本轮形态更彻底。

   ★ 2026-10-09 同一轮追加令：「掌握度地图，太大，占用空间太多。9个图，缩小，排成2行。」
   ⇒ 排布 3 列 × 3 行 → 每行 5 格（9 格 = 5 ＋ 4 两行），且整体小一号。
   ★ 同日第二令：「掌握度地图，再适当缩小。」⇒ 在同一套排布下再收一档：
     环 132 → 84 → **68**，格内边距 8 → **6**，科目名 / 账文字号 / 色点带各再降一档。
     ⚠ 环变小后环心字必须**反向补字号**，见下面 `.km-cell .chart text.rg-c / .rg-s`。

   ★ 同日第三令（本轮 · 最新）：「仿照『启航授衔』，将掌握度地图排成一行，
     光标靠近时，图标放大一倍。」⇒ 上一令的"两行"随本条**作废**：
     ① 排布 5 ＋ 4 两行 → **九格一行**，列数 = 9，与「启航授衔」的 `.hn-grid` 同款
        （`grid-template-columns: repeat(9, minmax(0, 1fr))`）。上一版用 flex 是为了让
        "4 格末行居中"；改一行九列之后没有"末行"，flex 那一套随两行一起撤下。
        （附注 2026-10-14：那张九列卡当日已删；九列形态现由「学业授衔」的 `.rk-ladder` 承接，
         本条"九格一行"的排布与判据不变。）
     ② 图标（这一格的环）**光标靠近时放大一倍**：`transform: scale(2)`（68 → 136 视觉大小）。
        用 transform 而**不改 `--ring` 尺寸**：改尺寸会撑动 grid 行高，光标一扫过整卡跳版；
        transform 只改"画多大"、不动布局（同 `.pend-item:hover .nm` 与知识树 `.tg-node`
        的定例：hover 放大一律走 transform）。
     ③ 放大后大环会与同格小字穿帮 —— 环心是透明的，科目名 / 账 / 色点带会从环洞里透出来。
        ⇒ 放大时把同格那三行**压淡到 .28**，让"放大的一环"成为这一格的焦点；松手即复原。
     三轮（缩小 → 再缩小 → 排一行＋悬停放大）都不动"哪几科、什么次序、什么数"，
     压淡的也只是"画"，台账里每个数原样在。 */
.km-grid { display: grid; grid-template-columns: repeat(9, minmax(0, 1fr)); gap: 8px; }
.km-cell { position: relative;
  border: 1px solid var(--line); border-radius: var(--r); padding: 6px 6px 4px;
  background: var(--panel-2); text-align: center; min-width: 0; }
.km-cell.live { background: var(--panel); border-color: var(--g40); }
.km-ct { font-size: 11.5px; font-weight: 600; color: var(--ink); margin-bottom: 0; }
.km-cell .km-sum { font-size: 9.5px; color: var(--ink-3); line-height: 1.4; text-align: center; }
.km-cell .km-sum b { color: var(--g80); }
/* 环心那两行字：**必须反向补字号**（2026-10-09「再适当缩小」）。
   环是 `<svg viewBox="0 0 140 140" width=68>` —— viewBox 坐标系里的 font-size 会随
   `width` **等比缩掉**。全局那条 `.chart text.rg-c{21px}` 是按 132px 的环定的，
   到 68px 环上只剩 21×(68/140) ≈ 10px，而 `.rg-s` 的 8.5px 更只剩约 4px —— 直接糊成一团。
   所以在这里按 68/140 的比例补回来：想要**视觉 12.6px / 7.8px**，
   viewBox 里就得写 26px / 16px（26×0.486≈12.6，16×0.486≈7.8）。
   这是"把地图缩小"这件事里**唯一不能只改尺寸**的地方 —— 只缩 svg 不管字，
   环心就从"平均掌握度"变成一团墨点。 */
.km-cell .chart text.rg-c { font-size: 26px; }
.km-cell .chart text.rg-s { font-size: 16px; }
/* 环的**显示尺寸**：尺寸只在 app.js `chart.ring` 的 `size` 参数里定义一处，
   这里取它挂下来的 `--ring`（兜底 68px = 与 stu-grow.js 传的 size 同值）。
   ⚠ 必须在这里压住全局那条 `.chart svg { width: 100% }`（app.css:629）——
   它按**父容器宽度**把 svg 撑满：五格一排时环就是 195px，这才是"地图太大"的真因。
   只改 JS 的 `size` 是无效的（svg 的 width 属性优先级低于类选择器）—— 上一轮就栽在这里。 */
.km-cell .ring-chart svg { width: var(--ring, 68px); height: var(--ring, 68px); }
/* ★ 2026-10-09 第三令：「光标靠近时，图标放大一倍。」
   —— 图标 = 这一格的环。放大走 `transform: scale(2)`：只改"画多大"，不动布局盒，
      所以 grid 行高纹丝不动，光标扫过不跳版（改 `--ring` / `width` 会撑高整行）。
   `z-index` 抬高：放大的环要盖在**邻格**之上，不然会被右边的格子裁掉半个。
   `transform-origin` 用默认的 center —— 从格心向外涨，视觉上就是"这一格被拎起来"。
   同格三行小字放大时压淡（见下）：环心透明，不压淡就会从环洞里透出来互相穿帮。 */
.km-cell .ring-chart svg { transition: transform .16s cubic-bezier(.2, .7, .3, 1.15); }
.km-cell:hover { z-index: 6; box-shadow: var(--sh-lg); border-color: var(--g40); }
.km-cell:hover .ring-chart svg { transform: scale(2); }
.km-cell .km-ct, .km-cell .km-sum, .km-cell .km-bands { transition: opacity .16s ease; }
.km-cell:hover .km-ct, .km-cell:hover .km-sum, .km-cell:hover .km-bands { opacity: .28; }
/* 七档计数带：横向一排小色点 + 该档考点数。
   为什么不在格内竖排七行：九格竖排会把这一张卡撑高三倍，而用户要的是"九图同时、一眼看完"。
   档名与区间进 `title`（悬停可查）；`data-band` / `data-n` 供审计逐个复算 ——
   不带这两个属性，审计就只能读渲染后的文字，字号一改就误报。
   2026-10-09（缩小排两行 → 再适当缩小）：带内 gap 与色点直径各收一档，
   让它在一行五格的窄格里仍能排成一到两行 —— 数是原来的数，只是点更小。 */
.km-cell .km-bands { display: flex; flex-wrap: wrap; justify-content: center; gap: 1px 5px; margin-top: 4px; }
.km-cell .km-b { display: inline-flex; align-items: center; gap: 2px; padding: 0;
  font-size: 9.5px; grid-template-columns: none; }
.km-cell .km-b > i { width: 6px; height: 6px; flex: 0 0 6px; }
.km-cell .km-b > b { font-weight: 700; font-variant-numeric: tabular-nums; }

/* 环心文字（生成在 app.js 的 chart.ring 里）：
   大字是**平均掌握度**、小字说明它是什么。用墨色而非七色 ——
   环心一旦按分数染成七色之一，这一圈颜色就开始"表示好坏"，环就没人看构成了。 */
.ring-chart { justify-content: center; }
.chart text.rg-c { fill: var(--ink); font-size: 21px; font-weight: 700; }
.chart text.rg-s { fill: var(--ink-3); font-size: 8.5px; }
@media (max-width: 620px) {
  .km-body { gap: 10px; }
  .km-bands { flex: 1 1 100%; }
}

/* ── 学习画像：四维竖排 ──
   样本不够的那一维显示"积累中"、条是灰的。灰**不是"不好"**，是"还看不清" ——
   所以它不能借用七级色标里的任何一种颜色：色标每一档都在判高低，
   拿它来表示"暂无"，等于把一个还没测的维度说成了低分。 */
.pf { display: grid; gap: 10px; }
.pf-d { border: 1px solid var(--line); border-radius: var(--r-sm); padding: 9px 11px; background: var(--panel-2); }
.pf-d.acc { background: var(--panel); border-style: dashed; }
.pf-h { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.pf-h > b { font-size: 13px; color: var(--g80); }
.pf-v { font-size: 16px; font-weight: 700; color: var(--g80); font-variant-numeric: tabular-nums; }
.pf-v i { font-style: normal; font-size: 11px; font-weight: 500; color: var(--ink-3); margin-left: 1px; }
.pf-d.acc .pf-v { font-size: 12.5px; font-weight: 600; color: var(--ink-3); }
.pf-r { font-size: 11.8px; color: var(--ink-2); line-height: 1.7; }
.pf-f { font-size: 11.5px; color: var(--ink-3); margin-top: 2px; }

/* ── 2026-11-05 用户令：「学习画像由 4 个维度表达，要把这四个维度的计算依据、
   过程写在说明里。」⇒ 每一维在"口径"之外再落两行：
     .pf-b 依据 —— 数据取自哪张表、为什么这么定义（小字，比口径浅一档，不抢分数）；
     .pf-c 过程 —— 把当前数代进公式的那一步（浅绿底 + 等宽数字，可逐字核对）。
   两行都**不折叠**：用户要的是"写在说明里"，塞进 details 等于没写。
   底色用 --g00（与画像阅读区同一支嫩的绿），不新立色相。 */
.pf-b { font-size: 11.2px; color: var(--ink-3); line-height: 1.68; margin-top: 3px; }
.pf-c {
  font-size: 11.5px; color: var(--g80); line-height: 1.75; margin-top: 4px;
  background: var(--g00); border-radius: 4px; padding: 3px 8px;
}
.pf-c .mono { font-size: 11.5px; color: var(--ink); font-variant-numeric: tabular-nums; }

/* ── 雷达图的维度标签（生成在 app.js 的 chart.radar 里）──
   名字用墨色、值用**图表蓝**（与折线图同一条），"未开课 / 未测"用灰：
   图上凹到圆心的角，得由标签说清它是哪一种 —— 图只负责形状。 */
.chart text.rl { fill: var(--ink-2); font-size: 10.5px; }
.chart text.rl tspan.rl-v { fill: #1d4ed8; font-weight: 700; }
.chart text.rl tspan.rl-none { fill: var(--ink-3); }

/* ── 「我的天赋」占位页（student.js 的 renderTalent）── */
.tl-line { font-size: 12.7px; line-height: 1.85; color: var(--ink-2); margin-top: 4px; }
.tl-line code, .tl-line .mono { font-size: 12px; }

/* ── 窄屏：三值对照竖排，否则三个数各挤成一条 ── */
@media (max-width: 620px) {
  .compare3 { grid-template-columns: minmax(0, 1fr); }
  .compare3 .op { padding: 2px 0; }
  .stagebar .link { min-width: 8px; margin: 0 6px; }
  .stagebar .sg .tx i { display: none; }
  .self-num b { font-size: 34px; }
  .scope-item .mt .tm { margin-left: 0; }
  .ch-row .mt { display: none; }
  /* 窄屏：3 纵列是给桌面端的承诺；手机上三列会把章名挤成"第一章…"，
     那就老实退回单列 —— 宁可翻页，不要给学生三列半截字。
     单列之后 subgrid 与"跨两行"都没有意义了，一并撤掉（否则每个列容器
     还占两行，六册的展开次序会看着像被夹断）。 */
  .term-cols { grid-template-columns: minmax(0, 1fr); grid-template-rows: none; }
  .term-col { grid-template-rows: none; grid-row: auto; }
  /* 窄屏退成单列后，"一列 = 一个年级"这句话不再成立（同一屏里三个年级都往下排），
     列头标签若还留着就会变成三行孤零零的"初一/初二/初三"堆在最上面 —— 反而更乱。
     隐藏它、不留空位：网格轨道是 auto 的，hide 掉等于这条轨道不占高度。 */
  .tc-cap { display: none; }
  .rank-hero .medal { width: 52px; height: 52px; flex: 0 0 52px; font-size: 16px; }
}

/* ═══════════════════════════════════════════════════════════════
   学习台 · 2026-09-22 第二次重排（用户令）
   「点击"学生学习台"，右边的具体内容应该是一个概要性的介绍」＋
   「导航"学习台"三个字下面，是学习台的二级目录，缩进一个字」＋
   「健康守护：…50 分钟叮叮 / 22:15 嘟嘟 / 22:30 嘟嘟嘟 / 23:30 锁住所有学生台」
   配色仍守"非阅读区归绿"：下面全部是界面件，一律绿族，不出现蓝。
   ═══════════════════════════════════════════════════════════════ */

/* ── 侧栏二级目录 = 胶囊 ──
   用户 2026-09-22：「导航列的文字"学生台"三个字下面的功能分类的文字（比如：弱项检测），
   **向右缩进一格**，每个功能文字，**制作成胶囊，蓝字浅绿色胶囊**。」
   · 缩进量法：量的是**文字左缘**，不是元素左缘 —— 用户原话的主语就是"文字"。
     第一版按元素左缘缩进 24px（`margin-left:24px`），文字左缘只挪了 **5px**
     （胶囊自带 13px 内边距 + 1px 描边，而一级项文字前面还站着一个 14px 图标 + 9px 间距），
     屏幕上看着等于没缩进 —— 用户要的是"看得出缩了一格"，5px 不是一个字。
     按文字左缘对齐后：一级文字左缘 45px（12 侧栏 + 10 自身 + 14 图标 + 9 间距），
     一个汉字宽 ≈ 14px ⇒ 二级文字左缘取 59px；胶囊 13px 内边距 + 1px 描边 = 14px
     ⇒ 胶囊左缘须落在 45px，即 **margin-left = 45 − 12 = 33px**。
     改完必须用 `_audit/probe-nav.mjs` 复测（十秒出数），
     文字左缘差落在 12–18px 才算达标；`tools/audit-student.mjs` 用同一量法同一区间。
   · 配色：**蓝字 + 绿底**，且底色必须与一级项当前格**同一个面**——
     用户 2026-09-22 第三轮：「把胶囊的背景也与上下的蓝条条一致！」
     上一版给的是 `--g00`（最浅那档）+ 描边，单看是"浅绿胶囊"，放在一级项旁边
     就成了"没上色的空壳"：上下两条是实面，中间五个是白面，不成一体。
     底与描边取同色 ⇒ 没有可见描边，就是"一块绿底药丸"，与一级项的条块同族。
   · **这一组取值跟着侧栏底走**（侧栏底一改，此处必须重算，不能沿用手抄值）：
       侧栏底 --g00（初版）→ 胶囊常态 --g20
       侧栏底 --g40（2026-09-23 第一轮）→ 胶囊常态 --g20（在深底上更浅，反差更大）
       侧栏底 --g25（2026-09-23 第二轮）→ 胶囊常态 **--g40**
     最后一次为什么是 --g40：`--g20` 压在 `--g25` 上只剩 1.093:1，胶囊会**糊进背景**；
     而"与一级项当前格同面"这条老令把两者绑在一起 —— 一级项已挪到 `--g40`
     （它就是 1.093:1 的受害者），胶囊必须**同时**挪，否则当场报红「不同面」。
     方向也随之翻转：前两版是"常态浅、hover/当前格更浅"；现在侧栏底已经最浅到 25%，
     再往浅走没有空间，于是常态沉到 `--g40`（1.316:1）、变化一律往**更浅**走。
       · `--g40` 底 + `--b80` 字 = **5.56:1**（过 4.5 底线）
       · hover `--g00` 底 + `--b80` 字 = **9.08:1**
       · 当前格 `--panel` 底 + `--b80` 字 = **8.3:1**
     描边仍与常态底同色（不外加一圈边）—— 这是上一版定的"药丸而不是空壳"，
     与侧栏底的深浅无关，故这轮不动它。
   · 蓝字是对"非阅读区一律绿"的一次**具名例外**（导航二级项），
     已登记进 tools/audit-ui.mjs 的 READING_SEL 并写明理由：
     不登记的话 ⑨ 会判它"界面件用蓝"而报红，报红的后果通常是有人把蓝改成绿 ——
     那就等于把用户的指令悄悄改掉了。假报红比漏报更伤，这一处尤其。

   ★ 2026-10-25 用户令：「所有角色的导航栏都按这个格式设置！」（参照＝学生学习台左栏）
     ⇒ 二级项**不再做成胶囊**：与一级项同字号（16px）、同内距（10/12px）、同左缘、同一条文字流。
       理由：学生台那一列是"一列名字"；教师台原来是"小字胶囊居中排"，
       同一个左栏里出现两种读法（左边对齐走 / 中间对齐），眼睛每换一组都要重新找起点。
       胶囊的三件遗物一并撤掉：33px 的左缩进、999px 圆角、112px 的最小宽度。
     ⇒ 蓝字回到绿字 `--g80`：那个"具名例外"本来只为"它还是胶囊"而设，
       胶囊没了，例外同步撤销（tools/audit-ui.mjs 的例外表已删掉这一条）。
     ⇒ 当前格仍用 `--g40` 沉底，与一级项 `.nav a.on` **同一个面** —— 那条老令继续有效。 */
.nav a.sub {
  align-self: stretch;
  margin: 0; padding: 10px 12px; border-radius: var(--r-sm);
  background: transparent; border: 0;
  color: var(--g80); font-size: 16px; line-height: 1.75;
}
.nav a.sub:hover { background: var(--g00); color: var(--g80); }
.nav a.sub.on { background: var(--g40); color: var(--g80); font-weight: 600; }

/* ── 页内段控 `.segx`（2026-10-26 用户令：把能合并的按钮归类，并整合功能）──────
   用途：一个门牌下的两个面（账号管理 = 统计表 / 增设；居间人管理 = 统计表 / 增设）。
   形制只三件：一条贯穿细线、一项一道短线、当前项字重加一档。**没有底块、没有圆角、没有胶囊**：
     · 底块会把"同一页的两面"画成"两个按钮"，看着像要按下去执行动作；
     · 而它要表达的只是"你现在站在哪一面"—— 界面上最轻的一种"我在哪"。
   ★ 颜色全部取既有绿族：线 `--g40`（描边档）、当前项 `--g60`（承重的深侧）、
     未选字 `--ink-3`。不引新色、不用蓝 —— 蓝族在这个工程里不用于版面件（app.css 顶部 §〇）。
   ★ `margin-bottom:-1px` 是为了让当前项那道短线**压住**贯穿线，而不是浮在它上面留一道缝：
     差 1px 的缝在深底侧看得很清楚，像是没对齐。 */
.segx { display: flex; flex-wrap: wrap; gap: 24px; align-items: baseline; width: 100%;
  border-bottom: 1px solid var(--g40); margin: 2px 0 14px; }
.segx a {
  display: inline-block; padding: 0 1px 9px; margin-bottom: -1px;
  color: var(--ink-3); font-size: 15.5px; line-height: 1.5;
  border-bottom: 2px solid transparent; text-decoration: none;
  transition: color .12s;
}
.segx a:hover { color: var(--g80); text-decoration: none; }
.segx a.on { color: var(--g80); font-weight: 600; border-bottom-color: var(--g60); }
/* 窄屏不另写断点：两段加起来约 190px，手机 360px 宽也放得下（§2.2 只认 1200/900/620 三档，
   为省 8px 间距新开一个 560 断点，是把"干净"换成"琐碎"——不做）。 */
/* ★ 段控必须站在**卡片外面**（`#pg-body` 顶层，`<nav>` 没有卡头，折叠件不会折它）。
   不是为了排版好看：折叠件会把带卡头的块折成一条目录条，段控放进卡里，
   进页就整条看不见 —— 而它正是"我在哪一面"的唯一提示（本轮先写进卡里，
   探针量到它的 rect 是 0×0，才发现屏幕上一个错都不报、页签却整个不见了）。
   卡片外面那条贯穿线也就成了页面的第一条线，卡头自己的下边框不必再让位。 */

/* ── 页头引言（阅读区，可用绿底）── */
.lead { margin: 2px 0 14px; }
.lead h2 { font-size: 17px; color: var(--g80); margin-bottom: 5px; }
.lead p { font-size: 13.2px; line-height: 1.8; color: var(--ink-2); max-width: 72ch; }
.lead b { color: var(--g80); }

/* ── 五扇门（首页概要的五块）· 2026-09-22 第二轮外观 ──
   用户原话：「你认为这样的排版符合美雅原则吗？必须按照我列的内容，分别一条条制作成
   漂亮的说明，比如："弱项检测"，制成**胶囊（也是热键）**，具体的说明是**浅绿底的蓝字**。」

   于是每一条 = 三件东西，各归其位：
     · **胶囊**（`.gt-pill`）= 名称，就是热键 —— 它是界面上看得见、点得动的**界面件** ⇒ 绿族；
       悬停时整枚胶囊转实底（`--g60` 承白字 4.99:1），"这里能点"不必靠一行小字解释。
     · **说明块**（`.gt-desc`）= 用户给的那句话，**浅绿底 + 蓝字** ⇒ 它是给人读的**阅读区**，
       蓝字合规（§〇 收口的"非阅读区一律绿"管的是界面件；阅读区本来就"或绿或蓝"）。
     · **此刻的样子**（`.gt-st`）= 这一格现在的状态，右对齐。
   一行一块而不是五张并排的卡：并排会让人以为要"选一张"，而它们其实是一条动线上的五格
   （测 → 练 → 挑 → 看 → 说）。整块可点 ⇒ 点胶囊自然也生效，手指不必瞄准。 */
.gates { display: flex; flex-direction: column; gap: 12px; }
.gate {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  gap: 4px 13px; align-items: start;
  padding: 16px 18px; border: 1px solid var(--line); border-radius: var(--r);
  background: var(--panel); text-decoration: none; color: inherit;
  transition: border-color .14s, background .14s, box-shadow .14s;
}
/* 选择器加一层 `.gates` 是**为框色轮转让路**（用户令 2026-09-23「框之间的颜色必须不同」）：
   轮转规则写的是 `.gate:nth-child(6n+k)`（特异性 0,2,0），若 hover 仍是 `.gate:hover`（同为 0,2,0），
   两条一比先后，后写的那条会把 hover 吃掉 —— 悬停时框色不再变化，"这里能点"就少了一个提示。
   提到 0,3,0 之后，轮转只管**常态**框色，hover 仍归 hover，两件事各管各的。 */
.gates .gate:hover { border-color: var(--g40); background: var(--panel); text-decoration: none; box-shadow: 0 2px 12px rgba(27, 84, 67, .07); }
.gate:focus-visible { outline: 2px solid var(--g60); outline-offset: 2px; }
/* 序号：很轻，只负责"这是第几条"，不跟胶囊抢注意力 */
.gate .gt-no { width: 13px; text-align: center; padding-top: 5px; font-size: 12.5px; font-weight: 700; color: var(--g60); opacity: .6; }
/* 名称与说明**同一行**（用户令 2026-09-22：「学习台的项目与项目的具体解释安排成同一行」）：
   原先名称一行、说明另起一行，五块就多占五行高度，读的时候眼睛还得跳一次行。
   并成一行后，"弱项检测 —— 通过检测，了解自己的强弱项…"可以一口气读完。
   说明块给 300px 的基础宽 + `flex-wrap`：宽屏上与胶囊并排，窄屏整块换行（而不是把字挤成一条）。 */
.gate .gt-body { display: flex; flex-wrap: wrap; align-items: center; gap: 9px 12px; min-width: 0; }
/* 胶囊 = 名称 = 热键（界面件 ⇒ 绿族） */
/* 五个胶囊**一样长**、文字**居中**（用户令 2026-09-22 第五轮：
   「截图中，胶囊长度要一致，胶囊里面的文字分布要均匀。」）
   · 用 `min-width` 而不是 `width`：定宽会在名字变长时**把字裁掉**，而信息不能被裁。
     168 = 当前最长那个（162：弱项检测（学情检测））+ 6px 余量。五个实测全部落在 168 ⇒ 一样长。
     哪天出现更长的名字，那一个会略长 —— 这比裁字/换行好，也不会把别的挤变形。
   · `justify-content: center` 对应"文字分布要均匀"：文字在胶囊里左右留白相等（实测差 0px）。
     只有 min-width 而不居中，宽度一撑开文字就贴左沿，反而像"左对齐的长条"。 */
.gate .gt-pill {
  display: inline-flex; align-items: baseline; justify-content: center;
  min-width: 168px;
  padding: 5px 15px; border-radius: 999px;
  background: var(--g20); border: 1px solid var(--g40); color: var(--g80);
  font-size: 14px; font-weight: 700; line-height: 1.55;
  transition: background .14s, border-color .14s, color .14s;
}
.gate:hover .gt-pill { background: var(--g60); border-color: var(--g60); color: #fff; }
.gate .gt-pill i { font-style: normal; font-size: 12px; font-weight: 400; opacity: .85; margin-left: 2px; }
/* 说明 = 用户给的那句话（阅读区 ⇒ 浅绿底 + 蓝字） */
.gate .gt-desc {
  flex: 1 1 300px; min-width: 0; padding: 9px 13px; border-radius: var(--r-sm);
  background: var(--g00); border-left: 3px solid var(--g40);
  color: var(--b80); font-size: 13.2px; line-height: 1.85;
}
.gate .gt-note { flex: 1 0 100%; display: block; font-size: 11.8px; color: var(--ink-3); padding-left: 4px; }
/* 此刻的样子：右栏窄条，五条竖着对齐着读 */
.gate .gt-st { max-width: 230px; text-align: right; padding-top: 6px; }
.gate .gt-st b { display: block; font-size: 12.8px; color: var(--g80); }
.gate .gt-st span { display: block; font-size: 11.8px; color: var(--ink-3); margin-top: 3px; line-height: 1.6; }

/* ── 健康守护（用户令四条）──
   放"概要之后、折叠之前"：它是边界而非动作，不进上面五扇门；
   但也不能藏进折叠块 —— 学生得先知道"会响铃、会锁台"，
   否则半夜被锁在外面会以为平台坏了。 */
/* 边框交还给文件末尾的「板块框色轮转」（用户令 2026-09-23）：
   原先这里把边框钉死为 `--gline`，等于给这张卡开了个"不参与轮转"的例外，
   首页上就总有一张卡与邻居同色 —— 而用户要的正是"框之间的颜色必须不同"。
   底色仍留 `--g00`：它表达的是"这是边界、不是一件待办"，与框色是两件事。 */
/* ── 2026-09-23 用户令：「仿照截图修改健康守护的颜色。」─────────────────
   截图取自**旧版学生台**（`Claw/深慧宝/study.html` 的 `.health-card .hz-*` 一套）：
   三张警示卡**按语义各染一色**（22:15 预备收工＝暖黄 · 22:30 结束学习＝粉红 ·
   50 分钟休息＝浅绿），每张卡左上角一个白底小时间块、数字用同族深色；
   锁定态的状态条是浅红。

   为什么要照它改：这一块讲的是**三条性质不同的边界**（提醒 / 收尾 / 硬停），
   以前三种全染同一种浅绿，学生扫一眼分不出"哪条是到点就锁" —— 颜色在这里是真的在传信息。
   注意这不是"按数据染色"那种禁忌（框色轮转那条令禁的是"按掌握度高低染色"）：
   这里染的是**规则本身的类型**，规则有几条、各是什么类型，由 lib/health.js 的 rules() 决定，
   与学生的成绩、排名无关，不会变成第六套色标。

   色对**复用 .note 四型已有的那一套令牌**（底 / 框 / 字三件成套），不新造色 ——
   新造色要同时改 tools/audit-ui.mjs 的 CORE_COLORS 白名单，而白名单里每多一个色值，
   "颜色有多少种"这件事就松一分。 */
.card.guard { margin-top: 14px; background: var(--panel); }
.guard-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(215px, 1fr)); gap: 8px; }
.guard-row .gd {
  display: flex; gap: 8px; align-items: flex-start;
  padding: 9px 11px; background: var(--panel); border: 1px solid var(--gline); border-radius: var(--r-sm);
}
.guard-row .gd-ic { flex: 0 0 auto; font-size: 14px; line-height: 1.5; }
/* 时间块（旧版 .hz-time）：白底 + 同族深字 —— 三张卡的时刻要能"一眼扫到"，
   所以它不跟正文同色，而是单独一块。`tabular-nums` 让 22:15 与 50 之类等宽，三张卡才对得齐。 */
.guard-row .gd-at {
  display: inline-block; font-style: normal; font-weight: 700; font-size: 12.6px;
  font-variant-numeric: tabular-nums; padding: 1px 7px; border-radius: 6px; margin-bottom: 3px;
  background: var(--panel); color: var(--g80);
}
.guard-row .gd b { display: block; font-size: 12.8px; color: var(--g80); }
.guard-row .gd span { display: block; font-size: 12px; color: var(--ink-2); line-height: 1.7; margin-top: 2px; }
/* 四型：前三条按"提醒强度"递进（绿 → 暖黄 → 粉红），第四条是"边界说明"、保持中性不抢色。
   `kind` 由 lib/health.js 的 rules() 下发，前端不自己判类型。 */
.guard-row .gd-ding { background: var(--g00); border-color: var(--gline); }
.guard-row .gd-du   { background: var(--warn-soft); border-color: #f6e2bd; }
.guard-row .gd-du .gd-at, .guard-row .gd-du b { color: var(--warn-d); }
.guard-row .gd-dudu { background: var(--danger-soft); border-color: var(--danger-line); }
.guard-row .gd-dudu .gd-at, .guard-row .gd-dudu b { color: var(--danger); }
.guard-row .gd-lock { background: var(--panel-2); border-color: var(--line-2); }
.guard-foot { margin-top: 10px; font-size: 12.2px; color: var(--ink-2); line-height: 1.75; }

/* ── 折叠块里的清单（今天最该补的）── */
.fold-body .rv-t { font-size: 12px; font-weight: 700; color: var(--g80); margin: 11px 0 3px; }
.fold-body .rv { display: flex; justify-content: space-between; gap: 8px; font-size: 12.5px; padding: 4px 0; border-bottom: 1px solid var(--line-2); }

/* ── 胶囊选择（师生交流的话题；界面件 ⇒ 绿族）── */
.chips { display: flex; flex-wrap: wrap; gap: 7px; }
.chip {
  cursor: pointer; font: inherit; font-size: 12.5px; padding: 4px 12px; border-radius: 999px;
  border: 1px solid var(--gline); background: var(--panel); color: var(--ink-2);
  transition: background .12s, border-color .12s, color .12s;
}
.chip:hover { background: var(--g00); border-color: var(--g40); }
.chip.on { background: var(--g60); border-color: var(--g60); color: #fff; font-weight: 600; }

/* ── 师生交流的往来行 ── */
.tk-line { display: flex; margin-bottom: 9px; }

/* ── 添加文件（用户令 2026-10-13 第 7 项）──
   一个件挂两处：学生台/教师台的往来输入框、发布教案。样式只写这一份（kp-shared.attach）。
   设计取向与页面一致：低调、不抢内容 —— 按钮是"幽灵键"，文件名用等宽小字，
   移除键是一枚不起眼的文字链（它不是主操作，不该长得像按钮）。 */
.att { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
.att .att-pick { font-size: 12.5px; padding: 5px 12px; }
.att .att-pick:disabled { opacity: .6; cursor: default; }
.att .att-tip { font-size: 12px; color: var(--ink-3, #7a8a82); }
.att .att-item { display: inline-flex; align-items: center; gap: 7px; padding: 4px 10px;
  border: 1px solid var(--line); border-radius: 999px; background: var(--panel-2); max-width: 100%; }
.att .att-nm { font-size: 12.5px; font-weight: 600; color: var(--ink); overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; max-width: 240px; }
.att .att-sz { font-size: 11.5px; font-style: normal; color: var(--ink-3, #7a8a82); }
.att .att-x { font-size: 12px; color: var(--danger); text-decoration: none; }
.att .att-x:hover { text-decoration: underline; }
.att .att-msg { font-size: 12px; color: var(--danger); }

/* 往来气泡里的附件：可点即下。与文字拉开一行，避免和正文糊在一起。 */
.bubble .att-dl { display: inline-flex; align-items: center; gap: 5px; margin-top: 6px;
  padding: 3px 9px; border: 1px solid var(--bline); border-radius: 999px;
  background: var(--panel); color: var(--b80); font-size: 12.5px; font-weight: 600;
  text-decoration: none; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bubble .att-dl:hover { border-color: var(--b60, var(--b80)); text-decoration: underline; }

/* ── 健康提醒横幅：看得见、不挡路 ──
   不用模态框：模态会盖住题目、逼学生先关掉它。提醒一旦打断学习，就成了干扰。
   所以贴顶部、可一键关、12 秒自己退场。 */
.hb {
  position: fixed; left: 50%; transform: translateX(-50%); top: 12px; z-index: 60;
  display: flex; align-items: center; gap: 10px; max-width: min(680px, calc(100vw - 24px));
  padding: 11px 14px; border-radius: var(--r-sm);
  background: var(--g80); color: #fff; font-size: 13px; line-height: 1.7;
  box-shadow: 0 6px 22px rgba(27, 84, 67, .28);
}
.hb .hb-ic { font-size: 16px; }
.hb .btn { background: rgba(255, 255, 255, .18); border-color: rgba(255, 255, 255, .4); color: #fff; }
.hb-close { opacity: .9; }

/* ── 夜锁：整页接管（服务端 423 与前端到点都走这里）──
   深绿主色底 + 浅字：学生多半是深夜看到它，深底比白底护眼；
   且它必须一眼看出"这不是故障页"——所以给月亮、给晚安，不给"错误 423"。 */
.locked-body { background: var(--g80); margin: 0; }
.locked-veil {
  position: fixed; inset: 0; z-index: 999; background: var(--g80);
  display: grid; place-items: center; padding: 20px; overflow: auto;
}
.locked-wrap { max-width: 520px; text-align: center; color: var(--g00); }
.locked-wrap .locked-moon { font-size: 40px; line-height: 1; margin-bottom: 12px; }
.locked-wrap h1 { font-size: 21px; color: #fff; margin-bottom: 10px; }
.locked-wrap .locked-now { font-size: 13.5px; color: var(--g20); }
.locked-wrap .locked-now b { color: #fff; }
.locked-wrap .locked-text { font-size: 13.6px; line-height: 1.9; color: var(--g00); margin: 12px 0 18px; }
.locked-wrap .locked-rules { display: grid; gap: 8px; text-align: left; }
.locked-wrap .locked-rules > div {
  padding: 10px 13px; border-radius: var(--r-sm);
  background: rgba(255, 255, 255, .09); border: 1px solid rgba(255, 255, 255, .16);
}
.locked-wrap .locked-rules b { display: block; font-size: 13px; color: #fff; }
.locked-wrap .locked-rules span { display: block; font-size: 12.2px; color: var(--g20); margin-top: 3px; line-height: 1.7; }
.locked-wrap .locked-foot { margin-top: 18px; font-size: 13px; color: var(--g20); }

@media (max-width: 620px) {
  /* 窄屏：状态那一栏会被挤成半截字，索性收到说明下面去（序号 + 正文两列） */
  .gate { grid-template-columns: auto minmax(0, 1fr); padding: 14px 14px; }
  .gate .gt-st { grid-column: 2; max-width: none; text-align: left; padding: 7px 0 0; }
  .guard-row { grid-template-columns: minmax(0, 1fr); }
  .hb { top: auto; bottom: 12px; }
}

/* ── 弱项检测（stu-weak.js，用户令 2026-09-22「把弱项检测的功能赋上」）──
   这一页是"只报数"的页：一行事实 + 分组清单。这里**一个色值都不写死** ——
   表格里的数字与它下面那条进度条，颜色都由 K.bandOf() 内联给出（七级色标唯一定义地），
   两处取同一个色，才保证"数字说 74%、条子画 74%"永远一致。 */
/* 这里曾经有四组规则：`.wk-facts`（一行事实）· `.wk-grp` / `.wk-grp-hd`（分组清单）·
   `.wk-dim`（"还没测过"那一档）。2026-09-26 删 —— 它们服务的旧看板已随用户令
   「照搬弱项检测内容、替代原来的弱项检测」撤除（审计 audit-student 需求3a ⑨ 断言
   这几个构件在页面上为 0）；清单本体现在住在六步向导的第⑤格与第⑥格，用向导自己的类。
   死规则不报错也不生效，留着只会让后来人以为"只是没渲染出来"。 */
.wk-advice { margin-bottom: 13px; }

/* ── 弱项检测说明 / 过程（用户令 2026-09-26「看图，这里的内容改为…」，stu-weak.js `briefHtml()`）──
   两句是这一步的**身份**（它是什么、它怎么走），写在页头卡里 —— 六步全程都在屏上；
   塞进某一步的正文，学生走到第③步就再也翻不回"这整件事是干嘛的"。
   只借既有令牌，不新增色值（色值一套一套地加，最后没人说得清哪个颜色在表示什么）。 */
.wk-brief { margin: 5px 0 0; max-width: var(--w-read); }
.wk-brief > p { margin: 0; font-size: 12.5px; line-height: 1.75; color: var(--ink-2); }
.wk-brief > p + p { margin-top: 3px; }
.wk-brief > p > b { color: var(--ink); font-weight: 650; }
/* 「过程：」是个**引子**，不是一句说明 —— 它下面紧跟的就是步骤条。
   故比「说明：」再退一档（灰、细），读起来是"往下看"，而不是"再读一句"。 */
.wk-brief > p.wk-proc { margin-top: 6px; color: var(--ink-3); }
.wk-brief > p.wk-proc > b { color: var(--ink-3); font-weight: 600; }
/* 末尾那条「检测结束…可从这进入」长在第①格里：起点处先把终点说清，并给出出口。 */
.wk-end a { color: var(--g80); font-weight: 650; text-decoration: none; white-space: nowrap; }
.wk-end a:hover { text-decoration: underline; }

/* ═══════════════════════════════════════════════════════════════
   一、板块框色轮转（用户令 2026-09-23：「所有板块都有框，但框之间的颜色必须不同。」）
   ───────────────────────────────────────────────────────────────
   取色见 :root 的 --fr1…--fr6。做法与理由：
   · 按**在父级中的序号**轮转，不按类型指派。用户看到的是"两两相邻的框"，
     而只有按父级序号轮转，才能保证同一父级下**相邻两块必不同色**；
     按类型指派（卡片一个色、折叠块一个色）在首页立刻就会出现"卡 1 与卡 2 同色"。
   · 特异性 `.card:nth-child(6n+k)` = 0,2,0，高于 `.card`(0,1,0) ⇒ 它接管**常态**框色。
     各部件自己的 hover 已提到 0,3,0（见 `.gates .gate:hover` 那条注释），
     所以"悬停时框色变绿"照旧成立，两件事各管各的。
   · `.kpi` **不参与**：它自带一条 3px 的左侧强调条（`border-left-color: --g60`），
     而 `border-color` 是四边简写 —— 把它吸进轮转会顺手改掉那条强调条的颜色。
     它是"板块里的数据格"，不是板块本身；框本来就有，不必轮转。
   · ⚠ 框色**不表语义**：不按掌握度、不按对错、不按状态，只按"第几个"。
     这条是它敢用六种色相的前提，已写进 docs/BNJH-UI-2026-001 §1.6。
     掌握度七色仍是"一个考点学到什么程度"的**唯一**色标，两者不许互相借色。
   ═══════════════════════════════════════════════════════════════ */
.card:nth-child(6n+1), .gate:nth-child(6n+1), .fold:nth-child(6n+1) { border-color: var(--fr1); }
.card:nth-child(6n+2), .gate:nth-child(6n+2), .fold:nth-child(6n+2) { border-color: var(--fr2); }
.card:nth-child(6n+3), .gate:nth-child(6n+3), .fold:nth-child(6n+3) { border-color: var(--fr3); }
.card:nth-child(6n+4), .gate:nth-child(6n+4), .fold:nth-child(6n+4) { border-color: var(--fr4); }
.card:nth-child(6n+5), .gate:nth-child(6n+5), .fold:nth-child(6n+5) { border-color: var(--fr5); }
.card:nth-child(6n),   .gate:nth-child(6n),   .fold:nth-child(6n)   { border-color: var(--fr6); }

/* ═══════════════════════════════════════════════════════════════
   二、全科雷达 / 学习画像（用户令 2026-09-23：
   「全科雷达和学习画像的底色要区分，外框颜色也要不同，高度要一致。」）
   ───────────────────────────────────────────────────────────────
   两张卡同行并排（growth 页的 `.grid.g2`）。改前它们底色框色都是默认的一套
   （近白面 + `--line` 描边）—— 并排时像"一张卡被竖线劈成两半"。
   · 底色分域：雷达是**图**（读数据）⇒ 阅读区蓝底 `--b00`；
     画像是**过程说明**（读文字）⇒ 阅读区绿底 `--g00`。
     两色都在既有的"阅读区可绿可蓝"口径内，没有新生色相。
   · 外框各自同族：`--bline` / `--gline`，与底色成对，不借轮转的框色
     （+1 层 `.main` 把特异性提到 0,3,0，压过上面的轮转）。
   · 高度一致：交给栅格 —— `.grid.g2` 默认 `align-items: stretch`，同行两项必然等高。
     不写 `height`：写死会在窄屏换行后把卡撑出空白。实测见 audit-student 需求4c。 */
.main .card.rdc { background: var(--b00); border-color: var(--bline); }
.main .card.pfc { background: var(--g00); border-color: var(--gline); }

/* ═══════════════════════════════════════════════════════════════
   三、十一级阶梯 · 五族行底色（用户令 2026-09-23：
   「十一级晋级阶梯中，列兵、士、尉、校、将之间的底色要不同。」）
   ───────────────────────────────────────────────────────────────
   族按级数切，与徽记 INS_FAMILY 同源，只把"兵/士"拆开（用户列了五个名字）：
     1 级 列兵 · 2–3 级 士 · 4–5 级 尉 · 6–8 级 校 · 9–11 级 将
   五个底色都是最浅一档，色相与徽记四色**同族**：
     兵 中性灰绿（银）· 士 浅绿 · 尉 浅蓝（靛）· 校 浅米黄（金）· 将 浅红（中国红）。
   "将"用浅红不是"警示"—— 徽记那枚中国红五角星就是这个族，红在这里=最高一级。
   落在 `tr > td` 而不是 `tr` 上：表格自身的底色画在 td，写 tr 会被它盖住。
   当前档 `tr.cur` 的高亮写在本段**之后**（同特异性 0,2,2，后写者胜），
   所以"我现在在第几级"仍一眼可见，不会被族色冲掉。 */
.rk-ladder tbody tr.rg1 > td { background: var(--panel-2); }
.rk-ladder tbody tr.rg2 > td { background: var(--g00); }
.rk-ladder tbody tr.rg3 > td { background: var(--b00); }
.rk-ladder tbody tr.rg4 > td { background: var(--warn-soft); }
.rk-ladder tbody tr.rg5 > td { background: var(--danger-soft); }
/* 当前档：近白高亮（原为 --g00，但五族底色也占着浅档，近白才跳得出来）。
   必须带 `tbody` 并把特异性提到 (0,2,3) —— 与上面的族色规则同档，靠"写在后面"取胜；
   写成 `tr.cur`（0,2,2）会被 `tbody tr.rgX`（0,2,3）压过去，那正是本轮实测踩到的坑。 */
.rk-ladder tbody tr.cur > td { background: var(--panel); }

/* ═══════════════════════════════════════════════════════════════
   四、师生交流 · 对话区高度（用户令 2026-09-23：
   「师生交流的对话框，高度增加一倍。」）
   ───────────────────────────────────────────────────────────────
   改前往来区是"随内容长"的：写一条就长一点，没有自己的高度 —— 于是这一页多高
   由"最近几条消息"决定，而看的人要的是"一片能滚的对话区"。
   现在给它固定高度：把原先常见的首屏（约 240px = 卡头 + 3～4 条气泡）**翻倍到 480px**，
   超出部分在框内滚动。窄屏（≤900px）给 360px —— 手机一屏本来就矮，
   照搬 480 会把左边"写一条"整块挤出屏幕，那不叫"变大"。
   两个值收在同一个变量 `--tk-h` 里：改高度只改一处，不必去翻三个地方。 */
.tk-list { --tk-h: 480px; height: var(--tk-h); overflow-y: auto; padding-right: 2px; }
@media (max-width: 900px) { .tk-list { --tk-h: 360px; } }

/* 教师台的往来区矮一档（320 而不是学生台的 480）。
   学生台那一页只有"读"（写的那栏在另一边），所以读得越长越好；
   教师台这一页**下面紧跟回信框** —— 480 会把「发给 XX」这个按钮推到折叠线以下，
   而"回"正是教师在这一页最常做的动作。首屏里看不见的东西，等于没有。
   降高度不是缩小信息量：超出部分照旧在框内滚动，只是把"能写"这件事留在眼前。 */
.tk-talk-list { --tk-h: 320px; }
@media (max-width: 900px) { .tk-talk-list { --tk-h: 240px; } }

/* ── 师生交流 · 教师台的「学生来信」列（用户令 2026-09-23）──
   学生台那一页的 `.tk-list` 装的是**往来**（要一条条读），教师台这一列装的是**名单**
   （先挑人、再读往来）。所以高度矮一档、每行压到两行摘要 —— 让"谁在等我"一屏看得完。
   一屏看不完，教师就会只回最上面那个，剩下的照样沉底。 */
.tk-picks { --tk-h: 372px; }

.tk-pick {
  padding: 9px 11px; margin-bottom: 8px; cursor: pointer;
  border: 1px solid var(--line-2); border-radius: var(--r); background: var(--panel);
  transition: background .12s, border-color .12s;
}
.tk-pick:hover { background: var(--g00); border-color: var(--g40); }
.tk-pick.on { border-color: var(--g60); background: var(--g00); }

.tk-hd { display: flex; align-items: center; gap: 7px; }
.tk-hd b { font-size: 13px; }
.tk-at { margin-left: auto; font-size: 11px; color: var(--ink-3); }
.tk-tag { font-size: 10.5px; font-weight: 700; padding: 1px 7px; border-radius: 999px; border: 1px solid; }
.tk-tag.wait { background: var(--danger-soft); border-color: var(--danger-line); color: var(--danger); }
.tk-tag.done { background: var(--panel-2); border-color: var(--line); color: var(--ink-3); }
.tk-txt {
  margin-top: 5px; font-size: 12.3px; line-height: 1.6; color: var(--ink-2);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}

/* 不可点的话题标签。与可点的 `.chip.on` 拉开距离：它只说明"这条在讲什么"，
   点它不会有任何事发生 —— 看着能点却没反应，比不做成按钮更让人困惑。 */
.chip.off {
  cursor: default; pointer-events: none; font-size: 11.5px; padding: 2px 9px;
  background: var(--panel-2); border-color: var(--line); color: var(--ink-3);
}

/* 「还没交流过的」单独一节。这一节的人**没有来信**，若混进上面那一列，
   KPI 的"涉及学生 / 待回复"两个数立刻虚高 —— 而这两个数正是教师用来判断
   "班里还有谁没跟我说过话"的。 */
.tk-idle { margin-top: 11px; padding-top: 11px; border-top: 1px dashed var(--line); }
.tk-idle-h { font-size: 11.5px; color: var(--ink-3); margin-bottom: 8px; }

/* ══ 平台通知（上传下达通道 · 《平台建设制度》§十三 · 2026-09-23）══
   两块内容：上面是"要我办的"（收件箱），下面是"我要报的"（上报表单 + 我发出的）。
   为什么不用表格：件是**带正文的**，而正文会长。表格会把长正文挤成一条细缝，
   教师读不完就点"已读"—— 那一按下去，回执率是涨了，制度要的"读到"却没发生。 */
.nt-no { font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 10.5px; color: var(--ink-3); letter-spacing: .3px; }
.nt-row { padding: 11px 0; border-bottom: 1px solid var(--line-2); }
.nt-row:last-child { border-bottom: 0; }
.nt-hd { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.nt-hd b { font-size: 13.4px; }
.nt-txt { margin-top: 6px; font-size: 12.6px; color: var(--ink-2); white-space: pre-wrap; line-height: 1.75; }
.nt-meta { margin-top: 6px; font-size: 11.4px; color: var(--ink-3); }
.nt-note { margin-top: 6px; padding: 6px 9px; border-left: 2px solid var(--g60); background: var(--g00); font-size: 12px; color: var(--ink-2); }
.nt-acts { margin-top: 9px; display: flex; gap: 7px; align-items: center; flex-wrap: wrap; }
.nt-input { flex: 1; min-width: 150px; padding: 6px 9px; border: 1px solid var(--line-2); border-radius: var(--r); background: var(--panel); font-size: 12.3px; font-family: inherit; }
.nt-form { display: flex; flex-direction: column; gap: 8px; }
.nt-form input, .nt-form textarea { padding: 8px 10px; border: 1px solid var(--line-2); border-radius: var(--r); background: var(--panel); font-size: 12.8px; font-family: inherit; }
.nt-form textarea { min-height: 92px; resize: vertical; line-height: 1.75; }
.nt-out { padding: 8px 0; border-bottom: 1px solid var(--line-2); font-size: 12.6px; }
.nt-out:last-child { border-bottom: 0; }

/* ── 班主任专页（2026-09-23 开工）───────────────────────────────────────
   复用 .nt-row/.nt-meta/.bar/.tk-tag 等既有件，这里只补它独有的几件。
   不另起一套"卡片/列表"样式：同一件事的呈现方式只该有一处定义（§0.2）。 */
.bz-sub { padding: 9px 0 11px; border-bottom: 1px solid var(--line-2); }
.bz-sub:last-child { border-bottom: 0; }
.bz-sub-h { display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap; }
.bz-sub-h b { min-width: 3.6em; font-size: 13.4px; }
.bz-num { font-size: 19px; font-weight: 700; font-variant-numeric: tabular-nums; letter-spacing: -.4px; }
.bz-item { padding: 9px 0; border-bottom: 1px solid var(--line-2); }
.bz-item:last-of-type { border-bottom: 0; }
.bz-item-h { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.bz-item-h b { font-size: 13.2px; }
.bz-acts { margin-top: 8px; display: flex; gap: 6px; flex-wrap: wrap; }
.bz-form { margin-top: 12px; padding-top: 11px; border-top: 1px dashed var(--line-2); display: flex; flex-direction: column; gap: 7px; }
/* 正文色变量名是 --ink（不是 --ink-1 —— 后者根本不存在，写了会静默继承父色，
   这种错法不报错、只是"看着哪儿不太对"）。本文件 :root 只有 --ink / --ink-2 / --ink-3。 */
.bz-form input, .bz-form select, .bz-form textarea { padding: 7px 9px; border: 1px solid var(--line-2); border-radius: var(--r); background: var(--panel); font-size: 12.6px; font-family: inherit; color: var(--ink); }
.bz-form textarea { min-height: 62px; resize: vertical; line-height: 1.7; }
.bz-form .btn { align-self: flex-start; }
/* 并列的两个下拉用 flex 平分：手机上挤成两行也不破版 */
.bz-inline { display: flex; gap: 7px; flex-wrap: wrap; }

/* ══ 备课 · 第 1 步「本次课的课题」（2026-10-26，用户令）══════════════════
   令文：「教师备课，选教学范围，选好的范围也就是课题，没有显示，老师因为看不见课题，
         会反复点击，给老师带来困惑。必须显示被选中的课题。」
   这一块要回答的只有一句：**这次课讲什么**。所以：
     · 一行一个课题（序号 · 课题名 · 章节路径 · 移除），不折成小胶囊；
     · 课题名 13.5px（与 .bz-item-h b 同档），路径 11.5px 灰字（它是核对用的，不是主角）；
     · 边框与底用既有令牌，不新立颜色 —— 它是一张"清单"，不是警示块。
   为什么不沿用 .tk-tag（10.5px 胶囊）：那是标签，用来标"状态/档位"；
   课题是这一页的**主语**，用标记状态的尺寸去写主语，等于没写。 */
.pp-scope { border: 1px solid var(--line); border-radius: var(--r-sm); background: var(--panel-2); padding: 9px 12px; }
.pp-scope-h { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-bottom: 4px; }
.pp-scope-h b { font-size: 13px; }
.pp-scope-h .c { font-size: 11.5px; color: var(--ink-3); }
.pp-scope ol { margin: 0; padding: 0; list-style: none; }
.pp-scope li { display: flex; align-items: baseline; gap: 8px; padding: 5px 0; border-top: 1px dashed var(--line); }
.pp-scope li:first-child { border-top: 0; }
.pp-scope .i { flex: none; width: 16px; text-align: right; font-size: 11.5px; color: var(--ink-3); font-variant-numeric: tabular-nums; }
.pp-scope .nm { font-size: 13.5px; font-weight: 600; color: var(--ink); }
.pp-scope .pa { flex: 1; min-width: 0; font-size: 11.5px; color: var(--ink-3); }
.pp-scope .x { flex: none; font-size: 11.5px; color: var(--ink-3); text-decoration: none; }
.pp-scope .x:hover { color: var(--danger); text-decoration: underline; }

/* ── 处方待确认 · 按科目分叠（2026-10-26 令文：不同科目的处方不能混） ──────────
   三件东西，各有用处，不合并：
   ① .pl-mine / .pl-warn —— 先说清"你登记的是哪几科"：没有这一句，
      老师看不懂为什么这里只有三行，会以为处方丢了；
   ② .pl-sep —— 两叠之间的那行说明（本班这一科没有任课教师），
      它回答的是"为什么这些也在你这儿"，所以它必须贴在两叠之间，不能收到页尾；
   ③ .pl-ro —— 只读行压一档灰：看得见、点不动，一眼分得清哪叠要签字。 */
.pl-mine { margin: 0 0 10px; padding: 7px 11px; border-radius: var(--r-sm); background: var(--panel-2); border: 1px solid var(--line); font-size: 12.5px; color: var(--ink-2); }
.pl-warn { margin: 0 0 10px; padding: 9px 12px; border-radius: var(--r-sm); background: var(--warn-soft); border: 1px solid var(--warn-line, var(--warn-soft)); font-size: 12.8px; line-height: 1.75; color: var(--ink-2); }
.pl-sep > td { padding: 12px 10px 10px; background: var(--panel-2); border-top: 1px solid var(--line); font-size: 12.3px; line-height: 1.7; color: var(--ink-2); }
.pl-sep b { display: block; margin-bottom: 3px; font-size: 12.8px; color: var(--ink); }
.pl-ro > td { color: var(--ink-2); background: var(--g00); }
.pl-no { font-size: 11.8px; color: var(--ink-3); }
.bz-inline > * { flex: 1 1 8em; min-width: 0; }
.bz-tag { display: inline-block; margin: 2px 5px 0 0; padding: 2px 7px; border-radius: 5px; background: var(--g00); border: 1px solid var(--line-2); font-size: 11.6px; color: var(--ink-2); }


/* ── 教师端 2026-09-24（备课/上课/判卷/班务/案例/记录）新增的三件 ────────────
   ① .bz-tabs  班主任页内的子页签（总体班情 / 学生画像 / 班务工作 / 典型案例 / 工作记录）
      —— 为什么用页内页签而不是再往侧栏加五格：这五件事**同属班主任这一顶帽子**，
      拆到侧栏会让人以为"班务工作"不是班主任的事（§0.2 一件事只有一个入口）。
   ② .grid-tbl 判卷的录分矩阵与两个视角表。原先台上没有表格样式（各处都是卡片），
      这里补一个最小的：只做"对齐 + 表头 + 行线"，不引入颜色（颜色由调用处按判据给）。
   ③ 均为**新增**，不改任何既有选择器 —— 改共用件会波及另外十台的页面。 */
.bz-tabs { display: flex; gap: 6px; flex-wrap: wrap; margin: 0 0 14px; }
.bz-tabs a {
  padding: 6px 13px; border-radius: 999px; font-size: 12.8px;
  background: var(--g00); border: 1px solid var(--line-2); color: var(--ink-2);
}
.bz-tabs a:hover { text-decoration: none; background: var(--g40); color: var(--g80); }
.bz-tabs a.on { background: var(--g40); border-color: var(--g40); color: var(--g80); font-weight: 600; }

.grid-tbl { border-collapse: collapse; }
.grid-tbl th, .grid-tbl td { padding: 5px 8px; text-align: left; border-bottom: 1px solid var(--line-2); }
.grid-tbl th { font-size: 12px; color: var(--ink-3); font-weight: 600; white-space: nowrap; }
.grid-tbl td { font-variant-numeric: tabular-nums; }
.grid-tbl tr:last-child td { border-bottom: 0; }


/* ══ 首页 · 成长区（闯关晋级 + 徽章墙）══════════════════════════════════════
   出处：用户令 2026-09-25 ——
     「复制 8780 的学生台中的『闯关晋级』和『徽章墙』样式和内在逻辑，
       搭建到 8791 的学生学习台的适当位置。」

   **搬来的**：版式与呈现逻辑。两件事上下串成一张卡，读序是"现在 → 积累"：
     上半「闯关晋级」答"我现在在哪一级、离下一级还差多少"（当下状态）；
     下半「徽章墙」答"我攒到了什么"（积累成就）—— 一格一枚徽章，圆徽 + 名称 + 条件，
     做到才亮、没做到的灰着并**把"还差什么"写在屏上**（不塞进 title 藏起来，那是诚实底线）。
     为什么上下串而不左右并排：上半是一行塞五件事的细条，下半是块状信息，
     并排必然一栏超长一栏留白。这一条是照搬 8780 的结论（它自己踩过）。
   **没搬的**：8780 那张卡的紫金配色。本工程 §1.5 是"非阅读区一律绿族"，
     全工程只有「军衔徽记四族四色」是具名例外；紫金搬进来既撞规范、也撞 audit-ui ⑨。
     所以本段底色与圆徽一律取绿族令牌 —— **本段 0 处硬编码色**（`#fff` 除外，
     它是全站既有的"深底承白字"写法，见 .btn.pri/.btn.act，不是语义色）。

   **框色不在这里写**：交还本文件末尾的「板块框色轮转」（用户令 2026-09-23
     「所有板块都有框，但框之间的颜色必须不同」）。本卡是 `.card`，
     会随它在首页里的位次自动拿到 --fr1…--fr6 之一；在这里写死框色，
     等于把那条制度挡在门外（而且会与 `.card:nth-child()` 打架，谁赢看特异性 —— 不划算）。

   **军衔胶囊的五档**：沿用 stu-grow.js 的"绿族五档深浅"（depth 0/20/40/60/80），
     但胶囊是**实底承字**，不能直接拿那五档当底色 —— 本文件自己写着 `--g40` 是描边档、
     "不许承白字"（对比只有 1.9:1）。所以按"承不承白字"重新折五档：
       0 → --g20 浅底深字   20 → --g40 浅底深字   40 → --g60 深底白字
       60 → --g80 深底白字  80 → --g80d 最深底白字
     视觉上仍是"越高级越深"，但每一档都过对比线。
   ─────────────────────────────────────────────────────────────────────── */
.gz-card { margin-top: 14px; }

/* 晋级条（对应 8780 的 .rank-strip）：卡中卡，浅绿底 + 绿边从白卡上浮起来 */
.gz-strip {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  background: var(--g00); border: 1px solid var(--gline);
  border-radius: var(--r-sm); padding: 10px 14px;
}
.gz-strip .gz-title { font-size: 13px; font-weight: 800; color: var(--g80); flex: none; }
.gz-strip .gz-medal { font-size: 12.5px; font-weight: 800; border-radius: 8px; padding: 3px 12px; flex: none; }
.gz-m0  { background: var(--g20); color: var(--g80); }
.gz-m20 { background: var(--g40); color: var(--g80); }
.gz-m40 { background: var(--g60); color: #fff; }
.gz-m60 { background: var(--g80); color: #fff; }
.gz-m80 { background: var(--g80d); color: #fff; }
.gz-strip .gz-rate { font-size: 12.5px; color: var(--ink-2); }
.gz-strip .gz-rate b { color: var(--ink); }
.gz-strip .gz-gap { font-size: 12.5px; color: var(--ink-2); }
.gz-strip .gz-gap b { color: var(--g80); }
/* 「进入闯关」胶囊：右边一坨**可点的东西**，学生一眼知道"该动手了"。
   8780 把这里从下划线文字链改成胶囊，理由是原文案像"站点导航"。
   本台的 `.gz-link` 是 `<a href="#grow">`（真链接：右键能复制、能新标签页打开），
   但要**长成按钮**而不是链接 —— 所以覆盖 a 的阅读区蓝与下划线。 */
.gz-strip .gz-link {
  margin-left: auto; font-size: 12px; font-weight: 700;
  color: var(--g80); background: var(--panel); border: 1px solid var(--gline);
  border-radius: 999px; padding: 3px 14px; white-space: nowrap; text-decoration: none;
}
.gz-strip .gz-link:hover { background: var(--g40); border-color: var(--g60); text-decoration: none; }

/* 徽章墙（对应 8780 的 .md-head / .md-grid / .md-item） */
.gz-head {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-top: 10px; border-top: 1px dashed var(--line); padding-top: 9px;
  font-size: 12.5px; font-weight: 700; color: var(--ink);
}
.gz-head .gz-count { margin-left: auto; font-size: 12px; font-weight: 400; color: var(--ink-3); }
.gz-head .gz-count b { color: var(--g80); font-size: 14px; font-weight: 700; }
/* 11 枚徽章排 6 列 = 6 + 5 两行（11 是质数，怎么排都有一行不满 —— 6 列比 5 列匀）。
   窄屏逐级降到 4 / 3 / 2 列：条件那行是整句中文，一格窄到读不顺就失去意义。 */
.gz-grid { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 9px; margin-top: 9px; }
@media (max-width: 1200px) { .gz-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
@media (max-width: 900px)  { .gz-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 620px)  { .gz-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

.gz-item {
  display: flex; gap: 8px; align-items: flex-start; min-width: 0;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--r-sm); padding: 7px 10px 8px;
}
.gz-item .gz-ic {
  width: 34px; height: 34px; border-radius: 50%; flex: none;
  display: flex; align-items: center; justify-content: center; font-size: 16px;
  background: var(--panel-2); border: 2px solid var(--line); color: var(--ink-3);
}
/* 已得：格子浮起（浅绿底 + 绿边），圆徽点亮。未得：灰着，但**条件照样写在屏上** ——
   "没做到也告诉你差什么"是这面墙存在的理由，置灰只是背景，不是把话藏起来。 */
.gz-item.got { background: var(--g00); border-color: var(--gline); }
.gz-item.got .gz-ic { background: var(--g40); border-color: var(--g60); color: var(--g80); }
.gz-item.lock .gz-ic { filter: grayscale(.55); }
.gz-item .gz-body { min-width: 0; flex: 1; display: flex; flex-direction: column; }
.gz-item .gz-name { display: block; font-size: 12px; font-weight: 700; color: var(--ink-2); line-height: 1.3; }
.gz-item.got .gz-name { color: var(--g80); }
.gz-item .gz-cond { display: block; font-size: 10.5px; color: var(--ink-3); margin: 2px 0 0; line-height: 1.35; }

/* 脚注行 = 平台的"怎么算账"（不发积分、不做排行榜、没做到写明清差什么）——
   属于游戏规则，必须留在屏上，不塞进 title 藏起来。
   （2026-09-26：原先这里还挂着一个 `.gz-auto` 胶囊写「🔄 随掌握率自动升降」——
     那句说的其实是**军衔**、不是徽章，已挪到军衔胶囊的 title 上（那里也不占屏上字数）；
     样式随之删除，不留没有使用者的规则。） */
.gz-tip { margin-top: 7px; font-size: 11px; color: var(--ink-3); line-height: 1.7; }

/* ══════════════════════════════════════════════════════════════════════
   弱项检测 · 六步向导（照搬 A 树 study.html「弱项测评」的内容 · 2026-09-25 用户令）
   ══════════════════════════════════════════════════════════════════════
   三条口径（都不是随手写的，改前先回来看）：

   ① **类名沿用 A 树的 `cep-*`**，一个都不改名。照搬的是"内容"，改名只会让
      两边从此对不上 —— 下次再比对时，谁也说不清哪个是"搬过来的那一块"。

   ② **颜色落到本工程的令牌上，不照抄 A 的色值**。A 树与 B 树的令牌体系不同
      （A 用 `--navy` / `--text2` / `--border`，B 用 `--b…` / `--g…` / `--ink-…`），
      把 A 的 hex 抄进来就会长出第六套色标 —— 而本工程的界面审计（audit-ui）
      正是为拦这件事写的。
      ⚠ 写注释时**不许出现 `星号+斜杠` 这样的连写**（哪怕只是想写"通配"）：
      CSS 注释在它那里就被闭合了，后半段会变成垃圾 CSS，而且页面**照常能显示**，
      只有审计会报"选择器里含注释碎片"。本块第一版就栽在这一条上。
      本题**整页都是"答题/测评"语境**，按 §颜色纪律 蓝族在阅读/答题语境可用，
      所以范围预览块、进度条、结果卡用蓝；**按钮底一律绿族五档**（audit-ui ⑥ 只认它们）。

   ③ **步骤条用 `.cep-progress`/`.cp`，绝不用 `.steps`/`.step`**。
      后者是**闭环页五步条**的类名；两页共用一个类名，audit-student 需求3a②
      「这一页没有闭环的五步条」这条判据就失效了 —— 判据失效比报红更麻烦。

   ⚠ 本块**不含任何 @media**：全站断点只允许 1200/900/620（audit-ui ⑤），
     窄屏靠 flex-wrap 自适应，不为这一块新开断点。
   ⚠ `.card` 的框色由 nth-child(6n+k) 轮转统一给（--fr1…--fr6），本块**不碰 border-color**。
   ══════════════════════════════════════════════════════════════════════ */

/* ── 六步进度条：三态（走完 / 你在这里 / 还没到）──
   双态是 A 树 2026-09-24 修的：原来只有一个 .on（"已完成的前 n-1 步"），
   学生看到一排同色块看不出"我此刻在哪一格"。这里照搬双态。 */
.cep-progress { display: flex; gap: 5px; margin-top: 12px; flex-wrap: wrap; }
.cep-progress .cp {
  font-size: 11.5px; color: var(--ink-3); background: var(--panel-2);
  border: 1px solid var(--line); border-radius: 9px; padding: 3px 10px;
}
.cep-progress .cp.done { background: var(--g00); border-color: var(--gline); color: var(--g60); }
.cep-progress .cp.cur { background: var(--g80); border-color: var(--g80); color: #fff; font-weight: 600; }

/* ── 每步「做什么 + 做完点哪里」的导语条 ──
   A 树加它的原因（原话）：学生「找不到通道」——通道就在按钮上，但他不知道该不该按。 */
.cep-guide {
  font-size: 12.5px; color: var(--ink-2); background: var(--b00); border: 1px solid var(--bline);
  border-radius: 10px; padding: 8px 12px; margin-top: 10px; line-height: 1.8;
}
.cep-guide b { color: var(--b80); }

/* ── ① 范围：三档（学过的全部 / 某一学期 / 某一章）+ 档位下的可选清单 ──
   选中态照搬本工程既有的 `.scope-item.sel` 语义（左色条 + 浅蓝底），
   因为这两处是同一件事：圈定一块范围。 */
.cep-scope { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 14px; }
.cep-scope .cs-opt {
  flex: 1 1 150px; min-width: 140px; text-align: left; font-family: inherit;
  border: 1.5px solid var(--line); border-radius: 12px; background: #fff;
  padding: 10px 14px; cursor: pointer; transition: .15s;
}
.cep-scope .cs-opt:hover { border-color: var(--bline); background: var(--panel-2); }
.cep-scope .cs-opt b { display: block; font-size: 13.5px; color: var(--ink); font-weight: 600; }
.cep-scope .cs-opt span { display: block; font-size: 11.5px; color: var(--ink-3); margin-top: 2px; }
.cep-scope .cs-opt.on { border-color: var(--bline); border-left: 4px solid var(--b80); background: var(--b00); }
.cep-scope .cs-opt.on b { color: var(--b80); }
.cep-pick { margin-top: 4px; }
.cep-pick .cep-pick-cap { font-size: 12px; color: var(--ink-3); margin-bottom: 8px; }
/* 限高 + 滚动（2026-09-27）：补上「节」「考点」两档后，最长的一档有 116 个考点，
   不限高会把「下一步」按钮顶到好几屏之外 —— 学生选完范围反而找不到怎么继续。 */
.cep-pick-list { display: flex; flex-wrap: wrap; gap: 7px; max-height: 300px; overflow-y: auto; padding-right: 4px; }
/* 可选条目 —— 就是选范围里那一类"可点一块"，带"考点数/题数/分钟数" */
.cep-kp {
  font-size: 12px; border: 1.5px solid var(--line); border-radius: 10px; background: #fff;
  padding: 7px 12px; cursor: pointer; transition: .15s; line-height: 1.5;
}
.cep-kp:hover { border-color: var(--bline); background: var(--panel-2); }
.cep-kp.on { border-color: var(--bline); background: var(--b00); color: var(--b80); font-weight: 600; }
.cep-kp .n { font-size: 11px; color: var(--ink-3); font-weight: 400; margin-left: 6px; }
.cep-kp.off { opacity: .55; }

/* ── 学期分组表头（2026-10-07 用户令：「节和考点的范围要把不同学期的分开，
   不同学期用不同的底色标识，最好是标上学期的名称。」）──────────────────────
   它在 .cep-pick-list 里占**整行**（flex:0 0 100%）—— 那个容器是 flex-wrap，
   不给 100% 的话表头会跟条目并排挤在一行里，"分组"就看不出来了。
   六色取 --fr1…--fr6（平台已有的"板块框色六色"，登记时的约束就是
   "只按第几个轮转、不表语义"，与"第几学期"同构）；
   底色用 color-mix 把它们调淡到 ~16%，不引入新的十六进制色值。
   为什么在列表里给底色、而不只给一条色条：用户要的是"不同底色标识"，
   而条目之间本来就有 7px 间距 —— 只给表头一条色条，条目本身仍是白的，
   隔着七八条之后就看不出"现在这组是哪一册"了。 */
.cep-term-hd {
  flex: 0 0 100%; display: flex; align-items: center; gap: 8px;
  padding: 5px 12px; border-radius: 9px; font-size: 12.5px; line-height: 1.4;
  border-left: 4px solid var(--fr1); background: color-mix(in srgb, var(--fr1) 16%, #fff);
}
.cep-term-hd b { font-weight: 600; }
.cep-term-hd span { color: var(--ink-3); font-size: 11.5px; }
.cep-term-hd.t2 { border-left-color: var(--fr2); background: color-mix(in srgb, var(--fr2) 16%, #fff); }
.cep-term-hd.t3 { border-left-color: var(--fr3); background: color-mix(in srgb, var(--fr3) 16%, #fff); }
.cep-term-hd.t4 { border-left-color: var(--fr4); background: color-mix(in srgb, var(--fr4) 16%, #fff); }
.cep-term-hd.t5 { border-left-color: var(--fr5); background: color-mix(in srgb, var(--fr5) 16%, #fff); }
.cep-term-hd.t6 { border-left-color: var(--fr6); background: color-mix(in srgb, var(--fr6) 16%, #fff); }

/* ── 范围预览四行（范围 / 考点数 / 题量 / 约需）──
   制度 §7.3 硬要求：圈选范围时实时给「考点数 → 题数 → 分钟数」——
   测评是有成本的（学生的时间），成本不能藏到点开卷子那一刻才暴露。 */
.cep-prev {
  background: var(--panel-2); border: 1px dashed var(--line); border-radius: 12px;
  padding: 12px 16px; margin-top: 12px;
}
.cep-prev .cp-r { display: flex; align-items: baseline; gap: 10px; font-size: 13px; padding: 3px 0; }
.cep-prev .cp-r span { width: 64px; flex-shrink: 0; font-size: 12px; color: var(--ink-3); }
.cep-prev .cp-r b { color: var(--b80); font-size: 13.5px; }
.cep-prev .cp-r:last-child b { color: var(--warn-d); font-size: 15px; }

/* ── ② 自述成绩：滑块 + 大字 + 一句自评描述 ── */
.cep-range { width: 100%; accent-color: var(--b80); height: 26px; margin: 8px 0 4px; }
.cep-self-val { display: flex; align-items: baseline; gap: 6px; margin-bottom: 6px; }
.cep-self-val b { font-size: 34px; color: var(--b80); }
.cep-self-val span { font-size: 13px; color: var(--ink-2); }
.cep-self-desc { font-size: 12.5px; color: var(--ink-2); }

/* ── ⑤ 结果着色：κ 四读数 + 同步块 ── */
.cep-kappa .spread { padding: 6px 0; border-bottom: 1px dashed var(--line); }
.cep-kappa .spread:last-child { border-bottom: 0; }
.cep-sync {
  display: flex; align-items: center; gap: 14px; background: var(--b00);
  border: 1px solid var(--bline); border-radius: 14px; padding: 14px 18px; margin-top: 14px;
}
.cep-sync .cs-ic { font-size: 26px; flex-shrink: 0; }
.cep-sync .cs-body { flex: 1; min-width: 0; }
.cep-sync .cs-t { font-size: 13.5px; font-weight: 700; color: var(--b80); }
.cep-sync .cs-d { font-size: 12.5px; color: var(--ink-2); margin-top: 3px; line-height: 1.7; }
.cep-sync .cs-d b { color: var(--b80); }

/* ── ⑥ 报告：三页签（学生版 / 家长版 / 历次报告）── */
.cep-report-tabs { display: flex; gap: 6px; margin-bottom: 12px; flex-wrap: wrap; }
.cep-report-tabs button {
  font-family: inherit; font-size: 12.5px; cursor: pointer;
  border: 1.5px solid var(--line); border-radius: 9px; background: #fff;
  color: var(--ink-2); padding: 6px 14px;
}
.cep-report-tabs button:hover { border-color: var(--gline); background: var(--g00); }
/* 选中页签 = 按钮实底 ⇒ 只能绿族（audit-ui ⑥：按钮底只认绿族五档 + 中性色） */
.cep-report-tabs button.on { background: var(--g60); border-color: var(--g60); color: #fff; font-weight: 600; }
.cep-rep-head { font-size: 15px; font-weight: 700; color: var(--ink); margin-bottom: 4px; }
.cep-rep-meta { font-size: 11.5px; color: var(--ink-3); margin-bottom: 8px; line-height: 1.7; }
/* 学生版里的薄弱考点行 */
.cep-weak .w-item {
  display: flex; align-items: center; gap: 10px; padding: 7px 0;
  border-bottom: 1px dashed var(--line); font-size: 12.5px;
}
.cep-weak .w-item:last-child { border-bottom: 0; }
.cep-weak .w-item .w-name { flex: 1; font-weight: 600; }
.cep-weak .w-item .w-tag { font-size: 10.5px; padding: 2px 8px; border-radius: 8px; font-weight: 600; }
.w-red { background: var(--danger-soft); color: var(--danger); }
.w-orange { background: var(--warn-soft); color: var(--warn-d); }
.w-yellow { background: var(--g00); color: var(--g60); }
/* 家长版的状态块 */
.cep-par-status { border: 1px solid var(--line); border-radius: 12px; padding: 12px 14px; background: var(--panel-2); margin-bottom: 10px; }

/* ══ 考点内容排版（2026-09-26）══════════════════════════════════════
   从 8780 搬来的 kn / warn 是**长文本**（中位 838 / 808 字），且个别条目带 markdown 表格。
   这里只补默认样式没给的三件：行距（长文挤在一起没法读）、表格边框（裸 table 会散成一堆字）、
   公式与正文的基线差。**不新立颜色、不新增色值** —— 一律用本树令牌。
   正文不用 pre-wrap：换行在 kp-typeset.js 里已经转成 br，再来一层会把每行拉成双倍行距。 */
.kp-body { line-height: 1.75; font-size: 13px; }
/* 2026-09-26 顺手修一处**不存在的令牌**：原先这里写的是 --ink-1，
   而 :root 里只有 --ink（没有 --ink-1）。CSS 遇到未定义的自定义属性**不报错、不报警**，
   只是让 `color` 变成无效值 → 该属性退回**继承**。所以它一直"看着对"：
   知识点正文里的加粗继承了正文字色。改回 --ink 才是当初写这句话的意思。
   （这一类只有人眼能发现：样式表里没有一个字符是错的，审计 ① 查的是"令牌齐备"、
     查不到"被引用的令牌是否存在"。） */
.kp-body b { color: var(--ink); }
.kp-body .katex { font-size: 1.04em; }
.kp-tb { border-collapse: collapse; margin: 8px 0; font-size: 12.5px; }
.kp-tb th, .kp-tb td { border: 1px solid var(--line); padding: 4px 9px; text-align: left; vertical-align: top; }
.kp-tb th { background: var(--g00); font-weight: 600; }

/* ══ 知识树 · 点读（kp-tts.js，用户令 2026-09-27）══════════════════════
   用户原话：「知识树——知识点/易错警示，要有朗读的，朗读到的文字，就有浅绿的高亮。
             有调朗读速度的按钮，有点读的提示。朗读的声音要清晰而圆润浑厚。」
   样式只管三件：念到的那一句怎么绿、点读条怎么排、点读的"可点"怎么看得出来。

   ── 为什么"念到"用**底绿 + 细描边**两件，而不是只换底色 ────────────────
   知识点所在的阅读区本身就有底（蓝底 .note.info.read / 暖底 .note.warn.read），
   只换底色时，"正在念"与"本来就是浅底"容易被看成同一种状态 ——
   高亮就白做了。加一圈 1px 深绿描边后，边界是硬的，
   在三种底色上都能一眼认出"就是这一句"。

   ── 色值不新立色相，只取绿族更浅的一档 ──────────────────────────────
   浅绿高亮属于**阅读区**用色（"非阅读区一律绿色"那条管的是界面件），
   取绿族的浅档，不引入第六种色相 —— 三处的浅绿（悬停 / 正在念 / 描边）深浅递进，
   是同一支绿的三个位置，不是三个颜色。 */
:root {
  --tts-hl: #cdeeb6;       /* 正在念的这一句：浅绿底 */
  --tts-hl-line: #8bc96a;  /* 描边：同支绿再深一档，用来把边界钉住 */
  --tts-hl-soft: #e8f8dd;  /* 悬停：比"正在念"再浅一档，只提示"这里能点" */
}
.tts-bar {
  display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
  margin: 2px 0 6px;
}
.tts-hint { font-size: 11.5px; line-height: 1.6; color: var(--ink-3); }
/* 音色名不是装饰：用户要的"圆润浑厚"唯一能核对的地方就是"用的是哪副嗓子"。
   读起来之后它会被填上真实音色名（没得挑时写"浏览器默认"）—— 不写就没法复核。 */
.tts-voice { font-size: 11.5px; color: var(--ink-3); }
.tts-body.speaking .tts-voice { color: var(--g60); }
.tts-bar.playing .tts-hint { color: var(--g60); }

/* 句子：可点、可高亮。`box-decoration-break: clone` 是为了跨行的那一句 ——
   不加它，一句被折成两行时只有第一行有底、第二行的底是断的（看着像少了一半）。 */
.tts-s {
  cursor: pointer;
  border-radius: 3px;
  padding: 0.5px 0;
  transition: background-color .12s linear;
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
}
.tts-s:hover { background: var(--tts-hl-soft); }
.tts-s.on {
  background: var(--tts-hl);
  box-shadow: inset 0 0 0 1px var(--tts-hl-line);
}

/* ══ 知识树 · 章节级（分级页签 + 三层）════════════════════════════════
   2026-09-26 用户令：知识树分「考点级 / 章节级」两档。
   这里只描述"点开一章之后"的那一层 —— 页签本身复用 .pill / .pill-group，
   不另造一套胶囊样式（同一页上三层胶囊若各自一套，看着就是三个来源）。
   颜色全部走本树令牌：章节级与考点级**共用同一套色标**，
   不在这里重新定义"什么算薄弱/什么算已过"（口径只能有一个）。 */
/* 分级页签：观感与 .pill.lg 一致，但**类名独立**。
   不用 .pill.lg 是有原因的：审计脚本用 `.pill.lg.on` 取"当前学段"
   （tools/audit-student.mjs:218），页签若也长成 .pill.lg，那个选择器就同时指向"当前分级"，
   语义被稀释 —— 学段项一旦没有 on，取回的会是分级页签的 dataset，值直接变空。
   一个选择器只该指向一类东西。 */
.lv-tab { display: inline-flex; align-items: center; gap: 7px; padding: 9px 22px; border-radius: 999px;
  border: 1px solid var(--gline); background: var(--panel); color: var(--g80);
  font: inherit; font-size: 14px; font-weight: 550; cursor: pointer; transition: .14s; white-space: nowrap; }
.lv-tab:hover { border-color: var(--g60); background: var(--g00); color: var(--g80); }
.lv-tab.on { background: var(--g80); border-color: var(--g80); color: #fff; }
.lv-tab .n { font-size: 11.5px; font-weight: 500; opacity: .72; }

.ch-lv { display: flex; flex-direction: column; gap: 11px; margin-top: 9px; }
.clv-sec { border: 1px solid var(--line); border-radius: 12px; padding: 11px 13px; background: var(--panel-2); }
.clv-h { display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap; margin-bottom: 9px; }
.clv-h b { font-size: 13.5px; }
.clv-n { font-size: 12px; color: var(--ink-3); }
.clv-kps { display: flex; flex-direction: column; gap: 6px; }
/* flex-wrap 而不是 grid：窄屏（手机）自己就降成单列，不必再开一个断点。
   核心句固定占满一行（flex:1 1 100%），所以"名 / 状态 / 分数"一行、核心句一行，任何宽度都读得下。 */
.clv-kp { display: flex; flex-wrap: wrap; align-items: center; gap: 5px 10px; padding: 7px 10px;
  border: 1px solid var(--line); border-radius: 9px; background: var(--panel); color: inherit; text-decoration: none; }
.clv-kp:hover { border-color: var(--g60); }
/* 考点名同 .kp .t 一条缘故（用户令 2026-10-07：两式同级同色）：跟着掌握度色走，
   色值由 stu-tree.js 内联在锚点上的 `--kpc` 给（与它上面那枚色签、左边那条色同源）。 */
.clv-kp .nm { font-weight: 600; font-size: 13px; color: var(--kpc, inherit); }
.clv-kp .tag { font-size: 11.5px; padding: 1px 7px; border-radius: 999px; white-space: nowrap; }
.clv-kp .vv { font-size: 12px; color: var(--ink-3); font-variant-numeric: tabular-nums; }
.clv-kp .core { flex: 1 1 100%; font-size: 12.5px; color: var(--ink-2); line-height: 1.6; }
.clv-mute { color: var(--ink-3); }
.clv-err { border: 1px solid var(--line); border-radius: 9px; padding: 6px 10px; background: var(--panel); }
.clv-err + .clv-err { margin-top: 6px; }
.clv-err summary { cursor: pointer; display: flex; gap: 9px; align-items: baseline; font-size: 12.5px; }
.clv-err summary .nm { font-weight: 600; }
.clv-err summary .go { margin-left: auto; color: var(--ink-3); font-size: 11.5px; white-space: nowrap; }
.clv-err .kp-body { margin-top: 8px; font-size: 12.5px; line-height: 1.65; }
/* ③「待补」的空位：用**虚线框**，与"有内容"的实线框一眼可辨 ——
   空位必须看着就是空位，不能被误读成"这一章没有易错"。 */
.clv-empty { font-size: 12.5px; color: var(--ink-3); line-height: 1.65;
  border: 1px dashed var(--line); border-radius: 9px; padding: 9px 11px; }

/* ③ 教研概述的版式（2026-09-26 22:5x 补）。
   此前**只有内容没有样式**：stu-tree.js 的 `.clv-jy` 结构早已上屏，
   而这一段 CSS 在 17:5x 那次写入时因磁盘满（os error 112）没落盘，
   于是第③层呈**裸文本** —— 「课标归属」标签与正文挤成一行、三条典型错误不分条、
   「为什么犯／怎么破」缩在同一个 inline 流里。①② 两层有版式、③ 没有，
   同一块里的三层长得不一样，读者只会以为第三层没写完。
   按 ①② 层同一套变量与尺度补齐（字号 12.5、行高 1.7+、--ink/--ink-2/--ink-3 三档）。 */
.clv-jy { display: flex; flex-direction: column; gap: 9px; }
.clv-jy-std { display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap; }
.clv-jy-std .v { font-size: 12.5px; font-weight: 600; color: var(--ink); }
.clv-jy-gist { margin: 0; font-size: 12.5px; line-height: 1.75; color: var(--ink-2); }
.clv-jy-risk { margin: 0; padding-left: 20px; display: flex; flex-direction: column; gap: 7px; }
.clv-jy-risk li { font-size: 12.5px; line-height: 1.7; color: var(--ink-2); }
.clv-jy-risk li .t { display: block; font-weight: 600; color: var(--ink); margin-bottom: 2px; }
.clv-jy-risk li .ln { display: block; }
.clv-jy-risk li .ln em { font-style: normal; font-weight: 600; color: var(--ink-3);
  font-size: 11.5px; margin-right: 5px; }
.clv-jy-src { font-size: 11.5px; line-height: 1.65; color: var(--ink-3);
  border-top: 1px solid var(--line); padding-top: 7px; }

/* ── 范围选择页 ·「这一档的学习内容」正文放大（2026-10-25 用户令）──────────
   用户原话：「学生学习台——弱项检测——范围选择——章——教研概述（字太小了，
   这也是bug！）」

   ★ 先量，再改。1440 视口实算（见 _probe_jy_font_1025.mjs）：
       .clv-jy-gist      12.5px / 行高 21.875px（1.75）
       .clv-jy-risk li   12.5px / 行高 21.25px
       .clv-jy-std .v    12.5px
       而 body 是 14px —— 这一块**唯一的正文**只有全站正文的 89%。
   平台自己写的护眼线是「正文 ≥14px、行高 ≥1.75」（本文件头注 §1.4）。
   它之所以一直没被拦住：tools/audit-ui.mjs 的 ⑦b 只量 `body { font }` 那一条声明，
   不量"正文实际落在哪个选择器上" —— 12.5px 从这条缝里过去了。
   所以这不是口味问题，是**判据的缺口**：正文落在了 .clv-jy-* 上却没有自己的底线。

   ★ 尺度与阅读页同源（不另造一套）：
     .rdx-body 里同一批类名是 16px / 1.95；这一块在卡内、密度略高，取 15px / 1.9。
     15px = body 14px 上一档，与 .stem（题干）同级 —— "这是要读的东西"。

   ★ 行宽必须同时收：原来 165 字排成 2 行、每行约 85 字。汉字没有词间空格，
     85 字一行，眼睛换行处必丢行。只放大字号不收行宽 = 放大后更难读。
     所以正文与错法列表都收进 --w-read（720px，本文件第 155 行的长文令牌，
     注释原话就是"长文超过这个宽度就开始丢行"）。720px @15px ≈ 每行 48 字。

   ★ 作用域只到 #cep-learn（范围选择页这一块），不动知识树详情面板那一处 12.5px ——
     那边是"栏里的小字"的尺度，场景不同，不能一把尺子量两处。 */
#cep-learn .clv-h b { font-size: 15px; }
#cep-learn .clv-h .clv-n { font-size: 12.5px; }
#cep-learn .clv-jy { gap: 11px; }
#cep-learn .clv-jy-std .v { font-size: 14.5px; }
#cep-learn .clv-jy-gist { font-size: 15px; line-height: 1.9; max-width: var(--w-read); }
#cep-learn .clv-jy-risk { padding-left: 22px; gap: 10px; max-width: var(--w-read); }
#cep-learn .clv-jy-risk li { font-size: 15px; line-height: 1.85; }
#cep-learn .clv-jy-risk li .t { font-size: 15.5px; margin-bottom: 4px; }
#cep-learn .clv-jy-risk li .ln em { font-size: 12.5px; margin-right: 6px; }
#cep-learn .clv-empty { font-size: 14.5px; line-height: 1.8; }

/* ══════════ 知识树 · 四级色阶说明（2026-09-26 用户令）══════════
   用户原话：「掌握度与知识树上的颜色相关，要从考点——节——章——册逐级变化，
   并且明确标注出来；考点颜色变化与节的颜色变化如何相关，你要有合理的原则。」

   "明确标注"落在两处：
     · **每一级自己**都带档名胶囊与"已过 a/b"（节头原来这两样都没有）；
     · **这一块**把规则写出来，并用当前屏上真实的一条链当例子（册→章→节→考点）。
   为什么不用示意色块当例子：示意色与屏上真色对不上时，看的人只会更糊涂 ——
   例子必须能指着屏上那几行说"就是它"。

   类名一律以 .lv-legend / .lvl- 开头，且都在 audit-ui ⑨ 的界面件域内不含蓝族令牌：
   它是界面件（说明与图例），按用户令 2026-09-22「非阅读区一律绿色」。 */
.lv-legend { margin-top: 12px; padding-top: 10px; border-top: 1px dashed var(--gline); }
.lv-legend .lvl-h { display: flex; flex-wrap: wrap; align-items: baseline; gap: 9px; margin-bottom: 8px; }
.lv-legend .lvl-h b { font-size: 13px; color: var(--g80); }
.lv-legend .lvl-n { font-size: 12px; color: var(--ink-3); }
/* 四级横排：窄屏自动折行（flex-wrap），不设写死宽度 —— 宽度的唯一定义地是全站栅格令牌 */
.lvl-lad { display: flex; flex-wrap: wrap; align-items: stretch; gap: 6px; }
.lvl-c {
  flex: 1 1 150px; min-width: 0; padding: 6px 9px;
  border: 1px solid var(--line); border-left: 3px solid var(--gline);
  border-radius: var(--r-sm); background: var(--panel);
}
.lvl-c .lvl-t { display: flex; align-items: baseline; gap: 7px; }
.lvl-c .lvl-lv { font-size: 11px; font-weight: 600; color: var(--g60); flex: none; }
.lvl-c .lvl-nm { font-size: 12.5px; font-weight: 600; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lvl-c .lvl-v { display: flex; align-items: baseline; justify-content: space-between; gap: 6px; margin: 5px 0; }
.lvl-c .lvl-u { font-size: 11.5px; color: var(--ink-3); font-variant-numeric: tabular-nums; flex: none; }
.lvl-c .bar { height: 4px; }
.lvl-ar { align-self: center; color: var(--g60); font-size: 13px; flex: none; }
.lvl-rules { margin-top: 9px; display: grid; gap: 5px; font-size: 12px; color: var(--ink-2); line-height: 1.7; }
.lvl-rules b { color: var(--ink); }
/* 规则里的那枚赤色小方片：色值由 JS 从 KPS.bandOf(0).color 取 —— 与考点格上的赤是同一个来源，
   不在这里写死一个十六进制（③ 会拦，而且写死了色标一改它就不跟着动）。 */
.lvl-rules i { display: inline-block; width: 9px; height: 9px; border-radius: 2px; vertical-align: -1px; }
.lvl-rules .lvl-f { color: var(--ink-3); border-top: 1px dashed var(--gline); padding-top: 6px; margin-top: 1px; }

/* ══ 图形知识树（2026-10-07 用户令：「知识树，还是用图形的知识树，见截图，复刻这个形式和功能！」）══
   一棵 SVG 大树 + 右侧详情面板 + 顶部统计条 + 底部图例，形制照截图像素。
   分两层责任，别混：
     · **形状**（树干/枝/叶、章圆、节圆、考点圆的大小与位置）由这里的 CSS 与 JS 布局给；
     · **颜色**只表达一件事 —— 章橙、节蓝是"这是哪一级"的固定语义（截图如此），
       考点的色**不在 CSS 里写死**，由 JS 按掌握度从 KPS.BANDS 取，CSS 只负责圆与虚线。
   为什么章/节到了"全过"要转绿：截图下方图例写明了「章/节（全部考点通过转绿）」——
   绿的语义是"这一整块拿下了"，与考点的"这一个拿下了"是同一套话，不是两套。 */
/* ── 知识树两种形式并列（2026-10-07 用户令）────────────────────────────────
   用户原话：「知识树设计为两种形式，图片式放在左边…表格式的放在右边（就是原来的）。
              版面各占50%。」
   所以是**等分** `1fr 1fr`，不是"主 + 副"：两式是同一份数据的两种读法，
   没有哪一式更主要 —— 写成 `1fr 400px` 那种，右式会被挤成一条，等于用版式
   宣布"表格式是附件"。窄屏（≤1040px）叠成一列：等分在两边都放不下时，
   唯一的结果是两边都不可读，那时候"并排"这个形式本身就失去了意义。 */
/* ★ 2026-10-07 用户令：「…与图片式知识树之间留点空间。」⇒ 列间距 14 → 26。
   等分 `1fr 1fr` 不动（那是"版面各占50%"的字面要求，见上段），加的是**两条之间的空**：
   上一版 14px 在两棵带边框的树中间几乎是一条缝，远看像"一整块被切成两半"；
   26px 之后中间那条空带才读得出"这是两件并排的东西"。
   窄屏（≤1040px）叠成一列时，这条 gap 变成两式之间的**纵向**间距 ——
   方向从横变竖，"留点空间"这句意思一个字没变。 */
/* ── 知识树折叠条（2026-10-09 用户令）─────────────────────────────────────
   用户令：「把"知识树"修改成能折叠，平时处在折叠状态，但功能不减。点击"展开"按钮，
     才展开。注意："知识点/易错警示"的内容不折叠。」
   ⇒ 折的是下面 .tg-wrap（图片式 + 表格式）与图例；详情面板 .tg-detail 在折壳**之外**，
     不折 —— 知识点与易错提醒就长在它里面。
   颜色全取令牌（非阅读区色纪律），不新增断点（全站只认 1200 / 900 / 620 那三档）。
   下面那两条 media 只管 .tg-wrap 的列数，与折条无关：折条本身就是整幅宽的一条。 */
.tg-fold { margin-bottom: 12px; }
.tg-fold-h { display: flex; align-items: center; gap: 10px; width: 100%; box-sizing: border-box;
  padding: 10px 14px; font: inherit; text-align: left; cursor: pointer;
  color: var(--ink); background: var(--panel-2);
  border: 1px solid var(--line); border-radius: var(--r, 12px); }
.tg-fold-h:hover { border-color: var(--line-2); }
.tg-fold-h:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.tg-fold-h .cv { flex: none; width: 12px; color: var(--ink-3); font-size: 11px; }
.tg-fold-h > b { font-size: 15px; color: var(--g80); }
.tg-fold-h .sub { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 12.5px; color: var(--ink-3); }
.tg-fold-h .op { flex: none; margin-left: auto; padding: 3px 12px; font-size: 12.5px;
  color: var(--ink-2); background: var(--panel);
  border: 1px solid var(--line); border-radius: 999px; }
.tg-fold-b { padding-top: 12px; }
.tg-wrap { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 26px; align-items: start; }
@media (max-width: 1040px) { .tg-wrap { grid-template-columns: 1fr; } }
.tg-col { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.tg-colhead { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; padding: 0 2px; font-size: 12.5px; color: var(--ink-3); }
.tg-colhead b { font-size: 14px; color: var(--g80); }
.tg-colhead .hint { margin-left: auto; font-size: 11.5px; color: var(--ink-3); }
/* 表格式的容器：**2 列 × 3 排**（用户令）。
   行 = 一个年级，列 = 上学期 / 下学期；册序由接口 termCols 摊平而来（见 stu-tree.js 的 tableDraw）。
   `align-items: start` 让每一排各自齐顶 —— 各册章数不同，行高本就不等，
   强行拉平只会让"章少的那一册"底下留一大块空白，看着像漏画了。 */
.term-rows { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; align-items: start; }
.tg-canvas { background: #fff; border: 1px solid var(--gline); border-radius: 18px; padding: 8px; overflow: auto; max-height: calc(100vh - 190px); box-shadow: 0 4px 18px rgba(15, 23, 42, .05); }
.tg-svg { display: block; width: 100%; height: auto; }
.tg-svg.fit { width: auto; max-width: 100%; max-height: calc(100vh - 220px); margin: 0 auto; }

/* ── 表格式知识树「缩小 · 秀气」（2026-10-07 用户令）────────────────────────
   用户原话：「表格式知识树，适当缩小，弄秀气点，与图片式知识树之间留点空间。」
   前两件事（缩小 / 秀气）落到这里；第三件"留点空间"落在上面 `.tg-wrap` 的 gap。

   ① **秀气 = 密度，不是幅面**：所以只收 font-size 与 padding，**一个
      grid-template-columns 都不碰**。为什么不把右列改窄（写成 `1fr 0.85fr` 之类）：
      `.tg-wrap` 那段注释里写着"等分 1fr 1fr"是用户上一版令（「版面各占50%」）的字面要求，
      收列宽等于把那条令改掉。同样的半幅宽里字小一号、留白收一档，读起来就细 ——
      这正是"秀气"这个词在说的事。

   ② **作用域只到 `#tg-table`（右式）** —— 这一条是**构造上的保证**，不是"碰巧"：
      `.ch-row`(app.css 968) / `.sec-group`(1029) / `.kp-grid`(540) 都不是表格式的私件，
      它们是**全局登记的公用件**（`.kp-grid` 还由 app.js 的 `BNJH.kpGrid` 统一造，
      `kps` 一变全站一起变）。**实测**：截至 2026-10-07 这三个类只被 stu-tree.js 用到，
      所以裸改公共规则今天也不会误伤别处 —— 但"今天不误伤"不等于"永远不误伤"：
      下一版谁在别处放一格考点（`BNJH.kpGrid` 是公开的），裸规则就会**静默**把那格也缩小，
      而且屏幕上看不出来是这里出的手。写成 `#tg-table 后代` 之后，这条令的边界
      就被选择器钉死了：**只缩表格式，别的照旧**，不靠"现在没人用"。
      附带好处：id 权重把特异性抬到 (1,x,0)，稳稳压过原规则 (0,x,0)，
      与"后写者胜"无关，也不靠 !important。

   ③ **颜色一个字不动**：下面没有一行 color / background / border-color。
      `.ch-row .nm`(#E25555) / `.sec-group > .sh .nm`(#3B82F6) / 考点格的 `--kpc`
      全部原样 —— "两式同级同色"（同日用户令）是**颜色**的对齐，与字号无关，
      缩字号不破坏它。左条的宽度也不动（节 3 · 考点 4 · 章 5 · 册 6 是层级语义）。

   收的幅度取"一档"（字号 13→12、内边距各减 1–2px）：用户说的是"适当缩小"，
   不是"缩到看不清" —— 一次收太多，下一次令就只能是"改回去"。 */
#tg-table { gap: 8px; }                                       /* 原 .term-rows 的 10px：2×3 排更紧 */
#tg-table .term-block > .th { padding: 5px 9px; gap: 8px; font-size: 12px; }
#tg-table .term-block > .th .nm { gap: 5px; }
#tg-table .term-block > .th .nm .tp { font-size: 12px; padding: 1px 9px; }   /* 学期胶囊同步收一档 */
#tg-table .term-block > .th .st { font-size: 11px; }
#tg-table .term-block > .th .bar { height: 5px; margin-top: 4px; }
#tg-table .term-body { padding: 6px 0 0 2px; gap: 6px; }
#tg-table .ch-row { padding: 5px 10px; gap: 7px; font-size: 12px; }
#tg-table .ch-row .mt, #tg-table .ch-row .avg { font-size: 11px; }
#tg-table .ch-block .bar { height: 4px; margin-top: 3px; }
#tg-table .ch-body { padding: 7px 0 2px 10px; }
#tg-table .ch-acts { margin: 0 0 7px 2px; }
#tg-table .ch-acts .dt { font-size: 11px; }
#tg-table .sec-group { margin-bottom: 8px; }
#tg-table .sec-group > .sh { padding: 3px 8px; gap: 6px; margin: 0 0 5px 2px; font-size: 11.5px; }
#tg-table .sec-group > .sh .nm { font-size: 12px; }
#tg-table .sec-group > .sh .st, #tg-table .sec-group > .sh .wk { font-size: 11px; }
#tg-table .sec-group .bar { height: 3px; margin-top: 2px; }
#tg-table .kp-grid { grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 6px; }
#tg-table .kp { padding: 6px 9px; }
#tg-table .kp .t { font-size: 12px; }
#tg-table .kp .kp-hd .v { font-size: 11px; }
#tg-table .kp .m { font-size: 11px; margin-top: 2px; }

/* ══════════ 表格式的"级别装置"（2026-10-12 用户令）══════════════
   用户原话（截图「关于知识树的修改」第 2 条）：
     「章节考点要注明，颜色和形式有区别，比如：
        初一上 / 第二章 ****** / 第四节****** / 考点1****** …」
   落地成**两件彼此独立的东西**：
     ① **级别的名字**（徽标）——章 = 从"第一章有理数"里切出来的"第一章"；
        节 = "第N节"（这一章内的第几节）；考点 = "考点N"（这一节内的第几个）。
        这正是"注明"二字：一屏都是框，光看框分不出谁含在谁里面。
     ② **逐级缩进** —— 学期 0 → 章 +12 → 节 +26 → 考点 +36，每级退一格。
        缩进是"含在谁里面"的唯一空间表达，且不依赖颜色（色盲也看得见）。
   ⚠ 三级的**名字颜色一个字不动**（章 #E25555 / 节 #3B82F6 / 考点 var(--kpc) 掌握度色）：
     那是 2026-10-07 用户令，且被门禁「两式同级同色」钉着 —— 徽标是**另加**的一件装置，
     它靠自己的形（实底块 / 空心块 / 浅灰块）与色族做区分，不是靠改名字的颜色。
   ⚠ 左色条宽度也不动（节 3 · 考点 4 · 章 5 · 册 6 是层级语义，写在上面那段里）。
   一句话说清分工：**徽标管"这是哪一级"，左色条与分值管"这一级学得怎样"。** */
.lvb { flex: none; font-weight: 700; letter-spacing: .3px; line-height: 1.5; white-space: nowrap; }
/* 章：**实底**方徽标（浅红底红字，与章名的红同族） */
.lb-ch { font-size: 11px; color: #C0392B; background: #FDECEC; border: 1px solid #F6C9C3;
  padding: 0 6px; border-radius: 5px; }
/* 节：**空心**方徽标（白底蓝框蓝字，与节名的蓝同族）—— 与章的实底一眼可分 */
.lb-sec { font-size: 10.5px; color: #2F6FD8; background: #fff; border: 1px solid #9CC5FB;
  padding: 0 6px; border-radius: 5px; }
/* 考点：最轻的一档（浅灰底灰字）。它**嵌在 .t 里**，不是 .kp-hd 的第三个 flex 子项 ——
   .kp-hd 用 justify-content:space-between，多一个平级子项会把考点名挤到中间去。 */
.lb-kp { font-size: 10.5px; color: #64748B; background: #F1F5F9; border: 1px solid #E2E8F0;
  padding: 0 5px; border-radius: 4px; margin-right: 5px; }

/* 缩进阶梯。只动 margin/padding，不碰上面任何颜色与字号。
   ⚠ 这三条必须写在这儿（这份样式表**后面**）：`.ch-body` 的 padding-left 在
     上面那段"适当缩小"里已写过一次，两条特异度相同、比的是源码先后。 */
#tg-table .ch-row { margin-left: 10px; }
#tg-table .ch-body { padding-left: 22px; }
#tg-table .kp-grid { padding-left: 12px; }

.tg-node { cursor: pointer; }
.tg-node text { user-select: none; pointer-events: none; }
/* 缩放放在内层，避免 transform 覆盖外层 SVG translate 造成抖动 */
.tg-ns { transition: transform .16s ease; transform-box: fill-box; transform-origin: center; }
/* 光标靠近文字就放大（2026-10-07 用户令「光标靠近文字稍再放大一些」）。
   ★ 2026-10-12 用户令：「把目标文字再放大些；把目标文字颤抖的毛病治好！」
   ⇒ 1.32 → 1.55。
   ⚠ "颤抖"已在这一版**从机制上**治好，所以这里才敢往上加：原先前一版
     1.32 都会抖，是因为缩放原点落在"圆点+长名字"这个**不对称框**的中心上，
     一放大圆点就滑出光标（实测位移 19px > 缩放后半径 18px，自激振荡）。
     现在每个 .tg-ns 里都先放了一块以圆点为中心的对称空框（见 stu-tree.js 的 nsAnchor），
     原点回到圆点本身 ⇒ 放大是"原地长大"，不再有位移。
   ⚠ 上限仍受版面约束：树上的节点是**定距**排的，放大到约 1.6 之后相邻节点的字会挨上；
     1.55 是"看得清"与"不压邻居"之间的当前取值。 */
.tg-node:hover .tg-ns { transform: scale(1.55); }
/* ★ 2026-10-07 用户令：「光标靠近图形式的考点，文字要继续放大。」
   ⇒ 考点（.tg-kpn）单独一档、**更大**：现为 章/节 1.55 → 考点 1.9（2026-10-12 同步上调，
     用户令「把目标文字再放大些」——两档一起抬，**差值仍在**）。
   为什么只给考点加大：章 / 节是"点开去看下一层"的路标，考点才是学生真正要动手的那一格；
   三档都放大到章名刚好看清的倍数时，考点那一串**更小的字**（17px，比章名 20px 小）仍嫌小。
   写在 .tg-node 那条**之后**、且选择器同一组特异度（0-3-0）—— 靠"后写者胜"覆盖，
   不靠 !important：下一版谁再调这两档，按顺序读一遍就看得懂谁压谁。 */
.tg-kpn:hover .tg-ns { transform: scale(1.9); }
.tg-node:hover .tg-chname, .tg-node:hover .tg-secname, .tg-node:hover .tg-kpname { text-decoration: underline; }
/* 学期胶囊：同样的"靠近就放大"，但**它不是可点的节点**，所以不共用 .tg-node
   （.tg-node 带 cursor:pointer，把不可点的东西画成手型，是在骗人）。 */
.tg-tnode { cursor: default; }
/* 学期胶囊 1.12 → 1.2（2026-10-12 与另两档一起抬）。
   它本来就**不抖**：胶囊的框是"以胶囊中心对称的矩形 + 居中的字"，
   缩放原点恰好落在胶囊中心，所以它从来不在"颤抖"那条名单里 ——
   这一档只是跟着用户令"再放大些"抬一点点。 */
.tg-tnode:hover .tg-ns { transform: scale(1.2); }

.tg-trunk { fill: #5A3418; }
.tg-root { fill: #5A3418; opacity: .5; }
.tg-branch { fill: none; stroke: #6B4222; stroke-width: 7; stroke-linecap: round; }
.tg-vine { fill: none; stroke: #6B4222; stroke-width: 2.4; stroke-linecap: round; }
.tg-twig { fill: none; stroke: #B0805A; stroke-width: 2; stroke-linecap: round; }
.tg-leaf { fill: #7CC576; opacity: .92; }
.tg-leaf.lt { fill: #9BD98F; }

.tg-tpill { fill: #00A63E; }
.tg-ttext { fill: #fff; font-weight: 800; text-anchor: middle; }

.tg-chc { fill: #F59E0B; stroke: #D97706; stroke-width: 1.8; }
.tg-chc.ok { fill: #22C55E; stroke: #15803D; }
.tg-chn { fill: #fff; font-weight: 800; text-anchor: middle; }
/* 章名（左式 · 图形式树）。层级红由 #DC2626 调浅为 #E25555 ——
   与右式 .ch-row .nm **同值、同日**改（2026-10-07 二次用户令，理由写在 .ch-row .nm 那段）。
   两式同级不同色会被读成两种状态，所以这一处不是"可跟着改"，是**必须同改**。 */
.tg-chname { fill: #E25555; font-weight: 700; }

.tg-secc { fill: #3B82F6; stroke: #2563EB; stroke-width: 1.4; }
.tg-secc.ok { fill: #22C55E; stroke: #15803D; }
.tg-secname { fill: #3B82F6; font-weight: 700; }
.tg-secname.ok { fill: #15803D; }

.tg-kpc { stroke-width: 1.2; }
.tg-kpname { font-weight: 700; }
/* 没教过的考点：空心虚线 —— 与全站同一条口径（没学过的东西不染任何色） */
.tg-kpc.off { fill: #fff; stroke: #B6BEC9; stroke-dasharray: 2.6 2.2; }
.tg-kpname.off { fill: #98A2B3; }

/* 两式**共用**的详情面板（2026-10-07 起落在两式之下、整幅宽）。
   原先它是右半幅的一根 sticky 侧栏；现在两式各占 50%，它就不再属于任何一式 ——
   硬留第三栏会破坏"版面各占50%"这句话的字面意思。
   去掉 sticky：一条通栏的带子吸在视口边上，会盖住它下面那一半页面，
   而它本身可能很长（考点清单 + 章节级三层内容），吸住 = 半屏永久被占。
   点击左半幅时由 stu-tree.js 的 reveal() 把它滚进视野，不靠 sticky 兜。 */
.tg-detail { margin-top: 14px; }
.tg-empty { border: 2px dashed var(--gline); border-radius: 16px; padding: 36px 22px; text-align: center; color: var(--ink-3); background: #fff; line-height: 2; }
.tg-empty .ico { font-size: 40px; line-height: 1; }
.tg-empty b { display: block; color: var(--g80); font-size: 15px; margin: 10px 0 6px; }
.tg-pan h3 { margin: 0 0 4px; font-size: 16px; }
.tg-pan .crumb { font-size: 11.5px; color: var(--ink-3); margin-bottom: 10px; }
.tg-pan .rowline { display: flex; flex-wrap: wrap; gap: 8px 16px; font-size: 12.5px; color: var(--ink-2); margin-bottom: 10px; }
.tg-pan .rowline b { color: var(--ink); }
.tg-legend { display: flex; justify-content: center; gap: 18px; flex-wrap: wrap; padding: 10px 12px; background: #fff; border: 1px solid var(--gline); border-radius: 14px; margin-top: 12px; font-size: 12px; color: var(--ink-3); }
.tg-legend span { display: inline-flex; align-items: center; gap: 5px; }
.tg-legend i { width: 11px; height: 11px; border-radius: 50%; display: inline-block; }
.tg-legend i.sq { border-radius: 3px; width: 16px; height: 11px; }
.tg-acts { display: flex; gap: 8px; justify-content: center; margin-top: 10px; flex-wrap: wrap; }

/* 绿色统计条（复刻截图）：绿底白字，只报数、不解释 */
.tg-subbar { background: #2E7D32; color: #fff; border-radius: 14px; padding: 11px 18px; display: flex; align-items: center; justify-content: space-between; gap: 14px; flex-wrap: wrap; margin-bottom: 14px; }
.tg-subbar .l b { font-size: 15px; }
.tg-subbar .l small { display: block; font-size: 11.5px; opacity: .92; margin-top: 2px; }
.tg-subbar .s { display: flex; gap: 18px; font-size: 12.5px; flex-wrap: wrap; }
.tg-subbar .s span { display: inline-flex; align-items: center; gap: 4px; }
.tg-subbar .s b { font-size: 16px; }
.tg-subbar .s .dim { opacity: .82; font-size: 11.5px; }
/* ── 变色与掌握度的关系（一行）────────────────────────────────────────────
   2026-10-07 用户令：「…关于变色与掌握度的关系部分放在『平均掌握度』的下一行。」
   所以这一条**紧贴绿色统计条下方**：上边距取负值，把统计条的 14px 下外边距吃掉大半，
   两条读起来是一整条带子（上一行读数字、这一行读颜色），而不是两个并列的标题。
   底色比统计条浅得多、不再是深绿：它是"数字怎么读"的注脚，不该与统计条争视觉重量。
   去掉 .tg-more（折叠块）那三条 —— 那一块已按同日用户令整体撤下，留着就是死规则。 */
.tg-colorline { background: #F4F8F4; border: 1px solid #E2EDE4; border-radius: 12px;
  padding: 8px 16px 9px; margin: -8px 0 14px; }
.tg-colorline .legend { font-size: 11.5px; }

/* ══ 授勋（用户令 2026-10-09《健航V4.0修改词》§三·4）══════════════════════════
   勋章底色走**绿族深浅**（--g00/--g25/--g40/--g60/--g80），与军衔同一个理由：
   七色色标是"某个考点掌握到哪一档"的专用语，混进勋章里，学生看见红色就分不清
   是"考点没达标"还是"勋章等级低"。等级名一律用文字写清楚（钻石/金质/银质/铜质/见习）。 */
/* ★ 2026-10-09 用户令：「『授勋』栏，把 9 科排成一排。」
   ⇒ 从"自动填列（148px 起）"改成**固定九列**：九门科目永远排成一行。
   `minmax(0, 1fr)` 里的 0 是关键：写成 auto / min-content，第九格的中文与数字
   会把列撑宽 —— 一张卡就此横向溢出（620 窄屏尤甚，审计有一条专抓横向溢出）。
   窄屏只收内边距与勋章尺寸，**列数不降**：用户要的就是"排成一排"。 */
.hn-grid { display: grid; grid-template-columns: repeat(9, minmax(0, 1fr)); gap: 8px; }
.hn-cell { border: 1px solid var(--line); border-radius: var(--r); padding: 10px 6px;
  text-align: center; background: var(--panel-2); min-width: 0; }
.hn-cell.won { background: var(--panel); border-color: var(--g40); }
/* 九列窄格：勋章与字号各收一档（九格并排后，每格只有原来自动填列时的六成宽）。 */
.hn-medal { width: 34px; height: 34px; display: block; margin: 0 auto 5px; }
.hn-cell > b { display: block; font-size: 12.5px; color: var(--ink); }
.hn-name { display: block; font-size: 11.5px; font-weight: 600; margin-top: 2px; }
.hn-rate { display: block; font-size: 10.5px; color: var(--ink-3); margin-top: 2px;
  font-variant-numeric: tabular-nums; }

/* ══ 成绩表（用户令 2026-10-09 §三·2）═════════════════════════════════════════
   「制成可折叠、按学期折叠」= 每一个学期一个 <details>，默认收着（一屏先给"有哪几学期"）。 */
.sc-term { margin-bottom: 8px; border: 1px solid var(--line); border-radius: var(--r); background: var(--panel); }
.sc-term > summary { cursor: pointer; padding: 9px 12px; font-size: 13.5px; font-weight: 600;
  color: var(--g80); list-style: none; }
.sc-term > summary::-webkit-details-marker { display: none; }
.sc-term > summary::before { content: '▸ '; color: var(--ink-3); }
.sc-term[open] > summary::before { content: '▾ '; }
.sc-term .tbl-wrap { margin: 0 12px 12px; }
/* ★ 2026-10-09 用户令：「把『成绩表』中的文字上下居中，把表格的线的颜色加深。」
   ① 上下居中：`vertical-align: middle` —— 考点格里的"三进程已通关/在做提升"小标
      与章、节两个 rowspan 合并格都跟着居中，整行读起来是一条线。
   ② 线加深：行线 --line-2 → **--g25**、表头线 → **--g40**，都取既有令牌，
      **不新造色值**（audit-ui ③ 对未登记色值报红；这也是本表"能加深到什么程度"的上限）。
   ③ 作用域限 `.sc-term`：只动成绩表 —— 全站其余表格的浅线是刻意的，不跟着变。
   ⚠ 权重：`table.tbl th` 是 (0,1,1)，这一组 (0,2,1) 才压得住，所以必须带 `.sc-term` 前缀；
     写 `.tbl th` 会**静默不生效**（页面看不出错，线还是浅的）—— 本工程栽过一次同类。 */
.sc-term table.tbl th, .sc-term table.tbl td { vertical-align: middle; border-bottom-color: var(--g25); }
.sc-term table.tbl thead th { border-bottom: 1px solid var(--g40); }
.sc-n { font-size: 11.5px; font-weight: 400; color: var(--ink-3); margin-left: 8px; }
.sc-ch { font-weight: 600; color: var(--ink); white-space: nowrap; }
.sc-sec { color: var(--ink-2); white-space: nowrap; }
.sc-acc { font-variant-numeric: tabular-nums; text-align: right; white-space: nowrap; }
.sc-acc.pass { color: var(--g60); font-weight: 700; }
.sc-acc.none { color: var(--ink-3); }
/* 成绩表 · 正确率热键（用户令 2026-10-11：「把成绩表里的正确率制成热键，点击热键直接进入
   相应的考点学习 —— 即：进入今日任务的『自主定任务』，并且范围已定为点击正确率的考点和层级」）。
   三件事一起做，才叫"看得出能点、但不喧宾夺主"：
     ① 数字**照旧**（不加重、不改色、不加下划线）—— 成绩表看起来还得是成绩表；
     ② 底色只在**悬停**时给（同 .tk-todo 的落法），一过就退回去，表格不花；
     ③ 键盘也给一条路（focus-visible 描边），与本站其余热键同一种口径。
   ⚠ 负 margin 抵消内边距：让悬停底色比数字略宽一点点，但**不把列撑开**（列宽由表头定）。 */
.sc-acc .sc-go { color: inherit; text-decoration: none; border-radius: 6px;
  padding: 1px 6px; margin: -1px -6px; display: inline-block; }
.sc-acc .sc-go:hover { background: var(--g00); text-decoration: underline; text-underline-offset: 2px; }
.sc-acc .sc-go:focus-visible { outline: 2px solid var(--g60); outline-offset: 1px; }
.sc-cur { display: inline-block; margin-left: 6px; font-size: 11px; color: var(--g60); font-weight: 600; }

/* 窄屏：学业授衔的九列 与 掌握度地图的九图（本轮两块新排布）────────────────────
   断点只取全站许可的那三档（1200 / 900 / 620，audit-ui ⑤），**不新开档**。
   授衔（九列科目）：列数**不降**（用户要"排成一排"），只收内边距与格子尺寸 ——
        九列在 620 下每格约 60px，正好放得下科名与百分数。
   地图：同授勋一样**列数不降**（2026-10-09 第三令："排成一行"），九格永远一行；
        窄屏只收"画多大"：断点处依次把环 68 → 58 → 48 → 38，
        并按 viewBox 比例**反向补环心字号**（缩环不补字，环心会糊成一团，
        理由见上面 `.km-cell .chart text.rg-c / .rg-s` 那段）。 */
@media (max-width: 1200px) {
  .km-grid { gap: 6px; }
  .km-cell { padding: 5px 5px 3px; }
  .km-cell .ring-chart svg { width: 58px; height: 58px; }
  .km-cell .chart text.rg-c { font-size: 30px; }
  .km-cell .chart text.rg-s { font-size: 19px; }
}
@media (max-width: 900px) {
  .hn-grid { gap: 6px; }
  .hn-cell { padding: 8px 3px; }
  .hn-medal { width: 28px; height: 28px; }
  .hn-cell > b { font-size: 11.5px; }
  .hn-name { font-size: 10.5px; }
  .hn-rate { font-size: 9.5px; }
  .km-cell { padding: 4px 3px 2px; }
  .km-cell .ring-chart svg { width: 48px; height: 48px; }
  .km-cell .chart text.rg-c { font-size: 33px; }
  .km-cell .chart text.rg-s { font-size: 21px; }
  .km-ct { font-size: 10.5px; }
  .km-cell .km-sum { font-size: 8.5px; }
}
@media (max-width: 620px) {
  .hn-cell { padding: 6px 2px; }
  .hn-medal { width: 22px; height: 22px; margin-bottom: 3px; }
  .hn-cell > b { font-size: 11px; }
  .hn-name, .hn-rate { font-size: 9.5px; }
  .km-grid { gap: 4px; }
  .km-cell { padding: 3px 2px 2px; }
  .km-cell .ring-chart svg { width: 38px; height: 38px; }
  .km-cell .chart text.rg-c { font-size: 36px; }
  .km-cell .chart text.rg-s { font-size: 23px; }
  .km-ct { font-size: 9.5px; }
  .km-cell .km-sum { font-size: 8px; }
  .km-cell .km-bands { gap: 0 3px; }
  .km-cell .km-b > i { width: 5px; height: 5px; flex: 0 0 5px; }
}

/* ══════════════════════════════════════════════════════════════════════
   页面折叠 —— 「进入页面只露目录条，点哪条展开哪块」（用户令 2026-10-09）

   用户原话：「因为页面内容太多，扰乱学生的注意力，消耗学生精力，还带来负面情绪……
     1. 点击导航栏的按钮后，进入的页面处于折叠状态，页面只有目录的按钮……
     2. 经过跳转进入的页面也一样处于折叠状态，只显示目录按钮。
     以上指令，适合所有角色的所有同类情况。」

   做法：**一处定义**（本节 + app.js 的 `foldBody()`），九台一次生效 ——
   各台视图脚本**不要**自己实现折叠、也不要各写一套（写了就是第二个口径）。

   折叠单位 = 正文 `#pg-body` 里的**块**（本工程的卡片惯例是 `.card`；
   `.grid` / 无 class 的包裹 div 这类**纯容器会被下钻**，收起来的是里面的块，
   否则收起一个网格会连"有哪些块"都看不见）。
   目录条 = 该块**原本的 `header`**（`.card > header` 的既有惯例），
   没有 header 的块用它的 h2/h3 —— **标题元素本身不动**，只给它加三角标与可点行为。
   这样"目录条"与原来的卡片头是同一个东西，不会长出"卡片头 + 目录条"两层皮。

   ⚠ 与 `.fold`（`<details class="fold">`）不是一回事：
     `.fold` 是**页内小节**的语法件（作者显式写进模板）；
     `.pgf` 是**页面级目录**（由外壳在渲染后统一施加，作者不写、也不该写）。
   ⚠ 开关：`localStorage.pgfold === 'off'` ⇒ 完全不折叠。
     门禁与取证脚本必须先设它 —— 否则"收起 = display:none"会让脚本读到的
     `innerText` 变成空白，把好功能误判成缺陷（这是本轮最容易踩的坑）。
   ══════════════════════════════════════════════════════════════════════ */
/* ⚠ 类名前缀必须是 `pgf-`（page fold），**不能**用 `pf-`：
   `.pf` / `.pf-h` / `.pf-v` 已被「学习画像·四维竖排」占用（stu-grow.js，见本文件 1523 行起）。
   本工程没有 CSS Modules，"起个新类名"这件事**必须先 grep 一遍全 CSS** —— 重名的后果是
   两边都悄悄变形，且**控制台零报错**（2026-10-09 本轮就差点犯，落笔前 grep 才发现）。 */
.pgf > .pgf-h {
  cursor: pointer; user-select: none;
}
/* ⚠ 三角要挂在**标题文字**上，不能挂在目录条容器上：
   目录条多半本身就是 flex 容器（`.card > header` 是 `justify-content: space-between`），
   往容器身上挂 `::before` 会多出一个 flex item —— 标题被一路挤到行尾，
   屏幕上变成"左边一个孤零零的 ▸、右边一行字"。2026-10-09 出图时正是这样，已改。 */
.pgf > .pgf-h h2::before, .pgf > .pgf-h h3::before, .pgf > .pgf-h h4::before,
.pgf > h2.pgf-h::before, .pgf > h3.pgf-h::before, .pgf > h4.pgf-h::before {
  content: '▸'; color: var(--g60); font-size: 11px; margin-right: 7px; display: inline-block;
}
.pgf.open > .pgf-h h2::before, .pgf.open > .pgf-h h3::before, .pgf.open > .pgf-h h4::before,
.pgf.open > h2.pgf-h::before, .pgf.open > h3.pgf-h::before, .pgf.open > h4.pgf-h::before { content: '▾'; }
.pgf > .pgf-h h2:hover::before, .pgf > .pgf-h h3:hover::before,
.pgf > h2.pgf-h:hover::before, .pgf > h3.pgf-h:hover::before { color: var(--g80); }
.pgf > .pgf-h:focus-visible { outline: 2px solid var(--g60); outline-offset: 3px; border-radius: 4px; }
/* 收起态 = 只剩目录条：其余子元素一律不参与渲染。
   ⚠ 这里必须 `!important`：本工程有相当多块的内容子元素带**内联** `style="display:grid|flex"`，
   内联样式优先于类选择器 —— 不加它就"折了等于没折"（父层藏了、子层自己又支棱起来）。
   而它的**外显症状是"像已经折好了"**：`innerText` 确实变短了（父层真被藏了），
   只有逐个子元素量"还有没有布局盒"才看得出来。2026-10-09 本轮被 audit-fold 逮住，
   涉及 `#grow` / `#talent` / 教师台备课 / 学校管理台概览 四页。 */
.pgf:not(.open) > *:not(.pgf-h) { display: none !important; }
/* 收起态下，标题下面已经没有东西了，那条分隔线和下边距就别留 */
.pgf:not(.open) > .pgf-h { margin-bottom: 0; padding-bottom: 0; border-bottom: 0; }
/* 块与块之间给一点呼吸（原来是内容撑开的，收起后要靠这条） */
.pgf { margin-bottom: 10px; }
/* 无 header、标题直接是 h2/h3 的块：收起态就是一行标题，给它一点可点区域 */
.pgf:not(.open) > h2.pgf-h, .pgf:not(.open) > h3.pgf-h { padding: 3px 0; }

/* ── 打印时一律展开（2026-10-11）──────────────────────────────────────────────
   折叠是给"屏幕上别被一堆块压住"用的；纸上没有"点一下展开"这回事，
   把折叠态原样打出来，报告就只剩几条目录条。
   实测（`_audit/_foldprobe2.mjs`，pgfold=on）：`#cep-rep-card` 收起时
   `#cep-tab-body` 的可见高度 = 0 —— 直接打就是一张封面加几行标题。
   ⚠ 必须写在上面那条 `display:none !important` **之后**：
     两条特异度相同，比的是**源码先后**（2026-10-11 第一版写在第 772 行那个
     `@media print` 里，被后面的 none 规则盖掉，实测 computed display 仍是 none）。
   ⚠ 用 `revert` 而不是写死 `display:block`：`revert` 回到 UA 默认
     （div→block、table→table、span→inline）；写死 block 会把 `<table>` 打成一坨。 */
@media print {
  .pgf:not(.open) > *:not(.pgf-h) { display: revert !important; }
}

/* ═══════════════ 说明件 · 用户令 2026-10-10 ═══════════════
   原话：「页面有许多说明的内容，严重干扰学生注意」——
     ① 题目 / 考点标出（已标出的保留，未标出的补上）；
     ② **其他说明性的小字一律隐藏**；
     ③ 说明内容尽量一行写完；
     ④ 所有页面一律简约 —— 即使**有必要**的说明也先隐藏。
   ★ 2026-10-10 用户令（第二条）撤回了 ② 里的"在按钮格子右边设『说明』热键"：
     「右边的[截图]，要删！」—— 右侧那一列的「说明 N」胶囊按钮**一个不留**。
     所以现在只剩"藏"这一半：说明件打 `.xnt` 默认 `display:none`，**不搬家、不删**；
     页面不再长任何热键。（"隐"由令(e)撑着，"钮"由本轮令撤掉 —— 两令不冲突。）
     · 与上面那套 `.pgf` 折叠是**两件事**，别混：折叠管"整块内容"，说明件管"块里的小字"。
       两者可叠加 —— 块折着时，说明件自然也看不见（父层已经 none 了）。
   ⚠ 说明件有 `<p>` 也有 `<span>`：**别**一律 `display:block` 地还回去（会把行内说明撑成整行）。
     门禁 / 取证环境下由 JS 把打标那一刻记下的原 display 以内联 `!important` 还回原位。 */
.xnt { display: none !important; }
/* 说明件展开态（仅门禁 / 取证环境短暂出现）：与主内容区隔开一点，不改字号色系 */
.xnt-shown { margin-top: 6px; }

/* ═══════════════ 页尾说明区（用户令 2026-10-25）═══════════════
   用户令（讨论 · 第八道令）：说明性内容干扰视线 —— 操作在前，说明集中收到页面最后，
   一个「说明」想读才展开。样板是 redeem.html / login.html 的 .ex（用户看过说"清爽多了"）。
   ★ 与上面 `.xnt` 是**一件事的两半**，别当成两套：
       `.xnt` 管"原地不显示"（版面干净）；`.xnx` 是页尾那一份**可查的副本**（信息不丢）。
       两份内容由 app.js 的 noteTail() 生成，本文件只管长相。
   ★ 颜色：整块是"非阅读区的辅助件"⇒ 走绿族 60/80 与 --ink-3，不引第二种色相（§配色收口）。
     字号 12.5 与全站说明件同一档，学生要读它时不会觉得"比正文小一号眼睛累"。
   ★ 手机：这一段在页尾、默认收起，只占一行 —— 窄屏不用另写媒体查询。 */
.xnx { margin: 26px 0 6px; border-top: 1px solid var(--line); padding-top: 10px; }
.xnx > summary { cursor: pointer; font-size: 12.5px; color: var(--ink-3); list-style: none;
  display: inline-flex; align-items: center; gap: 7px; user-select: none;
  padding: 3px 0; transition: color .15s; }
.xnx > summary::-webkit-details-marker { display: none; }
.xnx > summary:before { content: "?"; display: grid; place-items: center; width: 15px; height: 15px;
  border-radius: 50%; background: var(--g20); color: var(--g80);
  font-size: 10.5px; font-weight: 700; flex: none; }
.xnx[open] > summary:before { content: "−"; background: var(--g40); }
.xnx > summary:hover, .xnx[open] > summary { color: var(--g80); }
.xnx-n { font-size: 11.5px; color: var(--ink-3); opacity: .72; }
/* ★ 2026-10-25 第二版（用户否掉了第一版："一堆的文字，杂乱无章，看到心烦意乱"）：
   病根不在颜色，在**密度**与**层级**。第一版是 12.5px / 行高 1.75 / 段间距 4px 的一片灰字，
   十行连排看过去就是一整块砖。这一版按"能读进去"重排：
     · 字号 13px（不是 12.5 —— 说明是要读的字，不该比正文小一号）；
     · 行高 1.95、段间 7px（一段一行，行与行之间呼吸得开）；
     · 组标题 13px 深绿 + 组间 18px（一眼看出"这一段在讲哪一块"）；
     · 名词用 <b> 标深绿（"教师 / 导学师定任务："那一类），读的时候先抓名词再看解释；
     · 去掉左侧那条竖线：它不是层级，只是装饰，反而把每行都往里挤了一格。 */
.xnx-b { margin-top: 12px; }
.xnx-g { margin: 0 0 18px; }
.xnx-g:last-child { margin-bottom: 0; }
.xnx-g h5 { margin: 0 0 6px; font-size: 13px; font-weight: 600; color: var(--g80); letter-spacing: .4px; }
.xnx-g p { margin: 0 0 7px; font-size: 13px; line-height: 1.95; color: var(--ink-2); }
.xnx-g p:last-child { margin-bottom: 0; }
.xnx-g p b { color: var(--g80); font-weight: 600; }
.xnx-g p i { font-style: normal; color: var(--g60); margin-left: 4px; font-size: 11.5px; }
/* 打印：收起态不进纸（它不是报告内容）；展开了就照打 —— 学生自己要留一份说明时能带走。 */
@media print { .xnx:not([open]) { display: none !important; } }

/* ══ 知识树 · 阅读浮层（用户令 2026-10-11）══════════════════════════════════
   用户原话：「知识树——章节的概括内容和考点的知识点/易错警示，是本平台的重要内容，
             必须以浮动页面呈现，页面与常规同宽，字号加大，字体要漂亮，
             朗读声音要好，高亮与声音同步。」
   一句话五件，逐件在这里落：

   ① 浮动页面 —— 这些内容此前挤在右侧详情栏（.tg-pan，正文 12.5px）里。
      现在浮在整页之上：一层遮罩（.rdx-mask）＋ 一张阅读页（.rdx），
      关掉之后页面还是原来那一屏。它不是新开的一页 —— 跳页会丢滚动位置，
      而"读到一半往下翻"正是这一层要支持的动作。

   ② 与常规同宽 —— 宽度取 --w-content（正文区那一档），**不另立一个"弹窗宽度"**。
      弹窗惯用的 620px 只能放短表单；知识点中位 838 字、易错 808 字，
      塞在 620px 里就是一条细长缝。宽度带 calc(100vw - …) 兜底 ⇒ 窄屏自缩，不横向溢出。
      ⚠ audit-ui ④ 只拦"纯像素写死的内容宽度"，这种带相对兜底的写法是规范内的。

   ③ 字号加大 —— 正文 17px / 行高 1.95（原 12.5–13px / 1.75）。
      中文长句在 1.95 行高下才不用回头找行；公式随字号一起涨（.kp-body .katex 用 em）。

   ④ 字体要漂亮 —— 两套栈各司其职：
      · 标题走**宋体系衬线**（Source Han Serif / Songti / SimSun），压得住大标题；
      · 正文走**人文无衬线**（PingFang / HarmonyOS Sans / Source Han Sans / 雅黑），
        并开 kern 与 optimizeLegibility —— 一样是"漂亮"里最实在的一部分（字距不挤）。
      栈里**第一顺位是本机真有的字体**，拿不到才逐级退；退到最后仍是系统无衬线，不会变大花脸。

   ⑤ 朗读好 + 音字同步 —— 朗读条随之放大（.tts-bar 在浮层里 13px 提示 + 大按钮）；
      正在念的那一句在原有"浅绿底 + 绿描边"上再加**左侧一道竖条**（inset 3px 绿），
      大字号下一眼就能在整段里定位到"念到哪儿了"（色值仍取既有 --tts-* 与 --g60，不新立色相）。

   ⚠ 与 .modal 的关系：**不是同一个东西**。.modal 是"短一问一答"的弹层（760px 上限，表单用）；
     这里是"长文阅读"的浮层（--w-content 上限，读用）。两者宽度口径不同，
     所以各写各的类名，不互相借用 —— 借了之后"改弹窗宽度"会顺手改到阅读页。 */
.rdx-mask {
  position: fixed; inset: 0; z-index: 90; display: flex; align-items: center; justify-content: center;
  padding: 20px; background: rgba(18, 22, 30, .5);
}
.rdx {
  display: flex; flex-direction: column;
  width: min(var(--w-content), calc(100vw - 48px));
  max-height: min(92vh, 100%);
  background: var(--panel); border-radius: 16px; box-shadow: var(--sh-lg); overflow: hidden;
}
.rdx-h {
  display: flex; align-items: flex-start; gap: 14px; padding: 17px 30px 14px;
  border-bottom: 1px solid var(--line); background: var(--g00);
}
.rdx-ht { flex: 1 1 auto; min-width: 0; }
.rdx-kicker { font-size: 12.5px; letter-spacing: .04em; color: var(--ink-3); }
.rdx-title {
  margin: 3px 0 0; font-size: 26px; line-height: 1.35; font-weight: 700; color: var(--ink);
  font-family: "Source Han Serif SC", "Noto Serif CJK SC", "Songti SC", "STSong", "SimSun", Georgia, serif;
}
.rdx-hact { flex: 0 0 auto; }
.rdx-body {
  flex: 1 1 auto; overflow: auto; padding: 20px 30px 28px;
  font-size: 17px; line-height: 1.95; color: var(--ink);
  font-family: "PingFang SC", "HarmonyOS Sans SC", "Source Han Sans SC", "Noto Sans CJK SC",
    "Hiragino Sans GB", "Microsoft YaHei", system-ui, sans-serif;
  letter-spacing: .012em; text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
}
.rdx-f {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 12px 30px; border-top: 1px solid var(--line); background: var(--panel-2);
}
.rdx-note { font-size: 12.5px; line-height: 1.6; color: var(--ink-3); }
/* 提示句里点名的控件 —— 名字本身是热键（用户令 2026-10-20）：
   嵌在一句话中间，所以不能长成按钮：去外壳、继承正文的字号与行高，只留"可点的样子"。
   为什么必须有下划虚线：同一句里其余的字都不可点，不给出可点的外观，读者不会去试。 */
.rdx-hot {
  display: inline; margin: 0; padding: 0;
  border: 0; border-bottom: 1px dashed currentColor; background: none;
  font: inherit; color: var(--blue-2); cursor: pointer;
}
.rdx-hot:hover { background: rgba(47, 111, 235, .10); }
.rdx-hot:focus-visible { outline: 2px solid var(--blue-2); outline-offset: 2px; }
/* 阅读页里的一级小节（知识点 / 易错警示 / ①②③）—— 标签字随正文放大，但比正文沉一档 */
.rdx-body .note { font-size: 16px; }
.rdx-body .note > b { font-size: 15px; letter-spacing: .03em; }
.rdx-body .kp-body { font-size: 17px; line-height: 1.95; }
.rdx-body .kp-tb { font-size: 15px; }
.rdx-body .read-block { font-size: 14px; }
.rdx-body .mono { font-size: 14px; }
/* 章节级三层在阅读页里一并放大（它们在面板里是 12.5px，那是"栏里的小字"的尺度） */
.rdx-body .clv-h b { font-size: 17px; }
.rdx-body .clv-n { font-size: 13.5px; }
.rdx-body .clv-kp .nm { font-size: 16px; }
.rdx-body .clv-kp .core { font-size: 15px; line-height: 1.8; }
.rdx-body .clv-kp .tag, .rdx-body .clv-kp .vv { font-size: 13px; }
.rdx-body .clv-err summary { font-size: 15px; }
.rdx-body .clv-err .kp-body { font-size: 16px; line-height: 1.9; }
.rdx-body .clv-empty { font-size: 15px; }
.rdx-body .clv-jy-gist { font-size: 16px; line-height: 1.95; }
.rdx-body .clv-jy-risk li { font-size: 16px; line-height: 1.9; }
.rdx-body .clv-jy-risk li .t { font-size: 16.5px; }
.rdx-body .clv-jy-risk li .ln em { font-size: 13px; }
.rdx-body .clv-jy-std .v { font-size: 15px; }
.rdx-body .clv-jy-src { font-size: 12.5px; }
.rdx-body .rdx-stat { display: flex; flex-wrap: wrap; gap: 8px 18px; font-size: 14px;
  color: var(--ink-2); margin-bottom: 14px; }
.rdx-body .rdx-stat b { color: var(--ink); }
/* 朗读条在阅读页里放大一档：字号大的正文上，"念到哪句"要看得清 */
.rdx-body .tts-bar { margin: 0 0 12px; gap: 9px; }
.rdx-body .tts-hint, .rdx-body .tts-voice { font-size: 13px; }
.rdx-body .tts-s.on { box-shadow: inset 0 0 0 1.5px var(--tts-hl-line), inset 3px 0 0 0 var(--g60); }

/* ── 新五步第②步「学习记忆」的考点清单 + 阅读页里的笔记块（用户令 2026-10-11）──
   清单一行一个考点（读完变浅底），笔记块挂在阅读页正文末尾。
   色一律取既有令牌，不新立色相。 */
.lr-kp {
  display: flex; align-items: center; gap: 14px; padding: 11px 14px;
  border: 1px solid var(--line); border-radius: 10px; background: var(--panel);
}
.lr-kp.done { background: var(--panel-2); }
.lr-kp .lr-tx { flex: 1 1 auto; min-width: 0; }
.lr-kp .lr-tx b { color: var(--ink); }
.lr-kp .lr-meta { font-size: 12.5px; color: var(--ink-3); margin-top: 3px; line-height: 1.6; }
.lr-note-sec { border-top: 1px solid var(--line); padding-top: 14px; }
.lr-note-sec > b { font-size: 15px; letter-spacing: .03em; }
.lr-note {
  display: block; width: 100%; min-height: 92px; margin-top: 8px; padding: 11px 13px;
  border: 1px solid var(--line); border-radius: 10px; background: var(--panel-2);
  font-size: 15px; line-height: 1.8; color: var(--ink); resize: vertical; font-family: inherit;
}
.lr-note:focus { outline: none; border-color: var(--g40); background: var(--panel); }
.lr-note-tip { font-size: 12.5px; color: var(--ink-3); }
/* 第②步卡片头**右侧**的「章节 · 考点」（用户令 2026-10-13：
   「"理解牢记"（学习记忆）的**右侧必须有章节考点的显示**」）。
   它是"我在哪"的读法，与右边的状态 tag（已读 N/M）在观感上分开 ——
   所以用一块浅底小牌，不是 .tag；长章节名允许换行（右对齐）。
   ⚠ 类名不许改成 .read/.hint/.cap：那些是说明件机制（XNT_SEL）要收掉的类名，收了＝没显示。 */
.lr-hrt { display: flex; flex-direction: column; align-items: flex-end; gap: 6px; max-width: 62%; }
.lr-scope2 {
  font-size: 12.5px; font-weight: 650; line-height: 1.55; text-align: right;
  color: var(--g80); background: var(--g00); border: 1px solid var(--line);
  border-radius: 9px; padding: 4px 10px; max-width: 100%; word-break: break-word;
}
@media(max-width: 640px) {
  .lr-hrt { max-width: none; align-items: flex-start; }
  .lr-scope2 { text-align: left; }
}
@media(max-width: 900px) {
  .rdx-mask { padding: 10px; }
  .rdx { width: calc(100vw - 24px); max-height: 94vh; border-radius: 12px; }
  .rdx-h { padding: 13px 16px 11px; }
  .rdx-title { font-size: 21px; }
  .rdx-body { padding: 15px 16px 20px; font-size: 16px; }
  .rdx-body .kp-body { font-size: 16px; }
  .rdx-f { padding: 10px 16px; }
}
@media print {
  .rdx-mask { position: static; padding: 0; background: none; display: block; }
  .rdx { width: auto; max-height: none; box-shadow: none; border-radius: 0; overflow: visible; }
  .rdx-hact, .rdx-f, .rdx-body .tts-bar { display: none; }
  .rdx-body { overflow: visible; padding: 0; }
  .rdx-h { background: none; }
}

/* ═══════════════════════════════════════════════════════════════════════
   学生台 · 学情报告浮动页（用户令 2026-10-11）
   原话：「学情报告1和2都是浮动页面，可以打印。」
   浮层本体复用阅读浮层 .rdx-mask / .rdx（见 app.js 的 S.reportSheet），
   这里只补两件：① 报告表格的样式；② **打印时只留这一张**。
   ⚠ 必须写在文件末尾 —— `.rdx-mask` 的打印规则上面已各有一条，
     同特异度比的是**源码先后**（写早了会被后面的规则盖掉，本工程登记过的老坑）。
   ═══════════════════════════════════════════════════════════════════════ */
.rep-body .rep-tb { width: 100%; border-collapse: collapse; margin: 6px 0 2px; font-size: 15px; }
.rep-body .rep-tb th, .rep-body .rep-tb td { padding: 6px 10px; border-bottom: 1px solid var(--line); text-align: left; vertical-align: top; }
.rep-body .rep-tb th { font-weight: 650; color: var(--ink-2); background: var(--g00); white-space: nowrap; }
.rep-body .rep-tb .mono { font-variant-numeric: tabular-nums; }
.rep-body .rep-kv { display: grid; grid-template-columns: auto 1fr; gap: 4px 14px; font-size: 15px; margin: 4px 0 10px; }
.rep-body .rep-kv .k { color: var(--ink-3); white-space: nowrap; }
.rep-body .rep-sec { margin: 15px 0 4px; font-size: 13px; letter-spacing: .04em; color: var(--ink-3); font-weight: 650; }
.rep-body .rep-sec:first-child { margin-top: 2px; }
.rep-body .rep-big { font-size: 34px; font-weight: 700; line-height: 1.1; font-variant-numeric: tabular-nums; }
.rep-body .rep-weak { color: var(--danger); }
.rep-body .rep-ok { color: var(--ok); }
/* 页内那一份与浮层那一份共用 .rep-* 样式（类名前缀不绑 #rep-mask），
   §0.2 单一定义地：同一张表在页内和纸上不许长成两个样子。 */

@media print {
  /* 只印这一张报告：其余顶层节点（侧栏 / 台页 / 提示）一律不出现在纸上。
     不给这一条，浮层会与身后的台页一起印 —— 报告里混进导航与按钮。 */
  body.rep-printing > *:not(#rep-mask) { display: none !important; }
  body.rep-printing #rep-mask { position: static; inset: auto; padding: 0; background: none; display: block; }
  body.rep-printing #rep-mask .rdx { width: auto; max-height: none; box-shadow: none; border-radius: 0; }
  body.rep-printing #rep-mask .rdx-body { overflow: visible; max-height: none; }
  body.rep-printing .rep-noprint { display: none !important; }
  body.rep-printing #rep-mask .card { break-inside: avoid; }
}

/* ── 知识树详情面板：深链滚过来时别被吸顶横条压住（2026-10-20）──────────────
   起因：用户按深链进「初二上 · 第十二章全等三角形 · 全等三角形的应用」，
   说"没有看到『阅读本节』"。实测根因是**面板在页面最下面**（两式之下，文档 y≈2411），
   而深链只把页面滚到表格式中段（y≈1115），按钮落在视口外 300~1200px。
   改法两处：① stu-tree.js 里带 sec 的深链把滚动目标改成这块面板；
             ② 这里给它留出吸顶横条的高度 —— `.topbar` 是 `position:sticky; top:0`，
                `scrollIntoView({block:'start'})` 会把面板顶到 y=0，正好被横条盖住 60px。
   88px = 横条实测高 ~76px + 12px 余量；写成固定值而不是 var，是因为横条高度随字号行高变，
   而这里要的是一个"一定够"的下界，宁可多留 12px 也不能偶尔盖住标题。 */
#tg-side { scroll-margin-top: 88px; }

/* ── 表格式：节这一层必须和章、考点一样「名字本身就能点」（2026-10-20 用户附截图）──
   用户原话：「章——节——考点，章和考点是热键，节不是热键。」
   同一条树里三级两制：章名是 <a class="ch-row">、考点是 kpGrid 的 <a>，名字本身都能点；
   节名却是死 span，唯一入口是旁边那行小字「阅读本节」——
   而那行字当时**在节行里根本没有样式**：.th-jy 的规则写在 `.term-block > .th .th-jy`，
   作用域只在册头，节行里的它等于裸文本，跟右端的「最弱 xx · 24 分」混成一片。
   改法：节名成为主入口（悬停出下划线，与站内所有链接同一套语）；「阅读本节」补成胶囊样，
   只作**可见提示**，不再是唯一入口。两条路落点相同（都走 tableDraw 的 hooks.onSec）。 */
.sec-group > .sh .nm[data-th-jy] { cursor: pointer; }
.sec-group > .sh .nm[data-th-jy]:hover { color: var(--g80); text-decoration: underline; text-underline-offset: 3px; }
.sec-group > .sh .nm[data-th-jy]:focus-visible { outline: 2px solid var(--g40); outline-offset: 2px; border-radius: 3px; }
/* 整节全过时名字是绿的（.ok），悬停别再被上面的规则染回深绿以外的色 —— 只加下划线，颜色交给状态 */
.sec-group > .sh.ok .nm[data-th-jy]:hover { color: #15803D; }

/* ★ 2026-10-20 用户第二次反馈（附截图，鼠标悬停已弹出提示框）：
   「『阅读本节』能点击，但不是热键，所以我没找到。请将『阅读本节』制成热键，提示读者。」
   ⇒ 上一版把它做成了**浅灰细边框的小字胶囊**（color:var(--ink-3) + 1px 淡线）。
     灰字在中文界面里只有一个含义：**这不是可操作的东西**。它又能点、又长得像旁注，
     用户凭什么去找它？"能点"与"看着能点"是两件事，缺后者等于没做入口。
   改法：与册头的「本册概述 / 本学年」用**同一套热键语言**（绿字 + 下划线），
     再加三件更硬的可点信号 —— ① 浅绿底 + 绿边（有边界＝有可点区域）
     ② 书页图标（与图片式右栏「📖 阅读本节」同一个记号，两式认同一个形）
     ③ 悬停反白（整块变绿底白字），比"变深一点点"更容易被看见。
   为什么不做成大按钮：一行节头里还有节名、掌握度签、最弱考点、进度条，
   再塞一颗实心按钮会把节名挤成两行；这里要的是"一眼看出能点"，不是"最显眼"。 */
.sec-group > .sh .th-jy {
  font-weight: 700; font-size: 11.5px; color: var(--g60); cursor: pointer;
  margin-left: 2px; padding: 1px 8px 1px 6px; border-radius: 999px;
  border: 1px solid var(--g40); background: var(--g00); white-space: nowrap;
  text-decoration: underline; text-underline-offset: 2px;
  text-decoration-color: var(--g40); text-decoration-thickness: 1px;
  transition: background .12s, color .12s, border-color .12s;
}
.sec-group > .sh .th-jy .jy-ico {
  font-style: normal; margin-right: 3px; font-size: 11px;
  /* 图标不吃下划线：下划线只该落在"阅读本节"四个字上，否则看着像一条贯穿的删除线 */
  text-decoration: none; display: inline-block;
}
.sec-group > .sh .th-jy:hover {
  color: #fff; background: var(--g60); border-color: var(--g60);
  text-decoration-color: #fff;
}
.sec-group > .sh .th-jy:active { background: var(--g80); border-color: var(--g80); }
.sec-group > .sh .th-jy:focus-visible { outline: 2px solid var(--g60); outline-offset: 2px; }

/* 节名本身也是热键（2026-10-20 用户令："章和考点是热键，节不是"）。
   常态就已经是链接蓝 + 加粗，所以不比 .th-jy 再加边框 —— 两级用同一种"能点"的语言，
   而不是每级各发明一套。这里只补三件：手型、悬停下划线、键盘焦点环。 */

/* ── 热键提示（.hk）：正文里直接点名"这几个词能点"（2026-10-20 用户令）──
   用户连续三次"没找到入口"。前两次我都在加**入口**（节名可点、同名叫法），
   但没一次是**在屏幕上说清"哪些词能点"**。入口存在而无人告知，等于缺位。
   ⇒ 表格式列头与图例里各标一处：绿字 + 下划线 + 浅绿底，与热键本身的记号同一套
     （读者在提示里看到的"绿字带下划线"，就是他在行里要去找的那个样子）。
   覆盖 .tg-colhead b 的 14px/深绿是必需的：列头标题用了裸 <b>，
   不覆盖的话提示里的 <b> 会被那一条规则抓走，字号突然跳成标题那么大。 */
.hk,
.tg-colhead .hint .hk {
  font-size: 11.5px; font-weight: 700; color: var(--g60);
  text-decoration: underline; text-underline-offset: 2px;
  background: var(--g00); border-radius: 4px; padding: 0 3px; white-space: nowrap;
}
/* 图例那一条单独占一行：图例其余项是 inline-flex 的色块+词，
   混在一条 flex 里会把这句话切成若干段、每段之间塞 5px 间隙，读起来字都断了。 */
.tg-legend .hk-legend {
  display: block; width: 100%; text-align: center;
  padding-top: 6px; border-top: 1px dashed var(--line);
}
.tg-legend .hk-legend .hk { font-size: 11.5px; }

/* ═══════════════════════════════════════════════════════════════════════
   健康守护 · 左栏印章 + 浮窗（用户令 2026-10-25）
   用户令四句：「①把健康守护的按钮，设置在导航栏退出登录的下面，距离退出登录
   按钮 50mm。②健康守护永远处于开启状态，点击按钮弹出浮窗显示，不固定在右边的
   显示栏里显示。③按钮形状为正方形的印，印里四个字字体为隶书，纵向排列，先右后左。
   ④印的颜色为中国红。」

   ── 形制为什么这么落 ──────────────────────────────────────────
   · **35mm 是"距离"，不是"留白"**：CSS 里 `mm` 是绝对单位（1mm = 96/25.4 px ≈
     3.7795px ⇒ 35mm ≈ 132px；初版为 50mm，用户令"上移 15mm"后改成 35mm），
     量的是**从「退出登录」那一格的下沿到印的上沿**。
     所以这条 margin 必须挂在**紧跟 </nav> 的那层壳**上（`.seal-wrap`），
     不能挂在印章自己身上 —— 挂自己身上，量到的就是壳的内边距而非按钮间距。
   · **正方形**：宽高同取 54px（不写 54px/14mm 这种"看起来一样"的混搭；
     初版 68px，用户令"印章太大"后收到 54px）。
     54px 的依据：左栏内容宽 236−12×2−2×2 = 208px，印占约 1/4 居中，
     与左侧 16px 导航字号、40px 行高相比是一枚"印章"的体量，
     而不是一块抢视线的招牌。
   · **隶书纵向、先右后左**：四字按传统印面读法排 2×2 —— 右列「健康」、左列「守护」。
     实现用 `flex-direction: row-reverse`：DOM 顺序仍是"健康、守护"（读屏与源码
     都按常序），视觉上第一个落在右列。**不用 `writing-mode: vertical-rl`** ——
     它要靠容器宽度逼出换列，54px 里很容易变成一列四字或三字半，字位不稳定。
   · **中国红**：取既有令牌 `--rk-star`（军衔五角星那一枚红，用户点名的同一个），配深压边与浅高光，模拟印泥压在纸上的
     深浅不匀；字取纸白（白文印：红底白字），不做红字白底（朱文）——
     左栏底色是浅绿 `--g25`，白底红字在浅绿上一压就散，红底白字才立得住。
   · **「运行中」不是开关**：印下方那行小字是**状态陈述**（绿点 = 正在跑），
     没有 hover 变"可关闭"、没有点击切状态。守护在服务端一直算，屏上不提供关掉它的入口。
   ═══════════════════════════════════════════════════════════════════════ */
.seal-wrap {
  /* 35mm：量的是「退出登录」下沿 → 印上沿。
     ★ 2026-10-25 晚用户令：「印章太大，位置往上移动15mm。」
       原为 50mm（189px），上移 15mm ⇒ **35mm（132.28px）**。
     ⚠ 减 2px 是因为 `.nav` 自己有 `gap: 2px` —— 印是它的 flex 子项，
       那 2px 会**叠加**在 margin 之上（实测 134.28 = 132.28 + 2）。
       不减就是"每说一次 mm，实际多 2px"，量具与口令对不上。 */
  /* ★ 2026-10-25 三改（定稿）：「印章必须定在导航栏，不能移动！」
     病史四版，写在这里免得第五次再试：
       ① 50mm / 35mm 量距离 —— 距离建立在"整栏一起滚"上，侧栏一长印就被顶走；
       ② 印留在 `.nav` 里、改**印自己** sticky —— 学生台桌面断点下 `.side` 是等长静态
          （用户令 2026-10-06「导航栏与内容页等长」），整栏随文档滚，印跟着走；
       ③ 印挪到 `.side` 末尾再 sticky —— 同上，`.side` 自己会滚，`bottom` 基准是它的盒底，
          实测滚 900px 后印从 655 漂到 317，仍不成立；
       ④ 只有 `position:fixed` 经得住三种滚动的实测（页面滚 / 侧栏滚 / 两者都滚，
          三次量到的 top 完全一致）。
     ⇒ 定稿用 fixed，横坐标按 `.app` 居中规则算（`--w-page` 1440 居中）。
       它仍是"导航栏的印"：位置就在左栏内容区正中、左下角，与导航同一列。 */
  /* 底板：与侧栏同色（--g25）+ 一道上边线。它是**固定的栏脚**，
     导航项滚到它下面就"消失"，不会出现字压在印上那种脏画面。
     宽度取到侧栏内沿（236 − 两侧 border 2px = 232），左右与绿框严丝合缝。 */
  position: fixed;
  left: calc(max(0px, (100vw - var(--w-page)) / 2) + 2px);
  bottom: 0;
  width: calc(var(--w-side) - 4px);
  z-index: 40;
  margin: 0;
  padding: 10px 0 12px;
  background: var(--g25);
  border-top: 1px solid var(--g40);
  display: flex; flex-direction: column; align-items: center; gap: 5px;
}
/* 印是固定的栏脚，不再占文档流 —— 给导航留出让位，末两项（修改口令 / 退出登录）
   才能滚到它上面停住。量的是栏脚实高：叠边线 1 + padding 10+12 + 印 54 + gap 5 + 「运行中」13 ≈ 95，
   取 108 留一点余量。 */
.shell[data-role="S"] .side { padding-bottom: 108px; }
.seal {
  /* 2026-10-25 晚用户令「印章太大」⇒ 68px 收到 54px（字 23→18）。
     仍取正方形；缩小后与 16px 导航字、40px 行高并列，才是"一枚印"的体量，
     不是一块招牌。 */
  width: 54px; height: 54px; padding: 2px; box-sizing: border-box;
  border: 0; border-radius: 3px; cursor: pointer;
  /* 印面：底 = **既有的中国红令牌 `--rk-star`**（#e60012，用户 2026-09-22 点名的那一个），
     不另造新红 —— 同一个平台里"中国红"只能有一个值，否则军衔五角星与这枚印
     会差出两种红，谁也说不出该用哪个。
     深浅不匀靠**叠 rgba 层**（高光 + 暗角），不靠再写两个 hex：
     ① 那样印泥的斑驳感才是"光打上去"的感觉，而不是三段色带；
     ② 手工过渡色（深一档的暗红）一进 CSS 就要进色值登记表（§1.3），
        为一块 54px 的印添三行登记，是把账记错了地方。 */
  background:
    radial-gradient(120% 120% at 26% 14%, rgba(255, 255, 255, .20) 0%, rgba(255, 255, 255, 0) 52%),
    radial-gradient(150% 150% at 78% 98%, rgba(44, 0, 3, .34) 0%, rgba(44, 0, 3, 0) 58%),
    var(--rk-star);
  box-shadow:
    inset 0 0 0 2px rgba(255, 255, 255, .30),
    inset 0 0 14px rgba(84, 4, 8, .40),
    0 3px 9px rgba(120, 8, 12, .22);
  display: grid; place-items: center;
  transition: transform .16s ease, box-shadow .16s ease, filter .16s ease;
}
.seal:hover {
  transform: translateY(-1px); filter: saturate(1.06);
  box-shadow:
    inset 0 0 0 2px rgba(255, 255, 255, .38),
    inset 0 0 14px rgba(84, 4, 8, .36),
    0 7px 16px rgba(120, 8, 12, .28);
}
.seal:active { transform: translateY(0) scale(.955); }
.seal:focus-visible { outline: 2px solid var(--b60); outline-offset: 3px; }
.seal-grid { display: flex; flex-direction: row-reverse; align-items: center; }
.seal-grid .sc { display: flex; flex-direction: column; align-items: center; }
.seal-grid .sc i {
  /* 隶书优先，**楷体兜底**。
     ⚠ 本机实测（2026-10-25 晚）：`C:\Windows\Fonts` 里**没有 simli.ttf**，
       注册表也没有「隶书 / LiSu」字族（只有宋体 simsun、黑体 simhei、楷体 simkai、
       仿宋 simfang）—— 所以现在这台机器上实际显示的是**楷体**，不是隶书。
       把隶书列在第一位是有意的：谁装了「隶书」字库（Windows 可选功能
       "中文(简体)补充字体" 里就带 SimLi），这里**立刻**就是隶书，不必改代码。
       楷体排在 serif 之前：印章要的是笔意，宋体那种横平竖直的印刷体连"像印"都做不到。 */
  font-family: "隶书", LiSu, "STLiti", "华文隶书", "FZLiShu-S01", "方正隶书简体", KaiTi, "楷体", "Kaiti SC", STKaiti, serif;
  font-style: normal; font-size: 18px; line-height: 1.08;
  color: #fff; text-shadow: 0 1px 0 rgba(84, 4, 8, .55);
}
.seal-cap {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 10.5px; line-height: 1.2; letter-spacing: .5px; color: var(--g80); opacity: .8;
}
.seal-cap .dot {
  width: 6px; height: 6px; border-radius: 50%; background: var(--g60);
  box-shadow: 0 0 0 3px rgba(47, 125, 97, .18);
}

/* ── 浮窗（只在点印时出现；不是右栏常驻块）──────────────────────────
   `hidden` 必须显式压掉 display —— `.hd-mask` 是 flex，`[hidden]` 的 UA 规则
   （display:none）会被类选择器的 flex 盖掉，浮窗就会"关不掉、只是透明了"。
   这是模态框最常见的那个坑，写死在这里。 */
.hd-mask {
  position: fixed; inset: 0; z-index: 96;
  background: rgba(10, 24, 18, .42);
  -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
  display: flex; align-items: flex-start; justify-content: center;
  padding: 9vh 18px 28px; overflow-y: auto;
}
.hd-mask[hidden] { display: none; }
.hd-win {
  width: min(640px, 100%); background: var(--panel);
  border: 1px solid var(--line); border-radius: 16px; overflow: hidden;
  box-shadow: 0 26px 70px rgba(8, 28, 20, .30), 0 2px 8px rgba(8, 28, 20, .10);
  animation: hdIn .18s ease-out;
}
@keyframes hdIn {
  from { opacity: 0; transform: translateY(-8px) scale(.99); }
  to { opacity: 1; transform: none; }
}
.hd-head {
  display: flex; align-items: center; gap: 12px;
  padding: 15px 18px 12px; border-bottom: 1px solid var(--line-2);
}
.hd-mark {
  width: 38px; height: 38px; flex: 0 0 38px; border-radius: 4px;
  background:
    radial-gradient(120% 120% at 30% 18%, rgba(255, 255, 255, .20) 0%, rgba(255, 255, 255, 0) 55%),
    var(--rk-star);
  color: #fff; font-family: "隶书", LiSu, "STLiti", KaiTi, "楷体", STKaiti, serif;
  font-size: 21px; line-height: 1; display: grid; place-items: center;
  box-shadow: inset 0 0 0 1.5px rgba(255, 255, 255, .40), 0 2px 6px rgba(120, 8, 12, .24);
}
.hd-tt { flex: 1 1 auto; min-width: 0; }
.hd-tt b { display: block; font-size: 16px; color: var(--g80); }
.hd-tt small { display: block; font-size: 12px; color: var(--ink-3); margin-top: 3px; }
.hd-on {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 10px; border-radius: 999px; white-space: nowrap;
  background: var(--g00); border: 1px solid var(--gline);
  color: var(--g80); font-size: 12px;
}
.hd-on .dot {
  width: 6px; height: 6px; border-radius: 50%; background: var(--g60);
  box-shadow: 0 0 0 3px rgba(47, 125, 97, .18);
}
.hd-x {
  border: 0; background: transparent; cursor: pointer;
  font-size: 22px; line-height: 1; color: var(--ink-3);
  padding: 2px 7px; border-radius: 8px;
}
.hd-x:hover { background: var(--panel-2); color: var(--ink); }
.hd-x:focus-visible { outline: 2px solid var(--b60); outline-offset: 2px; }
.hd-body { padding: 14px 18px 18px; }
.hd-now { font-size: 13px; color: var(--ink-2); margin-bottom: 11px; }
.hd-now b { color: var(--g80); }
.hd-note {
  margin-top: 12px; padding: 9px 12px; border-radius: var(--r-sm);
  background: var(--panel-2); border: 1px dashed var(--line);
  font-size: 12.2px; line-height: 1.75; color: var(--ink-2);
}
.hd-load { padding: 26px 0; text-align: center; font-size: 13px; color: var(--ink-3); }

/* ── 导航分块：上面一段（门户 + 导航）单独成 div，账号组与印留在它下面 ──────
   ★ 2026-10-25 晚，第三版（定稿）：**不限高、不内滚、一项不隐藏**。
     病史（两版都被自己推翻，写在这里免得第四次再犯）：
       第一版：整条 `.nav` 限高内滚 ⇒ 「修改口令 / 退出登录」被卷进滚动区，
              学生要滚才找得到账号；印章量出来的"距退出登录 50mm"也是假的
              （那一格已滚出可视区，量到 0.22px）。
       第二版：只让 `.nav-main` 滚、账号与印常驻 ⇒ 账号能看见了，但
              **4 个导航项**（师生交流 / 我的天赋 / 选科与志愿）被卷进滚动条。
              这更要不得：用户当天刚问过"健康守护你删了？"，对"东西不见了"极敏感；
              拿 4 个菜单项去换"小屏也能看见印"，是拿大件换小件。
     ⇒ 定稿口径：**导航一项不少**。印 35mm 挂在「退出登录」下面，按文档流走：
       · 视口 ≥1100px（本机 1300 实测）：整枚印 + 「运行中」都在首屏；
       · 1080p（视口 ~940）：印落在首屏下沿外约 100px，页面滚一下就整枚进屏 ——
         它在一个**竖着能滚的文档**里，不是在裁切容器里，不存在"滚不到"。
       `.nav-main` 这一层保留（结构上把导航与账号分开），只是不给它限高。 */
.nav-main { display: flex; flex-direction: column; gap: 2px; }
/* 窄屏：侧栏已改成通栏一条（`.nav` 横排），35mm 会变成一整段空白，
   印缩到 40px 与账号两条并排。桌面上仍是 35mm / 54px。
   `display: contents` 让 `.nav-main` 这一层"消失"，子项直接排进横条 ——
   不这么做，横条里会多出一根竖着的柱子，导航就断成两截。 */
@media (max-width: 900px) {
  .nav-main { display: contents; }
  /* `flex: 0 0 auto` 不能省：横排的 `.nav` 是 flex 容器，印会被当成可压缩项 ——
     实测（390px 宽）它被压到 32px、四个字挤成一团。印是"印"，不是弹性留白。 */
  /* 2026-10-25：桌面端印作为左栏末项 sticky 钉住；窄屏左栏是横条，钉住会浮在页面中间，
     故在窄屏打回文档流，与账号两条并排。 */
  .seal-wrap { position: static; width: auto; left: auto; bottom: auto;
    flex: 0 0 auto; margin: 0 0 0 10mm; gap: 4px; flex-direction: row; align-items: center;
    padding: 0; background: transparent; border-top: 0; }
  .shell[data-role="S"] .side { padding-bottom: 16px; }
  .seal { width: 40px; height: 40px; flex: 0 0 40px; }
  .seal-grid .sc i { font-size: 14px; }
  .hd-mask { padding: 4vh 12px 20px; }
  .hd-body { padding: 12px 14px 16px; }
}

/* ── 综合题：一题干多问多空（2026-10-27）────────────────────────────────
   为什么要单独一套样式而不是复用 .opt：.opt 是"单选按钮"的形状（一个字母 + 一句话），
   综合题的空是"标签 + 输入框 + 提示"，三行叠在一起；拿 .opt 硬套会出现
   "输入框比选项矮一截、提示文字挤在字母位"的错位。这里只加必要的东西，不新造配色。 */
.cpart { border: 1px solid var(--bline); border-radius: 10px; padding: 10px 12px; margin: 10px 0; background: var(--panel); }
.cask { font-size: 13.5px; line-height: 1.7; margin-bottom: 6px; }
.cask .cpts { color: var(--ink-3); font-size: 12px; margin-left: 4px; }
.cblank { display: block; padding: 7px 0; border-top: 1px dashed var(--bline); }
.cblank:first-of-type { border-top: none; }
.clab { display: block; font-size: 12.5px; color: var(--ink-2); margin-bottom: 4px; }
.clab .cto { margin-left: 6px; padding: 0 6px; border-radius: 8px; font-size: 11px; background: var(--warnbg, #fdf3df); color: #8a5a00; }
.cin { width: 100%; box-sizing: border-box; padding: 7px 9px; font-size: 14px; line-height: 1.5;
  border: 1px solid var(--bline); border-radius: 8px; background: #fff; color: var(--ink); font-family: inherit; }
.cin:focus { outline: none; border-color: var(--learn); box-shadow: 0 0 0 3px rgba(27,100,200,.10); }
.cin:disabled { background: var(--panel-2, #f2f4f7); color: var(--ink-2); }
.chint { display: block; margin-top: 3px; font-size: 11.5px; color: var(--ink-3); font-style: normal; }
.cblank.ok .marks { color: var(--ok, #137a44); }
.cblank.bad .marks { color: var(--bad, #b3261e); }
.cblank.to .marks { color: #8a5a00; }
.cblank .marks { display: block; font-size: 12.5px; }

/* ══════════ 知识树 · 方块式（2026-11-05 用户令）══════════════════
   用户原话：「把表格式知识树修改为：按学期排列，每一章用一个 8×8mm 的圆角正方形表示，
             颜色及其变化与原来的规定同，光标指对某一正方形，呈现章的名称，点击该正方形，
             呈现所有节及节下面的考点，学生可以根据自己的需要选择（点击）考点学习。」

   这一版**只换形状，不换口径**，四条不改：
     ① **颜色照旧**：方块底 = 该章掌握度档色（§7.2 单向链，色由 `grpStat` 的"最弱子项"决定）；
        未测 = 浅底 + 虚线内圈，**不涂成"差"**；未教不参与任何统计。
     ② **两式同级同色照旧**：章名仍是 #E25555（与左式 `.tg-chname` 同一个红），
        只是位置从"方块里"挪到"展开卡头上"——方块里装不下 10 个汉字。
     ③ **四级色条递进照旧**：册 6px（.th 左条）→ 章（方块整块 = 章色）→ 节 3px → 考点 4px。
     ④ **记号照旧**：.term-block / .th / .term-body / .ch-block / .ch-row / .ch-body /
        .sec-group / .kp-grid 一个都没改名 —— 换外观不该顺手把验收体系推倒重来。

   两条《界面规范》的硬条款落在这里（用户 2026-09-22：「一要美雅，二必须护眼」）：
     · **非阅读区一律绿**：浮标底取 `--g80`（白字 8.77:1），方块之外的界面件一律绿族/中性，
       不借七级色标的名义往导航、按钮上刷蓝紫。
     · **实底白字 ≥4.5:1**：方块上的字**不是**一律白色 —— 黄(#ca8a04)、青(#0891b2)、
       绿(#16a34a) 三档与白字分别只有 3.2 / 4.3 / 3.1，达不到 4.5。
       所以字色由 stu-tree.js 的 `inkOn()` 按底色的相对亮度算：暗底白字、亮底深字，
       两侧都 ≥4.5。方块只有 8mm，字本来就小，再压对比度就是不给看。
     · **面板不纯白**：展开卡取 `--panel`（#fbfdfc），不是 #fff。
   ⚠ 8mm 用 CSS 的 mm 单位而不是 30px：用户的口径是毫米，96dpi 下换算 30.24px；
     写 mm 则打印稿与换屏时物理尺寸不变。 */

/* 六册纵向排开：一册一行，左 104px 是册栏（胶囊 + 已过 + 进度条），右侧是方块阵 */
#tg-table.term-rows { grid-template-columns: minmax(0, 1fr); gap: 0; }
#tg-table .term-block {
  display: grid; grid-template-columns: 118px minmax(0, 1fr);
  gap: 12px; align-items: start; padding: 9px 6px 9px 0;
  border-radius: 0;
}
#tg-table .term-block + .term-block { box-shadow: inset 0 1px 0 var(--line); }
#tg-table .term-block:hover { background: var(--g00); }

/* 册栏：拍平成"标签列"，不再是横贯的一条 */
#tg-table .term-block > .th {
  display: flex; flex-direction: column; align-items: flex-start; flex-wrap: nowrap;
  gap: 2px; padding: 0 0 0 8px; background: transparent;
  border: 0; border-left: 6px solid var(--line); border-radius: 0;
  font-size: 12px; cursor: pointer;
}
#tg-table .term-block > .th:hover { background: transparent; }
#tg-table .term-block > .th .nm { display: flex; flex-wrap: wrap; align-items: center; gap: 4px; }
#tg-table .term-block > .th .nm .tp { font-size: 11.5px; padding: 1px 9px; }
#tg-table .term-block > .th .tag { font-size: 10px; padding: 0 5px; }
#tg-table .term-block > .th .st { font-size: 10.5px; line-height: 1.6; white-space: nowrap; }
#tg-table .term-block > .th .bar { width: 100%; flex: 0 0 auto; height: 3px; margin-top: 3px; }
#tg-table .term-block > .th .th-jy { font-size: 10.5px; text-decoration: none; color: var(--ink-3); }
#tg-table .term-block > .th .th-jy:hover { color: var(--g60); background: transparent; }
/* 册栏里不再显示「收起 ▾」：胶囊与整条册栏本身就是折叠开关（aria-expanded 跟着它走），
   118px 的窄栏里再塞一枚箭头，只会把「已过 2/21」挤成第二行。可点性由光标（pointer）
   与悬停底色表达，语义由 aria-expanded 给屏幕阅读器 —— 不是把功能删掉。 */
#tg-table .term-block > .th .caret { display: none; }

/* 方块阵：一格 8mm，自动换行；展开的那一章跨满整行 */
#tg-table .term-body {
  display: grid; grid-template-columns: repeat(auto-fill, 8mm);
  gap: 9px; padding: 1px 0 0; align-content: start;
}
/* ★ 方块与展开区必须是**两个独立的格**，不能包在一个格子里：
   上一版把 .ch-block 做成 `grid-template-rows: auto auto` 再让展开的那一块跨满整行，
   结果展开区把**后面的方块**顶到了卡片下面 —— 屏幕上从"1 2 3 4 5"变成"1 [卡片] 2 3 4 5"，
   章序断了。改成 display:contents 让方块与展开区各自成为方块阵的格：
   方块照旧按列排队（第 1 行），展开区固定落第 2 行、跨满整行
   —— 这就是用户令里"点击某一正方形，呈现所有节及节下面的考点"该有的样子。 */
#tg-table .ch-block { display: contents; }
#tg-table .ch-body { grid-column: 1 / -1; grid-row: 2; }

/* ★ 章 = 8×8mm 圆角正方形。圆角 2mm ≈ 边长的 1/4，方而不死。 */
#tg-table .ch-row {
  width: 8mm; height: 8mm; padding: 0; margin: 0;
  display: flex; align-items: center; justify-content: center;
  border: 0; border-radius: 2mm;
  background: var(--cc, #94a3b8);
  font-size: 11px; font-weight: 700; line-height: 1;
  box-shadow: 0 1px 2px rgba(16, 32, 24, .16), inset 0 1px 0 rgba(255, 255, 255, .18);
  transition: transform .16s cubic-bezier(.2, .8, .3, 1.25), box-shadow .18s;
  transform-origin: center;
}
#tg-table .ch-row .cn { font-variant-numeric: tabular-nums; letter-spacing: -.2px; }
/* 悬停：只放大 + 抬阴影，**不改色**。改色就等于"这一章的掌握度变了"（§7.2 只有一条链）。 */
#tg-table .ch-row:hover, #tg-table .ch-row:focus-visible {
  transform: scale(1.16) translateY(-2px);
  background: var(--cc, #94a3b8);
  box-shadow: 0 0 0 2px var(--panel), 0 10px 20px -10px rgba(16, 32, 24, .55);
  outline: none; text-decoration: none;
}
/* 未测：浅底 + 虚线内圈（outline 不占尺寸，8mm 不会被虚线吃掉） */
#tg-table .ch-row.unt {
  background: var(--panel-2); color: var(--ink-3) !important;
  box-shadow: none; outline: 1.5px dashed var(--g40); outline-offset: -3px;
}
#tg-table .ch-block.open > .ch-row {
  background: var(--cc, #94a3b8);
  box-shadow: 0 0 0 2px var(--panel), 0 0 0 4px var(--cc, #94a3b8);
}

/* 展开区：方块下方整幅宽的一张卡 */
#tg-table .ch-body { padding: 10px 0 4px; }
#tg-table .cb-card {
  background: var(--panel); border: 1px solid var(--gline); border-left: 4px solid var(--cc);
  border-radius: var(--r-sm); padding: 10px 12px 11px;
  animation: tgCbIn .24s ease both;
}
@keyframes tgCbIn { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }
#tg-table .cb-hd {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding-bottom: 8px; margin-bottom: 8px; border-bottom: 1px dashed var(--line);
}
#tg-table .cb-hd .cb-lv {
  font-size: 11px; font-weight: 700; color: var(--ink-2);
  background: var(--g00); border: 1px solid var(--gline); border-radius: 5px; padding: 0 6px;
}
/* 章名：仍是 #E25555（左式 .tg-chname 同值），两式同级的名字同色 —— 这条没动。 */
#tg-table .cb-hd .nm { font-size: 15px; font-weight: 700; color: #E25555; letter-spacing: .2px; }
#tg-table .cb-hd .cb-meta { font-size: 11.5px; color: var(--ink-3); font-variant-numeric: tabular-nums; }
#tg-table .cb-hd .cb-x {
  margin-left: auto; font-size: 11px; color: var(--ink-3); cursor: pointer;
  border: 1px solid var(--line); border-radius: 999px; padding: 1px 9px; background: var(--panel);
  transition: color .16s, border-color .16s;
}
#tg-table .cb-hd .cb-x:hover { color: var(--g80); border-color: var(--g40); }
#tg-table .cb-hd .cb-x:focus-visible { outline: 2px solid var(--g40); outline-offset: 1px; }

/* 章名浮标：光标停到方块上才出名字。
   深绿底（--g80）+ 白字 = 8.77:1；不用深灰/黑底 —— 那是"非阅读区"的界面件，
   按用户令一律绿族。 */
.tg-tip {
  position: fixed; left: 0; top: 0; z-index: 90; pointer-events: none;
  max-width: 300px; padding: 8px 11px; border-radius: 9px;
  background: var(--g80); color: #fff;
  box-shadow: 0 14px 30px -16px rgba(16, 32, 24, .72);
  opacity: 0; transform: translateY(3px) scale(.985);
  transition: opacity .12s ease, transform .12s ease;
  font-size: 12px; line-height: 1.55;
}
.tg-tip.on { opacity: 1; transform: none; }
.tg-tip b { display: block; font-size: 13px; letter-spacing: .2px; }
.tg-tip span { display: block; color: var(--g20); }
.tg-tip i { display: block; font-style: normal; font-size: 11px; color: var(--g40); margin-top: 1px; }

/* 手机：册栏再窄一档，方块仍是 8mm（口径不放宽） */
@media (max-width: 620px) {
  #tg-table .term-block { grid-template-columns: 84px minmax(0, 1fr); gap: 9px; }
  #tg-table .term-block > .th .th-jy { display: none; }
}

/* ══ 学生台左栏 · 2026-11-06 六条微调（用户令，逐句照抄）══════════════════════════
   用户原话：「学生台的导航栏的logo放大 / 所有文字整体向下移30mm / 新手启航字号稍微缩小
   并且颜色淡一点点 / 今日任务与新手启航的距离增大10mm / 辅助功能与成长图的距离增大10mm /
   账号、修改口令、退出登录三组整体向下移20mm。」

   ── 为什么这一段**单独挂在文件末尾**、且每条都带 `.shell[data-role="S"]` ──
     ① 用户点名的是**学生台**。九个台共用同一份 `app.js` 外壳，改 `.brand` / `.nav`
        这类裸选择器会把另外八台的左栏一起顶下去 —— 那一栏是教师/超管每天用的，
        没人让改。故一律加角色前缀，作用域只剩学生台。
     ② 挂在末尾是为了**级联顺序**：下面 30mm 这条要压过窄屏那一段（≤900px）里
        `.nav` 的横排规则。放前面就得去改那条媒体查询，改坏了别的台看不见。
        本段自带一条 ≤900px 的复位（见末尾），所以手机里左栏仍是横条、不多出空白。
     ③ 全部用 `mm`：用户是按毫米给的口令，CSS 的 `mm` 是绝对单位
        （1mm = 96/25.4 px ≈ 3.7795px），量具与口令同一个刻度，不靠"大概"。
        ⚠ `.nav` 是 flex 且 `gap: 2px` —— 行间距里那 2px 会**叠在** margin 之上，
        与印章那段的记档同一条坑；下面每条都按"量到的是净增"来落，不额外减 2px，
        因为这里量的是**相邻两格之间的净距变化量**（2px 在改前改后都在，不参与差值）。

   ── ① logo 放大：30px → 42px（字 13 → 19px，圆角 9 → 13px）──
     等比放到 1.4 倍：三处一起放，才不会出现"框大了、字还小、圆角显得方"的样子。
     42 的依据：左栏内容宽 236−12×2−2×2 = 208px，42px 约占 1/5，
     比站名那行（14.5px）高一头，仍是一枚"标记"而不是一块招牌。 */
.shell[data-role="S"] .brand i {
  width: 42px; height: 42px; flex: 0 0 42px; border-radius: 13px; font-size: 19px;
}

/* ── ② 所有文字整体向下移 30mm ──
   挂 `.nav` 的 `padding-top`，不挂 `.side`：logo 留在原处（它刚被放大，是这一栏的锚），
   要下移的是**那列文字**。挂 `.nav` 的另一个好处是正文组的「账号 / 修改口令 / 退出登录」
   与导航格**同一条基准**一起下移 —— 否则第③条"账号三组再下移 20mm"就没有可比的原点。
   ⚠ 健康守护那枚印是 `position: fixed` 的栏脚，不参与文档流，故它**不跟着下移** ——
     这是对的：印钉在左下角是上一轮定稿（"印章必须定在导航栏，不能移动"）。 */
.shell[data-role="S"] .nav { padding-top: 30mm; }

/* ── ③ 新手启航：字号 16 → 15px，"颜色淡一点点" ──
   淡的办法用 `opacity: .85`，**不换色值**：
     · 换 `--g60`(#2f7d61) 压在这条浅绿底（--g25 #cae2d7）上只有 **3.64:1** ——
       低于护眼条款的 4.5 线，等于"淡"到要用力看，那是没改对；
     · .85 的不透明度合成后是 **4.63:1**（把 alpha 先混进底色再算，与顶栏那条判据同一算法），
       既退了一档、又仍过线。
   `opacity` 而不是 rgba 字面量：本工程色值只有一个来源（§1.3），多写一个 rgba(27,84,67,…)
   就是给同一支绿添了第二个写法。 */
.shell[data-role="S"] .nav a[href$="#qihang"] { font-size: 15px; opacity: .85; }

/* ── ④ 今日任务与新手启航之间 +10mm ──
   加在「今日任务」的 `margin-top` 上：相邻两格之间只剩这一处可加，加了就只影响这一段。 */
.shell[data-role="S"] .nav a[href$="#tasks"] { margin-top: 10mm; }

/* ── ⑤ 辅助功能与成长图之间 +10mm ──
   「辅助功能」是 `<details class="navfold">`（抽屉的外壳），不是 `.nav a`，
   所以这条必须挂在 `.navfold` 上；原来那行 `.nav .navfold { margin: 0 }` 的
   特指度低于本行（这里是 3 个类级 + 1 个属性选择器），能压住它。 */
.shell[data-role="S"] .nav .navfold { margin-top: 10mm; }

/* ── ⑥ 账号 / 修改口令 / 退出登录 三组整体再下移 20mm ──
   三件是连续的同级节点，只需把**头一件**（「账号」分组标签）推下去，后两件跟着走。
   `+` 相邻兄弟是刻意的：`.nav-main` 之后紧跟的那一个 `.grp` 就是账号组标签
   （「导航」那个 `.grp` 在 `.nav-main` **里面**，选不到），换个人改结构也不会误伤别处。 */
.shell[data-role="S"] .nav > .nav-main + .grp { margin-top: 20mm; }

/* ── 窄屏（≤900px）复位 ────────────────────────────────────────────────────
   900px 以下左栏是**通栏横条**（`.nav` 改 flex-direction: row），毫米级的纵向位移
   在横条里只会变成一截空白与错位。故这一段把上面五条纵向调整全部归零；
   logo 放大**保留**（横条里也看得见，且不占纵向空间）。 */
@media (max-width: 900px) {
  .shell[data-role="S"] .nav { padding-top: 0; }
  .shell[data-role="S"] .nav a[href$="#tasks"],
  .shell[data-role="S"] .nav .navfold,
  .shell[data-role="S"] .nav > .nav-main + .grp { margin-top: 0; }
}
