/* ============================================================
   精进编程 · 设计系统
   方向：浅科技蓝 + 活力橙 ——「一张摊在灯下的蓝色作业纸，配一支橙色马克笔」
   浅科技蓝底 + 深蓝字做**结构**（导航 / 描边 / 标题）；
   活力橙只留给**行动**（主按钮 / 需要点的那一下 / 注意标签）。
   容器一律「只描边、不填充」；按钮走 layui 风格：实心、边界清楚、四态对比强
   （normal / hover / active / disabled）。
   字体：Bahnschrift（展示/数字）+ 微软雅黑（正文）+ Cascadia Code（代码）
   ============================================================ */
:root {
  /* —— 表面：全站浅科技蓝，容器一律不填充 —— */
  --bg: #e9f3fe;             /* 页面底色：浅科技蓝 */
  --surface: transparent;    /* 面板：只描边 */
  --surface-2: transparent;  /* 次级按钮：同样只描边 */

  /* —— 文字：深蓝墨色 —— */
  --ink: #10304f;
  --ink-2: #2a4e73;
  --ink-3: #5b7a99;
  --ink-4: #8aa6c0;

  /* —— 描边：一律浅蓝 —— */
  --line: #a9cdec;
  --line-2: #cfe3f5;

  /* —— 主题色：科技蓝（浅）—— */
  --brand: #2b7fe4;          /* 结构色：导航 / 描边 / 链接 */
  --brand-2: #4b9bf5;        /* 悬停 / 高亮 */
  --brand-3: #1a5fb8;
  --brand-tint: rgba(43, 127, 228, .10);
  --cyan: #22a6dd;           /* 装饰性电光蓝 */

  /* —— 活力橙：全站唯一的「行动色」（主按钮 / 强调标签）—— */
  --orange: #ff7a18;
  --orange-2: #ff9a3d;       /* 悬停提亮 */
  --orange-3: #e8630a;       /* 按下 / 深色 */
  --orange-tint: rgba(255, 122, 24, .13);
  --amber: #ff7a18;
  --amber-tint: var(--orange-tint);
  --yellow: #f07c0a;         /* 兼容旧名：注意状态 */

  /* —— 语义 —— */
  --ok: #16a34a;
  --bad: #e8443a;
  --ok-tint: rgba(22, 163, 74, .12);
  --bad-tint: rgba(232, 68, 58, .10);

  /* —— 纸面：编辑器 / 作业纸共用的浅蓝 —— */
  --paper: #f6fbff;
  --paper-line: rgba(90, 150, 210, .30);
  --paper-ink: #10304f;
  --paper-ink-2: #2a4e73;
  --rule-red: rgba(47, 128, 237, .55);

  /* —— 屏幕（代码 / 输出）：浅蓝屏 + 深蓝字 —— */
  --term-bg: #eef6ff;
  --term-ink: #1b3a5c;
  --term-dim: #8aa6c0;

  /* —— 字体 —— */
  --display: "Bahnschrift", "Bahnschrift SemiBold", "Segoe UI Semibold", "Microsoft YaHei", sans-serif;
  --sans: "Microsoft YaHei", "PingFang SC", "Segoe UI", system-ui, sans-serif;
  --mono: "Cascadia Code", Consolas, "JetBrains Mono", ui-monospace, monospace;

  /* —— 形状 / 阴影（浅色底上只用极淡的蓝影）—— */
  --r-sm: 8px;
  --r: 14px;
  --r-lg: 20px;
  --r-btn: 9px;              /* 按钮圆角：layui 的利落感 + 一点童趣 */
  --glow: 0 0 0 1px rgba(47, 128, 237, .08), 0 16px 36px -26px rgba(16, 48, 79, .28);
  --shadow-s: 0 1px 2px rgba(16, 48, 79, .05), 0 6px 16px -12px rgba(16, 48, 79, .22);
  --shadow-m: 0 2px 6px rgba(16, 48, 79, .06), 0 20px 40px -28px rgba(16, 48, 79, .30);
  --shadow-l: 0 10px 26px rgba(16, 48, 79, .10), 0 40px 70px -40px rgba(16, 48, 79, .34);

  /* —— 动效 —— */
  --ease: cubic-bezier(.22, .61, .36, 1);
  --ease-out: cubic-bezier(.16, 1, .3, 1);
  --dur-fast: 160ms;
  --dur-base: 320ms;
  --dur-slow: 640ms;
}

* { box-sizing: border-box; }

/* ⚠️ body 千万别写成 height: 100%（第 21 轮踩过）：
   那会把 body 的盒子压成「正好一屏高」，而 position: sticky 只能在自己的
   包含块（body）里活动 —— 页面往下滚过一屏后，首行就跟着 body 一起滚走了，
   看起来像「没有固定」。sticky 要的是一条能延伸到底的滚动链，所以用 min-height。 */
html { height: 100%; }
/* 第 30 轮按需求删掉了「风格背景」：26px 蓝图网格 + 三团 radial 光晕 + 3% 噪点
   全部下线。现在是一层干净的浅蓝纸面，靠留白、细描边与字体层级撑版式
   （前端设计规范的 DO：干净表面 + 有节制的重音；DON'T：装饰性纹理堆叠）。 */
body {
  min-height: 100%;
  margin: 0;
  font-family: var(--sans);
  color: var(--ink);
  font-size: 15px;
  line-height: 1.7;
  -webkit-font-smoothing: antialiased;
  background-color: var(--bg);
  background-image: linear-gradient(180deg, #eef6ff 0%, var(--bg) 340px, var(--bg) 100%);
  background-repeat: no-repeat;
  background-attachment: fixed;
}

a { color: var(--brand-2); }

h1, h2, h3, h4 {
  font-family: var(--display);
  font-weight: 700;
  letter-spacing: -.005em;
  margin: 0;
}

code, kbd, pre, .mono { font-family: var(--mono); }

/* 数字全部等宽，进度与分数不会跳 */
.chip, .score-val, .hw-score, .time, .info, .meta, .level-btn .n, td.c-seq {
  font-variant-numeric: tabular-nums;
}

::selection { background: rgba(47, 128, 237, .28); }

:focus-visible {
  outline: 2px solid var(--brand-2);
  outline-offset: 2px;
  border-radius: 6px;
}

/* ---------------------------------------------------------- 顶栏 */
.topbar {
  position: sticky;
  top: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 10px clamp(14px, 3.4vw, 40px);
  background: rgba(246, 251, 255, .92);
  backdrop-filter: saturate(1.4) blur(14px);
  border-bottom: 1px solid var(--line-2);
  box-shadow: 0 1px 0 rgba(47, 128, 237, .08), 0 12px 30px -26px rgba(16, 48, 79, .26);
}
/* 第 30 轮：底边那道霓虹渐变去掉了 —— 顶栏现在是「一条干净的水位线」，
   不抢级别菜单的注意力。 */

.brand {
  display: flex;
  align-items: center;
  gap: 12px;
  text-decoration: none;
  color: var(--ink);
  font-weight: 700;
  flex: none;
}
/* 品牌图形：直接用用户的 logo（已抠成透明底 + 深色底专用版本）
   logo 里是「精进编程」四个字 + 一把螺丝刀，横向长条；高一点字才够醒目（小学生要看清）。 */
.brand-img {
  height: 44px;
  width: auto;
  display: block;
  transition: transform var(--dur-fast) var(--ease);
}
.brand:hover .brand-img { transform: translateY(-1px) scale(1.03); }

/* logo 图片里已经写着「精进编程」四个字，所以不再另配文字标 ——
   只给子页面跟一个很轻的英文小标（区分 code / codes / list），靠左边一条竖线分隔 */
.page-code .brand::after, .page-codes .brand::after, .page-list .brand::after {
  content: "";
  display: none;
}
.page-tag {
  font-family: var(--mono); font-size: 10.5px; font-weight: 700;
  letter-spacing: .2em; text-transform: uppercase; color: var(--ink-4);
  padding-left: 11px; margin-left: 2px;
  border-left: 1px solid var(--line);
  line-height: 1.2;
}

.brand-text { display: flex; flex-direction: column; line-height: 1.12; }
.brand-text b {
  font-family: var(--display);
  font-size: 19px;
  font-weight: 700;
  letter-spacing: .04em;
}
.brand-text span {
  font-size: 10.5px;
  color: var(--ink-3);
  letter-spacing: .22em;
  text-transform: uppercase;
  font-family: var(--mono);
}

.topbar .spacer { flex: 1; }

/* ---------------------------------------------------------- 按钮
   第 56 轮（需求）：改用 layui 风格 —— 实心、边界清楚、四态对比强烈。
   结构色是科技蓝，**行动色是活力橙**：主按钮（.solid / .green）一律橙。
   兼容名：.layui-btn（默认描边蓝）/ .layui-btn-normal（主行动·橙）/
           .layui-btn-danger（红）/ .layui-btn-disabled
   状态：hover 换色 + 投影、active 按下 1px、disabled 变灰且 cursor:not-allowed。 */
.btn,
.layui-btn {
  --bg: transparent;
  --fg: var(--brand-3);
  --bd: #a9cdec;
  --bg-h: var(--brand-tint);
  --fg-h: var(--brand-3);
  --bd-h: var(--brand-2);
  --bg-a: rgba(43, 127, 228, .20);
  --fg-a: var(--brand-3);
  --bd-a: var(--brand-3);
  --sd: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  min-height: 38px;
  padding: 8px 18px;
  font: inherit;
  font-family: var(--sans);
  font-size: 14px;
  font-weight: 700;
  letter-spacing: .01em;
  color: var(--fg);
  background: var(--bg);
  border: 1.5px solid var(--bd);
  border-radius: var(--r-btn);
  cursor: pointer;
  user-select: none;
  box-shadow: var(--sd);
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast),
              border-color var(--dur-fast), box-shadow var(--dur-fast),
              transform 120ms var(--ease);
  white-space: nowrap;
}

.btn:hover,
.layui-btn:hover { background: var(--bg-h); color: var(--fg-h); border-color: var(--bd-h); }
.btn:active,
.layui-btn:active {
  background: var(--bg-a); color: var(--fg-a); border-color: var(--bd-a);
  transform: translateY(1px);
}
.btn[disabled], .btn.layui-disabled, .btn.is-disabled,
.layui-btn.layui-btn-disabled {
  background: #eef1f6; color: #a7b5c6; border-color: #dbe3ee;
  box-shadow: none; cursor: not-allowed; transform: none;
}
/* 第 69 轮（需求 3）：老师关掉「允许举手 / 允许呼叫」后，学生端那两枚按钮再压一层灰，
   一眼就能看出「不是我没点，是老师没开」。 */
.btn.is-off { filter: grayscale(1); opacity: .55; }
/* codes 页「清除全部」右边那两枚课堂开关 */
.cls-switches {
  display: inline-flex; gap: 12px; align-items: center;
  margin-left: 8px; padding-left: 12px; border-left: 1px dashed var(--line-2);
}
.cls-switches .chk { margin: 0; white-space: nowrap; }

/* 主按钮：活力橙实心 + 橙色投影 —— 全站最打眼的「点这里」 */
.btn.solid, .btn.green, .btn.warm,
.layui-btn-normal {
  --bg: linear-gradient(180deg, var(--orange-2), var(--orange));
  --fg: #ffffff;
  --bd: #e8630a;
  --bg-h: linear-gradient(180deg, var(--orange), var(--orange-3));
  --fg-h: #ffffff;
  --bd-h: #c9530a;
  --bg-a: linear-gradient(180deg, var(--orange-3), #c9530a);
  --fg-a: #ffffff;
  --bd-a: #b34a08;
  --sd: 0 6px 16px -10px rgba(232, 99, 10, .70);
}
.btn.solid:hover, .btn.green:hover, .btn.warm:hover,
.layui-btn-normal:hover { --sd: 0 9px 22px -10px rgba(232, 99, 10, .80); }

/* 危险按钮：删除 / 挂断 */
.btn.danger,
.layui-btn-danger {
  --bg: linear-gradient(180deg, #f4705f, var(--bad));
  --fg: #ffffff; --bd: #c9382f;
  --bg-h: linear-gradient(180deg, var(--bad), #c9382f);
  --fg-h: #ffffff; --bd-h: #b32f27;
  --bg-a: #b32f27; --fg-a: #ffffff; --bd-a: #9c2822;
  --sd: 0 6px 16px -10px rgba(200, 56, 47, .65);
}

/* 幽灵按钮：只留文字，hover 才浮出一层浅蓝 */
.btn.ghost {
  --bg: transparent; --fg: var(--ink-2); --bd: transparent; --sd: none;
}
.btn.ghost:hover { --bg: var(--brand-tint); --fg: var(--brand-3); --bd: transparent; }
.btn.ghost:active { --bg: rgba(43, 127, 228, .18); }
.btn.amber { --bg: transparent; --fg: var(--brand-3); --bd: var(--brand); }

.btn.sm, .layui-btn-sm { min-height: 32px; padding: 5px 13px; font-size: 13px; gap: 6px; }
.btn.block { width: 100%; }

.btn .dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: currentColor; opacity: .6;
}
.btn.rec-on {
  --bg: linear-gradient(180deg, #f4705f, var(--bad));
  --fg: #fff; --bd: #c9382f;
  --bg-h: linear-gradient(180deg, var(--bad), #c9382f);
  --fg-h: #fff; --bd-h: #b32f27;
  --bg-a: #b32f27; --fg-a: #fff; --bd-a: #9c2822;
}
.btn.rec-on .dot { background: #fff; opacity: 1; animation: pulse 1s infinite; }

@keyframes pulse { 50% { opacity: .2; } }

/* 播放按钮：录完音后出现的圆形小按钮 */
.audio-play {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 30px;
  padding: 5px 12px 5px 8px;
  border-radius: 999px;
  border: 1px solid var(--brand);
  background: var(--brand-tint);
  color: var(--brand-2);
  font: inherit;
  font-size: 12.5px;
  font-weight: 700;
  cursor: pointer;
  transition: background var(--dur-fast), color var(--dur-fast), transform var(--dur-fast) var(--ease);
}
.audio-play:hover { background: rgba(47, 128, 237, .18); transform: translateY(-1px); }
.audio-play.playing {
  background: var(--brand);
  border-color: var(--brand-2);
  color: #ffffff;
}
.audio-play svg { width: 14px; height: 14px; }

/* ---------------------------------------------------------- 表单控件（深色壳里统一） */
.field { margin-bottom: 15px; }
.field label {
  display: block;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--ink-2);
  margin-bottom: 6px;
  letter-spacing: .02em;
}
.field input, .field textarea, .field select,
.grade-panel input, .grade-panel textarea, .grade-panel select {
  width: 100%;
  padding: 11px 13px;
  font: inherit;
  font-size: 14.5px;
  color: var(--ink);
  background: #ffffff;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  transition: border-color var(--dur-fast), box-shadow var(--dur-fast);
}
.field input::placeholder, .field textarea::placeholder,
.grade-panel input::placeholder, .grade-panel textarea::placeholder { color: var(--ink-4); }
.field input:focus, .field textarea:focus, .field select:focus,
.grade-panel input:focus, .grade-panel textarea:focus, .grade-panel select:focus {
  outline: 0;
  border-color: var(--brand-2);
  box-shadow: 0 0 0 3px rgba(47, 128, 237, .18);
}
.field .rule { font-size: 12px; color: var(--ink-3); margin-top: 5px; }
.field.bad input { border-color: var(--bad); }
.field.bad .rule { color: #b33126; }
.field.good input { border-color: var(--ok); }

.err {
  display: none;
  padding: 10px 13px;
  border-radius: var(--r-sm);
  background: var(--bad-tint);
  border: 1px solid rgba(217, 66, 47, .40);
  color: #b33126;
  font-size: 13px;
  margin-bottom: 14px;
}
.err.on { display: block; animation: shake .34s var(--ease); }
@keyframes shake {
  25% { transform: translateX(-5px); }
  50% { transform: translateX(5px); }
  75% { transform: translateX(-3px); }
}

.tips {
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px dashed var(--line);
  font-size: 12.5px;
  color: var(--ink-3);
}
.tips b { color: var(--ink-2); }
.tips code { color: var(--brand-2); }

.pw-meter { display: flex; gap: 4px; margin-top: 7px; }
.pw-meter i {
  flex: 1; height: 4px; border-radius: 2px; background: rgba(47, 128, 237, .14);
  transition: background .25s;
}
.pw-meter i.on { background: var(--brand-2); }
.pw-meter i.mid { background: var(--yellow); }
.pw-meter i.weak { background: var(--bad); }

/* ---------------------------------------------------------- 布局 */
.wrap {
  position: relative;
  z-index: 2;
  width: min(1200px, 100% - clamp(24px, 7vw, 88px));
  margin: 0 auto;
}

.wrap.wide { width: min(1840px, 100% - clamp(10px, 1.4vw, 22px)); }

/* ---------------------------------------------------------- 首页
   第 30 轮：级别卡片挪进顶栏后，首屏不再有 hero，课程板直接开场。
   保留 .hero 这条以防别的页面复用它 —— 只留一点上边距。 */
.hero { padding: 10px 0 0; }

.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-family: var(--mono);
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--cyan);
}
.eyebrow::before {
  content: "";
  width: 26px; height: 2px;
  background: linear-gradient(90deg, var(--brand), var(--cyan));
  border-radius: 2px;
}

/* 第 30 轮：级别菜单从「四张大卡片」变成顶栏里的一排药丸（和 LOGO 同一行）。
   只留「Python 一级」这一个关键字 —— 没有 L1 徽标、没有副标题。
   选中态用实心蓝（全站唯一允许大面上色的地方），其余只描边。 */
.levels {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  min-width: 0;
}

.level-btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  min-height: 34px;
  padding: 5px 14px;
  border: 1px solid transparent;
  background: transparent;
  border-radius: 999px;
  font: inherit;
  font-family: var(--display);
  font-weight: 700;
  font-size: 14.5px;
  letter-spacing: .01em;
  line-height: 1.2;
  color: var(--ink-2);
  white-space: nowrap;
  cursor: pointer;
  transition: color var(--dur-fast), background var(--dur-fast), border-color var(--dur-fast), transform var(--dur-fast) var(--ease);
  animation: rise var(--dur-base) var(--ease-out) both;
}

.level-btn:hover {
  color: var(--brand-3);
  border-color: var(--line);
  background: rgba(255, 255, 255, .8);
}
.level-btn:active { transform: translateY(1px); }

.level-btn .lvname { display: inline-block; }

/* 选中：实心蓝药丸。蓝底白字是这一版唯一的「上色」处，一眼看得出学到哪一级。 */
.level-btn[aria-selected="true"] {
  color: #fff;
  background: var(--brand);
  border-color: var(--brand);
  box-shadow: 0 6px 16px -10px rgba(47, 128, 237, .9);
}
.level-btn[aria-selected="true"]:hover { background: var(--brand-2); border-color: var(--brand-2); }

/* 第 80 轮（需求）：内页顶栏那排课程药丸（core.js#mountCourseNav 挂进来的）。
   和首页同一套 .level-btn，但更紧凑 —— 内页顶栏右边已经挤着 3~4 枚按钮
   和一枚「上课控制」，照首页那套 34px 高 / 14.5px 字会把顶栏撑开。
   永不换行：课程多了就在这条里横向滚动。换行会把整页内容顶下去，
   而顶栏是 sticky 的，一换行等于永久吃掉一块屏。 */
.topbar .levels-nav {
  flex: 0 1 auto;
  min-width: 0;
  max-width: min(46vw, 620px);
  flex-wrap: nowrap;
  gap: 4px;
  padding: 2px 0;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: thin;
  scrollbar-color: rgba(43, 127, 228, .25) transparent;
  /* ⚠️ 这两行不能少：文件末尾 @media (max-width:1100px) 里有一条
     `.levels { order: 3; width: 100% }` —— 那是给**首页**用的（药丸太多就整行换行，
     顶栏变两行）。内页照抄会把课程菜单甩到第二行、把整站顶栏永久撑高一倍。
     选择器特异性（.topbar .levels-nav = 0,2,0）比 .levels 高，这里显式顶回去。 */
  order: 0;
  width: auto;
  /* 右边 18px 渐隐 —— 课程多了会在这里裁断，硬切一刀看着像 bug，淡出就是「还有」 */
  -webkit-mask-image: linear-gradient(90deg, #000 0, #000 calc(100% - 18px), transparent 100%);
  mask-image: linear-gradient(90deg, #000 0, #000 calc(100% - 18px), transparent 100%);
}
.topbar .levels-nav::-webkit-scrollbar { height: 4px; }
.topbar .levels-nav::-webkit-scrollbar-track { background: transparent; }
.topbar .levels-nav::-webkit-scrollbar-thumb { background: rgba(43, 127, 228, .25); border-radius: 4px; }
.topbar .levels-nav .level-btn {
  flex: 0 0 auto;
  min-height: 28px;
  padding: 3px 11px;
  font-size: 13px;
  animation: none;        /* 内页不逐枚弹出，省得每次翻页都晃一阵 */
}
/* 窄屏（手机 / 分屏）：顶栏本来就挤成一锅，这排整条收起 —— 回首页照样切课 */
@media (max-width: 980px) {
  .topbar .levels-nav { display: none; }
}
/* 1024 左右的笔电：右侧还挂着「在线编程 / 我的记录 / 退出 / 上课控制」，
   只留窄窄一条（约两三门课），多了自己横向滚，绝不跟按钮抢位置 */
@media (max-width: 1100px) and (min-width: 981px) {
  .topbar .levels-nav { max-width: 26vw; }
}

/* ---------------------------------------------------------- 课程板 */
.board {
  margin: 26px 0 70px;
  background: linear-gradient(180deg, transparent, transparent);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--glow);
  overflow: hidden;
}

.board-head {
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  padding: 16px clamp(16px, 2.4vw, 28px);
  border-bottom: 1px solid var(--line-2);
  background: linear-gradient(180deg, rgba(47, 128, 237, .08), transparent);
}
.board-head h2 { font-size: 22px; letter-spacing: .01em; }
.board-head .meta { font-size: 13px; color: var(--ink-3); }
.board-head .spacer { flex: 1; }

.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 11px;
  border-radius: 999px;
  font-size: 12.5px;
  font-weight: 700;
  background: var(--brand-tint);
  color: var(--brand-2);
  border: 1px solid rgba(47, 128, 237, .18);
}
.chip.amber { background: var(--amber-tint); color: #c9530a; border-color: rgba(255, 122, 24, .34); }
.chip.gray { background: rgba(47, 128, 237, .07); color: var(--ink-3); border-color: var(--line-2); }

/* 表格 */
table.courses { width: 100%; border-collapse: collapse; }

table.courses thead th {
  text-align: left;
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--ink-3);
  font-weight: 700;
  padding: 12px clamp(10px, 1.6vw, 22px);
  border-bottom: 1px solid var(--line-2);
  background: rgba(47, 128, 237, .08);
  white-space: nowrap;
}

table.courses td {
  padding: 13px clamp(10px, 1.6vw, 22px);
  border-bottom: 1px solid var(--line-2);
  vertical-align: middle;
}
table.courses tbody tr { transition: background var(--dur-fast) var(--ease); }
table.courses tbody tr:hover { background: rgba(47, 128, 237, .06); }
table.courses tbody tr:last-child td { border-bottom: 0; }
/* 教师列表：整行可点＝直接进编辑（不再单独放一个「批改 / 编辑」按钮） */
table.courses tbody tr.row-link { cursor: pointer; }

td.c-seq {
  font-family: var(--mono);
  font-size: 13px;
  color: var(--ink-4);
  width: 62px;
}

td.c-name a {
  font-weight: 700;
  color: var(--ink);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: border-color var(--dur-fast), color var(--dur-fast);
}
td.c-name a:hover { color: var(--brand-2); border-color: var(--brand-2); }
td.c-name .sub { display: block; font-size: 12.5px; color: var(--ink-3); font-weight: 400; margin-top: 1px; }

/* 题型徽标：四种霓虹色，一眼分出讲解/选择/判断/编程 */
.kind {
  display: inline-block;
  font-size: 11px;
  font-weight: 700;
  padding: 2px 8px;
  border-radius: 5px;
  margin-right: 8px;
  vertical-align: 1px;
  letter-spacing: .04em;
  border: 1px solid transparent;
}
.kind.lesson { background: rgba(47, 128, 237, .12); color: var(--cyan); border-color: rgba(47, 128, 237, .22); }
.kind.choice { background: rgba(47, 128, 237, .12); color: #1c62c4; border-color: rgba(47, 128, 237, .34); }
.kind.judge { background: var(--amber-tint); color: var(--yellow); border-color: rgba(217, 139, 0, .32); }
.kind.code { background: rgba(23, 160, 94, .12); color: #0f8b4f; border-color: rgba(23, 160, 94, .30); }

td.c-score { width: 96px; }
.score-val {
  font-family: var(--mono);
  font-weight: 700;
  font-size: 16px;
}
.score-val.none { color: var(--ink-4); font-weight: 400; }
.score-val.hi { color: var(--ok); }
.score-val.lo { color: #cc3b2b; }

td.c-audio { width: 74px; }

.spk {
  width: 32px; height: 32px;
  display: inline-grid;
  place-items: center;
  border-radius: 50%;
  border: 1px solid var(--brand);
  background: var(--brand-tint);
  color: var(--brand-2);
  cursor: pointer;
  transition: transform var(--dur-fast) var(--ease), background var(--dur-fast);
}
.spk:hover { transform: scale(1.1); background: rgba(47, 128, 237, .30); }
.spk.playing { background: var(--brand); color: #ffffff; }
.spk svg { width: 15px; height: 15px; }

/* 首页课程列表两列：左表排前半、右表接后半，中间一条分隔线。
   窄屏（≤1200px）自动退回单列 —— 这时右表的表头隐藏、并补一条上边框，
   视觉上依旧是一张从上贯到下、序号连续的列表。

   第 24 轮：列数交给 --cols（由 index.js 按条目数写入）——
   ≤10 条固定两列；更多时在宽屏自动加到 3~4 列，保证所有课程都在这一页里看全。
   列数写在自定义属性上而不是 grid-template-columns 上，下面的 @media 才压得住。 */
.board-cols { display: grid; grid-template-columns: repeat(var(--cols, 2), minmax(0, 1fr)); }
.board-cols > * { min-width: 0; }
.board-cols > table.courses:not(:first-child) { border-left: 1px solid var(--line-2); }

/* 表格行入场是从下往上 12px（rowRise）——最后一行在动画期间会探出板子底边，
   被 .board 的 overflow:hidden 切掉一截。留出这 12px，任何时刻都切不到。 */
#boardBody { padding-bottom: 12px; }

@media (min-width: 1201px) {
  /* 两列时每列只有 600px 上下，单元格收紧一点，免得「名称」列被挤扁 */
  .board-cols table.courses thead th,
  .board-cols table.courses td { padding: 11px 12px; }
  .board-cols td.c-seq { width: 46px; }
  .board-cols td.c-score { width: 74px; }
  .board-cols td.c-audio { width: 62px; }
  .board-cols td.c-time { width: 116px; white-space: nowrap; }
  .board-cols td.c-view { width: 62px; }
  /* 3~4 列时更挤，再收一档 */
  .board-cols[data-cols="3"] table.courses thead th,
  .board-cols[data-cols="3"] table.courses td,
  .board-cols[data-cols="4"] table.courses thead th,
  .board-cols[data-cols="4"] table.courses td { padding: 10px 9px; }
}

@media (max-width: 1200px) {
  .board-cols { grid-template-columns: 1fr; }
  .board-cols > table.courses:not(:first-child) { border-left: 0; border-top: 1px solid var(--line-2); }
  .board-cols > table.courses:not(:first-child) thead { display: none; }
}

td.c-time { width: 150px; }
/* 「查看」列：交了作业才出现一个小按钮，点进去看最近一次作答与正确答案 */
td.c-view { width: 76px; text-align: right; white-space: nowrap; }
td.c-view .btn.sm { padding: 3px 10px; min-height: 26px; font-size: 12.5px; }

/* ------------------------------------------------- 老师：课程列表 / 课程内容的增删改
   第 24 轮加的三样东西：级别瓦片上的「＋ 新增课程列表」、板子标题行的三个动作按钮、
   每行末尾一个很轻的 ✕（删这条内容）。都是「只描边、不填充」那套语汇。 */
.btn.danger { color: var(--bad); border-color: rgba(255, 74, 61, .38); }
.btn.danger:hover { background: rgba(255, 74, 61, .08); border-color: var(--bad); }

.level-btn.lv-add {
  border-style: dashed;
  border-color: var(--line);
  background: transparent;
  color: var(--ink-3);
  font-size: 13.5px;
}
.level-btn.lv-add:hover { color: var(--brand-3); border-color: var(--brand-2); background: rgba(255, 255, 255, .8); }
.level-btn.lv-add .lvname { color: inherit; }

/* 第 29 轮：课程列表标签上的「删除整个列表」✕（仅老师、且不止一个列表时才有）。
   ✕ 是药丸右上角一枚很轻的圆点按钮，平时隐形，hover 才浮出来 ——
   删列表是个重操作，别让人误点。 */
.lv-wrap { position: relative; display: inline-flex; }
.lv-wrap > .level-btn { display: inline-flex; }
.lv-del {
  position: absolute;
  top: -4px;
  right: -5px;
  z-index: 2;
  width: 18px;
  height: 18px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: var(--paper);
  color: var(--ink-4);
  font: inherit;
  font-size: 10px;
  line-height: 1;
  cursor: pointer;
  opacity: 0;
  transform: scale(.8);
  transition: opacity var(--dur-fast), transform var(--dur-fast) var(--ease), color var(--dur-fast), border-color var(--dur-fast), background var(--dur-fast);
}
.lv-wrap:hover .lv-del,
.lv-del:focus-visible { opacity: 1; transform: scale(1); }
.lv-del:hover { color: var(--bad); border-color: var(--bad); background: #fff3f1; }
.lv-del:active { transform: scale(.92); }
/* 触屏没有 hover：这个宽度下整天点不到 ✕ 更糟，干脆常显 */
@media (hover: none) {
  .lv-del { opacity: .75; transform: none; }
}

/* 药丸入场动画：包了一层 .lv-wrap，所以延时要从 wrap 数 */
.lv-wrap:nth-child(2) .level-btn { animation-delay: 60ms; }
.lv-wrap:nth-child(3) .level-btn { animation-delay: 120ms; }
.lv-wrap:nth-child(4) .level-btn { animation-delay: 180ms; }

td.c-del { width: 34px; text-align: right; }
/* 第 25 轮：行尾多了个「修改名称」，两个小按钮并排靠右 */
td.c-act { width: auto; text-align: right; white-space: nowrap; }
td.c-act .row-ren { margin-right: 6px; }
.row-ren {
  padding: 2px 8px;
  border: 1px solid transparent;
  border-radius: 7px;
  background: none;
  color: var(--ink-3);
  font-family: var(--sans);
  font-size: 12.5px;
  font-weight: 700;
  line-height: 1.6;
  cursor: pointer;
  transition: color var(--dur-fast), border-color var(--dur-fast), background var(--dur-fast);
}
.row-ren:hover { color: var(--brand); border-color: var(--line); background: rgba(47, 128, 237, .07); }
.row-del {
  width: 24px;
  height: 24px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 7px;
  background: none;
  color: var(--ink-4);
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
  transition: color var(--dur-fast), border-color var(--dur-fast), background var(--dur-fast);
}
.row-del:hover { color: var(--bad); border-color: rgba(255, 74, 61, .38); background: rgba(255, 74, 61, .07); }
table.courses tbody tr:hover .row-del { color: var(--ink-3); }

#editKind { margin-bottom: 0; }
#editKind button { padding: 9px 4px; font-size: 13px; }
#editNameRow { margin-bottom: 14px; }
#editTagRow { margin-bottom: 14px; }
.time {
  font-family: var(--mono);
  font-size: 13px;
  color: var(--ink-2);
  cursor: help;
  border-bottom: 1px dashed var(--line);
}
.time.none { color: var(--ink-4); border: 0; cursor: default; }

.empty {
  padding: 56px 20px;
  text-align: center;
  color: var(--ink-3);
}

/* 行入场动画
   只做位移、**不做透明度**：表格行带 inline 的 animation-delay 错峰，
   若从 opacity:0 起跑，慢一点的环境（以及无头截图）会拍到「半透明的空表」，
   内容看起来像没加载出来。位移入场一样有编排感，但任何时刻都读得到。 */
tbody tr { animation: rowRise .46s var(--ease-out) both; }
@keyframes rowRise {
  from { transform: translateY(12px); }
  to { transform: none; }
}
@keyframes rise {
  from { opacity: 0; transform: translateY(12px); }
  to { opacity: 1; transform: none; }
}

/* ---------------------------------------------------------- 对话框 / 登录注册 */
.mask {
  position: fixed;
  inset: 0;
  z-index: 90;
  display: grid;
  place-items: center;
  padding: 20px;
  background: rgba(16, 48, 79, .38);
  backdrop-filter: blur(6px);
  animation: fade .2s var(--ease);
}
.mask[hidden] { display: none; }
@keyframes fade { from { opacity: 0; } }

.dialog {
  width: min(440px, 100%);
  background: linear-gradient(180deg, #ffffff, #ffffff);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-l), 0 0 0 1px rgba(47, 128, 237, .10);
  padding: 26px;
  animation: pop .3s var(--ease-out);
}
@keyframes pop {
  from { opacity: 0; transform: translateY(16px) scale(.96); }
}

.dialog h3 { font-size: 24px; margin-bottom: 4px; letter-spacing: .01em; }
.dialog .hint { color: var(--ink-3); font-size: 13px; margin: 0 0 18px; }

/* ============ 第 129 轮：登录框右上角的「×」 ============
   需求：「在登陆页面右上角添加关闭按钮」。只作用在登录框这一处 ——
   `.dialog` 本身没有定位上下文，这里**只给 `#authMask` 里那一个**加 relative，
   别的 dialog（编辑课程 / 大图预览 …）排版一点没动。
   30px 的圆，平时淡灰无边框，悬停才出浅底；`.dialog` 的 26px 内边距让 h3
   右边正好留出位置，所以只给 h3 补一点右内边距防止长标题压到按钮上。 */
#authMask .dialog { position: relative; }
#authMask .dialog h3 { padding-right: 36px; }
.dialog-x {
  position: absolute; top: 8px; right: 8px;
  width: 32px; height: 32px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  border: 0; border-radius: 50%;
  background: transparent; color: #8b9aad;
  font: inherit; font-size: 22px; line-height: 1;
  cursor: pointer;
  transition: background var(--dur-fast), color var(--dur-fast);
}
.dialog-x:hover { background: #eef4fb; color: var(--ink); }
.dialog-x:active { background: #e2ecf8; }
.dialog-x:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }

.seg {
  display: flex;
  gap: 4px;
  padding: 4px;
  background: #f4f9ff;
  border: 1px solid var(--line-2);
  border-radius: 999px;
  margin-bottom: 20px;
}
.seg button {
  flex: 1;
  padding: 9px;
  border: 0;
  background: transparent;
  border-radius: 999px;
  font: inherit;
  font-weight: 700;
  font-size: 14px;
  color: var(--ink-3);
  cursor: pointer;
  transition: background var(--dur-fast), color var(--dur-fast);
}
.seg button[aria-selected="true"] {
  background: linear-gradient(180deg, #4b9bf5, #2f80ed);
  color: #ffffff;
  box-shadow: 0 6px 16px -8px rgba(47, 128, 237, .35);
}

/* ---------------------------------------------------------- 详情页 */
.detail-top {
  position: sticky; top: 0; z-index: 40;
  display: flex; align-items: center; gap: 10px;
  padding: 8px clamp(10px, 1.6vw, 20px);
  background: rgba(255, 255, 255, .90);
  backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--line-2);
}
.crumb { display: flex; align-items: center; gap: 9px; min-width: 0; flex: 0 1 auto; }
.crumb .sep { color: var(--ink-4); }
.crumb h2 { font-size: 17px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.detail-top .spacer { flex: 1 1 auto; }
.detail-top .btn, .detail-top .chip, .detail-top .brand { flex: none; }

.work {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 0;
  align-items: start;
  padding: 12px 0 26px;
}

/* ------------------------------------------------ 目录：自动隐藏的侧边抽屉 */
.rail {
  position: fixed;
  top: 0; left: 0; bottom: 0;
  z-index: 70;
  width: min(320px, 82vw);
  padding: 16px 14px 40px;
  background: linear-gradient(180deg, #ffffff, #ffffff);
  border: 0;
  border-right: 1px solid var(--line);
  border-radius: 0;
  overflow: auto;
  box-shadow: none;
  overscroll-behavior: contain;
  transform: translateX(-102%);
  transition: transform var(--dur-slow) var(--ease-out);
}
.rail.open { transform: none; box-shadow: 40px 0 80px -40px rgba(16, 48, 79, .18); }
.rail[hidden] { display: none; }

.rail h4 {
  display: flex; align-items: center; gap: 8px;
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .1em;
  color: var(--ink-2);
  margin: 0 0 6px;
  padding: 2px 4px 0;
}
.rail h4 .spacer { flex: 1; }
.rail h4 .rail-close {
  width: 28px; height: 28px; flex: none;
  display: grid; place-items: center;
  border: 1px solid var(--line); border-radius: 8px;
  background: #f4f9ff; color: var(--ink-3);
  font-size: 13px; line-height: 1; cursor: pointer;
  transition: border-color var(--dur-fast), color var(--dur-fast);
}
.rail h4 .rail-close:hover { border-color: var(--brand-2); color: var(--brand-2); }
.rail .rail-lv {
  font-size: 11.5px; color: var(--ink-4);
  padding: 0 5px 10px;
  border-bottom: 1px solid var(--line-2);
  margin-bottom: 10px;
}
.rail ol { list-style: none; margin: 0; padding: 0; }
.rail li a {
  display: flex;
  gap: 9px;
  padding: 9px 10px;
  border-radius: 8px;
  font-size: 13.5px;
  color: var(--ink-2);
  text-decoration: none;
  border: 1px solid transparent;
  transition: background var(--dur-fast), color var(--dur-fast), border-color var(--dur-fast);
}
.rail li a:hover { background: rgba(47, 128, 237, .10); color: var(--ink); border-color: var(--line-2); }
.rail li a.on {
  background: linear-gradient(90deg, rgba(47, 128, 237, .30), rgba(47, 128, 237, .06));
  border-color: rgba(47, 128, 237, .45);
  color: var(--brand-3);
  font-weight: 700;
}
.rail li a .s { font-family: var(--mono); font-size: 12px; opacity: .65; flex: none; }

.toc-mask {
  position: fixed; inset: 0; z-index: 65;
  background: rgba(16, 48, 79, .30);
  backdrop-filter: blur(3px);
  animation: fadeIn .2s var(--ease);
}
.toc-mask[hidden] { display: none; }
@keyframes fadeIn { from { opacity: 0; } }

/* ---------------------------------------------------------- 面板（浮在实验台上的仪器窗口） */
.panel {
  position: relative;
  background: linear-gradient(180deg, transparent, transparent);
  border: 1px solid var(--line);
  border-radius: var(--r);
  box-shadow: var(--glow);
  margin-bottom: 14px;
  overflow: hidden;
}
/* 四角括号：科技面板的记忆点之一 */
.panel::before, .panel::after {
  content: "";
  position: absolute;
  width: 14px; height: 14px;
  border: 2px solid var(--cyan);
  opacity: .5;
  pointer-events: none;
  z-index: 3;
}
.panel::before { top: 6px; left: 6px; border-right: 0; border-bottom: 0; border-radius: 5px 0 0 0; }
.panel::after { bottom: 6px; right: 6px; border-left: 0; border-top: 0; border-radius: 0 0 5px 0; }

.panel-head {
  display: flex; align-items: center; gap: 11px; flex-wrap: wrap;
  padding: 9px 16px;
  border-bottom: 1px solid var(--line-2);
  background: linear-gradient(180deg, rgba(47, 128, 237, .08), transparent);
}
.panel-head h3 { font-size: 16px; letter-spacing: .01em; }
.panel-head .spacer { flex: 1; }
.panel-head .note { font-size: 12.5px; color: var(--ink-3); }

/* 预览主题下拉：容器只描边不填充（同主题铁律），尺寸跟小按钮 / chip 对齐 */
.theme-pick { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--ink-3); }
.theme-pick > span { letter-spacing: .02em; }
.theme-pick select {
  appearance: none; -webkit-appearance: none;
  padding: 4px 26px 4px 11px;
  border-radius: 999px;
  border: 1px solid var(--line);
  background-color: transparent;
  /* 右侧下拉小箭头：内联 SVG，不引外链 */
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='9' height='6' viewBox='0 0 9 6'><path d='M1 1l3.5 3.5L8 1' fill='none' stroke='%235b7a99' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/></svg>");
  background-repeat: no-repeat;
  background-position: right 10px center;
  background-size: 9px 6px;
  color: var(--ink-2);
  font: inherit; font-size: 12.5px; font-weight: 700;
  cursor: pointer;
}
.theme-pick select:hover { border-color: var(--brand); color: var(--brand-2); }
.theme-pick select:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }
/* 下拉展开的选项由系统绘制，浅蓝主题下给白底深字，别出现浅底浅字 */
.theme-pick select option { background: #ffffff; color: #10304f; }
.panel-body { padding: 18px; }
.panel-body.tight { padding: 0; }

/* 两栏 */
.cols2 {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0;
}
.cols2 > * { min-width: 0; }
.cols2 > *:first-child { border-right: 1px solid var(--line-2); }

.col-label {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap; row-gap: 4px;
  padding: 8px 14px;
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-3);
  background: rgba(47, 128, 237, .07);
  border-bottom: 1px solid var(--line-2);
}
.col-label .spacer { flex: 1; }
/* 列标题行右侧的「N 行 · M 字符」「耗时」等信息 */
.col-label .info {
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0;
  color: var(--ink-4);
  white-space: nowrap;
  text-transform: none;
}
.col-label .n {
  display: grid;
  place-items: center;
  width: 18px; height: 18px;
  flex: none;
  border-radius: 5px;
  font-family: var(--mono);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0;
  background: var(--brand);
  color: #ffffff;
}
.col-label.out { color: var(--cyan); background: rgba(47, 128, 237, .08); }
.col-label.out .n { background: var(--cyan); color: #ffffff; }

/* 编程区「代码 / 输出」两列的标题行：
   「N 行 · M 字符」与「重置」「运行」都归到「代码」列的最右侧。
   左列标题里挂着按钮（约 29px），比右列纯文字高，所以这里用 min-height 把两边顶到同一高度，
   否则两列标题行的下边框会差 3px 对不齐（verify_layout.py 会断言「标题行等高」）。 */
.code-cols .col-label { flex-wrap: nowrap; min-height: 46px; }
.code-cols .col-label .info { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.code-cols .col-label .btn { flex: none; }

/* 内容编辑区保持浅色 —— 它就是那张"作业纸" */
.editor-host, .preview-host { background: #f4f9ff; }
.editor-host { height: clamp(400px, calc(100vh - 206px), 1600px); }
.preview-host { height: clamp(400px, calc(100vh - 206px), 1600px); overflow: auto; padding: 22px clamp(16px, 2vw, 32px); }

/* 代码 / 输出两栏容器：
   --code-rows 由 JS 按「代码行数 + 1」写入（见 detail.js 的 sync / syncLines），
   高度 = 行数 × 行高 + 上下内边距，于是代码区永远刚好比代码多留一行，
   左右两栏读同一个变量，所以始终等高。 */
.code-cols { display: grid; grid-template-columns: 1fr 1fr; --code-lh: 26px; }
.code-cols > * { min-width: 0; }
.code-cols > *:last-child { border-left: 1px solid var(--line-2); }

/* 代码编辑器：浅蓝屏 + 语法着色。
   结构是「叠层」——底层是一块着色好的 <pre>，上面浮一层文字全透明的 <textarea>；
   两者字体 / 行高 / 内边距 / tab-size 必须逐项对齐，差一点就会错行。 */
.codewrap {
  position: relative;
  height: calc(var(--code-rows, 8) * var(--code-lh, 24px) + var(--code-pad, 28px));
  display: flex;
  background: var(--term-bg);
}
.codewrap .ln {
  width: 46px;
  padding: 14px 8px 14px 0;
  text-align: right;
  font-family: var(--mono);
  font-size: 14.5px;
  line-height: 26px;   /* 与下方 textarea 用同一个固定行高，行号才不会越走越偏 */
  color: #8aa6c0;
  background: rgba(47, 128, 237, .05);
  border-right: 1px solid rgba(47, 128, 237, .07);
  user-select: none;
  overflow: hidden;
  white-space: pre;
}
.codepad { position: relative; flex: 1; min-width: 0; }
.codepad > .hl,
.codepad > textarea {
  position: absolute;
  inset: 0;
  margin: 0;
  padding: 14px 16px;
  border: 0;
  outline: 0;
  font-family: var(--mono);
  /* 编程题代码比正文大 2 号（15px → 17px）：投影上看得清。
     底稿与输入层共用这条规则，字号一变两层一起变，仍然逐像素对齐。 */
  font-size: 17px;
  line-height: 26px;
  tab-size: 4;
  white-space: pre;
  word-wrap: normal;
  overflow: auto;
}
/* 底稿：真正带颜色的那一层，只负责显色，不接收事件 */
.codepad > .hl {
  pointer-events: none;
  color: var(--term-ink);
  z-index: 0;
}
/* 输入层：文字透明只留光标，键盘输入与选区都还在它身上 */
.codepad > textarea {
  z-index: 1;
  resize: none;
  background: transparent;
  color: transparent;
  caret-color: var(--cyan);
}
.codepad > textarea::placeholder { color: var(--term-dim); }
.codepad > textarea::selection { background: rgba(47, 128, 237, .22); }

/* ---------- Python 语法着色（课程里的代码区专用） ----------
   类名统一挂 .pyhl，选择器写成 .pyhl .hljs-xxx —— 两条类的权重比
   md-editor-rt 那边全局的 .hljs-xxx 高一级，谁先加载都不会被盖掉。
   配色跟站点同一套语汇：紫=关键字、蓝=函数名、绿=字符串、琥珀=数字、灰=注释。 */
/* 预览区自带的 github 主题有一条 div.github-theme pre code{padding:22px 1em;
   letter-spacing:1px}，会把着色稿的字整体推歪 —— 这一层的使命就是和 textarea
   逐像素对齐，所以把字体相关的量全部显式抹平。选择器故意写深一层
   （.codepad > .hl > .pyhl）压过它；.code-read 那边同理换用同样深的写法。

   ⚠️ 这组必须带 !important：扩展包的主题不是全都守规矩的。
   实测——预览内容区挂着 md-editor-scrn 这个 md-editor-rt 自带的类，而 arknights
   主题里有一条
       .md-editor-scrn pre code { padding-left: 3.5em !important }   ← 没有 .arknights-theme 前缀
   于是**不管选哪套主题**它都生效，顺着祖先链砸到我们的着色底稿上，
   把内层 <code> 顶出 52.5px 左内边距（3.5em × 15px），和 textarea 彻底错位。
   另一条 `.arknights-theme pre code{line-height:1.75 !important}` 只在该主题下生效，
   但同样会砸歪行高。!important 只能用 !important 压，而我们的选择器权重更高
   （.codepad > .hl > .pyhl = 3 个类 > 1 个类 + 2 个标签），所以这样写就压得住。
   这些属性全是「只为对齐服务」的量，不承载任何有意的主题样式，抹平它们没有副作用。 */
.codepad > .hl > .pyhl,
.code-read pre > .pyhl {
  font: inherit !important;
  line-height: inherit !important;
  letter-spacing: inherit !important;
  word-spacing: inherit !important;
  white-space: inherit !important;
  tab-size: inherit !important;
  padding: 0 !important;
  margin: 0 !important;
  border: 0;
  background: none;
  color: var(--term-ink);
}

/* ---------------------------------------------------------------- 块级代码的行号
   同一个「主题没加前缀」的坑（第 23 轮）：arknights.css 里还有一条

       .md-editor-scrn span[rn-wrapper]{ position:absolute; top:1em; left:0; width:3em; ... }

   它是给老版行号写法准备的，而 md-editor-rt 7.0.0 是「每一行里塞一个 [rn-wrapper]，
   用 data-line-number 显示行号、靠 .md-editor-code-line 的 grid 分栏」。
   于是这条规则把每一个行号都绝对定位到代码块左上角同一个点上 —— 全部叠在一起，
   肉眼看就是「只有最后一个数字孤零零飘在那儿」（输入块本该是 1/2，看到的只有一个 2）。
   它同样没有主题前缀，选任何主题都生效，所以这里按同一套思路压回去：
   把行号还原成 grid 里的普通单元。选择器权重 0,3,1 > 0,2,1，不需要 !important。 */
.md-editor .md-editor-scrn span[rn-wrapper] {
  position: static;
  top: auto;
  left: auto;
  width: auto;
  letter-spacing: normal;
  counter-reset: none;
}

/* ------------------------------------------------- 块级代码块的宽度（第 24 轮 → 第 30 轮收紧）
   以前围栏代码块一律拉满整行，写两行「6 / 4」也占掉整幅版面，跟正文的宽窄节奏对不上。
   现在宽度只跟**实际内容**走：width:fit-content 让块子缩到最长那行那么宽，
   主题自带的 pre code 左右内边距（合计约 10 个字符宽）就是天然的留白，
   于是整块 ≈ 行号槽 + 最长行 + 10 个字符。
   太长的行不换行、也不撑破版面 —— max-width 卡在容器宽，超出部分横向滚动。
   ⚠️ 用 :has() 精确定位「带行号槽的围栏块」，别误伤 mermaid / 内嵌 pyarea 里的 pre。

   第 30 轮补的那一刀（「所有宽度都由代码宽度确定」的最后一环）：
   md-editor-rt 7.0.0 给每一行套了 grid（行号槽 + minmax(0,1fr)），
   而 .md-editor-code-line-content 默认是 white-space:pre-wrap + overflow-wrap:anywhere ——
   只要被换行，行盒就吃掉整幅宽，fit-content 量到的永远是容器宽，块子照样拉满。
   这里把行内容改成 pre（不换行），宽度才真正由最长那行决定；太长时滚动由 pre 承担。 */
.md-editor-preview .md-editor-code pre:has(code .md-editor-code-line),
.md-editor-preview pre:has(.md-editor-code-block-lines),
.md-editor-preview pre:has(code .md-editor-code-line) > code {
  width: fit-content;
  max-width: 100%;
}
.md-editor-preview pre:has(code .md-editor-code-line) { overflow-x: auto; }
.md-editor-preview .md-editor-code pre code:has(.md-editor-code-line) {
  overflow-x: visible;                 /* 横向滚动交给外层 pre，别双重滚动条 */
}
.md-editor-preview .md-editor-code pre code .md-editor-code-line { min-width: 0; }
.md-editor-preview .md-editor-code pre code .md-editor-code-line-content {
  white-space: pre;                    /* 关键：不换行 → 行宽 = 代码实际宽度 */
  overflow-wrap: normal;
  word-break: normal;
}
.pyhl .hljs-comment, .pyhl .hljs-quote { color: #7f9ab3; font-style: italic; }.pyhl .hljs-keyword, .pyhl .hljs-meta, .pyhl .hljs-selector-tag, .pyhl .hljs-doctag {
  color: #7a3fbf; font-weight: 600;
}
.pyhl .hljs-built_in, .pyhl .hljs-title, .pyhl .hljs-section,
.pyhl .hljs-title.function_, .pyhl .hljs-title.class_ {
  color: #2f80ed; font-weight: 600;
}
.pyhl .hljs-string, .pyhl .hljs-regexp, .pyhl .hljs-addition, .pyhl .hljs-attribute {
  color: #0f8a5f;
}
.pyhl .hljs-number, .pyhl .hljs-literal { color: #b26a00; }
.pyhl .hljs-params, .pyhl .hljs-variable, .pyhl .hljs-template-variable { color: #1b3a5c; }
.pyhl .hljs-operator, .pyhl .hljs-punctuation, .pyhl .hljs-symbol, .pyhl .hljs-bullet {
  color: #4a7fa8;
}

/* 输出终端：高度＝输出内容 + 1 行（--out-rows 由 fitOutTerm 按实测内容算出来），
   跟左边代码有多长无关 —— 代码写 5 行、程序只 print 了 2 行，这里就只占 3 行 */
.term {
  flex: none;
  height: calc(var(--out-rows, 2) * var(--term-lh, 24px) + var(--code-pad, 28px));
  overflow: auto;
  padding: 14px 16px;
  background: var(--term-bg);
  font-family: var(--mono);
  font-size: 14.5px;
  line-height: 24px;
  color: var(--term-ink);
  white-space: pre-wrap;
  word-break: break-all;
}
.term .ph { color: var(--term-dim); font-family: var(--sans); }
.term .ok { color: var(--ok); }
.term .errtxt { color: #cc3b2b; }
.term .hd {
  display: inline-block;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .1em;
  padding: 1px 7px;
  border-radius: 4px;
  margin-bottom: 6px;
  background: rgba(47, 128, 237, .12);
  color: var(--cyan);
}

/* turtle 绘图结果：右栏「输出」下面的一块常驻区域（第 22 轮起不再默认隐藏）——
   输出区只占「内容 + 1 行」，剩下的高度都归它，所以有没有图位置都不会跳。
   评价页（review.js）里的只读输出区还带 hidden，那条规则保留。 */
.svg-stage {
  flex: 1 1 auto;
  min-height: 132px;
  display: flex;
  flex-direction: column;
  padding: 8px 12px 14px;
  background: #f4f9ff;
  border-top: 1px solid var(--line-2);
}
.svg-stage[hidden] { display: none; }
.svg-stage .svg-cap {
  margin: 0 0 8px;
  font-family: var(--sans);
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: .1em;
  color: #5b7a99;
}
.svg-stage .svg-body {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: auto;
}
.svg-stage .svg-body > .ph {
  color: var(--term-dim);
  font-family: var(--sans);
  font-size: 12.5px;
  text-align: center;
}
/* SVG 已经带 viewBox 与内在尺寸，这里按栏宽等比放大：
   width:100% + height:auto 让图撑满整栏，max-height 兜住过高的图；
   preserveAspectRatio="xMidYMid meet" 保证超出的部分是被「缩小」而不是被裁掉。 */
.svg-stage svg {
  display: block;
  width: 100%;
  height: auto;
  max-width: 100%;
  max-height: 380px;
  margin: 0 auto;
}

/* ------------------------------------------------- 内容内嵌的「编程题」区块
   由编辑区「＋ 编程题」按钮插入，写在 Markdown 里：
   ```pyarea
   print("Hello")
   ```
   预览区与学生端课程内容都会把它渲染成「左代码 / 右输出」两栏。 */
.pyarea {
  margin: 1.3em 0;
  border: 1px solid var(--line-2);
  border-radius: var(--r);
  background: transparent;
  box-shadow: var(--shadow-s);
  overflow: hidden;
}
/* 内容里内嵌的编程题同样是「一台显示器」：
   外框（边框/圆角/发光）交给 .screen-bezel，这里只保留纸张上的上下间距。 */
.pyarea.screen {
  margin: 1.5em 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  overflow: visible;
}
/* 显示器丝印：内嵌块比面板窄，字号略收 */
.pyarea.screen .screen-chin .label { font-size: 9.5px; letter-spacing: .14em; }
.pyarea.screen .screen-stand { width: 86px; height: 12px; }
.pyarea.screen .screen-base { width: 168px; height: 8px; margin-bottom: 6px; }
/* 标准输入行 —— 内嵌编程区与编程题面板共用。
   第 24 轮起它**长在右栏里**（输出区下方、绘图区上方），不再横在整台显示器顶上；
   没有「输入」按钮：带演示输入、或代码里出现 input( 时自动出现。
   它下面那个「完成输入」= 把填好的输入交给程序跑一遍（等价于按「运行」）。 */
.py-stdin {
  flex: none;
  padding: 11px 14px 12px;
  /* 第 50 轮：输入区挪到输出区上面了 —— 分隔线从「上边」改到「下边」，
     正好把它与下面那条「输出」标题分开。 */
  border-bottom: 1px solid var(--line-2);
  background: #f4f9ff;
}
.py-stdin[hidden] { display: none; }
.py-stdin label {
  display: block;
  font-family: var(--mono);
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: .08em;
  color: var(--ink-3);
  margin-bottom: 6px;
}
.py-stdin textarea {
  width: 100%;
  padding: 8px 12px;
  border: 1px solid var(--line);
  border-radius: 8px;
  outline: 0;
  resize: vertical;
  font-family: var(--mono);
  font-size: 14px;
  line-height: 1.6;
  color: var(--ink);
  background: #ffffff;
}
.py-stdin textarea:focus { border-color: var(--brand-2); }
/* 第 25 轮：输入区一分为二 —— 左列输入框、右列「完成输入」。
   第 27 轮：输入区改成「一行一个框」——程序要几次输入就开几个框，
   每个框各带一个「完成输入」（按钮只占两个汉字宽，文字竖排两行）。 */
.py-stdin-tip {
  font-family: var(--mono);
  font-size: 11.5px;
  color: var(--brand-3);
  margin-bottom: 6px;
}
.py-stdin-tip:empty { display: none; }
.py-stdin-list { display: flex; flex-direction: column; gap: 6px; }
.py-stdin-row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: 8px;
  align-items: stretch;
}
/* 第几个输入：等着的那个框描品牌色，交掉的框点个小勾 */
.py-stdin-no {
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 22px;
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 700;
  color: var(--ink-3);
}
.py-stdin-row textarea { min-width: 0; resize: none; overflow: hidden; }
.py-stdin-row.is-active textarea { border-color: var(--brand-2); }
.py-stdin-row.is-done .py-stdin-no { color: var(--ok); }
.py-stdin-row.is-done .py-stdin-no::after { content: '✓'; margin-left: 3px; }
.py-stdin-row .py-stdin-ok {
  align-self: stretch;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0;
  min-height: 0;
  padding: 4px 8px;
  border-radius: 9px;
  font-size: 12.5px;
  line-height: 1.35;
  letter-spacing: .06em;
  white-space: nowrap;
}
.py-stdin-row .py-stdin-ok > span { display: block; }
/* 编程器（```pydemo）：同一台显示器，但不要求学生作答 —— 只多一个灰角标 */
.pyarea.is-demo .py-demo-chip { font-family: var(--mono); font-size: 10.5px; letter-spacing: .04em; }

.pyarea .code-cols { border-top: 0; --code-pad: 24px; }
.pyarea .col-label { padding: 7px 12px; font-size: 11.5px; }
.pyarea .col-label .btn.mini { padding: 3px 10px; font-size: 11.5px; }
/* 高度交给 --code-rows × --code-lh + --code-pad 统一算，这里不再写死 */
.pyarea .codewrap .ln { width: 44px; font-size: 14.5px; padding: 12px 7px 12px 0; }
.pyarea .codepad > .hl,
.pyarea .codepad > textarea { padding: 12px 14px; }
.pyarea .term { padding: 12px 14px; font-size: 14.5px; }
.pyarea .svg-stage { padding: 8px 14px 12px; }
.pyarea .svg-stage svg { max-height: 340px; }
@media (max-width: 620px) {
  .pyarea .code-cols { grid-template-columns: 1fr; }
  .pyarea .code-cols > *:last-child { border-left: 0; border-top: 1px solid var(--line-2); }
}

/* 列标题行里的小按钮：编辑区的「＋ 选择题 / ＋ 判断题 / ＋ 编程题」，
   以及代码/输出标题行里的「重置 / 运行」，共用同一套尺寸 */
.col-label .btn.mini {
  padding: 4px 11px;
  min-height: 28px;
  font-family: var(--sans);
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0;
  text-transform: none;
  line-height: 1.5;
}
.ins-group { display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap; }
/* 三个插入按钮各带一点自己的色相，小孩一眼分得清 */
.ins-choice { border-color: rgba(47, 128, 237, .45) !important; color: #1c62c4 !important; }
.ins-choice:hover { background: rgba(47, 128, 237, .14) !important; }
.ins-judge { border-color: rgba(217, 139, 0, .45) !important; color: var(--yellow) !important; }
.ins-judge:hover { background: rgba(217, 139, 0, .13) !important; }
.ins-code { border-color: rgba(23, 160, 94, .45) !important; color: #0f8b4f !important; }
.ins-code:hover { background: rgba(23, 160, 94, .13) !important; }

/* 题目区 markdown 排版 —— 永远排在浅色纸上，所以用深色字 */
.md-body { font-size: 15px; color: var(--paper-ink-2); }
.md-body > :first-child { margin-top: 0; }
.md-body h1, .md-body h2, .md-body h3 { color: var(--paper-ink); margin: 1.4em 0 .5em; line-height: 1.3; }
.md-body h1 { font-size: 26px; }
.md-body h2 { font-size: 20px; }
.md-body h3 { font-size: 17px; }
.md-body p { margin: .7em 0; }
.md-body ul, .md-body ol { padding-left: 1.35em; margin: .7em 0; }
.md-body li { margin: .28em 0; }
.md-body blockquote {
  margin: 1em 0;
  padding: 10px 16px;
  border-left: 3px solid var(--amber);
  background: rgba(217, 139, 0, .09);
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
  color: #8a6410;
}
.md-body code {
  background: rgba(47, 128, 237, .08);
  padding: .12em .4em;
  border-radius: 5px;
  font-size: .88em;
  color: #1c62c4;
}
.md-body pre {
  background: #f4f9ff;
  border: 1px solid rgba(47, 128, 237, .20);
  border-radius: var(--r-sm);
  padding: 14px 16px;
  overflow: auto;
  font-size: 13px;
  line-height: 1.6;
}
.md-body pre code { background: none; padding: 0; color: var(--paper-ink); }
.md-body table { border-collapse: collapse; width: 100%; margin: 1em 0; font-size: 14px; }
.md-body th, .md-body td { border: 1px solid rgba(47, 128, 237, .22); padding: 7px 12px; text-align: left; }
.md-body th { background: rgba(47, 128, 237, .06); font-weight: 700; }
.md-body img { max-width: 100%; border-radius: var(--r-sm); }
.md-body a { color: var(--brand-3); }

.preview-host .md-body { font-size: 15px; }

/* ============================================================
   作答区
   选择题 / 判断题 → 作业纸；编程题 → 一台显示器
   「（边框）需要完成」标记——未完成前一直是醒目的琥珀色
   ============================================================ */

/* 需要完成的角标：未完成 = 琥珀脉冲；已完成 = 绿色对勾 */
.need-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 12px;
  border-radius: 999px;
  font-size: 12.5px;
  font-weight: 700;
  border: 1px solid transparent;
}
.need-badge.todo {
  background: rgba(217, 139, 0, .14);
  border-color: rgba(217, 139, 0, .50);
  color: var(--yellow);
  animation: needPulse 2.1s var(--ease) infinite;
}
.need-badge.todo::before {
  content: "";
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--yellow);
  box-shadow: 0 0 0 0 rgba(217, 139, 0, .60);
  animation: needDot 1.5s infinite;
}
.need-badge.done {
  background: var(--ok-tint);
  border-color: rgba(23, 160, 94, .45);
  color: #0f8b4f;
}
.need-badge.done::before { content: "✓"; font-weight: 700; }
@keyframes needPulse {
  50% { box-shadow: 0 0 0 4px rgba(217, 139, 0, .08); }
}
@keyframes needDot {
  70% { box-shadow: 0 0 0 7px rgba(217, 139, 0, 0); }
  100% { box-shadow: 0 0 0 0 rgba(217, 139, 0, 0); }
}

/* —— 作业纸 —— */
.sheet {
  position: relative;
  padding: 22px 24px 24px 64px;
  border-radius: 10px;
  color: var(--paper-ink);
  background-color: var(--paper);
  /* 横向的格线 */
  background-image: repeating-linear-gradient(
    180deg, transparent 0 33px, var(--paper-line) 33px 34px);
  background-position: 0 -12px;
  box-shadow:
    0 1px 0 rgba(255, 255, 255, .8) inset,
    0 14px 34px -20px rgba(16, 48, 79, .16);
  overflow: hidden;
}
/* 左侧红色装订边线 */
.sheet::before {
  content: "";
  position: absolute;
  top: 0; bottom: 0; left: 50px;
  width: 2px;
  background: var(--rule-red);
  pointer-events: none;
}
/* 打孔：三个小圆 */
.sheet::after {
  content: "";
  position: absolute;
  left: 18px; top: 0; bottom: 0;
  width: 16px;
  pointer-events: none;
  background-image: radial-gradient(circle at 8px 8px, rgba(47, 128, 237, .26) 0 6px, transparent 6px);
  background-size: 16px 120px;
  background-position: 0 26px;
  background-repeat: repeat-y;
}

.sheet-head {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 16px;
  padding-bottom: 10px;
  border-bottom: 2px solid var(--paper-ink);
}
.sheet-head .sheet-title {
  font-family: var(--display);
  font-size: 18px;
  font-weight: 700;
  letter-spacing: .04em;
  color: var(--paper-ink);
}
.sheet-head .spacer { flex: 1; }
.sheet-head .sheet-tip { font-size: 12.5px; color: #6b7a8c; }

/* 选项：像试卷上的选项条目 */
.opts { display: grid; gap: 10px; }
.opt {
  display: flex; align-items: flex-start; gap: 12px;
  padding: 13px 16px;
  border: 1px solid rgba(47, 128, 237, .22);
  border-radius: 10px;
  background: rgba(255, 255, 255, .92);
  cursor: pointer;
  text-align: left;
  font: inherit;
  font-size: 15px;
  color: var(--paper-ink);
  transition: border-color var(--dur-fast), background var(--dur-fast), transform var(--dur-fast) var(--ease), box-shadow var(--dur-fast);
}
.opt:hover {
  border-color: var(--brand);
  background: #fff;
  transform: translateX(3px);
  box-shadow: 0 8px 20px -14px rgba(16, 48, 79, .22);
}
.opt .k {
  flex: none;
  width: 26px; height: 26px;
  border-radius: 50%;
  display: grid; place-items: center;
  border: 2px solid rgba(47, 128, 237, .34);
  background: transparent;
  font-family: var(--mono);
  font-size: 13px;
  font-weight: 700;
  color: var(--paper-ink-2);
  transition: background var(--dur-fast), color var(--dur-fast), border-color var(--dur-fast);
}
.opt[aria-pressed="true"] {
  border-color: var(--brand);
  background: linear-gradient(180deg, #e8f4ff, #d7ecff);
  box-shadow: 0 0 0 2px rgba(47, 128, 237, .18);
}
.opt[aria-pressed="true"] .k { background: var(--brand); border-color: var(--brand); color: #fff; }
.opt.correct { border-color: #17a05e; background: #e6f8ee; }
.opt.correct .k { background: #17a05e; border-color: #17a05e; color: #fff; }
.opt.wrong { border-color: #d9422f; background: #fdeae7; }
.opt.wrong .k { background: #d9422f; border-color: #d9422f; color: #fff; }

.judge-row { display: flex; gap: 14px; flex-wrap: wrap; }
.judge-row .opt {
  flex: 1; min-width: 160px;
  justify-content: center; align-items: center;
  font-weight: 700; font-size: 17px;
  padding: 18px 16px;
}
.judge-row .opt .k { width: 32px; height: 32px; font-size: 16px; }

.answer-state {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: 13px; font-weight: 700;
  padding: 4px 11px; border-radius: 999px;
}
.answer-state.ok { background: var(--ok-tint); color: #0f8b4f; }
.answer-state.bad { background: var(--bad-tint); color: #cc3b2b; }
.answer-state.wait { background: rgba(47, 128, 237, .08); color: var(--ink-3); }

/* 点「检查」之后只落一个表情：对 ✅ / 错 ❌ —— 不再堆文字提示语 */
.qmark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  font-size: 17px;
  line-height: 1;
}
.qmark.ok { background: var(--ok-tint); box-shadow: inset 0 0 0 1px rgba(34, 208, 122, .45); }
.qmark.bad { background: var(--bad-tint); box-shadow: inset 0 0 0 1px rgba(255, 74, 61, .40); }

/* —— 显示器：编程题的整个面板就是一台老式 CRT/液晶显示器 —— */
.screen {
  position: relative;
  margin: 0;
  padding: 8px 8px 0;   /* 边距最小：只留一圈极窄的台面 */
}
/* 边框：科技蓝细实线（外侧再压一圈白 + 蓝光晕），
   让「这台显示器要你动手完成」一眼就能看出来；线要细，光晕跟着收，
   不然细线配大光晕会糊成一团 */
.screen-bezel {
  position: relative;
  border: 2px solid var(--brand);
  border-radius: 16px;
  background: var(--term-bg);
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, .55),
    0 0 0 1px rgba(255, 255, 255, .9),
    0 0 0 3px rgba(47, 128, 237, .11),
    0 0 16px -4px rgba(47, 128, 237, .40),
    0 22px 44px -26px rgba(16, 48, 79, .28);
  overflow: hidden;
}
/* 屏幕反光 —— 只留斜向那抹浅蓝扫光，让屏面有点玻璃感。
   第 33 轮：原来这里还有一层「扫描线」（repeating-linear-gradient，每 3px 一条
   5% 深蓝细线），铺在浅蓝屏面上看着像一片灰格子，很脏；现在整站（detail 页的
   编程题 / 内嵌 ```pyarea，以及在线编程两页）一律去掉。 */
.screen-bezel::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 4;
  background: linear-gradient(150deg, rgba(47, 128, 237, .05), transparent 42%);
}
/* 屏幕内侧再描一道细亮线，和外框一起构成「双层框」 */
.screen-bezel::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 5;
  border: 1px solid rgba(31, 159, 216, .6);
  border-radius: 12px;
}
/* 下巴：指示灯 + 机型丝印（边框跟着外框走，整台机器外围是一圈连续的蓝线） */
.screen-chin {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 12px;
  background: linear-gradient(180deg, #dbe9f8, #dbe9f8);
  border: 2px solid var(--brand);
  border-top: 0;
}
.screen-chin .led {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--ok);
  box-shadow: 0 0 8px 1px rgba(23, 160, 94, .55);
}
.screen-chin .led.blink { animation: pulse 1s infinite; }
.screen-chin .label {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: .18em;
  color: #5b7a99;
  text-transform: uppercase;
}
.screen-chin .spacer { flex: 1; }
.screen-chin .power {
  font-family: var(--mono);
  font-size: 11px;
  color: #7d9cba;
  white-space: nowrap;
}
/* 下巴右下角＝站名丝印（空闲态显示「精进编程」，跑起来后临时让位给「运行中 / 出错 / 通过」） */
.screen-chin .power.brand {
  font-weight: 600;
  letter-spacing: .16em;
  color: #56789a;
}
/* 底座：脖颈 + 托盘，纯 CSS */
.screen-stand {
  width: 108px;
  height: 16px;
  margin: 0 auto;
  background: linear-gradient(180deg, #cfe3f5, #bcd7ee);
  clip-path: polygon(30% 0, 70% 0, 100% 100%, 0 100%);
}
.screen-base {
  width: 210px;
  height: 10px;
  margin: 0 auto 10px;
  border: 2px solid var(--brand);
  border-top: 0;
  border-radius: 0 0 9px 9px;
  background: linear-gradient(180deg, #dbe9f8, #cfe3f5);
  box-shadow: 0 10px 20px -10px rgba(16, 48, 79, .20);
}
/* 显示器里的一切都贴在屏上，去掉面板自己的圆角与描边 */
.screen .code-cols { border-radius: 0; }
.screen .py-stdin { background: rgba(47, 128, 237, .05); }
.screen .py-stdin label { color: var(--term-dim); }

/* 屏幕内的代码/输出标题行也走终端配色 */
.screen .col-label { background: rgba(47, 128, 237, .06); }
.screen .col-label.out { background: rgba(47, 128, 237, .08); }

/* ---------------------------------------------------------- 首行操作区 */
.head-actions {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: 8px;
  flex: none;
}

/* 得分胶囊 */
.score-pill {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 4px 12px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: transparent;
  font-size: 12.5px;
  color: var(--ink-3);
  white-space: nowrap;
}
.score-pill b {
  font-family: var(--display);
  font-size: 20px;
  line-height: 1.25;
  color: var(--brand-2);
  letter-spacing: -.01em;
}
.score-pill b.none { color: var(--ink-4); font-size: 16px; }

/* 从首行展开的浮层（老师评语等） */
.head-pop {
  position: absolute;
  top: calc(100% + 12px);
  right: 0;
  z-index: 60;
  width: min(400px, calc(100vw - 28px));
  max-height: calc(100vh - 120px);
  overflow: auto;
  padding: 16px 18px;
  background: linear-gradient(180deg, #ffffff, #ffffff);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-l);
  text-align: left;
  font-size: 13.5px;
  line-height: 1.7;
  color: var(--ink-2);
  animation: pop .26s var(--ease-out);
}
.head-pop p { margin: 8px 0 0; white-space: pre-wrap; }
.head-pop b { color: var(--ink); }
#gradeBox:empty { display: none; }

.grade-panel {
  position: absolute;
  top: calc(100% + 12px);
  right: 0;
  z-index: 60;
  width: min(420px, calc(100vw - 28px));
  max-height: calc(100vh - 118px);
  overflow: auto;
  text-align: left;
  background: linear-gradient(180deg, #ffffff, #ffffff);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-l);
  padding: 18px;
  animation: pop .26s var(--ease-out);
}
.grade-panel h4 { font-size: 17px; margin-bottom: 3px; }
.grade-panel .sub { font-size: 12.5px; color: var(--ink-3); margin: 0 0 14px; }
.grade-panel .row { display: flex; gap: 10px; }
.grade-panel .row > * { flex: 1; min-width: 0; }

.rec-row { display: flex; align-items: center; gap: 10px; margin-top: 4px; flex-wrap: wrap; }
.rec-tip { font-size: 12px; color: var(--ink-3); }
.rec-tip.live { color: #cc3b2b; font-weight: 700; }

.audio-mini { display: flex; align-items: center; gap: 8px; margin-top: 9px; flex-wrap: wrap; }
.audio-mini audio { height: 34px; flex: 1; min-width: 0; }

/* ---------------------------------------------------------- 提示条 */
.toast {
  position: fixed;
  left: 50%;
  bottom: 30px;
  transform: translateX(-50%) translateY(14px);
  z-index: 120;
  padding: 12px 22px;
  border-radius: 999px;
  background: linear-gradient(180deg, #ffffff, #ffffff);
  border: 1px solid var(--line);
  color: var(--ink);
  font-size: 14px;
  font-weight: 600;
  box-shadow: var(--shadow-l);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-fast) var(--ease), transform var(--dur-base) var(--ease-out);
}
.toast.on { opacity: 1; transform: translateX(-50%); }
.toast.bad { background: linear-gradient(180deg, #fdeceb, #fbdad6); border-color: rgba(217, 66, 47, .45); }
.toast.good { background: linear-gradient(180deg, #e9f8f0, #d4f0e2); border-color: rgba(23, 160, 94, .45); }

/* ---------------------------------------------------------- 骨架屏 */
.skel { padding: 20px; }
.skel i {
  display: block; height: 15px; border-radius: 6px; margin-bottom: 11px;
  background: linear-gradient(90deg, rgba(47, 128, 237, .16) 25%, rgba(47, 128, 237, .18) 50%, rgba(47, 128, 237, .16) 75%);
  background-size: 300% 100%;
  animation: shim 1.3s infinite linear;
}
@keyframes shim { to { background-position: -300% 0; } }

/* ---------------------------------------------------------- 响应式 */
@media (max-width: 1100px) {
  /* 级别药丸挤不下就换行，顶栏自动变高（sticky 仍然贴顶） */
  .topbar { flex-wrap: wrap; row-gap: 6px; }
  .levels { order: 3; width: 100%; }
}
@media (max-width: 980px) {
  /* 教师端「编辑器 + 预览」在窄屏改为上下排列 */
  .cols2 { grid-template-columns: 1fr; }
  .cols2 > *:first-child { border-right: 0; border-bottom: 1px solid var(--line-2); }
  .editor-host, .preview-host { height: 62vh; }
  /* 代码区保持左右两列：左代码 / 右结果，任何时候都不堆叠 */
  .code-cols { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  .codewrap .ln { width: 38px; padding-right: 6px; }
  /* 标题行右侧挂着「重置 / 运行」按钮，字号不宜比按钮小太多 */
  .code-cols .col-label { padding: 7px 10px; letter-spacing: .06em; }
  .term { padding: 12px; font-size: 12.5px; }
  .sheet { padding: 18px 16px 18px 54px; }
  .sheet::before { left: 42px; }
}
@media (max-width: 900px) {
  .detail-top { flex-wrap: wrap; row-gap: 7px; }
  .crumb { flex: 1 1 auto; }
  .head-actions { width: 100%; justify-content: flex-start; order: 5; }
  .grade-panel, .head-pop { left: 0; right: auto; }
}
@media (max-width: 620px) {
  /* 手机竖屏才允许代码区上下堆叠 */
  .code-cols { grid-template-columns: 1fr; }
  .code-cols > *:last-child { border-left: 0; border-top: 1px solid var(--line-2); }
  .level-btn { font-size: 13px; padding: 4px 11px; min-height: 31px; }
  .sheet { padding: 16px 14px 16px 44px; }
  .sheet::before { left: 34px; }
  .sheet::after { display: none; }
}
@media (max-width: 760px) {
  table.courses thead { display: none; }
  table.courses tr {
    display: grid;
    grid-template-columns: 42px 1fr auto;
    gap: 2px 10px;
    padding: 12px 14px;
    border-bottom: 1px solid var(--line-2);
  }
  table.courses td { padding: 0; border: 0; }
  /* 手机上的每行排成一张小卡片：
     [序号] 名称 得分
           完成时间 评价
           ──── 查看 ──── */
  td.c-seq { grid-column: 1; grid-row: 1 / 3; }
  td.c-name { grid-column: 2; grid-row: 1; }
  td.c-score { grid-column: 3; grid-row: 1; text-align: right; width: auto; }
  td.c-time { grid-column: 2; grid-row: 2; }
  td.c-audio { grid-column: 3; grid-row: 2; width: auto; text-align: right; }
  td.c-view { grid-column: 2 / 4; grid-row: 3; width: auto; text-align: right; padding-top: 6px; }
  /* 教师列表只有四格：序号 / 名称 / 提交份数 / 删除 ✕ */
  td.c-cnt { grid-column: 3; grid-row: 1; width: auto; text-align: right; }
  td.c-del { grid-column: 3; grid-row: 2; width: auto; text-align: right; }
  td.c-act { grid-column: 2 / 4; grid-row: 2; width: auto; text-align: right; }
  .brand-text span { display: none; }
  .brand-img { height: 32px; }
}

/* ============================================================
   教师端 · 学生作业看板（/view）
   左「未评」/ 右「已评」两列，每列各带一条筛选栏
   ============================================================ */
.hw-head { padding: 20px 0 14px; }
.hw-head .eyebrow { display: flex; margin-bottom: 10px; }
.hw-head h1 { display: inline-block; vertical-align: middle; font-size: 30px; margin-right: 12px; letter-spacing: .01em; }
.hw-head .meta { vertical-align: middle; font-size: 13px; color: var(--ink-3); }

.hw-board {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
  align-items: start;
  padding-bottom: 34px;
}
.hw-board > .hw-loading {
  grid-column: 1 / -1;
  background: linear-gradient(180deg, transparent, transparent);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--glow);
}

.hw-col {
  display: flex;
  flex-direction: column;
  min-width: 0;
  max-height: calc(100vh - 150px);
  background: linear-gradient(180deg, transparent, transparent);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--glow);
  overflow: hidden;
}

.hw-col-head {
  display: flex; align-items: center; gap: 10px;
  padding: 13px 16px;
  border-bottom: 1px solid var(--line-2);
  background: linear-gradient(180deg, rgba(47, 128, 237, .10), transparent);
}
/* 未评列偏琥珀 —— 一眼看出"这还有活要干" */
.hw-col[data-col="todo"] .hw-col-head { background: linear-gradient(180deg, rgba(217, 139, 0, .14), transparent); }
.hw-col[data-col="todo"] .hw-col-head h3 { color: var(--yellow); }
.hw-col-head h3 { font-size: 17px; }
.hw-col-head .spacer { flex: 1; }
.hw-col-head .note { font-size: 12px; color: var(--ink-3); }

/* 筛选栏：用户名 / 提交时间 / 分数 / 课程 */
.hw-filters {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, .95fr) minmax(0, .85fr) minmax(0, 1.5fr);
  gap: 8px;
  padding: 10px 14px 11px;
  border-bottom: 1px solid var(--line-2);
  background: rgba(47, 128, 237, .05);
}
.hw-filters .fld { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.hw-filters .fld > span {
  font-family: var(--mono);
  font-size: 10.5px; font-weight: 700; letter-spacing: .1em;
  color: var(--ink-3); padding-left: 2px;
}
.hw-filters input, .hw-filters select {
  width: 100%; min-width: 0;
  padding: 7px 9px;
  font: inherit; font-size: 13px;
  color: var(--ink);
  background: #ffffff;
  border: 1px solid var(--line);
  border-radius: 8px;
  transition: border-color var(--dur-fast), box-shadow var(--dur-fast);
}
.hw-filters input:focus, .hw-filters select:focus {
  outline: 0; border-color: var(--brand-2);
  box-shadow: 0 0 0 3px rgba(47, 128, 237, .16);
}

.hw-list {
  flex: 1;
  display: grid;
  gap: 10px;
  align-content: start;
  padding: 12px;
  overflow: auto;
}
.hw-empty { padding: 36px 16px; text-align: center; color: var(--ink-3); font-size: 13.5px; }

/* 作业卡片 */
.hw-card {
  padding: 12px 14px;
  background: #ffffff;
  border: 1px solid var(--line);
  border-radius: var(--r);
  box-shadow: var(--shadow-s);
  animation: rise .4s var(--ease-out) both;
  transition: border-color var(--dur-fast), box-shadow var(--dur-base), transform var(--dur-fast) var(--ease);
}
.hw-card:hover {
  border-color: var(--brand-2);
  box-shadow: 0 18px 40px -24px rgba(47, 128, 237, .34);
  transform: translateY(-2px);
}

.hw-card-top { display: flex; align-items: center; gap: 8px; }
.hw-card-top .spacer { flex: 1; }
.hw-card-top .kind { margin-right: 0; }
.hw-who { font-weight: 700; font-size: 15px; }

.hw-score { font-family: var(--display); font-weight: 700; font-size: 20px; }
.hw-score.hi { color: var(--ok); }
.hw-score.lo { color: #cc3b2b; }
.hw-score.none { font-family: var(--sans); font-size: 12.5px; font-weight: 700; color: var(--ink-4); }

.hw-course { display: flex; align-items: center; gap: 6px; margin-top: 5px; font-size: 13px; color: var(--ink-2); }
.hw-course .lv {
  font-family: var(--mono); font-size: 11px; font-weight: 700;
  color: var(--brand-2); background: var(--brand-tint);
  border: 1px solid rgba(47, 128, 237, .30);
  border-radius: 5px; padding: 1px 6px;
}
.hw-course .s { font-family: var(--mono); font-size: 12px; color: var(--ink-4); }

.hw-card-bot { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; margin-top: 10px; }
.hw-card-bot .spacer { flex: 1; }
.hw-time { font-family: var(--mono); font-size: 12px; color: var(--ink-3); }

.hw-flag {
  font-size: 11px; font-weight: 700;
  padding: 2px 8px; border-radius: 5px;
  background: rgba(47, 128, 237, .08); color: var(--ink-3);
  border: 1px solid var(--line-2);
}
.hw-flag.ok { background: var(--ok-tint); color: #0f8b4f; border-color: rgba(23, 160, 94, .35); }
.hw-flag.bad { background: var(--bad-tint); color: #cc3b2b; border-color: rgba(217, 66, 47, .35); }

/* ============================================================
   教师端 · 作业查看与评价（/review）
   ============================================================ */
.rv-grid {
  display: grid;
  /* 第 70 轮（需求 3）：左栏横着排 3 列题目卡，右栏「评分与评价」让位 ——
     宽度缩到原来的一半（原来是 minmax(330px, .92fr)，实测约 660px，现在封顶 340px）。 */
  grid-template-columns: minmax(0, 1fr) minmax(300px, 340px);
  gap: 14px;
  align-items: start;
  padding: 12px 0 30px;
}
.rv-main { display: flex; flex-direction: column; gap: 14px; min-width: 0; }
.rv-main .panel, .rv-side .panel { margin-bottom: 0; }
.rv-side { position: sticky; top: 58px; min-width: 0; }

/* 题目预览：高度自适应、不出现内部滚动条 */
.preview-host.auto { height: auto; overflow: visible; padding: 18px clamp(14px, 2vw, 26px); }
.preview-host.auto .md-editor-preview-wrapper { padding: 0; }

.rv-hint { margin: 0; font-size: 13px; color: var(--ink-3); }
.rv-hint a { color: var(--brand-2); }
.rv-ans { margin: 0; font-size: 15px; color: var(--ink-2); }
.rv-ans b {
  font-family: var(--mono); font-size: 18px;
  color: #0f8b4f; background: var(--ok-tint);
  border: 1px solid rgba(23, 160, 94, .35);
  padding: 1px 9px; border-radius: 6px;
}

/* 学生回答 / 标准答案 对照 */
.cmp { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
.cmp-cell {
  display: flex; flex-direction: column; gap: 3px;
  padding: 10px 13px;
  background: #f4f9ff;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  min-width: 0;
}
.cmp-cell > span {
  font-family: var(--mono);
  font-size: 10.5px; font-weight: 700; letter-spacing: .1em; color: var(--ink-3);
}
.cmp-cell b { font-size: 15px; font-weight: 700; overflow-wrap: anywhere; }
.cmp-cell b.ok { color: #0f8b4f; }
.cmp-cell b.bad { color: #cc3b2b; }
.cmp-cell b.none { color: var(--ink-4); font-weight: 400; }

/* 只读选项（第 100 轮需求 3）：**排成一行、与题干左对齐**
   以前是 grid 一条一条竖着排，三列布局里光选项就把卡片撑高一倍；
   现在整行流式排列 —— 一行放得下就一行，放不下自动换行，永远不会溢出卡片。
   选项里原来那两枚「学生所选 / 标准答案」小标签已经删掉了（挤得选项只剩半行），
   判断依据改回底色：蓝 = 学生所选、绿 = 标准答案、红 = 选了但不对。 */
.ro-opts { margin-top: 4px; }
.opts.ro-opts {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 2px 14px;
  /* <ul> 自带 40px 的 padding-inline-start，不清掉的话整排选项会比题干向右缩进一大截 */
  margin: 0; padding: 0; list-style: none;
}
.opts.ro-opts .opt.ro,
.opt.ro {
  cursor: default;
  width: auto; flex: 0 0 auto;
  align-items: center;
  gap: 6px;
  /* 左边不留 padding：选项里的字母圆片要和题干的第一行字严格左对齐 */
  padding: 2px 10px 2px 0;
  border: 1px solid transparent;
  border-radius: 999px;
  background: transparent;
  font-size: 14px; line-height: 1.6;
}
.opt.ro:hover { transform: none; border-color: transparent; background: transparent; box-shadow: none; }
.opt.ro .rv-optxt { flex: none; min-width: 0; }
.opts.ro-opts .opt.ro .k,
.opt.ro .k {
  width: 20px; height: 20px; border-width: 1.6px; font-size: 11.5px;
}
.opt.ro.pick { border-color: rgba(47, 128, 237, .40); background: var(--brand-tint); }
.opt.ro.pick .k { background: var(--brand); border-color: var(--brand); color: #ffffff; }
.opt.ro.key { border-color: rgba(23, 160, 94, .40); background: var(--ok-tint); }
.opt.ro.key .k { background: var(--ok); border-color: var(--ok); color: #ffffff; }
.opt.ro.pick.good { border-color: rgba(23, 160, 94, .45); background: var(--ok-tint); }
.opt.ro.pick.bad { border-color: rgba(217, 66, 47, .45); background: var(--bad-tint); }
.opt.ro.pick.bad .k { background: var(--bad); border-color: var(--bad); color: #fff; }
/* 判断题那排是大按钮（.judge-row .opt），别被上面那套"小胶囊"盖掉 */
.judge-row .opt.ro,
.judge-row .opt {
  width: auto; flex: 1 1 120px;
  padding: 18px 16px; gap: 12px;
  border-width: 2px; border-radius: 10px;
  font-size: 17px; font-weight: 700;
  justify-content: center;
}
.judge-row .opt.ro .k,
.judge-row .opt .k { width: 32px; height: 32px; font-size: 16px; border-width: 2px; }

/* 第 51 轮（需求）：review 页左侧「客观题」列表 —— 一道题一张卡，
   四段式：题目内容 → 选项（标出学生所选 / 标准答案）→ 学生的回答 / 正确答案 → 解析 */
/* 第 70 轮（需求 3）：一屏里横着排 **3 列**（错题由 JS 排到最前面）。
   窄一点就退成 2 列 / 1 列，免得选项文字挤成一竖条。 */
.rv-qs {
  list-style: none; margin: 0; padding: 0;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  /* 第 98 轮（需求 2）：行距比列距大一档 —— 同一行的卡靠得近、行与行离得远，
     再配上「整行同色」，一眼就能看出这是一行。 */
  gap: 16px 12px;
  align-items: start;
}
@media (max-width: 1560px) { .rv-qs { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 1080px) { .rv-qs { grid-template-columns: minmax(0, 1fr); } }
.rv-q {
  padding: 14px 16px 15px;
  background: rgba(255, 255, 255, .62);
  border: 1px solid var(--line);
  border-radius: var(--r);
}
/* 第 72 轮（需求 1）：四种题型各配一层淡淡的底色（单选蓝 / 多选青 / 判断橙 / 填空灰），
   一屏三列时不用逐张去看小标签就知道哪道是单选、哪道是判断。 */
.rv-q.qk-choice { background: #edf4ff; border-color: #d3e3fa; }
.rv-q.qk-multi { background: #ebf7fd; border-color: #d0e8f4; }
.rv-q.qk-judge { background: #fff4e6; border-color: #f7e1c2; }
.rv-q.qk-fill { background: #f4f1fd; border-color: #e4ddf7; }
/* 错题优先：写成不透明底色，才不会被上面那层题型色透上来搅成脏粉 */
.rv-q.is-wrong { border-color: rgba(217, 66, 47, .30); background: #fff5f3; }
/* 第 98 轮（需求 2）：**整行同色** —— 奇数行（data-band="1"）在同一题型色上压深一档，
   同一行的卡颜色完全一致，看上去就是一整条横带；换行时颜色一变，
   老师就知道「上头那一排读完了」。行号由 review.js 按实际坐标算好写进 data-band。 */
.rv-q.qk-choice[data-band="1"] { background: #cfe3fc; border-color: #aecdf3; }
.rv-q.qk-multi[data-band="1"]  { background: #c9e6f5; border-color: #a7d3e6; }
.rv-q.qk-judge[data-band="1"]  { background: #ffdfb0; border-color: #ecc793; }
.rv-q.qk-fill[data-band="1"]   { background: #ded2f7; border-color: #c8b8ec; }
/* 错题：奇数行也要压深，但不能把「这道答错了」的粉底弄丢 */
.rv-q.is-wrong[data-band="1"]  { background: #ffd6cb; border-color: rgba(217, 66, 47, .5); }
.rv-q-h { display: flex; align-items: center; gap: 8px; margin-bottom: 9px; }
.rv-q-n { font-family: var(--mono); font-size: 12px; font-weight: 700; color: var(--ink-3); }
.rv-q-stem {
  font-size: 15px; line-height: 1.72; color: var(--ink);
  margin-bottom: 10px; white-space: pre-wrap; overflow-wrap: anywhere;
}
.rv-q .ro-opts { margin-top: 0; }
.rv-q .cmp { margin-top: 10px; }
.rv-q-exp {
  margin-top: 10px; padding: 9px 12px;
  background: var(--ok-tint);
  border-left: 3px solid var(--ok);
  border-radius: var(--r-sm);
  font-size: 13.5px; line-height: 1.7; color: var(--ink-2);
  overflow-wrap: anywhere;
}
.rv-q-exp b { margin-right: 7px; color: #0f8b4f; }
.rv-q-exp.dim { background: rgba(47, 128, 237, .05); border-left-color: var(--line-2); }
.rv-q-exp.dim b { color: var(--ink-3); }

/* 只读代码块（学生代码 / 参考代码） */
.code-read {
  display: flex;
  background: var(--term-bg);
  border-top: 1px solid var(--line-2);
  max-height: 380px;
  overflow: auto;
  border-radius: 0 0 var(--r) var(--r);
}
.code-read .ln {
  width: 46px; flex: none;
  padding: 12px 8px 12px 0;
  text-align: right;
  font-family: var(--mono); font-size: 14.5px; line-height: 26px;
  color: #8aa6c0;
  background: rgba(47, 128, 237, .05);
  border-right: 1px solid rgba(47, 128, 237, .07);
  user-select: none; white-space: pre;
}
.code-read pre {
  flex: 1; min-width: 0;
  margin: 0; padding: 12px 16px;
  font-family: var(--mono); font-size: 17px; line-height: 26px;
  color: var(--term-ink); white-space: pre;
}

/* 输出区：评价页里按内容自动撑高 */
.term.auto { height: auto; min-height: 92px; max-height: 300px; }

/* 评分面板 */
.rv-grade .panel-body { padding: 16px; }
.rv-grade .row { display: flex; align-items: center; gap: 10px; margin-top: 6px; }
.rv-grade .row .spacer { flex: 1; }
/* 第 72 轮（需求 2）：「分数」挪到标题行右端 —— 右栏只有 300~340px，
   标签压成小字、输入框 48px、四个快捷分排成 2×2 的小键盘，一行放得下 */
.rv-grade-h { flex-wrap: nowrap; gap: 8px; }
.rv-grade-h h3 { flex: none; white-space: nowrap; }
.score-inline { display: flex; align-items: center; gap: 6px; margin-left: auto; }
.score-inline .sc-lab {
  flex: none; font-size: 11px; font-weight: 700; letter-spacing: .04em;
  color: var(--ink-3);
}
/* ⚠️ 这个 input 已经不在 .field 里了 —— 主题那套边框/字号不会再自动生效，
   必须自己写全（不然拿到的是浏览器默认的 2px 灰框，占位符还被切掉半个字） */
.score-inline input {
  width: 68px; height: 24px; flex: none; padding: 2px 5px;
  font-size: 12.5px; font-family: var(--mono); text-align: center;
  color: var(--ink); background: #ffffff; border: 1px solid var(--line);
  border-radius: 5px;
}
.score-inline input::placeholder { color: var(--ink-4); }
.score-inline input:focus {
  outline: 0; border-color: var(--brand-2);
  box-shadow: 0 0 0 2px rgba(47, 128, 237, .16);
}
.score-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 3px; flex: none; }
/* ⚠️ .layui-btn 带着 min-height:38px —— 不复位的话两行小键就是 79px，标题行被撑到 98px */
.score-grid .quick {
  padding: 0 4px; min-width: 0; min-height: 0; height: 18px;
  line-height: 1; font-size: 10.5px; font-family: var(--mono);
  color: var(--brand-3); background: rgba(47, 128, 237, .09);
  border-color: rgba(47, 128, 237, .22); border-radius: 4px;
}
.score-grid .quick:hover { background: rgba(47, 128, 237, .18); }

/* 第 71 轮（需求 1）：右栏「各题正确率」——
   第 86 轮（需求 2）改成「得分」块：标题右边是自动算出来的得分，下面一行小字说明
   「4分/题，共96分」；单选 / 判断的 12 道**一行排满**（竖排小块：题号在上、正确率在下）。
   右栏只有 ~327px，12 列平均 25px —— 字号压到 9/9.5px，并且每格 overflow:hidden，
   少了这一句超宽的「100%」会溢出糊到隔壁格。 */
#rvStats:not(:empty) { margin-bottom: 12px; }
.rv-stat .panel-body { padding: 8px 9px 10px; }
.rv-stat-h { align-items: flex-start; gap: 8px; }
.rv-stat-h h3 { flex: none; white-space: nowrap; }
.rv-score { display: flex; flex-direction: column; align-items: flex-end; line-height: 1.1; margin-top: -2px; }
.rv-score .num { display: flex; align-items: baseline; gap: 3px; }
.rv-score .num b {
  font-family: var(--mono); font-size: 21px; font-weight: 800; color: var(--brand-3);
  letter-spacing: -.02em;
}
.rv-score .num i { font-family: var(--mono); font-style: normal; font-size: 11px; color: var(--ink-4); }
.rv-score .note {
  font-style: normal; font-size: 9.5px; color: var(--ink-4);
  white-space: nowrap; letter-spacing: .01em;
}
.rv-score .note::before { content: ''; }

.rate-sec { display: flex; align-items: baseline; gap: 6px; margin: 9px 0 4px; }
.rate-sec:first-of-type { margin-top: 2px; }
.rate-sec b { font-size: 12px; color: var(--ink-2); }
.rate-sec .rate-cnt { margin-left: auto; font-size: 10.5px; color: var(--ink-4); white-space: nowrap; }
.rate-avg { font-family: var(--mono); font-weight: 700; font-size: 12px; }

/* 一行 12 格；每格竖排「题号 / 正确率」。
   ⚠️ 右栏 320px 时每格被压到 25px（minmax 会自己缩），窄屏右栏变整宽后
   每格最多 44px —— 不封顶就是 12 个 86px 的大方块，看着像漏了内容。 */
.rate-row {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 44px));
  gap: 2px;
}
.rate-row .rc {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 1px; min-width: 0; padding: 2px 0 3px;
  border: 1px solid rgba(16, 48, 79, .10);
  border-radius: 4px;
  background: #ffffff;
  overflow: hidden;
}
.rate-row .rc.is-empty { background: #f7fafd; border-style: dashed; border-color: rgba(16, 48, 79, .07); }
.rate-row .rc .k {
  font-family: var(--mono); font-style: normal;
  font-size: 9px; font-weight: 600; color: var(--ink-4); line-height: 1;
}
.rate-row .rc .v {
  font-family: var(--mono); font-weight: 700; font-size: 9.5px; line-height: 1.15;
  letter-spacing: -.02em; white-space: nowrap;
}
.rate-row .rc .v.hi { color: #1e7a52; }
.rate-row .rc .v.mid { color: #b96a00; }
.rate-row .rc .v.lo { color: #b3261e; }
.rate-avg.hi { color: #1e7a52; }
.rate-avg.mid { color: #b96a00; }
.rate-avg.lo { color: #b3261e; }

/* 第 90 轮（需求 3）：顶栏那两枚「上一位 / 下一位」按钮删了，
   .stu-nav 那一套样式也一起撤掉（翻页改走键盘 ← / →）。 */
/* ⚠️ .btn 是 display:inline-flex，会盖掉浏览器默认的 [hidden]{display:none} ——
   「上一份 / 下一位」这类按钮全靠 hidden 藏着，不补这一句就永远露着。 */
.btn[hidden] { display: none !important; }
.audio-mini .lbl {
  font-family: var(--mono);
  font-size: 10.5px; font-weight: 700; letter-spacing: .1em;
  color: var(--ink-3); white-space: nowrap;
}

.rv-ref {
  width: 100%; padding: 11px 13px;
  font-family: var(--mono); font-size: 14px; line-height: 1.7;
  color: var(--term-ink); background: var(--term-bg);
  border: 1px solid var(--line); border-radius: var(--r-sm);
  resize: vertical;
}
.rv-ref:focus { outline: 0; border-color: var(--brand-2); box-shadow: 0 0 0 3px rgba(47, 128, 237, .18); }

/* 窄屏：看板与评价页都退回单列 */
@media (max-width: 1200px) {
  .hw-filters { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
}
@media (max-width: 1100px) {
  .rv-grid { grid-template-columns: 1fr; }
  .rv-side { position: static; }
}
@media (max-width: 1000px) {
  .hw-board { grid-template-columns: 1fr; }
  .hw-col { max-height: none; }
  .hw-list { overflow: visible; }
}
@media (max-width: 620px) {
  .cmp { grid-template-columns: 1fr; }
  .hw-card-bot .btn { width: 100%; }
}

/* ---------------------------------------------------------- 动效降级
   所有动画都只是"锦上添花"，关掉之后布局与状态提示必须完全照旧可用 */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
}

/* ============================================================
   B 站视频（```bilibili 围栏）
   和编程块 / 题目卡同一套打法：预览里的围栏块整块换成我们自己的 DOM，
   一块 16:9 播放器 + 一条说明栏。站点里不落视频文件，全是 B 站官方播放器。
   ============================================================ */
.bili {
  margin: 1.3em 0;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--paper);
  overflow: hidden;
}
.bili-frame {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  background: #0b1420;
}
.bili-frame > iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
}
.bili-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  border-top: 1px solid var(--line-2);
}
.bili-tag {
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .12em;
  color: #fff;
  background: #fb7299;            /* B 站粉 */
  border-radius: 4px;
  padding: 2px 6px;
}
.bili-id { font-family: var(--mono); font-size: 12.5px; color: var(--ink-2); }
.bili-open { font-size: 12.5px; color: var(--brand); text-decoration: none; }
.bili-open:hover { text-decoration: underline; }

/* 编辑区里那条「粘贴链接」的小输入条（点＋B站视频才出现） */
.bili-pick {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0 0 10px;
  padding: 8px 10px;
  border: 1px dashed var(--line);
  border-radius: var(--r-s);
  background: rgba(47, 128, 237, .05);
}
.bili-pick[hidden] { display: none; }
.bili-pick-lbl { font-size: 12.5px; font-weight: 700; color: var(--ink-2); flex: none; }
.bili-pick input {
  flex: 1;
  min-width: 0;
  height: 32px;
  padding: 0 10px;
  font-family: var(--mono);
  font-size: 13px;
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-s);
  outline: 0;
}
.bili-pick input:focus { border-color: var(--brand); }

/* ============================================================
   题目卡（md-editor-rt 扩展）
   单选题 / 多选题 / 判断题 / 填空题 —— 一律**上下结构**：
   上面是题型标签与题干，下面是选项（作答区），最底下一块是答案与解析。
   每张卡占页面宽度 **30%**，挨着出现的卡片会被收进同一个 .quiz-deck，
   一排正好三张，题目之间用虚线分开（窄屏自动退回两张 / 一张）。
   默认「答案模式」把答案与解析亮出来；切到「交互作答」就藏起来让学生点
   ============================================================ */

.quiz {
  margin: 1.3em 0;
  border: 1px solid var(--line);
  border-radius: var(--r);
  /* 纸面用浅蓝，其余不填充 */
  background: var(--paper);
  overflow: hidden;
}

/* 「这道题你还没作答」：给漏掉的那张卡描一圈琥珀色。
   只动描边与光晕、绝不用 opacity —— 从透明起跑的动画会被无头截图拍到半透明，
   看着像内容没渲染出来（表格行入场踩过同一个坑，见 rowRise）。 */
.quiz.miss {
  border-color: var(--yellow);
  animation: missPulse 1.25s ease-in-out infinite;
}
@keyframes missPulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(217, 139, 0, 0); }
  50%      { box-shadow: 0 0 0 4px rgba(217, 139, 0, .16); }
}

/* 题目卡：**一排三道，每张占页面宽度 30%**；卡内改成上下结构
   （上面题干、下面选项、最下面答案与解析）。
   卡片是「排」(.quiz-deck) 的格子，只有挨着出现的卡片才会被收进同一排。
   题目之间**不画任何分隔线** —— 间距本身就是分界，虚线会显得页面很碎。 */
.quiz-deck {
  display: grid;
  grid-template-columns: repeat(3, 30%);   /* 3×30% + 2×5% = 100%，横向刚好铺满 */
  column-gap: 5%;
  row-gap: 24px;
  margin: 1.3em 0;
  align-items: stretch;                    /* 同一排的卡片等高 */
}
.quiz-deck > .quiz {
  position: relative;
  margin: 0;
}

.quiz-grid { display: grid; grid-template-columns: minmax(0, 1fr); }
.quiz-grid > * { min-width: 0; }

/* 上半：题型标签 + 题干 */
.quiz-left {
  padding: 14px 16px 10px;
  border-bottom: 1px dashed var(--line-2);
}
.quiz-meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 8px; }
.quiz-kind {
  display: inline-block;
  font-size: 11px;
  font-weight: 700;
  padding: 2px 9px;
  border-radius: 5px;
  letter-spacing: .04em;
  border: 1px solid transparent;
}
.k-single { background: var(--brand-tint); color: var(--brand-3); border-color: rgba(47, 128, 237, .34); }
.k-multi { background: rgba(31, 159, 216, .12); color: #0f6f9c; border-color: rgba(31, 159, 216, .34); }
.k-judge { background: var(--amber-tint); color: var(--amber); border-color: rgba(217, 139, 0, .34); }
.k-fill { background: var(--ok-tint); color: #0f8b4f; border-color: rgba(23, 160, 94, .34); }
.quiz-tagline {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: .06em;
  color: var(--ink-4);
}

.quiz-stem { font-size: 15px; font-weight: 600; color: var(--ink); line-height: 1.7; }
.quiz-stem p { margin: 0; }

/* 答案 / 解析：只有 .reveal 时才出现 */
.quiz-key, .quiz-explain { display: none; }
.quiz-key {
  margin-top: 10px;
  font-size: 13.5px;
  color: var(--ok);
}
.quiz-key b {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: .08em;
  color: var(--ink-3);
  margin-right: 6px;
  font-weight: 700;
}
.quiz-key i {
  font-style: normal;
  font-weight: 700;
  padding: 1px 8px;
  border-radius: 5px;
  background: var(--ok-tint);
  border: 1px solid rgba(23, 160, 94, .35);
}
.quiz-explain {
  margin-top: 8px;
  padding: 8px 12px;
  border-radius: var(--r-sm);
  background: rgba(47, 128, 237, .07);
  border-left: 3px solid var(--brand);
  font-size: 13px;
  line-height: 1.7;
  color: var(--ink-2);
}
.quiz-explain b { color: var(--brand-3); margin-right: 4px; }
.quiz.reveal .quiz-key, .quiz.reveal .quiz-explain { display: block; }

/* 下半：选项 / 作答区，最底下是答案与解析 */
.quiz-right { padding: 10px 16px 14px; }
/* 答案与解析单独收一块，和上面的选项用虚线隔开 */
.quiz-foot { margin-top: 12px; padding-top: 10px; border-top: 1px dashed var(--line-2); }
.quiz-foot > :first-child { margin-top: 0; }
.quiz:not(.reveal) .quiz-foot { display: none; }   /* 学生作答时整块不出现 */

.qopts { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
.qopt {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 10px 13px;
  border: 1px solid var(--line-2);
  border-radius: 10px;
  background: rgba(255, 255, 255, .72);
  font-size: 14.5px;
  color: var(--ink);
  transition: border-color var(--dur-fast), background var(--dur-fast), transform var(--dur-fast) var(--ease);
}
.qopt .k {
  flex: none;
  width: 24px; height: 24px;
  border-radius: 50%;
  display: grid; place-items: center;
  border: 2px solid var(--line);
  font-family: var(--mono);
  font-size: 12.5px;
  font-weight: 700;
  color: var(--ink-3);
}
.quiz.interactive .qopt { cursor: pointer; }
.quiz.interactive .qopt:hover { border-color: var(--brand); transform: translateX(3px); }
/* 答案模式：把正确项标绿 */
.quiz.reveal .qopt.key { border-color: var(--ok); background: var(--ok-tint); }
.quiz.reveal .qopt.key .k { background: var(--ok); border-color: var(--ok); color: #fff; }
/* 学生作答后的即时判定 */
.qopt.good { border-color: var(--ok); background: var(--ok-tint); }
.qopt.good .k { background: var(--ok); border-color: var(--ok); color: #fff; }
.qopt.bad { border-color: var(--bad); background: var(--bad-tint); }
.qopt.bad .k { background: var(--bad); border-color: var(--bad); color: #fff; }

/* 填空题的输入区 */
.qfill { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.qfill input {
  flex: 1;
  min-width: 0;
  padding: 9px 12px;
  font: inherit;
  font-size: 14.5px;
  color: var(--ink);
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 10px;
}
.qfill input:focus { outline: 0; border-color: var(--brand-2); box-shadow: 0 0 0 3px rgba(47, 128, 237, .18); }
.qfill .qblank-tip { font-size: 12.5px; color: var(--ink-3); margin-top: 8px; flex-basis: 100%; }

/* 交错重绘时避免闪烁 */
.quiz { animation: rise .34s var(--ease-out) both; }

/* 窄屏：排里的卡片退回两张 / 一张，分隔线的 nth-child 也跟着换一套 */
@media (max-width: 1200px) {
  .quiz-deck { grid-template-columns: repeat(2, 47.5%); }   /* 2×47.5% + 5% = 100% */
}
@media (max-width: 700px) {
  .quiz-deck { grid-template-columns: 1fr; }
}

/* ============================================================
   编辑区扩展按钮组：单选 / 多选 / 判断 / 填空 / 交互作答 / 导入题库 / 导出PDF
   ============================================================ */
.ins-group { display: inline-flex; align-items: center; gap: 5px; flex-wrap: wrap; }
.ins-group .btn.mini {
  padding: 4px 9px;
  min-height: 27px;
  font-family: var(--sans);
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0;
  text-transform: none;
  line-height: 1.5;
}
.ins-single { border-color: rgba(47, 128, 237, .5) !important; color: var(--brand-3) !important; }
.ins-single:hover { background: rgba(47, 128, 237, .12) !important; }
.ins-multi { border-color: rgba(31, 159, 216, .5) !important; color: #0f6f9c !important; }
.ins-multi:hover { background: rgba(31, 159, 216, .12) !important; }
.ins-judge { border-color: rgba(217, 139, 0, .5) !important; color: var(--amber) !important; }
.ins-judge:hover { background: rgba(217, 139, 0, .12) !important; }
.ins-fill { border-color: rgba(23, 160, 94, .5) !important; color: #0f8b4f !important; }
.ins-fill:hover { background: rgba(23, 160, 94, .12) !important; }
.ins-code { border-color: rgba(47, 128, 237, .34) !important; color: var(--ink-2) !important; }
.ins-code:hover { background: rgba(47, 128, 237, .08) !important; }
/* 宽弹窗（答题报告等） */
.dialog.wide { width: min(720px, 100%); }

/* ---------------------------------------------------------- 打印
   不再另建打印稿（导出 PDF 按钮已按需求移除），直接把当前页面打出来：
   藏掉交互控件，题目卡的答案与解析亮着，方便打成纸质练习。 */
@media print {
  @page { margin: 14mm 12mm; }
  html, body { background: #fff !important; }
  .topbar, .rail, .toc-mask, .head-actions, .ins-group, .col-label,
  .editor-host, .mask, .audio-play, .spk, .need-badge, #stdinRow { display: none !important; }
  .panel, .quiz, .sheet, .screen-bezel, .codewrap, .term { box-shadow: none !important; }
  .panel::before, .panel::after { display: none !important; }
  .quiz, .sheet { background: #fff !important; break-inside: avoid; page-break-inside: avoid; }
  .quiz-key, .quiz-explain { display: block !important; }
  .quiz-foot { display: block !important; }   /* 打印稿一律带上答案与解析 */
  .qopt { background: #fff !important; }
  .cols2 { display: block !important; }
  .preview-host { height: auto !important; overflow: visible !important; background: #fff !important; padding: 0 !important; }
}

/* 题目卡里的零碎：多选选中态 / 检查按钮间距 / 判定条 */
.qopt.sel { border-color: var(--brand); background: var(--brand-tint); }
.qopt.sel .k { background: var(--brand); border-color: var(--brand); color: #fff; }
.quiz-right .qcheck { margin-top: 10px; }
.qverdict { margin-top: 10px; }
.qverdict:empty { display: none; }
.qin.good { border-color: var(--ok) !important; }
.qin.bad { border-color: var(--bad) !important; }

/* ============================================================
   答题报告弹窗
   学生每次「保存提交」后自动弹出；课程列表的「查看」链接也走它
   ============================================================ */
.dialog.rep {
  width: min(760px, 100%);
  max-height: min(86vh, 900px);
  display: flex;
  flex-direction: column;
  padding: 0;
  overflow: hidden;
}
.rep-head {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 15px 18px;
  border-bottom: 1px solid var(--line-2);
}
.rep-head h3 { font-size: 20px; margin: 0; }
.rep-sub { font-size: 12.5px; color: var(--ink-3); }
.rep-x {
  width: 28px; height: 28px; border-radius: 8px; cursor: pointer;
  border: 1px solid var(--line); background: transparent; color: var(--ink-3); font-size: 13px;
  transition: var(--dur-fast) var(--ease);
}
.rep-x:hover { border-color: var(--brand); color: var(--brand); }

.rep-body { flex: 1; padding: 14px 18px 2px; overflow: auto; }
.rep-stats { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 15px; }
.rep-chip {
  display: flex; flex-direction: column; gap: 2px;
  padding: 7px 12px; border: 1px solid var(--line); border-radius: 10px;
}
.rep-chip i { font-style: normal; font-size: 11px; color: var(--ink-4); }
.rep-chip b { font-family: var(--display); font-size: 15px; color: var(--ink); }
.rep-chip.ok { border-color: rgba(23, 160, 94, .45); }
.rep-chip.ok b { color: var(--ok); }
.rep-chip.bad { border-color: rgba(217, 66, 47, .45); }
.rep-chip.bad b { color: var(--bad); }
.rep-chip.dim b { color: var(--ink-3); }

.rep-sec { margin-bottom: 16px; }
.rep-sec > h4 { margin: 0 0 8px; font-size: 13px; letter-spacing: .06em; color: var(--ink-3); }
.rep-none {
  padding: 12px 14px; border: 1px dashed var(--line);
  border-radius: 10px; color: var(--ink-3); font-size: 13px;
}

/* 选项逐条对照 */
.rep-opts { display: grid; gap: 6px; list-style: none; margin: 0; padding: 0; }
.rep-opt {
  display: flex; align-items: center; gap: 9px;
  padding: 8px 12px; border: 1px solid var(--line-2); border-radius: 10px; font-size: 14px;
}
.rep-opt .k {
  flex: none; width: 22px; height: 22px; border-radius: 50%;
  display: grid; place-items: center;
  border: 1px solid var(--line); font-family: var(--mono); font-size: 12px; color: var(--ink-3);
}
.rep-opt .t { flex: 1; }
.rep-opt.key { border-color: rgba(23, 160, 94, .55); background: var(--ok-tint); }
.rep-opt.key .k { border-color: var(--ok); background: var(--ok); color: #fff; }
.rep-opt.wrong { border-color: rgba(217, 66, 47, .55); background: var(--bad-tint); }
.rep-opt.wrong .k { border-color: var(--bad); background: var(--bad); color: #fff; }
.rep-tag { padding: 2px 7px; border-radius: 999px; border: 1px solid var(--line); font-size: 11px; color: var(--ink-3); }
.rep-tag.mine { border-color: rgba(47, 128, 237, .5); color: var(--brand-3); }
.rep-tag.key { border-color: rgba(23, 160, 94, .5); color: #0f8b4f; }
.rep-mark { font-size: 14px; font-weight: 700; }
.rep-mark.ok { color: var(--ok); }
.rep-mark.bad { color: var(--bad); }

/* 编程题：你的代码 / 参考代码 并排 */
.rep-codes { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
.rep-code { border: 1px solid var(--line); border-radius: 10px; overflow: hidden; }
.rep-code.dim { opacity: .6; }
.rep-code-h {
  padding: 6px 10px; border-bottom: 1px solid var(--line-2);
  background: rgba(47, 128, 237, .05); font-size: 11.5px; color: var(--ink-3);
}
.rep-code pre {
  margin: 0; padding: 10px 12px; max-height: 240px; overflow: auto;
  font-family: var(--mono); font-size: 12.5px; line-height: 1.6; color: var(--ink-2);
  white-space: pre-wrap; word-break: break-word;
}

/* 内容里嵌的题目：题干 + 正确答案 + 解析 */
.rep-qs { display: grid; gap: 8px; list-style: none; margin: 0; padding: 0; }
.rep-q { padding: 10px 12px; border: 1px solid var(--line-2); border-radius: 10px; }
.rep-q-h { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
.rep-q-n { font-family: var(--mono); font-size: 11.5px; color: var(--ink-4); }
.rep-q-stem { font-size: 14px; color: var(--ink); }
.rep-q-key { margin-top: 6px; font-size: 13px; color: var(--ok); }
.rep-q-key b { margin-left: 6px; }
.rep-q-exp {
  margin-top: 6px; padding: 7px 10px; border-radius: 8px;
  background: rgba(47, 128, 237, .06); font-size: 12.5px; color: var(--ink-2);
}
.rep-q-exp b { margin-right: 6px; color: var(--brand-3); }

/* 老师评价 */
.rep-fb {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 12px; border: 1px solid var(--line-2); border-radius: 10px;
  font-size: 13.5px; color: var(--ink-2);
}
.rep-fb + .rep-fb { margin-top: 8px; }
.rep-fb b { flex: none; color: var(--brand-3); }

.rep-foot {
  display: flex; align-items: center; gap: 9px; flex-wrap: wrap;
  padding: 12px 18px; border-top: 1px solid var(--line-2);
}
.rep-tip { font-size: 11.5px; color: var(--ink-4); }

@media (max-width: 620px) {
  .rep-codes { grid-template-columns: 1fr; }
  .rep-head h3 { font-size: 18px; }
}

/* ============================================================
   第 30 轮 · 在线编程三页（/code · /codes · /list）
   共同语汇：只描边的容器 + 一条细蓝分隔线 + 字重拉层级，
   不靠背景纹理。顶栏一律 .topbar（和首页同一根 LOGO 线）。
   ============================================================ */

/* 顶栏里的「在线编程」入口：只描边，鼠标过来才亮 */
#btnCode { font-family: var(--display); letter-spacing: .02em; }

/* 可点用户名：chip 长得像标签，但其实是进成长记录的入口 */
.chip.link {
  cursor: pointer;
  text-decoration: none;
  border-color: rgba(47, 128, 237, .42);
  color: var(--brand-3);
  background: var(--brand-tint);
  transition: background var(--dur-fast), border-color var(--dur-fast), transform var(--dur-fast) var(--ease);
}
.chip.link:hover { background: rgba(47, 128, 237, .18); border-color: var(--brand); }
.chip.link:active { transform: translateY(1px); }
.chip.link::after { content: "›"; margin-left: 2px; opacity: .7; font-weight: 700; }

/* 顶栏「实时」小灯 */
.live-dot {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--mono); font-size: 11px; letter-spacing: .16em;
  text-transform: uppercase; color: var(--ink-3);
}
.live-dot i {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--ok);
  box-shadow: 0 0 0 3px var(--ok-tint);
  animation: livePulse 2s var(--ease) infinite;
}
@keyframes livePulse {
  0%, 100% { opacity: 1; }
  50% { opacity: .35; }
}

/* 面板头：三页共用的小标题条 */
.pane-head {
  display: flex; align-items: center; gap: 10px;
  padding: 12px clamp(12px, 1.6vw, 18px);
  border-bottom: 1px solid var(--line-2);
}
.pane-eyebrow {
  display: inline-flex; align-items: center; gap: 8px;
  font-family: var(--mono); font-size: 11.5px; font-weight: 700;
  letter-spacing: .18em; text-transform: uppercase; color: var(--brand);
}
/* 第 56 轮：分区标题前的一小条活力橙 —— 全站统一的「章节记号」 */
.pane-eyebrow::before {
  content: ""; width: 4px; height: 13px; border-radius: 2px; flex: none;
  background: linear-gradient(180deg, var(--orange-2), var(--orange));
}
.pane-meta { font-size: 13px; color: var(--ink-3); }
.pane-time { font-size: 12px; color: var(--ink-4); font-family: var(--mono); }
.pane-head .spacer { flex: 1; }

/* 屏蔽页（非老师打开 /codes，或未登录打开 /list） */
.codes-guard, .empty-box {
  grid-column: 1 / -1;
  margin: clamp(30px, 8vh, 90px) auto;
  max-width: 520px; text-align: center;
  padding: 30px 26px;
  border: 1px dashed var(--line); border-radius: var(--r-lg);
  color: var(--ink-2);
}
.codes-guard b, .empty-box b { display: block; font-family: var(--display); font-size: 20px; margin-bottom: 8px; }
.codes-guard p, .empty-box p { margin: 4px 0; font-size: 13.5px; color: var(--ink-3); }
.codes-guard code { padding: 1px 6px; border: 1px solid var(--line-2); border-radius: 5px; font-size: 12.5px; }

/* ---------------------------------------------------------- /code 页 */
.page-code { display: flex; flex-direction: column; min-height: 100vh; }

.code-main {
  flex: 1;
  display: grid;
  /* 左栏宽度走变量：点「题目」把手收起时 JS 把 --task-w 置 0，
     整列塌成 0，右侧的编程题就吃满整个主区（第 34 轮）。 */
  --task-w: 380px;
  grid-template-columns: minmax(0, var(--task-w)) minmax(0, 1fr);
  gap: 0;
  width: min(1840px, 100% - clamp(10px, 1.4vw, 22px));
  margin: 0 auto;
  padding: 16px 0 34px;
  align-items: start;
  position: relative;
}
.code-main.task-off { --task-w: 0px; }

/* 左：题目 */
.code-task {
  position: relative;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--glow);
  margin-right: 16px;
  overflow: hidden;
  transition: margin-right var(--dur-base) var(--ease), opacity var(--dur-base), transform var(--dur-base) var(--ease);
}
.code-task.hide {
  width: 0; margin-right: 0;
  opacity: 0; transform: translateX(-12px);
  pointer-events: none;
  border-width: 0;
}

/* 「题目」把手：一条很窄的竖排小标签，正好骑在题目区的右边框上（不占内容区）。
   收起后题目区塌成 0，把手自动挪到主区最左边，随时能再点开。 */
.task-toggle {
  position: absolute;
  top: 30px;
  left: calc(var(--task-w) - 28px);   /* 380-16(间距)=364 是边框位置，按钮 24 宽 → 居中压线 */
  width: 24px; height: 58px;
  padding: 0;
  writing-mode: vertical-rl;
  font-family: var(--sans); font-size: 11px; font-weight: 600;
  letter-spacing: .1em; line-height: 1;
  color: var(--ink-3);
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: 8px;
  box-shadow: var(--glow);
  cursor: pointer;
  z-index: 6;
  transition: left var(--dur-base) var(--ease), color var(--dur), border-color var(--dur), background var(--dur);
}
.task-toggle:hover { color: var(--brand); border-color: var(--brand-2); background: var(--brand-tint); }
.task-toggle:focus-visible { outline: 2px solid var(--brand-2); outline-offset: 1px; }
.code-main.task-off .task-toggle { left: 2px; }

/* 题目里的图片：点一下看大图 */
.task-body .task-img { cursor: zoom-in; }
.imgbox {
  position: fixed; inset: 0; z-index: 90;
  display: flex; align-items: center; justify-content: center;
  padding: 3vh 3vw;
  background: rgba(9, 32, 56, .74);
  cursor: zoom-out;
}
.imgbox[hidden] { display: none; }
.imgbox img {
  max-width: 100%; max-height: 94vh;
  border-radius: 12px; background: #fff;
  box-shadow: 0 24px 70px rgba(6, 24, 44, .45);
  cursor: zoom-out;
}
.task-head {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--line-2);
}
.task-eyebrow {
  display: inline-flex; align-items: center; gap: 8px;
  font-family: var(--mono); font-size: 11.5px; font-weight: 700;
  letter-spacing: .18em; text-transform: uppercase; color: var(--brand);
}
.task-eyebrow::before {
  content: ""; width: 4px; height: 13px; border-radius: 2px; flex: none;
  background: linear-gradient(180deg, var(--orange-2), var(--orange));
}
.task-head .spacer { flex: 1; }
.task-time { font-size: 12px; color: var(--ink-4); font-family: var(--mono); }

.task-body { padding: 14px 16px 18px; font-size: 14.5px; line-height: 1.75; }
.task-body p { margin: 0 0 10px; }
.task-body .task-gap { height: 6px; }
.task-body code {
  padding: 1px 6px; border: 1px solid var(--line-2); border-radius: 5px;
  background: var(--paper); font-size: 13px; color: var(--brand-3);
}
.task-body .task-pre {
  margin: 0 0 12px; padding: 11px 13px;
  border: 1px solid var(--line-2); border-radius: 10px;
  background: var(--paper); overflow-x: auto;
}
.task-body .task-pre code { border: 0; background: none; padding: 0; color: var(--ink-2); }
.task-body .task-img {
  display: block; max-width: 100%; height: auto;
  margin: 10px 0; border: 1px solid var(--line-2); border-radius: 10px;
}
.task-body ul { margin: 0 0 10px; padding-left: 20px; }
.task-body li { margin: 2px 0; }

.task-empty {
  padding: 26px 18px; text-align: center;
  border: 1px dashed var(--line); border-radius: var(--r);
  margin: 8px 0;
}
.task-empty b { display: block; font-family: var(--display); font-size: 17px; margin-bottom: 6px; }
.task-empty p { margin: 3px 0; font-size: 13.5px; color: var(--ink-3); }
.task-empty .dim { color: var(--ink-4); font-size: 12.5px; }

/* 老师端题目编辑器 */
.task-editor { display: flex; flex-direction: column; gap: 10px; }
.task-lab { font-size: 12px; color: var(--ink-3); letter-spacing: .02em; }
.task-input {
  width: 100%; min-height: 240px; resize: vertical;
  padding: 12px 14px;
  border: 1px solid var(--line); border-radius: var(--r);
  background: var(--paper); color: var(--ink);
  font-family: var(--sans); font-size: 14px; line-height: 1.7;
}
.task-input:focus { outline: none; border-color: var(--brand-2); box-shadow: 0 0 0 3px var(--brand-tint); }
.task-tools { display: flex; align-items: center; gap: 8px; }
.task-tools .spacer { flex: 1; }
.task-note { font-size: 12px; color: var(--ink-4); line-height: 1.6; }

/* 右：编程舞台 */
.code-stage {
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--glow);
  min-width: 0;
  overflow: hidden;
}
.stage-head {
  display: flex; align-items: center; gap: 12px;
  padding: 12px clamp(12px, 1.6vw, 18px);
  border-bottom: 1px solid var(--line-2);
}
.stage-head h2 { font-size: 17px; letter-spacing: .01em; }
.stage-head .meta { font-size: 12.5px; color: var(--ink-4); }
.stage-head .spacer { flex: 1; }
.pane-host { padding: clamp(10px, 1.6vw, 20px); }

/* 代码页里那台显示器不需要再撑满整屏 */
.page-code .pane-host .pyarea.screen,
.page-codes .pane-host .pyarea.screen { margin: 0; }

/* ---------------------------------------------------------- /codes 页 */
.page-codes { display: flex; flex-direction: column; min-height: 100vh; }

.codes-main {
  flex: 1;
  display: grid;
  grid-template-columns: minmax(0, 460px) minmax(0, 1fr);
  gap: 0;
  width: min(1840px, 100% - clamp(10px, 1.4vw, 22px));
  margin: 0 auto;
  padding: 16px 0 34px;
  align-items: start;
}
.codes-list {
  border: 1px solid var(--line); border-radius: var(--r-lg);
  box-shadow: var(--glow); margin-right: 16px; overflow: hidden;
}
.codes-stage {
  border: 1px solid var(--line); border-radius: var(--r-lg);
  box-shadow: var(--glow); min-width: 0; overflow: hidden;
}

.live-table-wrap { max-height: calc(100vh - 190px); overflow: auto; }
table.live-table { width: 100%; border-collapse: collapse; table-layout: fixed; }
table.live-table thead th {
  position: sticky; top: 0; z-index: 1;
  padding: 9px 14px; text-align: left;
  font-size: 11.5px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-4); font-weight: 700;
  background: rgba(246, 251, 255, .96);
  backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--line-2);
}
/* 第 56 轮（需求）：学生一列收窄到「4 个汉字」宽（72px ≈ 4×14px 正文）
   ⚠️ 别写成 em：表头字号只有 11.5px，table-layout:fixed 认表头那一行的宽度，
   4.4em 会被算成 50px，名字就会被挤成两行。 */
table.live-table th.c-user { width: 72px; padding: 9px 8px; }
table.live-table td { padding: 10px 14px; border-bottom: 1px solid var(--line-2); vertical-align: top; }
table.live-table td.c-user { padding: 10px 8px; }

.live-row { cursor: pointer; transition: background var(--dur-fast); }
.live-row:hover { background: rgba(47, 128, 237, .05); }
.live-row.is-picked { background: var(--brand-tint); box-shadow: inset 3px 0 0 var(--brand); }
.live-row:focus-visible { outline: 2px solid var(--brand-2); outline-offset: -2px; }
/* 第 33 轮：这一行刚被更新过 → 轻轻闪一下（只闪背景，不闪掉选中态的左边条） */
.live-row.is-fresh { animation: liveFresh .9s var(--ease); }
@keyframes liveFresh {
  0% { background: rgba(47, 128, 237, .15); }
  100% { background: transparent; }
}

.u-name { font-weight: 700; font-size: 14px; color: var(--ink); word-break: break-word; }
.u-sub { font-size: 11.5px; color: var(--ink-4); font-family: var(--mono); margin-top: 2px; }
/* 第 56 轮（需求）：姓名下方那一块 —— 原来「评分」独立一列，现在并到这里。
   学生列只有 4 个汉字宽，这块字号收一档，默认分才不至于折成两行。 */
.u-score { margin-top: 5px; font-size: 11.5px; line-height: 1.45; }
.u-score .score-mini { font-size: 12.5px; }
.u-score .u-sub { white-space: normal; }
.u-off {
  display: inline-block; margin-left: 3px; padding: 0 5px;
  font-size: 10px; font-weight: 700; line-height: 15px;
  color: var(--ink-4); background: rgba(138, 166, 192, .14);
  border: 1px solid var(--line-2); border-radius: 999px;
}

/* 第 62 轮（需求 3）：底部那层羽化淡出看着像模糊，整段去掉；超过 172px
   不再硬截断，改成块内滚动 —— 内容一字不丢，滑块收窄到最细，不抢眼。 */
.live-code {
  margin: 0; white-space: pre-wrap; word-break: break-word;
  font-family: var(--mono); font-size: 12px; line-height: 1.6;
  color: var(--ink-2);
  max-height: 172px; overflow: auto;
  scrollbar-width: thin; scrollbar-color: rgba(47, 128, 237, .30) transparent;
}
.live-code::-webkit-scrollbar { width: 5px; height: 5px; }
.live-code::-webkit-scrollbar-track { background: transparent; }
.live-code::-webkit-scrollbar-thumb {
  background: rgba(47, 128, 237, .28); border-radius: 999px;
}
.live-code::-webkit-scrollbar-thumb:hover { background: rgba(47, 128, 237, .5); }
.live-code .dim { color: var(--ink-4); font-style: italic; }

/* 第 64 轮（需求 2）：「切换」按钮 —— 浅蓝实底，四态（常态/hover/active/禁用）走 .btn 体系 */
.btn.sky {
  --bg: linear-gradient(180deg, #8ec2f8, #64a7f1);
  --fg: #ffffff;
  --bd: #3b86df;
  --bg-h: linear-gradient(180deg, #64a7f1, #4c95e9);
  --fg-h: #ffffff;
  --bd-h: #3374c6;
  --bg-a: linear-gradient(180deg, #4c95e9, #3374c6);
  --fg-a: #ffffff;
  --bd-a: #2b63ad;
  --sd: 0 6px 16px -10px rgba(76, 149, 233, .70);
}
.btn.sky:hover { --sd: 0 9px 22px -10px rgba(76, 149, 233, .80); }

/* 第 74 轮（需求 2）：「清除举手」浅绿、「清除呼叫」浅紫。
   跟「切换」的浅蓝实底并排，三个课堂动作一眼分得开；两枚都用**浅底深字**
   （不是白字实底），免得跟左边那枚橙色主按钮抢视线。四态照 .btn 的变量走。 */
.btn.mint {
  --bg: #e9f8f0; --fg: #1b7a53; --bd: #bfe7d3;
  --bg-h: #d7f2e4; --fg-h: #146141; --bd-h: #9dd8bd;
  --bg-a: #c7ebd9; --fg-a: #0f5132; --bd-a: #8acdae;
  --sd: none;
}
.btn.lilac {
  --bg: #f4eefd; --fg: #6a45bf; --bd: #dccff6;
  --bg-h: #ebe1fa; --fg-h: #5837a4; --bd-h: #c8b4f0;
  --bg-a: #e0d2f7; --fg-a: #4a2f8b; --bd-a: #b9a1e9;
  --sd: none;
}

/* ===== 第 64 轮（需求 2）：codes 页视图 2 =====
   body.codes-view2 挂上后：编程屏提到上方、学生列表挪到下方横跨整页，
   内部 3 列网格铺开。列号 / 奇偶行号由 codes.js 按 DOM 顺序标（g-c0..2 / g-alt，
   行优先：第 i 张卡片落在 i%3 列、第 floor(i/3) 行），三列分别浅蓝 / 浅绿 / 浅橙，
   同列奇偶行一亮一暗。卡片内沿用视图 1 的 td 内边距与 .live-code 的 172px 上限，
   两种视图每一行一样高。 */
body.codes-view2 .codes-main { display: flex; flex-direction: column; align-items: stretch; }
body.codes-view2 .codes-stage { order: -1; margin: 0 0 16px; }
body.codes-view2 .codes-list { margin-right: 0; }
body.codes-view2 .live-table-wrap { max-height: none; }
body.codes-view2 table.live-table thead { display: none; }
body.codes-view2 #liveBody {
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: 10px; padding: 10px; align-items: start;
}
body.codes-view2 tr.live-row {
  display: grid; grid-template-columns: 76px minmax(0, 1fr);
  position: relative;
  border-radius: 10px; overflow: hidden;
}
/* 卡片内部保持视图 1 的「名字列 + 代码列」左右结构，行高由同一份内容决定 ——
   两种视图每一行一样高；✕/📞 挪到卡片右上角绝对定位。 */
body.codes-view2 tr.live-row td.c-user { grid-column: 1; grid-row: 1; padding: 10px 8px; border-bottom: 0; }
body.codes-view2 tr.live-row td.c-code { grid-column: 2; grid-row: 1; padding: 10px 14px; border-bottom: 0; }
body.codes-view2 tr.live-row td.c-act { display: block; position: absolute; top: 6px; right: 8px; }
/* 列色：浅蓝 / 浅绿 / 浅橙；.g-alt = 该列的偶数行（第 2、4、6…行），亮度明显更浅 */
body.codes-view2 tr.g-c0 { background: #d7e9fb; }
body.codes-view2 tr.g-c0.g-alt { background: #f2f8fe; }
body.codes-view2 tr.g-c1 { background: #d9f1e2; }
body.codes-view2 tr.g-c1.g-alt { background: #f1faf4; }
body.codes-view2 tr.g-c2 { background: #fcecd8; }
body.codes-view2 tr.g-c2.g-alt { background: #fef6eb; }
/* 选中 / 求助 / 通话 / 刚更新这几个状态在视图 2 里也要看得见，写在列色后面盖过它；
   is-fresh 的原动画刷的是背景色，会把列色刷掉，视图 2 换成亮度闪一下。 */
body.codes-view2 tr.live-row.is-picked { background: var(--brand-tint); }
body.codes-view2 tr.live-row.is-help { background: rgba(217, 66, 47, .09); }
body.codes-view2 tr.live-row.is-calling { background: rgba(23, 178, 106, .11); }
body.codes-view2 tr.live-row.is-fresh { animation: liveFresh2 .9s var(--ease); }
@keyframes liveFresh2 {
  0% { filter: brightness(.90); }
  100% { filter: none; }
}
body.codes-view2 #liveBody tr.live-empty { grid-column: 1 / -1; }

/* 第 62 轮（需求 4）：首页底部中间那行公告。admin 在 /lesson 页最下方改。 */
.home-foot {
  text-align: center;
  padding: 20px 16px 30px;
  font-size: 13px;
  letter-spacing: .02em;
  color: var(--ink-3);
}

.live-empty td { padding: 34px 20px; text-align: center; color: var(--ink-3); }
.live-empty b { display: block; font-family: var(--display); font-size: 16px; margin-bottom: 6px; color: var(--ink-2); }
.live-empty p { margin: 3px 0; font-size: 13px; color: var(--ink-3); }
.live-empty code { padding: 1px 6px; border: 1px solid var(--line-2); border-radius: 5px; font-size: 12.5px; }

/* ---------------------------------------------------------- /list 页 */
.page-list { display: flex; flex-direction: column; min-height: 100vh; }

.list-main {
  flex: 1;
  width: min(1840px, 100% - clamp(10px, 1.4vw, 22px));
  margin: 0 auto;
  padding: clamp(20px, 3vw, 40px) 0 60px;
}
.list-head { margin-bottom: 22px; }
.list-head h1 { font-size: clamp(24px, 3vw, 34px); letter-spacing: .01em; }
.list-sub { margin: 8px 0 0; font-size: 13.5px; color: var(--ink-3); }
.list-sub b { color: var(--brand-3); font-family: var(--display); font-size: 15px; }

/* 六列成长表：宽屏一行 6 列，中屏 3 列，小屏 2 / 1 列 */
.growth {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: 14px;
  align-items: start;
}
.gcol {
  border: 1px solid var(--line);
  border-radius: var(--r);
  box-shadow: var(--shadow-s);
  overflow: hidden;
  animation: rise var(--dur-base) var(--ease-out) both;
}
.gcol:nth-child(2) { animation-delay: 50ms; }
.gcol:nth-child(3) { animation-delay: 100ms; }
.gcol:nth-child(4) { animation-delay: 150ms; }
.gcol:nth-child(5) { animation-delay: 200ms; }
.gcol:nth-child(6) { animation-delay: 250ms; }
.gcol.is-todo { border-style: dashed; }

.gcol-head {
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  padding: 11px 13px;
  border-bottom: 1px solid var(--line-2);
  background: rgba(47, 128, 237, .04);
}
.gcol-head h2 { font-size: 14.5px; letter-spacing: .01em; }
.gcol-count {
  margin-left: auto;
  font-family: var(--mono); font-size: 11px; letter-spacing: .06em;
  color: var(--ink-3);
}
.gcol.is-todo .gcol-count { color: var(--yellow); }

.gcol-body { display: flex; flex-direction: column; max-height: 62vh; overflow: auto; }

/* 一条记录 = 一个整行可点区域 */
.grow {
  display: grid;
  grid-template-columns: 1fr auto 10px;
  grid-template-areas: "time time go" "title score go";
  gap: 1px 8px;
  width: 100%; text-align: left;
  padding: 9px 12px;
  border: 0; border-bottom: 1px solid var(--line-2);
  background: transparent;
  font: inherit; color: var(--ink);
  cursor: pointer;
  transition: background var(--dur-fast);
}
.grow:last-child { border-bottom: 0; }
.grow:hover { background: rgba(47, 128, 237, .06); }
.grow:focus-visible { outline: 2px solid var(--brand-2); outline-offset: -2px; }
.g-time { grid-area: time; font-family: var(--mono); font-size: 11px; color: var(--ink-4); }
.g-title {
  grid-area: title; font-size: 13px; line-height: 1.45;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.g-score {
  grid-area: score; align-self: center;
  font-family: var(--display); font-weight: 700; font-size: 13px;
  color: var(--brand-3); white-space: nowrap;
}
.g-score.is-todo { color: var(--yellow); }
.g-go { grid-area: go; align-self: center; color: var(--ink-4); font-size: 15px; }

.grow-empty { padding: 20px 13px; text-align: center; color: var(--ink-3); }
.grow-empty b { display: block; font-family: var(--display); font-size: 14px; margin-bottom: 4px; color: var(--ink-2); }
.grow-empty p { margin: 2px 0; font-size: 12px; color: var(--ink-4); line-height: 1.55; }

/* 记录详情弹窗 */
.dialog.wide { width: min(760px, 94vw); }
.det-meta {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin: 2px 0 12px; font-size: 12px; color: var(--ink-3);
}
.det-meta .dim { color: var(--ink-4); }
.det-body { max-height: 56vh; overflow: auto; }
.det-code {
  margin: 0; padding: 14px 16px;
  border: 1px solid var(--line-2); border-radius: var(--r);
  background: var(--paper);
  font-family: var(--mono); font-size: 12.5px; line-height: 1.7;
  white-space: pre-wrap; word-break: break-word; color: var(--ink-2);
}
.det-text {
  padding: 14px 16px; border: 1px solid var(--line-2); border-radius: var(--r);
  background: var(--paper); font-size: 14px; line-height: 1.8; white-space: pre-wrap;
}
.det-foot { display: flex; align-items: center; margin-top: 14px; }
.det-foot .spacer { flex: 1; }

/* ---------------------------------------------------------- 三页响应式 */
@media (max-width: 1400px) {
  .growth { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 1180px) {
  .code-main { --task-w: 320px; }
  .codes-main { grid-template-columns: 1fr; }
  .codes-list { margin-right: 0; margin-bottom: 16px; }
  .live-table-wrap { max-height: 42vh; }
}
@media (max-width: 900px) {
  .code-main { grid-template-columns: 1fr; --task-w: 0px; }
  .code-task { margin-right: 0; margin-bottom: 14px; }
  .code-task.hide { height: 0; margin-bottom: 0; }
  /* 窄屏改成上下堆叠，题目区占满整宽 —— 把手改贴主区右边框（就是题目区右边框） */
  .task-toggle, .code-main.task-off .task-toggle { left: auto; right: 2px; top: 26px; }
  .growth { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 620px) {
  .growth { grid-template-columns: 1fr; }
  .gcol-body { max-height: none; }
  .stage-head, .task-head { flex-wrap: wrap; row-gap: 6px; }
}

/* ============================================================
   第 31 轮 · code 页：「程序名称 + 新建 + 保存」搬进编程屏标题栏
   （原来的「同步给老师」删了 —— 代码本来就是自动上报的）
   ============================================================ */
.stage-tools {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  row-gap: 6px;
}

/* 「程序名称」这四字不用太大，它只是个提示；真说话的是右边那个输入框 */
.prog-lab {
  font-size: 12px;
  color: var(--ink-3);
  letter-spacing: .02em;
  white-space: nowrap;
}

.prog-name {
  width: clamp(130px, 16vw, 220px);
  padding: 5px 11px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--paper);
  color: var(--ink);
  font: inherit;
  font-size: 13px;
  line-height: 1.5;
  transition: border-color var(--dur-fast), box-shadow var(--dur-fast);
}
.prog-name::placeholder { color: var(--ink-4); }
.prog-name:focus {
  outline: none;
  border-color: var(--brand-2);
  box-shadow: 0 0 0 3px var(--brand-tint);
}
/* 忘了起名就点保存：框子闪两下红边，比一句 toast 更指得准 */
.prog-name.need {
  border-color: var(--bad);
  box-shadow: 0 0 0 3px rgba(217, 66, 47, .16);
  animation: nameNudge .42s var(--ease) 3;
}
@keyframes nameNudge {
  0%, 100% { transform: translateX(0); }
  25% { transform: translateX(-4px); }
  75% { transform: translateX(4px); }
}

/* 标题栏里的那段说明容易被工具区挤没，给它一条能自己缩小的通道 */
.stage-head .meta {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
@media (max-width: 900px) {
  .stage-head .meta { display: none; }          /* 窄屏先舍说明，保住按钮 */
  .stage-tools { width: 100%; }
  .prog-name { flex: 1; width: auto; min-width: 0; }
}

/* ============================================================
   第 32 / 33 轮 · 「编程题」的屏面清理
   ------------------------------------------------------------
   1. 面板标题里的「在线编程」四个字已从 code.html 里删掉
      （标题栏现在只剩那句操作说明 + 右侧「程序名称 / 新建 / 保存」）。
   2. 屏幕上的**扫描线暗格**去掉 —— 第 32 轮先做在线编程两页，第 33 轮
      起把这层 repeating-linear-gradient 直接从 .screen-bezel::before
      的基础规则里删掉了，detail 页的编程题 / 内嵌 ```pyarea 块一并生效，
      这里不再需要按页覆盖。
   3. 斜向那抹反光（150deg 的浅蓝扫光）留着 —— 它不是「格」，只是让屏面
      有点玻璃感。
   ============================================================ */

/* ============================================================
   第 35 轮 · 新增
   ------------------------------------------------------------
   1. code 页：「题目1 / 题目2」两个题目位的小切换标签（只有老师看得见）
   2. codes 页：每行学生右侧的 ✕（不再看这位学生）+ 右侧的「发给学生」
   3. /homework 页：时间筛选 + 评分 / 待改进卡片
   ============================================================ */

/* ---- 1. code 页 · 题目位 ---- */
.task-tabs {
  display: inline-flex; gap: 2px; padding: 2px;
  border: 1px solid var(--line-2); border-radius: 999px;
  background: var(--paper);
}
.task-tabs[hidden] { display: none; }
.task-tabs button {
  border: 0; background: none; cursor: pointer;
  font-family: var(--mono); font-size: 11.5px; font-weight: 700;
  letter-spacing: .04em; color: var(--ink-3);
  padding: 3px 10px; border-radius: 999px;
  transition: background var(--dur-fast), color var(--dur-fast);
}
.task-tabs button:hover { color: var(--brand); background: rgba(47, 128, 237, .08); }
/* 选中态用「浅底 + 蓝字 + 描边」，不给白字（跟全站一致） */
.task-tabs button[aria-selected="true"] {
  background: var(--brand-tint); color: var(--brand-3);
  box-shadow: inset 0 0 0 1px var(--brand-2);
}

/* 学生端两道题叠在一起时，中间一条虚线分开 */
.task-slot + .task-slot {
  margin-top: 15px; padding-top: 13px;
  border-top: 1px dashed var(--line-2);
}.task-slot-h { display: flex; align-items: baseline; gap: 8px; margin-bottom: 7px; }
.task-slot-h b { font-family: var(--mono); font-size: 11.5px; letter-spacing: .12em; color: var(--brand); }
.task-slot-h span { font-size: 11.5px; color: var(--ink-4); font-family: var(--mono); }

/* 第 119 轮（需求 2）：题目 1 右边那枚「编程已完成」浅绿徽标。
   它是**老师给的章**，学生点不动 —— 所以不做 hover、不设 cursor:pointer，
   免得学生以为点一下就能自己标完成。 */
.task-done {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 1px 9px; border-radius: 999px;
  font-size: 11.5px; font-weight: 700; line-height: 1.9;
  color: #1b7a53; background: #e9f8f0;
  box-shadow: inset 0 0 0 1px #bfe7d3;
  white-space: nowrap;
}
.task-done::before { content: '✓'; font-size: 11px; }
.task-done[hidden] { display: none; }

/* ---- 2. codes / homework 页 · 行尾按钮与评分列 ---- */
.btn[hidden], .chip[hidden], .live-dot[hidden] { display: none; }
table.live-table th.c-act { width: 40px; }
table.live-table th.c-score { width: 120px; }
.live-row td.c-act { text-align: right; }
.row-x {
  width: 22px; height: 22px; line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--line-2); border-radius: 7px;
  background: var(--paper); color: var(--ink-4);
  font-size: 11px; cursor: pointer; opacity: .52;
  transition: opacity var(--dur-fast), color var(--dur-fast), border-color var(--dur-fast);
}
.live-row:hover .row-x, .row-x:focus-visible { opacity: 1; }
.row-x:hover { color: var(--bad); border-color: var(--bad); }
.live-row.is-gone { opacity: 0; transform: translateX(10px); transition: opacity .14s, transform .14s; }
/* 第 119 轮（需求 2）：学生帐号**下面**那枚「编程已完成」。
   常态浅绿底深绿字（= 还没标，可以点）；标上之后转深绿实心白字「已标注」
   （第 120 轮改的字面 —— 「已完成」读起来像学生自己勾的，这枚其实是老师的章），
   大屏上一眼能分出「这道题过了」和「还没过」。 */
.row-done {
  display: inline-flex; align-items: center; justify-content: center;
  margin-top: 6px; padding: 1px 9px;
  font-size: 12px; font-weight: 700; line-height: 1.7;
  color: #1b7a53; background: #e9f8f0;
  border: 1px solid #bfe7d3; border-radius: 8px;
  cursor: pointer; white-space: nowrap;
  transition: background var(--dur-fast), border-color var(--dur-fast), color var(--dur-fast);
}
.row-done:hover { background: #d7f2e4; border-color: #9dd8bd; color: #146141; }
.row-done.is-on { background: #17b26a; border-color: #12945a; color: #fff; }
.row-done.is-on:hover { background: #12945a; border-color: #0f7d4d; }
.row-done[disabled] { opacity: .6; cursor: default; }
.score-mini { font-weight: 700; font-size: 13px; color: var(--brand-3); }
.live-row.is-graded .u-name { color: var(--brand-3); }

/* ---- 3. /homework 页 ---- */
.page-homework { display: flex; flex-direction: column; min-height: 100vh; }

.hw-filter {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 10px 14px;
  border-bottom: 1px solid var(--line-2);
  background: rgba(47, 128, 237, .03);
}
/* 第 70 轮（需求 1）：日期区间整块删了 —— 这排现在是「时间档位」+「倒序 / 顺序」两组小开关，
   选中的那枚填主题蓝。两组之间用 .spacer 把排序推到最右边。 */
.hw-filter .spacer { flex: 1; }
.hw-seg { display: inline-flex; align-items: center; gap: 6px; }
.hw-seg .btn { min-width: 58px; font-family: var(--mono); padding: 5px 8px; }
.hw-seg .btn.is-on {
  --bg: var(--brand); --fg: #ffffff; --bd: var(--brand);
  --bg-h: var(--brand-2); --fg-h: #ffffff; --bd-h: var(--brand-2);
  --bg-a: var(--brand-3); --fg-a: #ffffff; --bd-a: var(--brand-3);
  --sd: 0 5px 14px -9px rgba(43, 127, 228, .75);
}

.hw-grade {
  display: flex; flex-direction: column; gap: 8px;
  padding: 12px 14px;
  border-bottom: 1px solid var(--line-2);
  background: rgba(47, 128, 237, .03);
}
.hw-g-row { display: flex; align-items: center; gap: 8px; }
.hw-g-row > label { flex: 0 0 56px; font-size: 12.5px; font-weight: 700; color: var(--ink-3); }
.hw-g-row .spacer { flex: 1; }
.hw-g-row input[type="number"] {
  width: 86px; padding: 5px 9px;
  border: 1px solid var(--line-2); border-radius: 8px;
  font-family: var(--mono); font-size: 13.5px;
  color: var(--ink); background: var(--paper);
}
.hw-g-row input[type="number"]:focus,
.hw-g-row textarea:focus {
  outline: none; border-color: var(--brand-2); box-shadow: 0 0 0 3px var(--brand-tint);
}
.hw-g-row input:disabled, .hw-g-row textarea:disabled { opacity: .45; }
.hw-unit { font-size: 12px; color: var(--ink-4); }
/* 第 40 轮：评分默认 100，右边 90 / 80 / 70 / 60 一按就改分 */
.hw-quick { display: inline-flex; align-items: center; gap: 5px; }
.hw-quick .btn { min-width: 40px; padding: 4px 9px; font-family: var(--mono); }
.hw-tip { font-size: 11.5px; color: var(--ink-4); }
.hw-g-row textarea {
  flex: 1; min-width: 0; resize: vertical;
  padding: 6px 9px;
  border: 1px solid var(--line-2); border-radius: 8px;
  font-family: inherit; font-size: 13px; line-height: 1.6;
  color: var(--ink); background: var(--paper);
}
.hw-grade .btn:disabled { opacity: .5; cursor: not-allowed; }
@media (max-width: 900px) {
  .hw-g-row { flex-wrap: wrap; }
  .hw-tip { display: none; }
}

/* ---- 4. codes 页 · 「只看在线」勾选框（第 36 轮） ----
   默认勾上：老师只看到现在还开着 code 页的学生。去掉勾 = 回到「敲过代码的都列」。 */
.pane-head .chk {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 9px;
  border: 1px solid var(--line-2); border-radius: 999px;
  background: var(--paper);
  font-size: 12.5px; color: var(--ink-3); white-space: nowrap;
  cursor: pointer; user-select: none;
  transition: color var(--dur-fast), border-color var(--dur-fast);
}
.pane-head .chk:hover { color: var(--brand); border-color: var(--brand-2); }
.pane-head .chk input {
  width: 13px; height: 13px; margin: 0;
  accent-color: var(--brand); cursor: pointer;
}
/* 第 74 轮（需求 1）：这一行固定「班级下拉 → N位学生在线 → 只看在线」，
   三个都在同一行、谁也不换行；中间的「N位学生在线」是唯一可以被压缩的那个
   （放不下就省略号，不至于把右边的开关挤下去）。 */
.codes-list .pane-head { flex-wrap: nowrap; }
.codes-list .pane-head > select.head-sel,
.codes-list .pane-head > .chk { flex: none; }
.codes-list .pane-head > #listMeta {
  min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
}
.codes-list .pane-head > .chip[hidden] { display: none; }
@media (max-width: 900px) { .pane-head .chk span { display: none; } }

/* ============================================================
   第 37 轮 · 新增
   1. 首页两列课程列表：左右两列用浅色区分
   2. 首页老师可改序号（.seq-edit）
   3. detail 页课程名称输入框（.name-edit / .name-input）
   4. code 页班级选择（.task-class）
   5. 班级管理 /class 页
   6. 学生帐号管理 /accounts 页
   7. list 页「获奖情况」列（.award-*）+ 图片放大
   8. homework 页班级筛选（.hw-class）
   ============================================================ */

/* ---- 1. 首页两列课程列表：浅色区分 ---- */
.board-cols > table.courses { background: var(--paper); }
.board-cols > table.courses:nth-child(even) { background: rgba(47, 128, 237, .05); }
.board-cols > table.courses:nth-child(even) tbody tr:hover { background: rgba(47, 128, 237, .10); }

/* ---- 2. 可改序号 ---- */
.seq-edit {
  border: 1px dashed transparent; border-radius: 7px;
  background: none; cursor: pointer; padding: 2px 6px;
  font-family: var(--mono); font-size: 13px; color: var(--ink-4);
  transition: color var(--dur-fast), border-color var(--dur-fast), background var(--dur-fast);
}
.seq-edit:hover { color: var(--brand); border-color: var(--line-2); background: rgba(47, 128, 237, .07); }

/* ---- 3. detail 页课程名称 ---- */
.name-edit { display: inline-flex; align-items: center; gap: 7px; font-size: 12px; color: var(--ink-3); }
.name-input {
  width: clamp(160px, 24vw, 320px);
  padding: 5px 10px; border: 1px solid var(--line-2); border-radius: 8px;
  font-family: inherit; font-size: 13.5px; font-weight: 700; color: var(--ink);
  background: var(--paper);
}
.name-input:focus { outline: none; border-color: var(--brand-2); box-shadow: 0 0 0 3px var(--brand-tint); }

/* ---- 4. code 页班级选择 ---- */
.task-class {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 9px 13px; border-bottom: 1px solid var(--line-2);
  background: rgba(47, 128, 237, .04);
}
.task-class[hidden] { display: none; }
.task-class label { font-size: 12px; font-family: var(--mono); color: var(--ink-3); }
.task-class select {
  padding: 4px 9px; border: 1px solid var(--line-2); border-radius: 8px;
  font-size: 13px; color: var(--ink); background: var(--paper); max-width: 58%;
}
.task-class select:focus { outline: none; border-color: var(--brand-2); box-shadow: 0 0 0 3px var(--brand-tint); }
.task-class-tip { font-size: 11.5px; color: var(--ink-4); margin-left: auto; }

/* ---- 5. 班级管理 / 学生帐号管理 ---- */
.page-class, .page-accounts { display: flex; flex-direction: column; min-height: 100vh; }
/* 第 38 轮：班级管理页整页铺满视口 —— 左右两栏一直延伸到底部，各自内部滚动 */
.page-class { height: 100vh; overflow: hidden; }
.cls-main {
  flex: 1; min-height: 0; display: grid;
  grid-template-columns: minmax(0, 320px) minmax(0, 1fr);
  gap: 0; width: min(1500px, 100% - clamp(10px, 1.4vw, 22px));
  margin: 0 auto; padding: 16px 0 20px; align-items: stretch;
}
.cls-left {
  display: flex; flex-direction: column; min-height: 0;
  border: 1px solid var(--line); border-radius: var(--r-lg); box-shadow: var(--glow);
  margin-right: 16px; overflow: hidden;
}
.cls-right {
  border: 1px solid var(--line); border-radius: var(--r-lg); box-shadow: var(--glow);
  overflow: hidden; min-height: 0;
}
.cls-new { display: flex; gap: 8px; padding: 12px 13px; border-bottom: 1px solid var(--line-2); }
.cls-new input {
  flex: 1; min-width: 0; padding: 7px 10px;
  border: 1px solid var(--line-2); border-radius: 9px;
  font-size: 13.5px; color: var(--ink); background: var(--paper);
}
.cls-new input:focus { outline: none; border-color: var(--brand-2); box-shadow: 0 0 0 3px var(--brand-tint); }
.cls-list { flex: 1; min-height: 0; max-height: none; overflow: auto; }
.cls-item {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 11px 13px; border: 0; border-bottom: 1px solid var(--line-2);
  background: transparent; cursor: pointer; font: inherit; text-align: left;
  transition: background var(--dur-fast);
}
.cls-item:hover { background: rgba(47, 128, 237, .06); }
.cls-item.on { background: rgba(47, 128, 237, .12); }
.cls-item .nm { flex: 1; min-width: 0; font-size: 13.5px; font-weight: 700; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cls-item .cnt { font-family: var(--mono); font-size: 11.5px; color: var(--ink-4); }
.cls-x {
  width: 22px; height: 22px; flex: none; display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--line-2); border-radius: 7px; background: var(--paper);
  color: var(--ink-4); font-size: 11px; cursor: pointer; opacity: .45;
  transition: opacity var(--dur-fast), color var(--dur-fast), border-color var(--dur-fast);
}
.cls-item:hover .cls-x { opacity: 1; }
.cls-x:hover { color: var(--bad); border-color: var(--bad); }
.cls-empty { padding: 22px 14px; text-align: center; color: var(--ink-3); font-size: 13px; }

.std-search { display: flex; gap: 8px; padding: 12px 13px; border-bottom: 1px solid var(--line-2); }
.std-search input {
  flex: 1; min-width: 0; padding: 7px 10px;
  border: 1px solid var(--line-2); border-radius: 9px;
  font-size: 13.5px; color: var(--ink); background: var(--paper);
}
.std-search input:focus { outline: none; border-color: var(--brand-2); box-shadow: 0 0 0 3px var(--brand-tint); }
/* 第 38 轮：学生账号一律按 6 列网格铺开，纵向撑满所在的那一块 */
.std-wrap {
  flex: 1; min-height: 0; max-height: none; overflow: auto;
  display: grid; grid-template-columns: repeat(6, minmax(0, 1fr));
  align-content: start;
}
.std-wrap > .cls-empty { grid-column: 1 / -1; }
.std-row {
  display: flex; align-items: center; gap: 6px; min-width: 0;
  padding: 9px 10px; border-bottom: 1px solid var(--line-2); border-right: 1px solid var(--line-2);
}
.std-row .nm { font-size: 13.5px; font-weight: 700; color: var(--ink); cursor: pointer; }
.std-row .nm:hover { color: var(--brand); }
.std-row .in-cls { font-size: 10.5px; color: var(--ink-4); font-family: var(--mono); }
.std-row .spacer { flex: 1 1 0; min-width: 2px; }

/* ---- 第 37 轮改版：右栏上下两块 —— 上=本班学生（点名字进 list 页 / ✕ 移出），下=全部学生（点名字加入） ----
   第 38 轮：两块**平分**右栏高度，都一直延伸到底部，各自内部滚动。 */
.cls-right { display: flex; flex-direction: column; }
.cls-pane { display: flex; flex-direction: column; flex: 1 1 0; min-height: 0; }
.cls-pane + .cls-pane { border-top: 1px solid var(--line-2); }
.cls-pane > .pane-head { padding: 10px 13px; background: rgba(47, 128, 237, .04); flex: none; }
.cls-pane .std-wrap { max-height: none; }
.cls-pane:first-child .std-wrap,
.cls-pane:last-child .std-wrap { max-height: none; }
.std-name {
  padding: 0; border: 0; background: none; cursor: pointer; text-align: left;
  font-family: inherit; font-size: 13px; font-weight: 700; color: var(--ink);
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.std-name:hover { color: var(--brand); text-decoration: underline; }
.std-name.is-off { color: var(--ink-4); font-weight: 600; cursor: default; text-decoration: none; }
.std-name.is-off:hover { color: var(--ink-4); }
.std-x {
  flex: none; width: 22px; height: 22px; display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--line-2); border-radius: 7px; background: var(--paper);
  color: var(--ink-3); font-size: 11px; cursor: pointer; opacity: .72;
  transition: color var(--dur-fast), border-color var(--dur-fast), opacity var(--dur-fast);
}
.std-row:hover .std-x { opacity: 1; }
.std-x:hover { color: var(--bad); border-color: var(--bad); }
.add-hint { flex: none; font-size: 11.5px; font-family: var(--mono); color: var(--brand-3); opacity: .85; }
.std-row.is-in { background: rgba(16, 48, 79, .03); }

.acc-main { width: min(1360px, 100% - clamp(10px, 1.4vw, 22px)); margin: 0 auto; padding: 16px 0 40px; }
.acc-card {
  border: 1px solid var(--line); border-radius: var(--r-lg); box-shadow: var(--glow);
  margin-bottom: 16px; overflow: hidden;
}
.acc-head { display: flex; align-items: center; gap: 10px; padding: 12px 15px; border-bottom: 1px solid var(--line-2); background: rgba(47, 128, 237, .04); }
.acc-head h3 { font-size: 15px; }
.acc-imp { padding: 13px 15px; }
.acc-imp textarea {
  width: 100%; min-height: 96px; resize: vertical; padding: 9px 11px;
  border: 1px solid var(--line-2); border-radius: 10px;
  font-family: var(--mono); font-size: 13px; line-height: 1.6; color: var(--ink); background: var(--paper);
}
.acc-imp textarea:focus { outline: none; border-color: var(--brand-2); box-shadow: 0 0 0 3px var(--brand-tint); }
.acc-tip { font-size: 11.5px; color: var(--ink-4); margin: 6px 0 10px; }
.acc-table { width: 100%; border-collapse: collapse; }
.acc-table thead th {
  text-align: left; font-family: var(--mono); font-size: 11px; letter-spacing: .12em;
  text-transform: uppercase; color: var(--ink-3); font-weight: 700;
  padding: 10px 14px; border-bottom: 1px solid var(--line-2); background: rgba(47, 128, 237, .06);
  white-space: nowrap;
}
.acc-table td { padding: 10px 14px; border-bottom: 1px solid var(--line-2); font-size: 13.5px; }
.acc-table tbody tr:last-child td { border-bottom: 0; }
.acc-table td.n { font-family: var(--mono); color: var(--ink-3); }
.acc-acts { display: flex; gap: 8px; justify-content: flex-end; }

/* 第 39 轮：学生帐号列表改成 **3 列**，列与列之间画一条竖分隔线。
   （教师帐号卡片复用同一套网格，也是 3 列。）*/
.acc-grid {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0; padding: 6px 8px 12px;
}
.acc-grid > .cls-empty { grid-column: 1 / -1; }
.acc-card[hidden] { display: none; }
.acc-row {
  display: flex; align-items: center; gap: 7px; min-width: 0;
  padding: 8px 12px; border-bottom: 1px solid var(--line-2);
  border-right: 1px solid var(--line-2);      /* 3 列之间的竖线 */
}
/* 每行最右那一列不画右边框（网格里只有 .acc-row，所以 nth-child 计数是可靠的） */
.acc-grid > .acc-row:nth-child(3n) { border-right: 0; }
.acc-row .nm {
  font-size: 13px; font-weight: 700; color: var(--ink);
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.acc-row .nm .tag { margin-left: 5px; font-size: 10.5px; font-family: var(--mono); color: var(--ink-4); }
.acc-row .nm .tag.bad { color: var(--bad); }
.acc-row.is-off .nm { color: var(--ink-4); text-decoration: line-through; }
.acc-row .meta, .acc-row .cnt {
  flex: none; font-family: var(--mono); font-size: 10.5px; color: var(--ink-3); white-space: nowrap;
}
.acc-row .meta { color: var(--ink-4); }
.acc-row .spacer { flex: 1 1 0; min-width: 2px; }
.acc-row .acc-acts { flex: none; display: flex; gap: 3px; }
/* 图标式小按钮：3 列里横向空间紧，4 个动作才放得下 */
.acc-row .acc-acts .btn.ico {
  min-height: 26px; padding: 2px 6px; font-size: 12.5px; line-height: 1.35;
}
.acc-imp-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.acc-imp-row input {
  padding: 7px 10px; border: 1px solid var(--line-2); border-radius: 9px;
  font-size: 13.5px; color: var(--ink); background: var(--paper);
}
.acc-imp-row input:focus { outline: none; border-color: var(--brand-2); box-shadow: 0 0 0 3px var(--brand-tint); }
.acc-imp-row #teaName { flex: 1 1 240px; min-width: 160px; }
.acc-imp-row #teaPwd { flex: 0 1 210px; min-width: 140px; }
.acc-imp-row .acc-tip { flex-basis: 100%; margin: 0; }
.acc-q {
  padding: 5px 10px; border: 1px solid var(--line-2); border-radius: 8px;
  font-size: 13px; background: var(--paper); color: var(--ink);
}
.acc-q:focus { outline: none; border-color: var(--brand-2); box-shadow: 0 0 0 3px var(--brand-tint); }

/* 第 96 轮（需求 2）：导入卡片抬头里那一排「加入班级 + 班级下拉 + 新建班级」。
   抬头是 flex 且不换行，所以三样都给固定上限宽度 + min-width:0，
   班级名再长也不会把右边的「初始密码 a11111」挤出卡片。 */
.acc-head-lab {
  font-family: var(--mono); font-size: 11.5px; font-weight: 700; letter-spacing: .06em;
  color: var(--ink-3); white-space: nowrap;
}
/* 抬头里那个 .spacer 以前没有任何规则（只有 .topbar/.board-head 那几个有名有姓的
   .spacer 才有 flex:1），所以卡片抬头右半截是空的、「初始密码 a11111」这类说明
   紧贴着左边那堆控件。补上这一条，抬头恢复成「左标题 + 靠右说明」的排版。 */
.acc-head .spacer { flex: 1 1 auto; }
.acc-cls-sel { max-width: 190px; min-width: 0; text-overflow: ellipsis; }
.acc-cls-name { width: 150px; min-width: 0; }
.acc-cls-name::placeholder { color: var(--ink-4); }
.acc-q:disabled { background: #f2f5f9; color: var(--ink-4); cursor: not-allowed; }
@media (max-width: 900px) {
  /* 窄屏：抬头允许换行，别把三样控件压成一条缝 */
  .acc-head { flex-wrap: wrap; }
  .acc-cls-name { flex: 1 1 130px; }
}
/* 窄屏回落：3 列 → 2 列 → 1 列，竖分隔线跟着挪到最后一列上 */
@media (max-width: 1180px) {
  .acc-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .acc-grid > .acc-row:nth-child(3n) { border-right: 1px solid var(--line-2); }
  .acc-grid > .acc-row:nth-child(2n) { border-right: 0; }
}
@media (max-width: 760px) {
  .acc-grid { grid-template-columns: 1fr; }
  .acc-grid > .acc-row:nth-child(n) { border-right: 0; }
}

/* ---- 6. list 页「获奖情况」 ---- */
.growth { grid-template-columns: repeat(7, minmax(0, 1fr)); }
.gcol:nth-child(7) { animation-delay: 300ms; }
.gcol[data-key="award"] { border-color: var(--brand-2); }
.gcol[data-key="award"] .gcol-head { background: rgba(217, 139, 0, .07); }
.award-up {
  display: flex; align-items: center; justify-content: center; gap: 6px;
  margin: 10px 12px 4px; padding: 8px;
  border: 1px dashed var(--line-2); border-radius: 10px;
  font-size: 12.5px; color: var(--brand-3); cursor: pointer;
  transition: background var(--dur-fast), border-color var(--dur-fast);
}
.award-up:hover { background: rgba(47, 128, 237, .06); border-color: var(--brand-2); }
.award-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; padding: 10px 12px; }
.award-item { position: relative; margin: 0; border: 1px solid var(--line-2); border-radius: 10px; overflow: hidden; background: var(--paper); }
.award-item img { display: block; width: 100%; height: 84px; object-fit: cover; cursor: zoom-in; }
.award-item figcaption { font-size: 10.5px; color: var(--ink-4); font-family: var(--mono); padding: 3px 6px; }
.award-x {
  position: absolute; top: 4px; right: 4px; z-index: 2;
  width: 20px; height: 20px; display: inline-flex; align-items: center; justify-content: center;
  border: 0; border-radius: 6px; background: rgba(255, 255, 255, .86); color: var(--bad);
  font-size: 11px; cursor: pointer; opacity: .78;
}
.award-x:hover { opacity: 1; background: #fff; }
.award-mask { display: flex; align-items: center; justify-content: center; padding: 24px; }
.award-big { max-width: 94vw; max-height: 92vh; border-radius: 12px; box-shadow: var(--shadow); cursor: zoom-out; }

/* ---- 7. homework 页班级筛选 ---- */
.hw-class { display: flex; align-items: center; gap: 8px; padding: 10px 14px; border-bottom: 1px solid var(--line-2); }
.hw-class label { font-size: 12px; color: var(--ink-4); font-family: var(--mono); }
.hw-class select {
  padding: 4px 9px; border: 1px solid var(--line-2); border-radius: 8px;
  font-size: 13px; color: var(--ink-2); background: var(--paper); max-width: 70%;
}
.hw-class select:focus { outline: none; border-color: var(--brand-2); box-shadow: 0 0 0 3px var(--brand-tint); }

/* 第 73 轮（需求 5）：codes 页的班级下拉搬进了 pane-head（跟「只看在线」同一行）——
   离开 .hw-class 就丢样式，这里补一份同款；下面那一排按钮不许换行，
   「打开学生端在线编程 / 切换 / 清除举手 / 清除呼叫」始终在同一行，
   右边的班级提示挤不下就省略号。 */
.codes-list .pane-head select.head-sel {
  flex: none; max-width: 230px;
  padding: 4px 9px; border: 1px solid var(--line-2); border-radius: 8px;
  font-size: 13px; color: var(--ink-2); background: var(--paper);
}
.codes-list .pane-head select.head-sel:focus {
  outline: none; border-color: var(--brand-2); box-shadow: 0 0 0 3px var(--brand-tint);
}
.hw-class { flex-wrap: nowrap; }
.hw-class > .btn { flex: none; }
.hw-class .pane-meta {
  flex: 1; min-width: 0; overflow: hidden;
  white-space: nowrap; text-overflow: ellipsis;
}

/* ---- 响应式 ---- */
@media (max-width: 1400px) { .growth { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
@media (max-width: 1180px) {
  /* 单栏时不再整页锁死高度（内容要能自然往下排） */
  .page-class { height: auto; overflow: visible; }
  .cls-main { grid-template-columns: 1fr; align-items: start; }
  .cls-left { margin-right: 0; margin-bottom: 16px; }
  .cls-list { max-height: 40vh; }
  .cls-right { min-height: 320px; }
  .cls-pane { flex: none; }
  .std-wrap { grid-template-columns: repeat(3, minmax(0, 1fr)); max-height: 40vh; }
}
@media (max-width: 700px) {
  .std-wrap { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 900px) { .growth { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 620px) { .growth { grid-template-columns: 1fr; } }

/* ============================================================
   第 41 轮：首页课程列表 = 卡片墙（固定 3 列）
   一张卡片 = 预览缩略图（这节课正文里的第一张图）+ 课程名称；
   列表上只剩课程名称，序号 / 得分 / 评价 / 完成时间 / 查看 全部撤掉。
   抽不到正文图片的课程，用统一风格的占位块（淡蓝底 + 标题开头的图标）。
   屏幕再窄也保持 3 列（按需求「固定为 3 列」），只缩间距与字号。
   ============================================================ */
.course-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 16px;
  padding: 18px clamp(16px, 2.4vw, 28px) 24px;
}
.course-card {
  position: relative;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  border: 1px solid var(--line-2);
  border-radius: var(--r);
  background: var(--paper);
  transition: transform var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease);
}
.course-card.row-link { cursor: pointer; }
.course-card:hover { border-color: var(--brand-2); box-shadow: 0 10px 24px -18px rgba(16, 48, 79, .45); }
.course-card.row-link:hover { transform: translateY(-2px); }

/* 缩略图：该课程正文里的第一张图；无图时是 .cc-ph 占位块。
   正文里的图比例很杂（实测有 2560×1440、1073×219 的超宽条、也有竖向图），
   所以用 contain 完整缩放（缩略图的本义）而不是 cover 居中裁切 —— 裁切会把截图里的字切掉。
   第 42 轮：缩略图高度缩到原来的 0.6 —— 16/10 的 60% 高 ≈ 16/6。 */
.cc-thumb {
  position: relative;
  display: block;
  aspect-ratio: 16 / 6;
  overflow: hidden;
  background: #eef6ff;
  border-bottom: 1px solid var(--line-2);
}
.cc-thumb img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: center;
}
.cc-ph {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #dfeafb;
  font-size: 20px;           /* 第 42 轮：缩略图缩到 0.6，图标跟着缩（34 → 20） */
  line-height: 1;
}

/* 序号角标：只给老师看（管理员那版是可点的按钮，点一下改序号） */
.cc-seq {
  position: absolute;
  left: 8px;
  top: 8px;
  min-width: 34px;
  padding: 2px 7px;
  border: 1px solid var(--line);
  border-radius: 7px;
  background: rgba(246, 251, 255, .94);
  font-family: var(--mono);
  font-size: 12px;
  color: var(--ink-3);
  text-align: center;
}

.course-card .c-name {
  display: block;
  flex: 1;
  padding: 11px 13px 3px;
  min-height: 46px;
}
.course-card .c-name a {
  display: block;
  font-weight: 700;
  font-size: 14.5px;
  line-height: 1.5;
  color: var(--ink);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: color var(--dur-fast), border-color var(--dur-fast);
}
.course-card .c-name a:hover { color: var(--brand); border-color: var(--brand-2); }

/* 老师的管理入口：留在卡片右下角，学生卡片上没有这一块 */
.cc-act {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 6px;
  min-height: 30px;
  padding: 0 9px 9px;
}
.cc-act .row-ren { margin-right: 0; }
.cc-act:empty { display: none; }

@media (max-width: 700px) {
  .course-grid { gap: 9px; padding: 12px 10px 16px; }
  .cc-thumb { aspect-ratio: 20 / 9; }          /* 第 42 轮：4/3 的 0.6 高 ≈ 20/9 */
  .cc-ph { font-size: 14px; }
  .cc-seq { left: 5px; top: 5px; min-width: 26px; padding: 1px 5px; font-size: 11px; }
  .course-card .c-name { padding: 8px 9px 2px; min-height: 40px; }
  .course-card .c-name a { font-size: 12.5px; }
  .cc-act { min-height: 26px; padding: 0 6px 6px; }
}

/* ============================================================
   第 42 轮
   1. 老师 codes 页：给代码加「行背景标注」（浅红 / 浅绿 / 浅蓝）
   2. 学生 code 页：编程题左上角的「求助」按钮
   3. codes 页左侧：举手求助的学生名旁亮起 ✋
   ============================================================ */

/* ---------- 1. 波浪下划线标注（第 43 轮由「背景色块」改来） ----------
   像老师批改作业那样，在选中的那几个字**下面**划一道波浪线，不再铺底色。
   铺在着色稿（.hl）与输入层（textarea）**下面**，只显色不抢事件。
   线是 SVG 平铺出来的（8px 一个周期），元素的 left / width / top 由
   codeeditor.js 用量尺实测字符位置后写在行内样式里，所以起止与字符逐字对齐。
   .py-marks-in 的 left / top 也由 JS 按 textarea 的 padding 实时对齐。 */
.codepad .py-marks {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: 0;
}
/* ⚠️ 这里**不能**写成 `.codepad > .py-marks-in`（第 45 轮踩的坑）：
   DOM 里它是 `.py-marks` 的孩子，不是 `.codepad` 的孩子 —— 子选择器匹配不上，
   position / left / top 全部落空，于是 codeeditor.js 按 textarea 的内边距
   （14px / 12px）写进来的对齐量被整条忽略，波浪线整体**左偏 14px、上偏 12px**
   （肉眼约「往左挪了快 2 个字」）。改回后代选择器，左右上下同时归位。 */
.codepad .py-marks-in {
  position: absolute;
  left: 0;             /* JS 会按 textarea 的 padding-left 覆盖 */
  top: 0;              /* JS 会按 textarea 的 padding-top 覆盖 */
  height: 0;
}
.py-mark {
  position: absolute;
  left: 0;             /* 行内样式的 left / width / top 会盖掉这三个值 */
  top: 0;
  width: 0;
  height: 4px;         /* 波浪线自身高度 */
  display: block;
  background-repeat: repeat-x;
  background-position: left top;
  background-size: 8px 4px;
}
/* 量尺：隐形的一行文字，只用来量「某几个字符」在屏幕上占多宽。
   字体 / 字号 / tab-size / 内边距都由 codeeditor.js 从 textarea 复制过来。 */
.codepad .py-ruler {
  position: absolute;
  left: 0;
  top: 0;
  margin: 0;
  padding: 0;
  border: 0;
  visibility: hidden;
  white-space: pre;
  pointer-events: none;
  z-index: -1;
}
/* 波浪线本体：8×4 的 SVG 平铺。一个周期从 (0,2.7) 上拱到 (4,2.7) 再下凹回 (8,2.7)，
   首尾切向一致 → 平铺无缝，看着像手写的一串小波浪。stroke 用 %23 转义 #。 */
.py-mark.mk-red {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='4' viewBox='0 0 8 4'%3E%3Cpath d='M0 2.7Q2 0.7 4 2.7T8 2.7' fill='none' stroke='%23d9422f' stroke-width='1.4'/%3E%3C/svg%3E");
}
.py-mark.mk-green {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='4' viewBox='0 0 8 4'%3E%3Cpath d='M0 2.7Q2 0.7 4 2.7T8 2.7' fill='none' stroke='%2317a05e' stroke-width='1.4'/%3E%3C/svg%3E");
}
.py-mark.mk-blue {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='4' viewBox='0 0 8 4'%3E%3Cpath d='M0 2.7Q2 0.7 4 2.7T8 2.7' fill='none' stroke='%232f80ed' stroke-width='1.4'/%3E%3C/svg%3E");
}

/* ---------- 2. codes 页的标注色板 ---------- */
.anno-tools { display: inline-flex; align-items: center; gap: 6px; }
.anno-lab { font-size: 12px; color: var(--ink-4); white-space: nowrap; }
.anno-sw {
  width: 22px; height: 22px; padding: 0;
  border: 1px solid var(--line-2); border-radius: 7px;
  cursor: pointer;
  transition: transform var(--dur-fast) var(--ease), border-color var(--dur-fast), box-shadow var(--dur-fast);
}
.anno-sw:hover { transform: translateY(-1px); border-color: var(--brand-2); }
.anno-sw.mk-red { background: rgba(217, 66, 47, .30); }
.anno-sw.mk-green { background: rgba(23, 160, 94, .32); }
.anno-sw.mk-blue { background: rgba(47, 128, 237, .30); }
.anno-sw[aria-pressed="true"] {
  border-color: var(--brand);
  box-shadow: 0 0 0 2px var(--brand-tint);
}
.anno-tip { font-size: 11.5px; color: var(--ink-4); white-space: nowrap; }

/* ---------- 3. 举手求助 ---------- */
.live-row.is-help { background: rgba(217, 66, 47, .045); }
.live-row.is-help .u-name { color: var(--bad); }
.help-hand {
  display: inline-block;
  margin-left: 6px;
  font-size: 13px;
  line-height: 1;
  animation: helpWave 1.15s var(--ease) infinite;
}
@keyframes helpWave {
  0%, 100% { transform: rotate(-9deg); }
  50% { transform: rotate(11deg); }
}
@media (prefers-reduced-motion: reduce) { .help-hand { animation: none; } }
/* 第 67 轮（需求 2）：多人同时举手时，✋ 右边那枚「第几位」小圆号。
   ⚠️ 它是 .help-hand 的**兄弟**，不在那个会摆动的元素里面 —— 号码要稳，别跟着晃。 */
.help-seq {
  display: inline-block;
  min-width: 15px;
  margin-left: 2px;
  padding: 0 4px;
  border-radius: 8px;
  background: var(--bad);
  color: #fff;
  font-size: 11px;
  line-height: 15px;
  text-align: center;
  font-variant-numeric: tabular-nums;
  vertical-align: 1px;
}

/* ---------- 4. 学生端的「求助」按钮（编程题左上角） ---------- */
.btn.help-btn {
  --bg: #fff2ef;
  --fg: var(--bad);
  --bd: rgba(217, 66, 47, .42);
  font-weight: 700;
}
.btn.help-btn:hover {
  --bg: #ffe7e1;
  border-color: var(--bad);
  box-shadow: 0 6px 18px -8px rgba(217, 66, 47, .35);
}
.btn.help-btn.is-sent {
  --bg: rgba(217, 66, 47, .14);
  --bd: rgba(217, 66, 47, .42);
}

/* 第 42 轮：右侧面板头多了色板与「发给学生」，窄屏允许换行、先舍掉「行标注」三个字 */
.codes-stage .pane-head { flex-wrap: wrap; row-gap: 8px; }
@media (max-width: 1120px) { .anno-lab { display: none; } }

/* ============================================================
   第 47 轮
   1. 首页两栏：左边「直播预告（B站 / 抖音）+ 比赛信息」，右边课程卡片墙
   2. 左侧信息区的卡片 / 平台分区 / 比赛表格
   3. 左侧那块共用的编辑弹窗（图片字段）
   ============================================================ */

/* 首页专用容器：比别的页面宽 —— 课程墙右侧不动，多出来的一栏留给左侧信息区 */
.wrap.home { width: min(1640px, 100% - clamp(18px, 2.4vw, 56px)); }

.home-grid {
  display: grid;
  grid-template-columns: minmax(290px, 344px) minmax(0, 1fr);   /* 课程居右 */
  gap: 20px;
  align-items: start;
  margin-top: 26px;
}
.home-grid .board { margin: 0 0 70px; }

/* 窗口不够宽就上下堆：先直播预告、再课程墙，保证哪一栏都不会被挤成一条 */
@media (max-width: 1120px) {
  .home-grid { grid-template-columns: 1fr; gap: 16px; }
  .home-grid .board { margin-bottom: 40px; }
}

.home-side { display: flex; flex-direction: column; gap: 18px; min-width: 0; }

.side-card {
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--paper);
  box-shadow: var(--glow);
  overflow: hidden;
}
.side-card > h3 {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 12px clamp(12px, 1.4vw, 16px);
  border-bottom: 1px solid var(--line-2);
  background: linear-gradient(180deg, rgba(47, 128, 237, .08), transparent);
  font-size: 16px;
}
.side-card > h3 .spacer { flex: 1; }
.side-body { padding: 12px clamp(12px, 1.4vw, 16px) 14px; }

/* ---- 管理入口：管理员才会被渲染出来 ---- */
.side-acts { display: inline-flex; align-items: center; gap: 3px; flex: 0 0 auto; }
.side-acts .ico {
  border: 1px solid transparent;
  background: transparent;
  color: var(--ink-3);
  font-size: 12px;
  line-height: 1;
  padding: 4px 6px;
  border-radius: 7px;
  cursor: pointer;
  transition: background var(--dur-fast), color var(--dur-fast);
}
.side-acts .ico:hover { background: rgba(47, 128, 237, .12); color: var(--ink); }
.side-acts .ico.danger:hover { background: rgba(217, 66, 47, .12); color: var(--bad); }

/* ---- 直播预告：两块分区（第 49 轮删掉了头部的「B站 / 抖音」平台标签，只靠中间那条虚线分块） ---- */
.live-block + .live-block { margin-top: 12px; padding-top: 12px; border-top: 1px dashed var(--line-2); }
.live-head { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
/* 第 49 轮：.plat 胶囊整套已删（首页不再出现「B站 / 抖音」字样）；游客视角连 .live-head 都不渲染 */

/* 一行 = 可点的那条 + 右侧管理按钮（按钮不能落在 <a> 里，会被一起点走） */
.live-row { position: relative; }
.live-row + .live-row { margin-top: 8px; }
.live-row .side-acts { position: absolute; top: 7px; right: 7px; background: rgba(255, 255, 255, .86); border-radius: 8px; }

/* 第 48 轮：左边主题图片 + 右边两行（第一行直播时间，第二行主题） */
.live-item {
  display: flex;
  gap: 10px;
  align-items: stretch;
  padding: 8px;
  border: 1px solid var(--line-2);
  border-radius: var(--r);
  background: #f4f9ff;
  color: inherit;
  text-decoration: none;
  transition: border-color var(--dur-fast), box-shadow var(--dur-fast), transform var(--dur-fast);
}
a.live-item:hover { border-color: var(--brand-2); transform: translateY(-1px); box-shadow: 0 10px 22px -18px rgba(16, 48, 79, .5); }
.li-img {
  flex: 0 0 108px;
  /* 第 49 轮：高度不再写死 70px —— 交给图片自己按比例撑，
     align-self:center 让它垂直居中，别被 .live-item 的 align-items:stretch 拉满高 */
  align-self: center;
  border-radius: 10px;
  overflow: hidden;
  background: #dfeafb;
  min-height: 70px;          /* 没图 / 极扁的图时兜个底，别把条目压塌 */
  display: flex;
  align-items: center;
  justify-content: center;
}
/* 第 49 轮（需求）：图片自动调整、以显示完全 —— 宽度撑满 108，高度等比自适应，整张图不裁。
   （原来是 height:70px + object-fit:cover，宽高比不是 108:70 的图会被切掉两边） */
.li-img img { display: block; width: 100%; height: auto; }
.li-img .ph { font-size: 18px; opacity: .65; }
/* 右边两行：第一行时间、第二行主题（主题允许换行，别被压成一条） */
.li-body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  padding-right: 44px;
}
.li-time { font-family: var(--mono); font-size: 13px; font-weight: 700; color: var(--brand-3); }
.li-topic { font-size: 13.5px; font-weight: 700; line-height: 1.45; overflow-wrap: anywhere; }

/* ---- 比赛信息（第 48 轮）：一条 = 一张竖排卡片，三行「图片 / 名称 / 状态」，点开放大看详情 ---- */
.ct-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 9px;
  border: 1px solid var(--line-2);
  border-radius: var(--r);
  background: #f4f9ff;
  cursor: pointer;
  transition: border-color var(--dur-fast), box-shadow var(--dur-fast), transform var(--dur-fast);
}
.ct-card + .ct-card { margin-top: 10px; }
.ct-card:hover { border-color: var(--brand-2); transform: translateY(-1px); box-shadow: 0 10px 22px -18px rgba(16, 48, 79, .5); }
.ct-card:focus-visible { outline: 2px solid var(--brand-2); outline-offset: 2px; }
.ct-card .side-acts { position: absolute; top: 12px; right: 12px; background: rgba(255, 255, 255, .86); border-radius: 8px; }

/* 第 1 行：图片（整宽，按 16:9 裁） */
.ct-pic {
  height: 96px;
  border-radius: 10px;
  overflow: hidden;
  background: #dfeafb;
  display: flex;
  align-items: center;
  justify-content: center;
}
.ct-pic img { display: block; width: 100%; height: 100%; object-fit: cover; }
.ct-pic .ph { font-size: 22px; opacity: .6; }

/* 第 2 行：比赛名称 */
.ct-name { font-size: 13.5px; line-height: 1.45; overflow-wrap: anywhere; padding-right: 40px; }

/* 第 3 行：比赛状态 */
.ct-status {
  align-self: flex-start;
  max-width: 100%;
  padding: 2px 9px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 700;
  color: var(--brand-2);
  background: var(--brand-tint);
  border: 1px solid rgba(47, 128, 237, .18);
  overflow-wrap: anywhere;
}

.side-empty { color: var(--ink-4); font-size: 12.5px; padding: 4px 2px; }

/* ---- 弹窗里的图片字段：左边预览 + 右边「网址 / 上传」 ---- */
.imgf { display: flex; gap: 10px; align-items: flex-start; }
.imgf .pv {
  flex: 0 0 104px;
  height: 70px;
  border: 1px dashed var(--line);
  border-radius: 10px;
  background: #f4f9ff;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  color: var(--ink-4);
  font-size: 11.5px;
}
.imgf .pv img { display: block; width: 100%; height: 100%; object-fit: cover; }
.imgf .col { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 6px; }
.imgf .col .btn { align-self: flex-start; }

/* 首页左上角那两块空着的时候也要有存在感：给一句提示，不塌成一条线 */
.side-hint { color: var(--ink-3); font-size: 12.5px; }

/* ============================================================
   第 48 轮
   1. 直播预告：每条 = 左主题图 + 右两行（时间 / 主题），整条一个链接
   2. 比赛信息：竖排卡片（图 / 名称 / 状态）+ 点开放大的详情弹窗（md-editor-rt）
   ============================================================ */

/* ---- 比赛详情弹窗（放大）----
   正文是 md-editor-rt 渲染的，那套 css 点开卡片时才注入（比 app.css 靠后），
   所以下面几条要用 #ctMask 提优先级压住它，别让它把预览区涂成自己的底色。 */

/* ⚠️ 弹窗里凡是靠 hidden 属性切换显示的东西，都得自己把 display:none 写死一遍 ——
   .btn / .chip / .ct-foot / .ct-hero img 都显式写过 display（inline-flex / flex / block），
   浏览器默认的 [hidden]{display:none} 会被它们盖掉：明明 hidden，游客照样看得见
   「✏️ 编辑详情」，只读详情底下还挂着「取消 / 保存详情」那一排。（第 48 轮截图里抓到的） */
#ctMask [hidden] { display: none !important; }
.ct-dialog {
  width: min(880px, 100%);
  max-height: min(90vh, 960px);
  overflow: auto;
  padding: 20px 22px 22px;
}
.ct-bar { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }
.ct-bar h3 { font-size: 19px; margin: 0; overflow-wrap: anywhere; }
.ct-bar .spacer { flex: 1; }

.ct-hero {
  height: clamp(140px, 26vh, 240px);
  border: 1px solid var(--line-2);
  border-radius: var(--r);
  overflow: hidden;
  background: #eef6ff;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 14px;
}
.ct-hero img { display: block; width: 100%; height: 100%; object-fit: cover; }
.ct-hero .ph { font-size: 44px; opacity: .45; }

.ct-view { font-size: 15px; }
.ct-empty { color: var(--ink-4); font-size: 13.5px; padding: 20px 4px; text-align: center; }

#ctMask .ct-view .md-editor-preview { background: transparent; font-size: 15px; }
#ctMask .ct-view .md-editor-preview-wrapper { padding: 0; }

/* 编辑器本体：给个固定高度，弹窗自己滚 */
.ct-editor { height: min(56vh, 520px); }
.ct-editor .md-editor { height: 100%; }
.ct-editor .md-editor-content { background: #f4f9ff; }

.ct-foot { display: flex; align-items: center; gap: 10px; margin-top: 14px; }
.ct-foot .spacer { flex: 1; }

@media (max-width: 700px) {
  .ct-dialog { padding: 16px 14px 18px; }
  .ct-editor { height: 46vh; }
}

/* ============================================================
   第 51 轮：课堂直播
   ① 老师端每页右上那枚「直播」按钮
   ② /live 浮动窗口（选班级 / 直播网址 / 语音 / 全屏开关）
   ③ 学生端首页「直播」区里嵌的 B 站直播画面 + 「全屏 / 小窗」
   ============================================================ */

/* —— ① 顶栏「上课控制」按钮（第 54 轮：立体化 + 固定在每页最右）—— */
/* 立体化做法：竖向渐变（上亮下暗）+ 内高光 + 双层投影（一层贴边硬阴影做出「厚度」，
   一层远投做出「浮起」）。悬停再抬 2px、投影变大 —— 一眼就是个可按的立体块。 */
.btn.live-btn {
  --bg: linear-gradient(180deg, #ffa552 0%, #ff7a18 55%, #e8630a 100%);
  --fg: #ffffff;
  --bd: rgba(150, 60, 4, .35);
  background: var(--bg);
  color: var(--fg);
  min-height: 36px;
  padding: 8px 17px;
  font-size: 13.5px;
  border-radius: 10px;
  text-shadow: 0 1px 1px rgba(120, 45, 0, .35);
  box-shadow:
    0 1px 0 rgba(255, 255, 255, .45) inset,
    0 -2px 0 rgba(140, 55, 5, .28) inset,
    0 2px 0 #c9530a,
    0 6px 14px -4px rgba(232, 99, 10, .55);
}
.btn.live-btn:hover {
  --bg: linear-gradient(180deg, #ffb56e 0%, #ff8c33 55%, #f0700f 100%);
  transform: translateY(-2px);
  border-color: rgba(150, 60, 4, .42);
  box-shadow:
    0 1px 0 rgba(255, 255, 255, .5) inset,
    0 -2px 0 rgba(140, 55, 5, .3) inset,
    0 4px 0 #c9530a,
    0 12px 22px -6px rgba(232, 99, 10, .6);
}
.btn.live-btn:active {
  transform: translateY(1px);
  box-shadow:
    0 1px 0 rgba(255, 255, 255, .35) inset,
    0 -1px 0 rgba(140, 55, 5, .25) inset,
    0 1px 0 #c9530a,
    0 3px 8px -3px rgba(232, 99, 10, .5);
}
.btn.live-btn .dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: #7dffb0; opacity: 1; margin-right: 1px;
  box-shadow: 0 0 0 3px rgba(125, 255, 176, .28), 0 0 6px rgba(125, 255, 176, .8);
}
/* 正在上课时点变红（.live-btn 上由 live.js / 面板回写 on 状态时用得上） */
.btn.live-btn.is-on .dot {
  background: #ff8a7a;
  box-shadow: 0 0 0 3px rgba(255, 138, 122, .3), 0 0 6px rgba(255, 138, 122, .9);
}
/* 第 86 轮（需求 1）：面板**开着**时按钮压下去（内凹、无投影）——
   一眼就知道「再点一下会收起来」。⚠️ 不能复用 .is-on：那个是「正在上课」的红点，
   两者可以同时成立（上课中 + 面板开着）。 */
.btn.live-btn.is-panel {
  transform: translateY(1px);
  filter: saturate(.9) brightness(.96);
  box-shadow:
    0 2px 5px rgba(120, 45, 0, .22) inset,
    0 1px 0 rgba(255, 255, 255, .28) inset;
}
.btn.live-btn.is-panel:hover {
  transform: translateY(0);
  box-shadow:
    0 2px 6px rgba(120, 45, 0, .26) inset,
    0 1px 0 rgba(255, 255, 255, .3) inset;
}

/* —— ② /live 浮动窗口 —— */
.page-live { padding: 0 0 18px; }
.live-top { padding: 10px 14px; gap: 9px; }
.live-top .brand-img { height: 26px; }
.live-wrap { padding: 12px 14px 4px; display: flex; flex-direction: column; gap: 12px; }
.live-card {
  border: 1px solid var(--line);
  border-radius: var(--r);
  padding: 13px 15px 15px;
}
.live-card h3 { margin: 0 0 11px; font-size: 15px; }
.live-card .field { margin-bottom: 12px; }
.live-card .field:last-of-type { margin-bottom: 4px; }
.live-card .hint { margin: 6px 0 0; font-size: 12px; color: var(--ink-3); line-height: 1.6; }
.live-card select, .live-card input[type="text"] { width: 100%; }
.live-row2 { display: flex; gap: 10px; margin-top: 10px; }
.live-row2 .btn { flex: 1; }
.live-sw { display: flex; gap: 22px; margin-top: 12px; }
.sw { display: inline-flex; align-items: center; gap: 8px; font-size: 14px; cursor: pointer; }
.sw input { width: 17px; height: 17px; accent-color: var(--brand); cursor: pointer; }
.live-foot { padding: 0 4px; font-size: 12px; color: var(--ink-4); line-height: 1.7; }
.live-dot { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--ink-3); }
.live-dot i {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--ink-4); display: inline-block;
}
.live-dot.on i { background: var(--bad); box-shadow: 0 0 0 3px rgba(217, 66, 47, .18); }
.live-dot.on { color: var(--bad); font-weight: 700; }

/* ============================================================
   第 54 轮：老师端「上课控制」浮动面板（站内浮层，不新开窗口）
   —— .rt-panel 是 core.js 现建的外壳，里面塞 iframe(/live?embed=1)
   ============================================================ */
.rt-panel {
  position: fixed; z-index: 900;
  width: 468px; max-width: calc(100vw - 16px);
  background: #f4faff;
  border: 1px solid var(--line);
  border-radius: 14px;
  box-shadow: 0 18px 48px rgba(16, 48, 79, .18), 0 2px 8px rgba(16, 48, 79, .08);
  display: flex; flex-direction: column;
  overflow: hidden;
}
/* ⚠️ 铁律 9b：自己写了 display:flex，浏览器默认的 [hidden]{display:none} 就失效了 */
.rt-panel[hidden] { display: none !important; }
.rt-panel.is-drag { box-shadow: 0 24px 64px rgba(16, 48, 79, .28); }
.rt-panel-h {
  display: flex; align-items: center; gap: 8px;
  padding: 9px 10px 9px 13px;
  background: linear-gradient(180deg, #f3f8ff, #e9f2fe);
  border-bottom: 1px solid var(--line);
  cursor: move; user-select: none;
}
.rt-panel-t { font-size: 13.5px; font-weight: 700; color: var(--ink); }
/* 第 71 轮（需求 2）：标题栏里那个 .spacer 没设 flex，两枚按钮一直黏在标题右边
   → 补上 flex:1 把它们推到最右侧（「收起」在内、「关掉」最外）。 */
.rt-panel-h .spacer { flex: 1; }
.rt-panel-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--ink-4); flex: none; }
.rt-panel-dot.on { background: var(--bad); box-shadow: 0 0 0 3px rgba(217, 66, 47, .18); }
.rt-panel-btn {
  width: 24px; height: 24px; line-height: 1; flex: none;
  border: 1px solid transparent; border-radius: 7px;
  background: transparent; color: var(--ink-3); font-size: 13px; cursor: pointer;
}
.rt-panel-btn:hover { background: var(--brand-tint); color: var(--brand); }
/* 第 85 轮（需求 3）：固定 508px 时，加了「举手发言」那一条举手区就被裁掉一截（内容实测 628px）。
   现在高度由 iframe 里报上来的真实内容高度决定（core.js 收 postMessage 后改 style.height），
   这里的 640px 只是**消息到之前**的第一帧兜底 —— 两个模式都放得下，不会闪一下再长。 */
.rt-panel-bd { height: 640px; max-height: calc(100vh - 118px); background: var(--bg); overflow: hidden; }
.rt-panel-bd iframe { width: 100%; height: 100%; border: 0; display: block; }
.rt-panel.is-min .rt-panel-bd { display: none; }

/* /live?embed=1：被面板嵌进来时，把浏览器窗口那圈外壳收掉 */
body.is-embed { padding: 0; }
body.is-embed .live-top { display: none; }
body.is-embed .live-wrap { padding: 10px 12px 12px; gap: 10px; }
body.is-embed .live-foot { display: none; }
body.is-embed .live-card { padding: 11px 12px 13px; border-radius: 11px; }
body.is-embed .page-live { padding: 0; }

/* —— 第 54 轮追改：面板内紧凑排版（一屏放完、不出上下滚动条）—— */
/* 第 85 轮：留纵向滚动当**兜底** —— 小屏（视口矮于一屏放完）时 max-height 会卡住面板，
   以前 overflow:hidden 是硬裁（按钮直接看不见），现在能滚到底。高度够时不会出滚动条。 */
body.is-embed { overflow-y: auto; overflow-x: hidden; }
/* 嵌入态下整体再压缩一档间距 */
body.is-embed .live-wrap { gap: 6px; }
body.is-embed .live-card { padding: 8px 11px 9px; }
body.is-embed .live-card h3 { margin: 0 0 6px; font-size: 13.5px; }
body.is-embed .live-card-h { margin: 0 0 7px; }
body.is-embed .live-card .field { margin-bottom: 6px; }
body.is-embed .live-card .field:last-of-type { margin-bottom: 0; }
body.is-embed .live-card .hint { margin: 3px 0 0; font-size: 11px; line-height: 1.45; }
body.is-embed .live-links { gap: 6px; }
body.is-embed .btn.sm { min-height: 28px; padding: 4px 11px; font-size: 12px; }
/* 字段行内排：标签定宽 + 控件占满，一行放下 */
body.is-embed .field-inline { display: flex; align-items: center; gap: 9px; margin-bottom: 5px; }
body.is-embed .field-inline > label {
  margin: 0; flex: none; width: 64px; font-size: 12.5px;
}
body.is-embed .field-inline > select,
body.is-embed .field-inline > .seg { flex: 1; margin: 0; min-width: 0; }
/* 「直播网站」也走行内：label 左、输入框右，一行解决 */
body.is-embed .field.field-inline > input[type="text"] { flex: 1; margin: 0; }
body.is-embed .hint.compact { margin: 1px 0 6px 73px; font-size: 10.5px; color: var(--ink-4); }
/* 第 88 轮：面板里这行提示再压一号字，一行放得下「指令与声音只发给「XX」的 NN 位学生。」 */
body.is-embed .live-card-h .live-cls-tip { font-size: 10.5px; }
body.is-embed .live-mic.compact {
  display: flex; flex-wrap: wrap; align-items: center; gap: 2px 10px;
  margin: 6px 0 0; font-size: 11px;
}
body.is-embed .live-mic.compact span { white-space: nowrap; }
body.is-embed .live-mic.compact #rtMode { color: var(--ink-4); }
body.is-embed .live-mic.compact #rtPeers { color: var(--ink-4); }
body.is-embed .live-row2 { margin-top: 6px; gap: 8px; }
/* 教学管理下面的两枚（在线编程作业 / 学生自学作业）排成两列 —— 比一行两枚省一半高度。
   第 109 轮：上面那一行（标题 + 在线编程教学 + 查看学生实时代码）走 flex 自然宽度，
   所以 grid 只作用于 .live-links-sub。 */
body.is-embed .live-card-links .live-links-sub {
  display: grid; grid-template-columns: 1fr 1fr; gap: 6px;
}
body.is-embed .live-card-links .live-links-sub .btn { width: 100%; }
/* 控件再压一档：输入框 / 下拉 / 分段按钮都收窄高度，保证一屏放完 */
body.is-embed .live-card select,
body.is-embed .live-card input[type="text"] {
  min-height: 32px; padding: 4px 9px; font-size: 13px;
}
body.is-embed .live-card .seg button { min-height: 30px; padding: 4px 10px; font-size: 12.5px; }
/* 第 87 轮：语音模式四选一 / 开播按钮在面板里再压一档 */
body.is-embed .mode-row { margin: 1px 0 5px; gap: 6px; }
body.is-embed .mode-seg { padding: 3px; gap: 3px; }
body.is-embed .mode-seg button { padding: 4px 1px; font-size: 11px; }
body.is-embed .mode-seg button i { font-size: 10.5px; }
/* 第 126 轮：卡片版在面板里也压一档（468px 宽塞三张卡片 + 标签） */
body.is-embed .mode-row { gap: 4px; }
body.is-embed .mode-cards { gap: 3px; }
body.is-embed .mode-card { padding: 3px 5px; border-radius: 8px; }
body.is-embed .mode-card b { font-size: 11px; }
body.is-embed .mode-card i { font-size: 11px; }
body.is-embed .mode-card em { font-size: 9.5px; }
body.is-embed .mode-bar { margin: 0 0 4px; gap: 4px; }
body.is-embed .mode-chips { padding: 4px 5px; gap: 3px; margin: 0 0 4px; }
/* 第 89 轮：多出「开始语音」一枚，四格再窄一档 —— 字不能裁（量过 468 / 375 两种宽度） */
body.is-embed .voice-toggle { min-height: 30px; padding: 4px 7px; font-size: 11.5px; }
body.is-embed .live-toggle { min-height: 30px; padding: 4px 10px; font-size: 12.5px; }
body.is-embed .live-row2 .btn { min-height: 36px; }

/* 「上课」区：标题行右边挂在线人数 */
.live-card-h { display: flex; align-items: center; flex-wrap: wrap; gap: 4px 8px; margin: 0 0 11px; }
.live-card-h h3 { margin: 0; }
.live-online {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12px; font-weight: 700; color: var(--brand);
  background: var(--brand-tint); border-radius: 999px; padding: 3px 10px;
}
.live-online i { width: 7px; height: 7px; border-radius: 50%; background: var(--ok); flex: none; }
/* 第 88 轮（需求 1）：「指令与声音只发给「测试1」的 29 位学生。」钉在「在线 N 人」右边。
   嵌在标题行里，所以：不设块级外边距、不占满一行、允许折行（班级名长了会长一点），
   但不能反过来挤压「在线 N 人」—— 那个 pill 靠 flex:none 钉住，tip 自己让位。 */
.live-card-h .live-cls-tip {
  flex: 0 1 auto; min-width: 0;
  margin: 0; padding: 0;
  font-size: 11px; line-height: 1.35; color: var(--ink-4);
  word-break: break-word;
}
.live-card-h .live-online { flex: none; }
.live-links { display: flex; flex-wrap: wrap; gap: 9px; }
.live-links .btn { text-decoration: none; }
.live-mic { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.mic-state { font-weight: 700; color: var(--ink-2); }
/* 第 90 轮（需求 1）：麦克风总开关删了，.mic-switch / .mic-state.off 两套样式一起撤掉 */

/* 第 90 轮（需求 5）：语音自检结果 —— 一行一项，红了就带一句「该改哪一行」。
   面板宽度只有 468 左右，所以每一项竖排成三段（名字 / 结论 / 建议）。 */
.lk-probe {
  margin: 2px 0 6px;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: #f7fbff;
  font-size: 12px;
  line-height: 1.5;
}
.lk-probe[hidden] { display: none !important; }
.lk-row {
  display: grid;
  grid-template-columns: 14px 78px minmax(0, 1fr);
  gap: 2px 6px;
  padding: 3px 0;
  border-bottom: 1px dashed var(--line-2);
}
.lk-row:last-of-type { border-bottom: 0; }
.lk-row > i { font-style: normal; font-weight: 700; }
.lk-row > b { color: var(--ink-2); font-weight: 700; }
.lk-row > span { color: var(--ink-3); overflow-wrap: anywhere; }
.lk-row > em { grid-column: 2 / -1; font-style: normal; color: #9c5609; }
.lk-row.ok > i { color: #0e7548; }
.lk-row.bad > i { color: #b3261e; }
.lk-verdict {
  margin-top: 6px;
  padding-top: 6px;
  border-top: 1px solid var(--line-2);
  font-weight: 700;
}
.lk-verdict.ok { color: #0e7548; }
.lk-verdict.bad { color: #b3261e; }
body.is-embed .lk-probe { font-size: 11.5px; padding: 6px 8px; }
body.is-embed .lk-row { grid-template-columns: 12px 66px minmax(0, 1fr); }

/* —— ③ 学生端首页「直播」区里的 B 站画面 —— */
.rt-live-box { margin-top: 10px; }
.rt-live-box[hidden] { display: none !important; }
/* 第 63 轮（需求 4）：rt.js 在非首页自建的盒子只服务于「全屏」——
   小窗模式不在这些页面突然冒出画面（首页侧栏那只盒子照常显示小窗）。 */
.rt-live-box.rt-body-box:not(.rt-full) { display: none !important; }
.rt-live-frame {
  position: relative; width: 100%; aspect-ratio: 16 / 9;
  background: #0b1c30; border-radius: 10px; overflow: hidden;
  border: 1px solid var(--line);
}
.rt-live-frame iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
/* 第 53 轮（需求 1）：B 站播放器 —— 宽度占满父容器、自己按 16:9 算高度、没有边框。
   （和上面那条等价，单独给个类名，方便按老师给的样式对号入座。） */
.rt-live-frame iframe.bilibili-embed {
  width: 100%;
  aspect-ratio: 16 / 9;
  border: none;
}
.rt-live-bar { display: flex; align-items: center; gap: 8px; margin-top: 7px; }
.rt-live-bar .spacer { flex: 1; }
.rt-live-tag {
  font-size: 11px; font-weight: 700; color: #ffffff;
  background: var(--bad); padding: 2px 8px; border-radius: 5px;
}
/* 第 121 轮（需求 3）：画面下面**不再有任何按钮**。
   可浏览器规定「没被用户碰过的页面不许出声」，而跨域 iframe 里的点击父页面收不到 ——
   所以改为在画面**上面**铺一层**完全透明**的点击层（`.rt-live-gate`）：
   它不画任何东西、没有文字、也没有边框（不是按钮），只在学生还没碰过页面时存在。
   学生点画面任何位置 → 当场带声音重载播放器 → 这层自动摘掉，之后不再出现。
   `cursor: default`：桌面上鼠标移过去不会变成小手，避免看起来像能点的控件。 */
.rt-live-gate {
  position: absolute; inset: 0; z-index: 2;
  background: transparent; cursor: default;
  -webkit-tap-highlight-color: transparent;
}
/* 第 66 轮（需求 1）：画面下面那行老师名的样式（.rt-live-who）随节点一起删了 */
/* 第 63 轮（需求 4）：外链小字删了，配套样式一并清掉 */

/* 「全屏」= 把这个**视频窗口**铺满整屏（不是整页全屏）。
   真·element fullscreen 被浏览器放行时由它接管；没有用户手势被拒时就用这层兜底，
   两种情况下眼睛看到的是同一个东西。 */
.rt-live-box.rt-full {
  position: fixed; inset: 0; z-index: 9990; margin: 0;
  background: #06121f; display: flex; flex-direction: column;
}
.rt-live-box.rt-full .rt-live-frame {
  flex: 1; aspect-ratio: auto; height: auto; border-radius: 0; border: 0;
}
.rt-live-box.rt-full .rt-live-bar { margin: 0; padding: 7px 14px; background: rgba(6, 18, 31, .92); }

/* 第 101 轮（需求 1）：那枚「🔊 点一下，听老师说话」的小药丸删了 ——
   它对应的样式（.rt-tap）连同节点一起清掉，课上一枚按钮都不该挡在画面上。
   解锁改由 lk.js 的自动重试 + 全局手势完成，学生什么都不用点。 */

/* ============ 第 101 轮（需求 4）：手机 / 平板上的全屏 ============
   老师一切「全屏」，学生端（平板 / 手机）就整屏让位给直播：
     · 横着拿 → 直接把盒子铺满（.rt-mobile）；
     · 竖着拿 → 把画面**转 90°**铺满（.rt-vert），看着就是横屏视频，
       学生不用自己转设备（浏览器只有进了全屏才允许锁方向，进不去时用 CSS 兜）。
   右上角那枚 ✕ 只在手机上出现 —— 那里按不到 Esc。 */
.rt-live-exit {
  display: none;
  position: absolute; top: 10px; right: 10px; z-index: 3;
  width: 36px; height: 36px; padding: 0;
  align-items: center; justify-content: center;
  border: 0; border-radius: 50%;
  background: rgba(6, 18, 31, .58); color: #ffffff;
  font-size: 15px; line-height: 1; cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.rt-live-box.rt-full.rt-mobile .rt-live-exit { display: inline-flex; }
/* 手机上全屏时画面下面那一行整个让位（第 121 轮起那行里只剩「正在直播」小标，
   学生最需要的是画面本身）—— 声音不再靠按钮，见上面 .rt-live-gate 那段。 */
.rt-live-box.rt-full.rt-mobile .rt-live-bar { display: none; }

/* 竖屏：把 16:9 的画面转 90°，宽高按屏幕的**长边 / 短边**填满。
   变换后的可视宽 = height(100vw)，可视高 = width(100vh) —— 正好一整屏。 */
.rt-live-box.rt-full.rt-vert { overflow: hidden; }
.rt-live-box.rt-full.rt-vert .rt-live-frame {
  position: fixed; left: 50%; top: 50%;
  width: 100vh; height: 100vw;
  width: 100dvh; height: 100dvw;
  transform: translate(-50%, -50%) rotate(90deg);
  transform-origin: center center;
  border-radius: 0; border: 0; background: #000000;
}
@media (max-width: 900px) {
  .rt-live-box.rt-full .rt-live-bar { padding: 6px 10px; }
}

/* ============ 第 105 轮（需求 2）：老师端「直播监听」小窗 ============
   老师这一半的 B 站声音：钉在右下角，播的是和学生端**同一个**播放器地址
   （mute=0），所以老师听到的就是学生听到的。
   为什么不做进面板流里：面板按内容真实高度自适应（第 54 轮要求一屏放完），
   插一块 16:9 进去会把面板撑高一大截；浮层不占布局，还能自己收起。
   收起时用 height:0 而不是 display:none —— 藏掉的 iframe 里媒体可能被浏览器挂起。 */
.tch-live {
  position: fixed; right: 14px; bottom: 14px; z-index: 60;
  width: 236px;
  background: #06121f;
  border: 1px solid rgba(255, 255, 255, .14);
  border-radius: 10px;
  overflow: hidden;
  box-shadow: 0 12px 34px rgba(6, 18, 31, .42);
  font-family: var(--sans);
}
.tch-live[hidden] { display: none !important; }
.tch-live-h {
  display: flex; align-items: center; gap: 6px;
  padding: 5px 7px 5px 9px;
  background: rgba(255, 255, 255, .07);
  color: #eaf3fd;
  font-size: 11.5px; font-weight: 700; line-height: 1;
}
.tch-live-dot {
  flex: none; width: 6px; height: 6px; border-radius: 50%;
  background: #ff5a3c; box-shadow: 0 0 0 3px rgba(255, 90, 60, .22);
}
.tch-live-t { letter-spacing: .02em; white-space: nowrap; }
.tch-live-h .spacer { flex: 1 1 auto; }
.tch-live-btn {
  flex: none;
  width: 20px; height: 20px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; border-radius: 6px;
  background: rgba(255, 255, 255, .1); color: #eaf3fd;
  font-size: 11px; line-height: 1; cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.tch-live-btn:hover { background: rgba(255, 255, 255, .22); }
.tch-live-btn.off { color: #ffb4a2; }
.tch-live-bd { width: 100%; aspect-ratio: 16 / 9; background: #000; }
.tch-frame { display: block; width: 100%; height: 100%; border: 0; }
.tch-live.tch-min { width: 132px; }
.tch-live.tch-min .tch-live-bd { height: 0; aspect-ratio: auto; overflow: hidden; }

/* —— 第 53 轮（需求 2）：live 页的麦克风状态行（原来那块「语音」删了，只留这行）—— */
.live-mic { line-height: 1.95; }
.mic-state {
  display: inline-block; margin-left: 4px; padding: 1px 8px;
  border-radius: 999px; background: #eef2f7; color: var(--ink-3);
  font-weight: 700; white-space: nowrap;
}
.mic-state.on { background: #e6f6ec; color: #1f8a4c; }
.mic-state.bad { background: #fdeceb; color: var(--bad); }
.live-mic .btn { margin-left: 6px; vertical-align: middle; }

/* ============================================================
   第 87 轮（需求 1/2）：语音模式改成四选一（radiogroup）+ 独立的开播按钮
   —— 第 84 轮那套「四个大色块 + aria-pressed」撤了：
      现在同一时刻只可能有一种模式，选中才上色（绿/橙/紫/蓝沿用原来的语义），
      其余白底描边、字是灰的，一眼就能看出「这是单选」。
      标签「语音模式」在左，和上面「班级 / 直播网址 / 学生端画面」三行对齐（64px）。
   ============================================================ */
.live-card .field-inline { display: flex; align-items: center; gap: 9px; }
.live-card .field-inline > label { flex: none; width: 64px; margin: 0; }
.live-card .field-inline > input[type="text"],
.live-card .field-inline > select { flex: 1 1 0; min-width: 0; margin: 0; }
/* 独立打开 /live 时「学生端画面」也和别的控件一样宽，不要缩成一个小药丸 */
.live-card .field-inline > .seg { flex: 1 1 0; min-width: 0; max-width: 460px; margin: 0; }

/* 第 118 轮：「全班讨论」删掉，模式格回到两格（老师讲课 / 学生提问）。
   这一行仍然**不许折行** —— 两格 + 右边的「开始语音」+「一对一」标签要一起待着。
   `.mode-row` 保持 nowrap，`.mode-seg` 两列等分（挤的时候两格一起变窄，
   字被裁一点也比折成两行好看；embed 态另有更小的一档字号，见上文 is-embed）。 */
.mode-row { display: flex; align-items: center; gap: 9px; margin: 2px 0 6px; flex-wrap: nowrap; }
.mode-lab {
  flex: none; width: 64px;
  font-size: 12.5px; font-weight: 700; color: var(--ink-2); letter-spacing: .02em;
}
.mode-seg {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 4px;
  flex: 1 1 0; min-width: 0; max-width: 520px;
  padding: 4px; border: 1px solid var(--line-2); border-radius: 10px;
  background: #f4f9ff;
}
/* 第 124 轮：又回到三格（老师讲课 / 学生提问 / 全班讨论）—— 三格等分，
   每格 ≈140px 仍然放得下「图标 + 四个字」，不用缩字号。
   第 92 / 118 轮中间删过「全班讨论」，现在按 SFU 方案回来；这一行仍然**不许折行**。 */
.mode-seg button {
  min-width: 0; overflow: hidden;
  display: inline-flex; align-items: center; justify-content: center; gap: 2px;
  padding: 5px 3px;
  border: 1.5px solid var(--line); border-radius: 8px;
  background: #fff; color: var(--ink-3);
  font: inherit; font-size: 12px; font-weight: 700; line-height: 1.25;
  white-space: nowrap; cursor: pointer;
  transition: background var(--dur-fast), color var(--dur-fast), border-color var(--dur-fast);
}
.mode-seg button i { font-style: normal; font-size: 11.5px; line-height: 1; }
.mode-seg button:hover { border-color: var(--brand-2); color: var(--brand); }
/* 选中态：压实一层（内阴影 + 位移），和原来「按下去」的手感一致 */
.mode-seg button[aria-checked="true"] {
  color: #fff;
  box-shadow: inset 0 2px 5px rgba(8, 30, 52, .26), 0 4px 10px -8px rgba(16, 48, 79, .8);
}
.mode-seg button[data-mode="teach"][aria-checked="true"] {
  background: linear-gradient(180deg, #4ed29a, #22b573); border-color: #189a60;
}
.mode-seg button[data-mode="raise"][aria-checked="true"] {
  background: linear-gradient(180deg, var(--orange-2), var(--orange)); border-color: #e8630a;
}
/* 第 124 轮：「全班讨论」—— 蓝色系，和「讲课（绿）/ 提问（橙）」一眼分开。
   三格同时亮是不可能的（radiogroup），所以颜色只是「现在处在哪一种」。 */
.mode-seg button[data-mode="talk"][aria-checked="true"] {
  background: linear-gradient(180deg, #5aa9f0, #2f7fd1); border-color: #2768ad;
}
/* 第 119 轮（需求 5-2）：直播期间这两格**不能用**（话筒和画面都在 B 站那头）。
   灰化必须看得出来 —— 只是「点了没反应」的话，老师会以为面板坏了去翻服务器。
   当前所在那一格仍保留自己的颜色（一眼知道现在是什么模式），只是整体压暗。 */
.mode-seg button[disabled] { opacity: .45; cursor: not-allowed; }
.mode-seg button[disabled]:not([aria-checked="true"]):hover {
  border-color: var(--line); color: var(--ink-3);
}
/* 第 92 轮：一对一语音挪到 codes 页之后，这一页只用一枚小标签报事实（不提供开关）。
   第 112 轮：它原来占 `flex: 1 0 100%`（自己一整行）—— 现在这一行**不许折行**，
   所以改成「跟在最后一格后面、放不下就省略号」，绝不把整行挤成两行。 */
.mode-duo-tag {
  flex: 0 1 auto; min-width: 0; overflow: hidden;
  padding: 2px 0 0;
  font-size: 11.5px; font-weight: 700; color: #1a5fb8;
  white-space: nowrap; text-overflow: ellipsis;
}
.mode-duo-tag[hidden] { display: none !important; }
/* 第 118 轮：`.mode-note`（全班讨论那行统计）随功能一起删掉。
   提问模式里同一时刻只有一个人能说，不存在「几个人开成麦」这个问题。 */
/* 窄屏（手机 / 分屏）再让一档 —— 两格 + 开关 + 标签必须待在一行，
   放不下就从留白和「语音模式」这四个字上抠：标签从定宽 64 改成自适应，间距 9→6。 */
/* 第 127 轮：模式那一行现在有四格（三张卡片 + 关闭语音），窄屏先把它压实，
   保证四个永远在同一行、右端那枚不被挤出屏幕（需求 3 要的就是「同一行」）。 */
@media (max-width: 560px) {
  .mode-row { gap: 4px; }
  .mode-card { padding: 6px 4px; gap: 3px; }
  .mode-card b { font-size: 11.5px; }
  .mode-card i { font-size: 11.5px; }
  .mode-row .voice-toggle { padding: 4px 6px; font-size: 11px; }
}
@media (max-width: 430px) {
  .mode-row { gap: 6px; }
  .mode-lab { width: auto; font-size: 11.5px; }
  .mode-seg { max-width: none; gap: 3px; padding: 3px; }
  .mode-seg button { font-size: 11px; padding: 4px 1px; }
  .mode-seg button i { font-size: 10.5px; }
  /* 第 126 轮：卡片版窄屏 —— 说明那行整句收起来（只留图标 + 四个字），
     否则 3 张卡片各 ~110px，「举手 → 你点谁说」会被截成半个字。 */
  .mode-cards { max-width: none; gap: 3px; }
  .mode-card { padding: 4px 5px; column-gap: 3px; }
  .mode-card b { font-size: 11.5px; }
  .mode-card em { display: none; }
  .mode-card { grid-template-areas: "ico name"; }
  .voice-toggle { padding: 4px 8px; font-size: 11.5px; }
  .mode-duo-tag { font-size: 11px; }
}
/* 开 / 停播：钉在直播网址输入框右边，定宽不抢输入框的宽度 */
.live-toggle { flex: none; min-height: 32px; padding: 4px 12px; font-size: 13px; }

/* ============================================================
   第 126 轮：语音模式改成**卡片**（老师讲课 / 学生提问 / 全班讨论）+ 卡片下的学生按钮
   ============================================================
   为什么改卡片：原来三格只是三个小药丸，老师看不出「这一档谁能说话」；
   现在每张卡片多一行小字（只有你的麦 / 举手→你点谁说 / 申请→你点谁说），
   而且点哪张、那张底下就展开对应的学生按钮（`#rtModeChips`）。
   卡片仍然用 radiogroup 语义（role=radio + aria-checked），JS 判的是 aria-checked，
   所以配色规则和原来 `.mode-seg button` 一一对应，只是换了层皮。 */
/* 第 127 轮（需求 3）：**「关闭语音」和三张卡片同一行** —— 一行四个格子，
   卡片占前三（等分），按钮在最右端、不参与伸缩。窄屏也不换行（`nowrap`），
   卡片里已经没有说明小字了，四个字 + 图标在 80px 上下就放得下。 */
.mode-row {
  display: flex; align-items: stretch; gap: 6px;
  margin: 0 0 6px; min-width: 0; flex-wrap: nowrap;
}
.mode-row .voice-toggle { flex: none; align-self: stretch; white-space: nowrap; }
.mode-cards {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 5px;
  flex: 1 1 0; min-width: 0;
}
/* 第 127 轮：说明文字（只有你的麦 / 举手→你点谁说 / 申请→你点谁说）按需求删掉，
   卡片从「两行网格」变回**一行**：图标 + 名字居中。 */
.mode-card {
  min-width: 0; overflow: hidden;
  display: flex; align-items: center; justify-content: center; gap: 5px;
  padding: 7px 6px;
  border: 1.5px solid var(--line); border-radius: 10px;
  background: #fff; color: var(--ink-3);
  font: inherit; line-height: 1.2; text-align: center;
  cursor: pointer;
  transition: background var(--dur-fast), color var(--dur-fast),
              border-color var(--dur-fast), box-shadow var(--dur-fast);
}
.mode-card i { flex: none; font-style: normal; font-size: 13px; line-height: 1; }
.mode-card b {
  min-width: 0; overflow: hidden;
  font-size: 12.5px; font-weight: 700; white-space: nowrap; text-overflow: ellipsis;
}
/* `.mode-card em` 留着（老页面 / 缓存里的旧 HTML 可能还带说明行）：真出现了也不撑破卡片 */
.mode-card em {
  min-width: 0; overflow: hidden; display: none;
  font-style: normal; font-size: 10.5px; font-weight: 600;
  color: #7c8b9e; white-space: nowrap; text-overflow: ellipsis;
}
.mode-card:hover { border-color: var(--brand-2); color: var(--brand); }
.mode-card[aria-checked="true"] { color: #fff; box-shadow: 0 6px 14px -10px rgba(16, 48, 79, .9); }
.mode-card[aria-checked="true"] em { color: rgba(255, 255, 255, .82); }
/* 第 127 轮：选中态的绿压深一档 —— 白字压在 #4ed29a 上对比太低，
   投影到大屏上「老师讲课」四个字看着像灰的。 */
.mode-card[data-mode="teach"][aria-checked="true"] {
  background: linear-gradient(180deg, #2ec286, #128a58); border-color: #0f7a4d;
}
.mode-card[data-mode="raise"][aria-checked="true"] {
  background: linear-gradient(180deg, var(--orange-2), var(--orange)); border-color: #e8630a;
}
.mode-card[data-mode="talk"][aria-checked="true"] {
  background: linear-gradient(180deg, #5aa9f0, #2f7fd1); border-color: #2768ad;
}
.mode-card[disabled] { opacity: .45; cursor: not-allowed; }
.mode-card[disabled]:not([aria-checked="true"]):hover {
  border-color: var(--line); color: var(--ink-3);
}

/* 模式操作条：左侧「📞 一对一 / 和 X 通话中」，右侧「N 人申请 / 挂断 / 关闭语音」。
   挂断从「一对一语音」的标题上挪到这儿 —— 那一区在 raise / talk 下会整块收起。 */
.mode-bar {
  display: flex; align-items: center; gap: 6px;
  margin: 0 0 6px; min-width: 0;
}
.mode-bar .spacer { flex: 1 1 auto; }
.mode-bar .live-pair-who { font-size: 11.5px; font-weight: 700; color: #b3261e; white-space: nowrap; }
.mode-bar .live-pair-who[hidden] { display: none !important; }
.mode-bar .pair-ask-n { flex: none; }
.mode-bar .live-pair-hang { flex: none; min-height: 30px; padding: 3px 10px; font-size: 12px; }

/* 模式卡片下面那排学生按钮：和「一对一语音」区那一排长得一样（复用 .pair-chip），
   只是外面套一层虚线框，让人看出「这排属于上面那张卡片」。 */
.mode-chips {
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px;
  margin: 0 0 6px; padding: 6px 7px;
  border: 1px dashed #cbdcee; border-radius: 10px;
  background: #fbfdff;
}
.mode-chips[hidden] { display: none !important; }
.mode-chips .pair-empty { font-size: 11.5px; color: var(--ink-3); padding: 1px 2px; }

/* ============ 第 128 轮：学生名单**直接**排在模式卡片下面 ============
   第 127 轮这里是「两张页签（✋ 学生提问 / 💬 全班讨论）+ 内容格」的选项卡。
   现场用下来那张「全班讨论」页签是多余的 —— 模式本来就靠上面三张卡片切，
   页签再把同一件事列一遍，反倒让人以为「点页签」和「点卡片」是两回事。
   所以整条页签栏（`.mode-tab-h` / `.mode-tab-b`）连样式一起删掉：
   进哪一档，`#rtModeChips` 就画哪一档的人，外框只当一张「名单卡」。
   `#rtAskN`（📞 N 人在呼叫）平时不占位，有人呼你时才在左上角冒出来。 */
.mode-tab {
  margin: 0 0 6px; border: 1px solid #cbdcee; border-radius: 10px;
  background: #fbfdff; overflow: hidden;
}
.mode-tab[hidden] { display: none !important; }
.mode-tab-n {
  display: block; padding: 5px 8px 0;
  font-size: 11px; font-weight: 700; color: #b3261e; white-space: nowrap;
}
.mode-tab-n[hidden] { display: none !important; }
/* 名单那格：**沿用原来那排学生按钮的排版**（换行 + 限高滚动），
   外面的实线卡片就是它唯一的框，不再单独画一层。 */
.mode-tab-body {
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px;
  min-height: 26px; max-height: 150px; overflow-y: auto; overflow-x: hidden;
  padding: 6px 7px; background: #fbfdff;
}
.mode-tab-body .pair-empty { font-size: 11.5px; color: var(--ink-3); padding: 1px 2px; }

/* 第 89 轮（需求 1）：「开始语音 / 关闭语音」钉在「一对一」右边，和四选一同行。
   它是一个**开关**、不是模式（所以写在 radiogroup 外面），定宽、不参与四等分，
   挤的是 .mode-seg（四格随之变窄，embed 态另外再压字）。 */
.voice-toggle { flex: none; min-height: 32px; padding: 4px 11px; font-size: 12.5px; white-space: nowrap; }

/* 第 112 轮（需求 3）：原来这里是一整套「举手队列条」的样式
   （`.floor-bar` / `.floor-list` / `.floor-chip` 三种状态 + chipLive 呼吸光圈）——
   提问条整条删掉、圆片并进「一对一语音」区之后，这些类一个都不再出现，一并清掉。
   提问中的样子改由 `.pair-chip.ask`（绿白交替）表达，见本文件「一对一语音」那一段。 */

/* 第 92 轮：codes 页顶行那枚「打开 / 关闭一对一语音」（两态按钮，绿色系 = 语音）。
   关着时浅绿描边（一眼能看出「可以点」），开着时实绿（跟「通话中」同一个色）。 */
.btn.duo-btn {
  --bg: #e9f8f0; --fg: #1b7a53; --bd: #bfe7d3;
  font-weight: 700; white-space: nowrap;
}
.btn.duo-btn:hover { --bg: #d8f2e4; --bd: #9fdcc0; --sd: 0 8px 18px -10px rgba(23, 178, 106, .7); }
.btn.duo-btn.is-on {
  --bg: #17b26a; --fg: #ffffff; --bd: #12945a;
  --sd: 0 8px 18px -10px rgba(23, 178, 106, .85);
}
.btn.duo-btn[disabled] { opacity: .6; }

/* 第 119 轮（需求 3/4）：学生端顶栏这枚按钮改名叫「语音呼叫老师」，配色改成**浅绿**
   （原来「我要提问」是活力橙实心）。
   为什么改脸：这一轮把「学生提问」模式改成「师生都退出大房间」——
   它不再是一个「排队等老师点名」的动作，而是**呼叫老师单独说话**，
   浅绿在顶栏里和橙色主按钮分得开，也不会被误当成「上课」那种重磅动作。
   · 常态（v6 浅底深字）：浅绿底 + 深绿字
   · 已呼叫（is-wait）：绿底加深一点，表示「正在等」
   · 通话中（is-talk）：深绿实心 + 白字，麦是开的，一眼看得出 */
.btn.floor-ask {
  --bg: #e9f8f0;
  --fg: #1b7a53;
  --bd: #bfe7d3;
  --bg-h: #d7f2e4;
  --fg-h: #146141;
  --bd-h: #9dd8bd;
  --bg-a: #c7ebd9;
  --fg-a: #0f5132;
  --bd-a: #8acdae;
  --sd: none;
  font-weight: 700;
}
.btn.floor-ask:hover { --sd: 0 8px 20px -12px rgba(27, 122, 83, .55); }
.btn.floor-ask.is-wait {
  --bg: #cdeee0; --fg: #0f5132; --bd: #8acdae;
  --bg-h: #bde8d5; --fg-h: #0b3f27; --bd-h: #6fc39d;
  --bg-a: #aedfc8; --fg-a: #0b3f27; --bd-a: #5cb68e;
}
.btn.floor-ask.is-talk {
  --bg: linear-gradient(180deg, #2fb37c, #1b7a53);
  --fg: #ffffff; --bd: #15613f;
  --bg-h: #1b7a53; --fg-h: #ffffff;
}
.btn.floor-ask[disabled] { opacity: .75; cursor: default; }
.btn.floor-ask[hidden] { display: none !important; }

/* ============ 第 124 轮：学生端「✋ 举手发言」（SFU 星型方案 · 学生提问模式）============
   和上面那枚「🎤 语音呼叫老师」并排，但**语义完全不同**：
     · 呼叫老师 = 叫老师单独说两句（私密房，只有你们俩听得见）；
     · 举手发言 = 排队等老师点名，点到你 → 你的声音由 SFU 复制给**全班**。
   三态：
     默认（未授权）  浅黄底  「✋ 举手发言」
     is-wait         绿白交替闪（和老师面板上那枚 `ask` 是同一个语言）「✋ 已举手，等老师」
     is-talk         浅红    「🎤 正在发言 · 点此结束」 */
.btn.hand-ask {
  --bg: #fff7e6; --fg: #9a6200; --bd: #f0d7a6;
  --bg-h: #ffefd2; --fg-h: #7a4d00; --bd-h: #e6c583;
  --bg-a: #ffe7bd; --fg-a: #6b4400; --bd-a: #dcb96f;
  --sd: none; font-weight: 700;
}
.btn.hand-ask:hover { --sd: 0 8px 20px -12px rgba(154, 98, 0, .45); }
.btn.hand-ask.is-wait {
  --bg: #ffffff; --fg: #0f7a49; --bd: #1f9d5a;
  --bg-h: #17b26a; --fg-h: #ffffff; --bd-h: #12945a;
  --bg-a: #17b26a; --fg-a: #ffffff;
  animation: pairAskBlink 1.1s steps(1, end) infinite;
}
@media (prefers-reduced-motion: reduce) { .btn.hand-ask.is-wait { animation: none; } }
.btn.hand-ask.is-talk {
  --bg: #fdecec; --fg: #b3261e; --bd: #f0b4b0;
  --bg-h: #fbdada; --fg-h: #96201a; --bd-h: #e79b96;
  --bg-a: #f9cdcb; --fg-a: #7d1a15;
  animation: none;
}
.btn.hand-ask[disabled] { opacity: .75; cursor: default; }
.btn.hand-ask[hidden] { display: none !important; }
/* 第 127 轮：学生端**唯一**那枚说话按钮 `#btnSpeak`（三处落点共用一套样式）。
   直接搭 `.hand-ask` 的三态（平时 / is-wait / is-talk），只把**平时**那档换成蓝色系：
     · 提问模式 idle        → 浅蓝「📞 我要发言」
     · 提问模式 is-wait     → 红白闪「📞 已呼叫 · 点此取消」（和老师面板上那枚同一个语言）
     · 通话中 / 正在发言    → 浅红 is-talk「📞 通话中 · 点此挂断」/「🎤 正在发言 · 点此结束」 */
.btn.speak-ask {
  --bg: #eaf3ff; --fg: #1a5fb8; --bd: #b9d3f0;
  --bg-h: #dceafd; --fg-h: #134a91; --bd-h: #96bde8;
  --bg-a: #cfe2fa; --fg-a: #0f3d78; --bd-a: #7fb0e0;
}

/* ============================================================
   第 137 轮（需求 2 / 3 / 4）：这枚说话按钮改成**左缘磁吸小标签**
   ============================================================
   首页右上角、编程页「举手求助」右边、编辑页顶部中间的那枚「📞 我要发言」，
   三处统一挪到左上角 logo 下方，做成和右上角「目录」(.toc-mag / .toc-tab)
   同一套磁吸语言 —— 只不过它长在左边缘、文字竖排、只圆右边两角。
   fixed 定位，滚到哪儿都贴在窗口左缘（学生写代码滚到页尾也点得到）。

   结构：body > #speakMag（这层只管位置）> button.btn.speak-ask（这层只管长相），
   由 core.js 的 mountSpeakBtn() 建，显隐跟着按钮本身走。 */
.speak-mag {
  position: fixed;
  left: 0;
  /* 顶栏（logo 那条）大约 65px 高，88 是「刚好落在它下面」——
     和右上角「目录」同一个数值，两枚磁吸标签左右齐平。 */
  top: 88px;
  /* 60：压在正文/顶栏(40)之上，但在遮罩(65)、左抽屉(70)、对话框(90)之下 ——
     一弹窗，这枚小标签就自觉退到后面去。 */
  z-index: 60;
}
.speak-mag[hidden] { display: none; }

/* ⚠️ 选择器必须写到 .speak-mag > .btn.speak-ask（0,3,0）：
   只写 .speak-ask 是 0,2,0，压不过 .btn 的 min-height / padding / 圆角，
   竖排会变成一条被撑圆的横条。 */
.speak-mag > .btn.speak-ask {
  display: block;              /* .btn 是 inline-flex；竖排下 flex 会把字排成横的 */
  width: 28px;
  min-height: 0;
  height: auto;
  padding: 12px 0;
  writing-mode: vertical-rl;   /* 文字竖排，和目录那枚同款 */
  text-align: center;
  font-size: 12.5px;
  font-weight: 700;
  line-height: 1;
  letter-spacing: 2px;
  border-left: 0;              /* 贴死左缘，只留右边一条边 */
  border-radius: 0 10px 10px 0;
  box-shadow: 2px 2px 10px rgba(16, 48, 79, .14);
}
.speak-mag > .btn.speak-ask:hover { box-shadow: 3px 3px 14px rgba(16, 48, 79, .2); }
.speak-mag > .btn.speak-ask[hidden] { display: none !important; }

/* 手机（≤620px）跟「目录」一样挪到左下角：这个宽度的左上角留给返回 / 标题，
   竖排标签贴在那儿会压住第一行正文。 */
@media (max-width: 620px) {
  .speak-mag { top: auto; bottom: 92px; }
}

/* ============================================================
   第 52 轮（需求 7）：呼叫老师
   —— 学生 code 页那枚「📞 呼叫老师」、老师 codes 页行内的 📞 + 排队序号、
      以及「呼叫者不在当前列表里」的兜底条。
   ============================================================ */

/* ---------- 学生端：编程题左上角的「语音呼叫老师」（挨着「举手求助」） ---------- */
/* 第 119 轮（需求 3）：浅绿底 + 深绿字（原来是浅蓝）。
   浅绿 = .btn.mint 那一套色阶，和老师端「清除举手」那枚同色系：
   它和「举手求助」（浅蓝）并排，一个「叫人过来」、一个「单独说话」，颜色先分开。 */
.btn.call-btn {
  --bg: #e9f8f0;
  --fg: #1b7a53;
  --bd: #bfe7d3;
  --bg-h: #d7f2e4;
  --fg-h: #146141;
  --bd-h: #9dd8bd;
  --bg-a: #c7ebd9;
  --fg-a: #0f5132;
  --bd-a: #8acdae;
  --sd: none;
  font-weight: 700;
}
.btn.call-btn:hover { --bg: #d7f2e4; }
.btn.call-btn.is-wait {                 /* 已呼叫、等老师接通 */
  --bg: #cdeee0;
  --fg: #0f5132;
  --bd: #8acdae;
}
.btn.call-btn.is-live {                  /* 通话中 */
  --bg: #17b26a;
  --fg: #ffffff;
  --bd: #17b26a;
  animation: callPulse 1.5s var(--ease) infinite;
}
@keyframes callPulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(23, 178, 106, .5); }
  50%      { box-shadow: 0 0 0 6px rgba(23, 178, 106, 0); }
}
@media (prefers-reduced-motion: reduce) { .btn.call-btn.is-live { animation: none; } }

/* ---------- 老师端：学生行用户名右侧那枚 📞（排队会轻轻摆动，跟进「求助」） ---------- */
.row-call {
  display: inline-flex; align-items: center; gap: 2px;
  margin-left: 6px; padding: 1px 6px 1px 4px;
  /* 第 84 轮（需求 4）：等待接通也做成**绿色**电话图标（原来是浅蓝，
     只有通话中才转绿）。改成绿色以后，老师在名单里一眼就能扫到「有人在等我」。 */
  border: 1px solid rgba(23, 178, 106, .45);
  border-radius: 999px;
  background: #e8f8ef; color: #12945a;
  font: inherit; font-size: 11px; font-weight: 700; line-height: 1.55;
  vertical-align: middle;
  cursor: pointer;
  animation: helpWave 1.15s var(--ease) infinite;
}
.row-call:hover { background: #d3f2e2; border-color: #12945a; }
.row-call .rc-ico { font-size: 11px; line-height: 1; }
/* 第 92 轮：没人呼叫时**也挂一枚绿电话** —— 老师点它是「我主动叫这位学生」。
   不摆动（没有人在等我，晃起来会跟「有人在呼叫」混淆），鼠标移上去才亮。 */
.row-call.is-idle {
  padding: 1px 6px;
  background: rgba(232, 248, 239, .55); color: #12945a;
  border-color: rgba(23, 178, 106, .3);
  animation: none;
  opacity: .78;
}
.row-call.is-idle:hover { background: #d3f2e2; border-color: #12945a; opacity: 1; }
.row-call .rc-seq {
  min-width: 13px; padding: 0 3px; border-radius: 999px;
  background: #17b26a; color: #ffffff; font-size: 10px; text-align: center;
}
.row-call.is-on {                        /* 通话中：变绿、不再摆动 */
  background: #17b26a; border-color: #17b26a; color: #ffffff;
  animation: none;
}
.row-call.is-on:hover { background: #12945a; border-color: #12945a; }
.row-call .rc-tag { font-size: 11px; }
@media (prefers-reduced-motion: reduce) { .row-call { animation: none; } }

/* 正在通话的那一行，整行淡淡地泛绿 */
.live-row.is-calling,
.live-row.is-calling .u-name { color: #12945a; }
.live-row.is-calling { background: rgba(23, 178, 106, .06); }

/* ---------- 兜底条：有呼叫者不在当前列表里时（学生换班 / 关了在线筛选） ---------- */
.call-bar {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
  margin: 0 0 8px; padding: 8px 12px;
  border: 1px solid rgba(214, 158, 46, .45);
  border-left: 3px solid #d69e2e;
  border-radius: 8px;
  background: #fffaf0;
  font-size: 12.5px; color: var(--ink-3);
}
.call-bar .cb-lead { font-weight: 700; color: #a9691a; }
.call-bar .cb-item { display: inline-flex; align-items: center; gap: 5px; }
.call-bar .cb-item.is-on { color: #12945a; }
.call-bar .cb-seq { font-style: normal; font-size: 11px; color: var(--ink-4); }
.call-bar .cb-off { font-style: normal; font-size: 11px; color: var(--ink-4); margin-left: 2px; }
/* ⚠️ 铁律 9b：上面给了 display:flex，会把浏览器默认的 [hidden]{display:none} 盖掉 ——
   必须自己写死一条，否则「没有兜底呼叫」时这条永远挂在表格上方。 */
.call-bar[hidden] { display: none !important; }

/* ============================================================
   第 57 轮：课程管理（/lesson）+ 课程·课节内容编辑（/lessonedit）
   —— 一张表管课程、一张表管课节；状态是「显示/隐藏」的小开关，
      授权对象用一枚「设置」按钮弹对话框改。
   ============================================================ */
.ls-main {
  width: min(1420px, 100% - clamp(10px, 1.4vw, 22px));
  margin: 0 auto; padding: 16px 0 40px;
}
.ls-panel { margin-bottom: 18px; }
.ls-panel .panel-head { flex-wrap: wrap; row-gap: 8px; }
.ls-tip { font-size: 12px; color: var(--ink-4); }
.ls-foot-note {
  font-size: 12.5px; line-height: 1.85; color: var(--ink-3);
  border: 1px dashed var(--line-2); border-radius: var(--r); padding: 12px 15px; margin: 0;
  background: rgba(47, 128, 237, .04);
}
.ls-foot-note b { color: var(--brand-3); }

/* ---------------- 表格 ---------------- */
.ls-scroll { overflow-x: auto; }
.ls-table { width: 100%; border-collapse: collapse; min-width: 900px; }
.ls-table thead th {
  text-align: left; font-family: var(--mono); font-size: 11px; letter-spacing: .12em;
  text-transform: uppercase; color: var(--ink-3); font-weight: 700; white-space: nowrap;
  padding: 9px 13px; border-bottom: 1px solid var(--line-2); background: rgba(47, 128, 237, .06);
}
.ls-table tbody td { padding: 10px 13px; border-bottom: 1px solid var(--line-2); vertical-align: middle; }
.ls-row { cursor: pointer; transition: background var(--dur-fast) var(--ease); }
.ls-row:hover { background: rgba(47, 128, 237, .05); }
/* 选中的那门课：左侧一道橙条 + 淡橙底，一眼看出「右边课节属于它」 */
.ls-row.on { background: var(--orange-tint); box-shadow: inset 3px 0 0 0 var(--orange); }
.ls-table th.ls-st { width: 74px; }
.ls-table th.ls-seq { width: 62px; }
.ls-table th.ls-mid { width: 84px; }
.ls-table th.ls-grant { width: 230px; }
.ls-table th.ls-act { width: 300px; }
.ls-seq { font-family: var(--mono); font-size: 13px; color: var(--ink-3); }
.ls-name { min-width: 220px; }
.ls-title { font-size: 14.5px; font-weight: 700; color: var(--ink); text-decoration: none; }
.ls-title:hover { color: var(--brand); text-decoration: underline; text-underline-offset: 3px; }
.ls-sub { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 4px; }
.ls-owner { font-size: 11.5px; color: var(--ink-4); }
.ls-brief { font-size: 11.5px; color: var(--ink-3); }
.chip.ls-mini { font-size: 10.5px; padding: 1px 6px; }
.ls-gt { font-size: 12.5px; color: var(--ink-2); }
.ls-set { margin-left: 7px; }
.ls-act { white-space: nowrap; }
.ls-act .btn, .ls-act .btn.sm { margin-right: 6px; }
.ls-act .btn:last-child { margin-right: 0; }

/* 状态开关：显示 = 橙实心（行动中），隐藏 = 灰描边 */
.ls-st-chip {
  border-radius: var(--r-btn); border: 1.5px solid transparent;
  font-size: 12px; font-weight: 700; padding: 4px 11px; cursor: pointer;
  transition: all var(--dur-fast) var(--ease);
}
.ls-st-chip.on { background: linear-gradient(180deg, var(--orange-2), var(--orange)); border-color: var(--orange-3); color: #fff; }
.ls-st-chip.on:hover { filter: brightness(1.05); }
.ls-st-chip.off { background: #eef1f6; border-color: #dbe3ee; color: #7b8ea3; }
.ls-st-chip.off:hover { background: rgba(47, 128, 237, .10); border-color: var(--line); color: var(--ink-3); }
.ls-st-chip[disabled] { cursor: not-allowed; opacity: .65; }

/* ---------------- 授权选择 ---------------- */
.ls-pick {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 4px 10px; max-height: 240px; overflow: auto;
  border: 1px solid var(--line-2); border-radius: 10px; padding: 9px 11px; background: var(--paper);
}
.ls-ck { display: flex; align-items: center; gap: 7px; font-size: 13.5px; color: var(--ink-2); cursor: pointer; }
.ls-ck input { width: 15px; height: 15px; accent-color: var(--orange); }
.ls-dlg-foot { display: flex; gap: 10px; margin-top: 16px; }
.ls-dlg-foot .btn { min-height: 38px; }

/* ---------------- 内容编辑页 ---------------- */
.page-lessonedit .name-edit.ls-seq-input input { width: 62px; }
.page-lessonedit .brief-edit input { width: min(320px, 32vw); }
.ls-nav { margin-top: 18px; }
.ls-sib { display: flex; flex-wrap: wrap; gap: 7px; }
.ls-sib-item {
  font-size: 12.5px; padding: 4px 10px; border-radius: 999px;
  border: 1px solid var(--line-2); color: var(--ink-2); text-decoration: none;
  background: var(--paper);
}
.ls-sib-item:hover { border-color: var(--brand-2); color: var(--brand-3); }
.ls-sib-item.on { background: var(--orange-tint); border-color: rgba(255, 122, 24, .40); color: #c9530a; font-weight: 700; }

/* 正文里 ```pyarea / ```pydemo 围栏换成的可运行显示器 */
.py-live { margin: 14px 0; }
.py-live.is-demo .pyarea { --demo-hint: 1; }

/* 第 57 轮：首页顶栏那排「课程」标签 —— 隐藏的课程挂一个 .is-off（老师才看得到它） */
.level-btn.is-off .lvname { opacity: .55; text-decoration: line-through; text-decoration-thickness: 1px; }

/* ============================================================
   第 58 轮
   ============================================================ */

/* ---------------- 需求 1：codes / homework 的「学生」一列 ----------------
   改宋体、字号收小，保证 **4 个汉字排在同一行**。
   宋体（SimSun）的 CJK 步进恰好 = 1em：12.5px × 4 = 50px，
   列内容宽 = 76 - 16 = 60px → 留 10px 余量，4 个字稳稳一行。
   ⚠️ 别加 word-break: keep-all：那样纯中文名一旦超过 4 个字就整行溢出。
   ⚠️ 列宽写在 th 上（table-layout: fixed 认第一行），见下面的 th.c-user。 */
.page-codes table.live-table th.c-user,
.page-homework table.live-table th.c-user { width: 76px; }
.page-codes table.live-table .c-user .u-name,
.page-homework table.live-table .c-user .u-name {
  font-family: "宋体", SimSun, "Songti SC", serif;
  font-size: 12.5px;
  line-height: 1.4;
  letter-spacing: 0;
}
.page-codes table.live-table .c-user .u-score,
.page-homework table.live-table .c-user .u-score { font-family: "宋体", SimSun, serif; }

/* ---------------- 需求 2 / 3：橙色「在线编程」按钮 ---------------- */
.btn.stu-code { text-decoration: none; }
.btn.stu-code .ico { font-size: 15px; line-height: 1; }

/* ---------------- 第 61 轮（需求 4）：面板里「管理」+「教学管理」两区按钮
   外观完全统一 + 四色循环 ----------------
   第 58 轮是故意把两区分开的（管理区描边、教学管理区实心）；第 61 轮按需求
   **统一成同一套**：同一形状（圆角 / 内边距 / 字号 / 字重 / 投影都一样），
   只有色相不同 —— 活力橙（行动色）· 浅绿 · 浅紫 · 科技蓝（结构色）。
   「开始上课」（浅绿）和「下课」（活力橙）也用这一套，整个面板像一家人。
   ⚠️ 加色只改自定义属性（--bg/--bd/--bg-h/…），`background: var(--bg)` 仍写在 .btn 上 ——
      `[disabled]` 那条（0,2,0）照样能盖住它，禁用态仍然是灰的。 */
.btn.rt-btn {
  --fg: #ffffff;
  --fg-h: #ffffff;
  --fg-a: #ffffff;
  border-width: 1.5px;
  text-decoration: none;
  box-shadow: 0 5px 14px -10px rgba(16, 48, 79, .70);
}
.btn.rt-btn:hover { box-shadow: 0 9px 20px -11px rgba(16, 48, 79, .55); }

/* 活力橙 —— 全站「行动色」。「下课」按钮的背景就是它。 */
.btn.rt-btn.c-orange {
  --bg: linear-gradient(180deg, var(--orange-2), var(--orange));
  --bd: #e8630a; --bd-h: #c9530a; --bd-a: #b34a08;
  --bg-h: linear-gradient(180deg, var(--orange), var(--orange-3));
  --bg-a: linear-gradient(180deg, var(--orange-3), #c9530a);
}
/* 浅绿 —— 「开始上课」用它 */
.btn.rt-btn.c-green {
  --bg: linear-gradient(180deg, #4ed29a, #22b573);
  --bd: #189a60; --bd-h: #148352; --bd-a: #0f6d44;
  --bg-h: linear-gradient(180deg, #2cbe7c, #189a60);
  --bg-a: linear-gradient(180deg, #189a60, #0f6d44);
}
/* 浅紫 */
.btn.rt-btn.c-purple {
  --bg: linear-gradient(180deg, #b79bf9, #8b5cf6);
  --bd: #7443e0; --bd-h: #6636cc; --bd-a: #592cb4;
  --bg-h: linear-gradient(180deg, #9a72f8, #7443e0);
  --bg-a: linear-gradient(180deg, #7443e0, #592cb4);
}
/* 科技蓝 —— 全站「结构色」 */
.btn.rt-btn.c-blue {
  --bg: linear-gradient(180deg, #59a4f7, var(--brand));
  --bd: #1a5fb8; --bd-h: #144a90; --bd-a: #103f7c;
  --bg-h: linear-gradient(180deg, var(--brand), var(--brand-3));
  --bg-a: linear-gradient(180deg, var(--brand-3), #103f7c);
}
/* 第 85 轮（需求 1）：「举手发言」要活力橙，而「下课」原来就是活力橙 ——
   同一行两枚一模一样的橙分不清谁是谁，所以「下课」换成深一档的砖红（收尾语义也更准）。 */
.btn.rt-btn.c-stop {
  --bg: linear-gradient(180deg, #f2705c, #d9422f);
  --bd: #c2371f; --bd-h: #ad2f19; --bd-a: #962813;
  --bg-h: linear-gradient(180deg, #e8553f, #c2371f);
  --bg-a: linear-gradient(180deg, #c2371f, #962813);
}

/* ---------------- 需求 7：授权对象改「下拉式选项」 ---------------- */
.ls-sel {
  appearance: none; -webkit-appearance: none;
  font-family: var(--sans);
  font-size: 13px; font-weight: 700; color: var(--ink);
  padding: 6px 30px 6px 11px;
  min-height: 34px; max-width: 100%;
  background-color: var(--paper);
  background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.6 6 6.4l5-4.8' fill='none' stroke='%235b7a99' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
  background-size: 11px 7px;
  border: 1.5px solid var(--line);
  border-radius: var(--r-btn);
  cursor: pointer;
  transition: border-color var(--dur-fast), box-shadow var(--dur-fast), background-color var(--dur-fast);
}
.ls-sel:hover { border-color: var(--brand-2); background-color: #fff; }
.ls-sel:focus { outline: none; border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-tint); }
.ls-sel option { color: var(--ink); background: #fff; font-weight: 400; }

.ls-gsel { display: flex; flex-direction: column; align-items: flex-start; gap: 3px; }
.ls-gsel-lab { font-size: 11px; color: var(--ink-4); letter-spacing: .04em; }
.ls-gsel .ls-gt { font-size: 11.5px; color: var(--ink-4); }

/* ---------------- 需求 7：管理课节区改「每节一张两栏卡片 + 浅色区分」 ---------------- */
.ls-cards { display: flex; flex-direction: column; gap: 10px; padding: 14px 16px 18px; }
.ls-card {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;   /* 左：序号 + 名称　右：授权 / 状态 / 操作 */
  align-items: center;
  gap: 10px 20px;
  padding: 12px 15px;
  border: 1px solid var(--line-2);
  border-radius: var(--r);
  background: rgba(255, 255, 255, .70);
  transition: background var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
/* 明显的浅色交替 —— 一眼分得清这一节和下一节 */
.ls-card:nth-child(even) { background: rgba(43, 127, 228, .085); }
.ls-card:hover { border-color: var(--brand-2); background: var(--brand-tint); }
.ls-card-l { display: flex; align-items: center; gap: 13px; min-width: 0; }
.ls-card-name { min-width: 0; }
.ls-card-r { display: flex; align-items: center; justify-content: flex-end; flex-wrap: wrap; gap: 12px; }
.ls-card-act { display: flex; align-items: center; gap: 7px; }
.ls-card .ls-seq {
  flex: none;
  font-family: var(--mono); font-size: 13px; color: var(--ink-3);
  min-width: 34px; text-align: center;
  padding: 3px 6px; border-radius: 8px;
  background: rgba(43, 127, 228, .12);
}
@media (max-width: 980px) {
  .ls-card { grid-template-columns: minmax(0, 1fr); }
  .ls-card-r { justify-content: flex-start; }
}

/* ============================================================
   第 59 轮
   ============================================================ */

/* ---------------- 需求 3：课程表「状态」挪进「操作」列 ----------------
   独立的「状态」列撤了，开关就放在「操作」列里、紧挨「修改」左边。
   操作列里最多能放 5 件东西（＋课节 / 状态 / 修改 / 授权给老师 / 删除），给它宽一点。 */
.ls-table th.ls-act { width: 440px; }
.ls-act .ls-st-chip { margin-right: 6px; vertical-align: middle; }

/* ---------------- 需求 3：授权对话框的「查找 + 全选 / 全不选」 ----------------
   放在勾选框列表上方：左边输入框（按姓名过滤），右边两枚小按钮 + 已选计数。 */
.ls-pick-tools { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; flex-wrap: wrap; }
.ls-find {
  flex: 1 1 160px; min-width: 120px; min-height: 32px;
  font-family: var(--sans); font-size: 13px; color: var(--ink);
  padding: 5px 10px; border: 1.5px solid var(--line); border-radius: var(--r-btn);
  background: var(--paper);
  transition: border-color var(--dur-fast), box-shadow var(--dur-fast), background-color var(--dur-fast);
}
.ls-find::placeholder { color: var(--ink-4); }
.ls-find:hover { border-color: var(--brand-2); background: #fff; }
.ls-find:focus { outline: none; border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-tint); }
.ls-pick-n { font-size: 11.5px; color: var(--ink-4); margin-left: auto; white-space: nowrap; }
/* ⚠️ 同上：工具条自己写了 display:flex —— 选「全部…」时要整条藏掉，得显式补一条 */
.ls-pick-tools[hidden] { display: none !important; }
/* ⚠️ .ls-ck 自己写了 display:flex，浏览器默认的 [hidden]{display:none} 会被它盖掉 ——
   过滤掉的那些必须显式藏起来（铁律：加了 display 的组件要自己补 [hidden]）。 */
.ls-pick .ls-ck[hidden] { display: none !important; }

/* ---------------- 需求 4：管理课节 = 左右两列卡片网格 ----------------
   第 58 轮是「一列卡片、卡内左右两栏」；这一轮改成**两列网格**，
   每张卡片内部退成竖排：上「序号 + 课节名称」，下「授权 → 状态 → 操作」。 */
.ls-cards {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
  padding: 14px 16px 18px;
}
.ls-cards > .empty { grid-column: 1 / -1; }
.ls-card {
  display: flex; flex-direction: column; align-items: stretch;
  gap: 10px;
  grid-template-columns: none;          /* 盖掉第 58 轮那句 grid-template-columns */
}
.ls-card-r { justify-content: flex-start; }
.ls-card-act { margin-left: auto; }
.ls-card .ls-gsel { flex: 1 1 auto; min-width: 0; }
@media (max-width: 1100px) {
  .ls-cards { grid-template-columns: minmax(0, 1fr); }
}

/* ============================================================
   第 60 轮
   ============================================================ */

/* ---------------- 需求 1：code 页「控制学生端」区域 ----------------
   顶栏原来那枚「查看学生实时代码」搬到了左栏题目区上方，跟「打开在线编程」并排；
   外面套一层橙色浅底，左上角一枚「控制学生端」标签 —— 这两件事都跟学生端有关。 */
.task-push {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  padding: 8px 10px; border-bottom: 1px solid var(--line-2);
  background: rgba(255, 122, 24, .07);
}
/* 自己写了 display:flex —— 藏起来时得显式补一条（[hidden] 铁律） */
.task-push[hidden] { display: none !important; }
.task-push-lab {
  font-family: var(--mono); font-size: 11.5px; letter-spacing: .04em;
  color: var(--orange-3); background: rgba(255, 122, 24, .16);
  padding: 3px 9px; border-radius: 999px; white-space: nowrap;
}
/* ⚠️ 左栏只有 380px（窄屏 320px），三件东西要挤在一行：
   标签 + 「打开在线编程」+「查看实时代码」。整体收一号字距/内边距，
   一行刚够（1600 宽下约 318px ≤ 336px 可用），否则最后一枚会掉到第二行。 */
.task-push .btn.sm { padding: 4px 10px; font-size: 12.5px; gap: 5px; }
.task-push .btn .ico { font-size: 13px; line-height: 1; }

/* ---------------- 需求 2：课节卡内所有内容排成一行 ----------------
   第 59 轮是「两列网格 + 卡内竖排」；这一轮卡内改成**横排一行**：
   序号 + 课节名称 → 授权对象 → 状态 → 修改 删除。外面仍然是两列网格。 */
.ls-card { flex-direction: row; align-items: center; gap: 14px; }
.ls-card-l { flex: 1 1 auto; min-width: 0; gap: 10px; }
.ls-card-name { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.ls-card .ls-title { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ls-card-r { flex: 0 0 auto; flex-wrap: nowrap; justify-content: flex-end; gap: 10px; }
.ls-card-act { margin-left: 0; }
.ls-card .ls-gsel { flex-direction: row; align-items: center; gap: 6px; flex: 0 0 auto; }
.ls-card .ls-gsel-lab,
.ls-card .ls-gsel .ls-gt { white-space: nowrap; }

/* ---------------- 需求 2：「删除」按钮文字改白色 ----------------
   全局 .btn.danger（第 24 轮）是「红底 + 红字」那种很轻的 ✕，别的页面还在用；
   这里按需求把 lesson 页这两处做成**实心红底 + 白字**。
   ⚠️ 必须带 :not([disabled])：否则会盖掉 .btn[disabled] 的灰底，变成灰底白字看不清。 */
.page-lesson .ls-act .btn.danger:not([disabled]),
.page-lesson .ls-card-act .btn.danger:not([disabled]) {
  color: #fff; border-color: #c9382f;
}
.page-lesson .ls-act .btn.danger:not([disabled]):hover,
.page-lesson .ls-card-act .btn.danger:not([disabled]):hover {
  background: linear-gradient(180deg, var(--bad), #c9382f);
  border-color: #b32f27; color: #fff;
}
.page-lesson .ls-act .btn.danger:not([disabled]):active,
.page-lesson .ls-card-act .btn.danger:not([disabled]):active {
  background: linear-gradient(180deg, #c9382f, #b32f27);
  border-color: #9c2822; color: #fff;
}

/* ---------------- 需求 5：蓝色「在学生端打开此页」 ----------------
   全站按钮主色是活力橙（行动色）；这一枚按用户要求做成**蓝色实心**。 */
.btn.blue {
  --bg: linear-gradient(180deg, #4b9bf5, var(--brand));
  --fg: #ffffff; --bd: #1c62c4;
  --bg-h: linear-gradient(180deg, var(--brand), #1c62c4);
  --fg-h: #ffffff; --bd-h: #17539f;
  --bg-a: linear-gradient(180deg, #1c62c4, #17539f);
  --fg-a: #ffffff; --bd-a: #134585;
  --sd: 0 6px 16px -10px rgba(28, 98, 196, .70);
}
.btn.blue:hover { --sd: 0 9px 22px -10px rgba(28, 98, 196, .80); }
/* 顶栏中间那枚：跟标题之间留一点呼吸 */
/* 第 60 轮（需求 5）：这枚按钮要落在**顶栏正中间**（原来跟在面包屑后面，偏左）。
   ⚠️ `.detail-top` 是 `position:sticky`（已是定位元素）→ 绝对定位子元素以顶栏为基准，
   `left:50% + translateX(-50%)` 就是「水平居中」。
   窄屏（≤1100px）面包屑 + 右侧动作区会挤在一起，这时退回正常流（跟在标题右边）。
   第 61 轮（需求 3）：学生那枚活力橙「在线编程」(#btnStuCode) 占**同一个座位**，
   两枚按角色互斥、样式规则共用。
   第 69 轮（需求 1）：这个座位改成一个居中小组 .head-mid —— 里面按顺序放
   「在学生端打开此页 / 在线编程 / 保存提交」，所以「保存提交」天然就在「在线编程」右边，
   不用写死偏移量（按钮文字会变长变短，偏移量靠不住）。 */
.detail-top .head-mid {
  position: absolute; left: 50%; transform: translateX(-50%);
  display: flex; align-items: center; gap: 8px;
}
.detail-top .head-mid > .btn { position: static; transform: none; margin-left: 0; }
@media (max-width: 1100px) {
  .detail-top .head-mid { position: static; transform: none; margin-left: 6px; }
}
.detail-top #btnStuCode { text-decoration: none; }
.detail-top #btnStuCode .ico { font-size: 14px; line-height: 1; }

/* ---------------- 需求 6：kj 页（课节挂载） ---------------- */
.page-kj { display: flex; flex-direction: column; min-height: 100vh; background: var(--bg); }
.kj-main {
  flex: 1; width: min(1560px, 100% - clamp(10px, 1.4vw, 24px));
  margin: 0 auto; padding: 16px 0 24px;
  display: flex; flex-direction: column; gap: 14px;
}

/* 上：已选课节 —— 每行 5 个 */
.kj-picked-row {
  display: grid; grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 8px; margin-bottom: 8px;
}
.kj-chip {
  display: flex; align-items: center; gap: 6px; min-width: 0;
  padding: 6px 8px 6px 10px; border: 1px solid var(--line-2); border-radius: var(--r-btn);
  background: #fff; font-size: 13px; color: var(--ink);
}
.kj-chip-n { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.kj-chip-x {
  flex: none; width: 22px; height: 22px; line-height: 1; border-radius: 6px;
  border: 1px solid transparent; background: transparent; cursor: pointer;
  color: var(--bad); font-size: 13px;
}
.kj-chip-x:hover { background: rgba(255, 74, 61, .10); border-color: rgba(255, 74, 61, .34); }

/* 下：四个竖栏，每栏一种浅色底 */
.kj-pool { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; }
.kj-col { border: 1px solid var(--line-2); border-radius: var(--r); padding: 8px; min-height: 96px; }
.kj-col[data-c="1"] { background: rgba(43, 127, 228, .07); }
.kj-col[data-c="2"] { background: rgba(30, 160, 120, .07); }
.kj-col[data-c="3"] { background: rgba(255, 122, 24, .09); }
.kj-col[data-c="4"] { background: rgba(138, 108, 220, .09); }
.kj-col-h {
  display: flex; align-items: center; gap: 6px;
  font-size: 11.5px; color: var(--ink-4); margin-bottom: 6px;
}
.kj-row {
  display: grid; grid-template-columns: 30px minmax(0, 1fr) auto auto;
  align-items: center; gap: 6px;
  padding: 5px 6px; border-radius: 7px; cursor: pointer;
  font-size: 12.5px; color: var(--ink); border: 1px solid transparent;
}
.kj-row:hover { background: rgba(255, 255, 255, .88); border-color: var(--brand-2); }
.kj-row.on { opacity: .48; cursor: default; }
.kj-row.on:hover { background: transparent; border-color: transparent; }
.kj-seq { font-family: var(--mono); font-size: 11.5px; color: var(--ink-4); text-align: center; }
.kj-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.kj-owner, .kj-lv { font-size: 11px; color: var(--ink-4); white-space: nowrap; }
.kj-lv { padding: 1px 6px; border-radius: 999px; background: rgba(43, 127, 228, .12); color: var(--brand-3); }

/* 筛选条 */
.kj-filters { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.kj-filters label {
  font-size: 12px; color: var(--ink-3);
  display: inline-flex; align-items: center; gap: 4px;
}
.kj-filters select, .kj-filters input {
  min-height: 30px; padding: 3px 8px;
  border: 1.5px solid var(--line); border-radius: var(--r-btn);
  background: var(--paper); font-size: 12.5px; color: var(--ink);
  font-family: var(--sans);
}
.kj-filters input { flex: 1 1 120px; min-width: 104px; }
.kj-filters select:focus, .kj-filters input:focus {
  outline: none; border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-tint);
}
.kj-pool-empty { grid-column: 1 / -1; }

@media (max-width: 1200px) { .kj-pool { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 760px) {
  .kj-pool { grid-template-columns: minmax(0, 1fr); }
  .kj-picked-row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ================= 第 70 轮（需求 2）：课节题目作业的卡片 =================
   第 69 轮那块独立的「课节题目作业」区域已经删掉了 —— 它现在就是「未评作业」列里的
   一张普通卡片，只不过整张卡就是链接，点哪儿都能进 /review 批改。 */
.hw-card-lq { display: block; text-decoration: none; color: inherit; cursor: pointer; }
.hw-card-lq:hover { text-decoration: none; }
.hw-card-lq .kind.lessonquiz {
  background: var(--brand-tint); color: var(--brand-3);
  border-color: rgba(43, 127, 228, .35);
}
.hw-card-lq .lq-go {
  font-size: 12px; font-weight: 700; color: var(--brand-2);
}

/* ============ 第 99 轮（需求 1）：「打开作业」落地时给标题闪一下 ============
   学生被推到正文里「作业」那一级标题上，落地后如果只是静静地滚过去，
   他只会看到一片正文，不知道老师让他看哪一行 —— 所以那枚 h1 闪两下暖黄。
   类名由 lessonedit.js#scrollToJob 挂 3.4 秒再摘掉（内容一变 React 重渲染也不受影响）。 */
.preview-host .is-job-flash,
.md-editor-preview .is-job-flash {
  animation: jobFlash 1.7s ease-out 2;
  border-radius: 8px;
  scroll-margin-top: 72px;
}
@keyframes jobFlash {
  0%   { background-color: rgba(255, 196, 66, .55); box-shadow: 0 0 0 8px rgba(255, 196, 66, .28); }
  70%  { background-color: rgba(255, 196, 66, .22); box-shadow: 0 0 0 8px rgba(255, 196, 66, .10); }
  100% { background-color: transparent;             box-shadow: 0 0 0 8px rgba(255, 196, 66, 0); }
}

/* ============ 第 106 轮（需求 2）：右上角「目录」磁吸按钮 ============
   第 107 轮（需求 1）：那枚「目录」**常驻显示**在右上角（老师提的最多的一句是
   "找不到它"）—— 滚动到哪儿都贴在窗口右缘不动；指针移上去（触屏点一下）
   才展开目录面板，移开 / 再点一下收起。显隐仍由 CSS :hover + .on 管，
   JS 只负责生成条目 / 点击滚动 / 触屏固定。
   只在 /lessonedit 页出现（结构写在 lessonedit.html 里）。 */
.toc-mag {
  /* --toc-sb 由 lessonedit.js 现量：右侧那条整页滚动条有多宽就让多宽 ——
     否则按钮被压在滚动条底下，既看不清也点不着。 */
  position: fixed; right: var(--toc-sb, 0px); top: 88px; z-index: 70;
  width: 28px; height: 120px;
}
.toc-mag[hidden] { display: none; }

/* 那枚「很小的目录」：竖排两字、贴右缘、只圆左边两角 —— **一直可见** */
.toc-tab {
  position: absolute; right: 0; top: 0; z-index: 3;
  width: 24px; min-height: 64px; padding: 12px 0;
  writing-mode: vertical-rl; text-align: center;
  font: 600 12.5px/1 var(--sans, system-ui, "Microsoft YaHei", sans-serif);
  letter-spacing: 2px;
  color: var(--brand-3); background: #fff;
  border: 1px solid var(--line); border-right: 0;
  border-radius: 9px 0 0 9px;
  box-shadow: -2px 2px 10px rgba(16, 48, 79, .14);
  cursor: pointer;
  opacity: .96;
  transition: opacity .16s ease, background .16s, color .16s, box-shadow .16s;
}
/* 指针靠近整条磁吸带 / 触屏点开 / 面板正开着 → 按钮提亮一点，给个"我在这里"的反馈 */
.toc-mag:hover .toc-tab,
.toc-mag.on .toc-tab { opacity: 1; box-shadow: -3px 3px 14px rgba(16, 48, 79, .2); }
.toc-tab:hover { background: var(--brand); color: #fff; border-color: var(--brand); }
.toc-tab:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }
.toc-tab.muted { color: var(--ink-4); }

/* 目录面板：浮在磁吸带左边，白底浅蓝描边 */
.toc-panel {
  position: absolute; right: 100%; top: -6px;
  width: 252px; max-height: min(62vh, 520px);
  display: flex; flex-direction: column;
  background: #fff; border: 1px solid var(--line); border-radius: 12px;
  box-shadow: 0 12px 30px rgba(16, 48, 79, .16);
  overflow: hidden;
  opacity: 0; visibility: hidden; transform: translateX(8px); pointer-events: none;
  transition: opacity .16s ease .07s, transform .18s ease .07s, visibility .16s .07s;
}
.toc-mag:hover .toc-panel,
.toc-mag.on .toc-panel { opacity: 1; visibility: visible; transform: none; pointer-events: auto; }

.toc-head {
  display: flex; align-items: center; gap: 6px;
  padding: 9px 12px; border-bottom: 1px solid var(--line-2);
  font-size: 12px; font-weight: 700; color: var(--ink-3); letter-spacing: .5px;
}
.toc-n { font-weight: 400; color: var(--ink-4); }
.toc-list {
  flex: 1; min-height: 0; overflow: auto; padding: 6px;
  display: flex; flex-direction: column; align-items: stretch;
}
.toc-item {
  display: block; width: 100%;
  padding: 6px 8px; border: 0; border-radius: 7px; background: none;
  font: inherit; font-size: 13px; line-height: 1.45; text-align: left;
  color: var(--ink-2); cursor: pointer;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.toc-item:hover { background: var(--brand-tint); color: var(--brand-3); }
.toc-item.on { background: var(--brand-tint); color: var(--brand-3); font-weight: 700; }
.toc-item.lv1 { font-weight: 700; color: var(--ink); }
.toc-item.lv2 { padding-left: 18px; }
.toc-item.lv3 { padding-left: 30px; font-size: 12.5px; }
.toc-item.lv4 { padding-left: 42px; font-size: 12.5px; color: var(--ink-3); }
.toc-empty { padding: 14px 12px; font-size: 12.5px; line-height: 1.8; color: var(--ink-4); }

/* 跳到某一级标题时闪一下（和「打开作业」同款暖黄，落点看得见） */
.preview-host .is-toc-flash,
.md-editor-preview .is-toc-flash {
  animation: jobFlash 1.2s ease-out 1;
  border-radius: 8px;
}

/* 只有真正的手机才挪到右下角（那个宽度上右上角被顶栏按钮占满了）；
   平板 / 窄一点的笔记本仍然是「右上角一直浮动」。 */
@media (max-width: 620px) {
  .toc-mag { top: auto; bottom: 92px; height: 96px; }
  .toc-panel { width: min(74vw, 232px); top: auto; bottom: -6px; }
}
@media (prefers-reduced-motion: reduce) {
  .toc-tab, .toc-panel { transition: none; }
}

/* ============ 第 109 轮（需求 1）：课节拖动排序 ============
   手柄（⠿）按住就能拖；拖到目标卡片上半 = 插到它前面，下半 = 插到它后面。
   插入位置用 inset box-shadow 画一条蓝线 —— 不占布局，松手不会跳一下。 */
.ls-grip {
  flex: none;
  font-size: 15px; line-height: 1;
  color: var(--ink-4); cursor: grab;
  padding: 2px 4px; border-radius: 6px;
  user-select: none; -webkit-user-select: none;
}
.ls-grip:hover { color: var(--brand); background: var(--brand-tint); }
.ls-grip:active { cursor: grabbing; }
.ls-card.dragging { opacity: .5; border-style: dashed; }
.ls-card.drop-before { box-shadow: inset 0 3px 0 0 var(--brand); }
.ls-card.drop-after { box-shadow: inset 0 -3px 0 0 var(--brand); }

/* ============ 第 109 轮（需求 3 / 4）：上课控制版面 ============
   ① 「管理」「教学管理」的标题与按钮排在同一行（原来标题独占一行，白占一截高度）；
   ② 教学管理那两枚常用入口再窄一档，紧挨标题右边；
   ③ 新增「1对1语音」区：一行标题 + 浅红挂断，下面一排最小号的脱敏帐号按钮。 */
.live-card-h-links { gap: 6px 10px; margin: 0; }
.live-card-h-links h3 { flex: none; }
.live-links-tight { flex: 0 1 auto; min-width: 0; gap: 6px; }
.live-links-sub { margin-top: 8px; }
/* 调窄：只改留白和字号，颜色 / 形状仍是同一套 rt-btn */
.live-card .live-card-h-links .btn.btn-narrow {
  min-height: 28px; padding: 3px 9px; font-size: 12px;
}

/* —— 1对1语音 —— */
.live-pair-who {
  flex: none;
  font-size: 11px; font-weight: 700; color: #b3261e;
  background: #fdeaea; border-radius: 999px; padding: 2px 9px;
}
.live-pair-who[hidden] { display: none !important; }
/* 「挂断」用**浅红**（不是 c-stop 那种砖红实心）：它只是收尾，不该抢「开始直播」的风头 */
.btn.live-pair-hang {
  --bg: #fdecec; --bg-h: #fbdbdb; --bg-a: #f7caca;
  --bd: #f0b4b4; --bd-h: #e59a9a; --bd-a: #d98484;
  --fg: #b3261e; --fg-h: #96201a; --fg-a: #7d1a15;
  min-height: 28px; padding: 3px 12px; font-size: 12px; font-weight: 700;
  box-shadow: none;
}
.btn.live-pair-hang:hover { box-shadow: none; }
.btn.live-pair-hang[hidden] { display: none !important; }

/* 学生帐号按钮：最小号 + 最小间距。一个班三四十人，一排扫过去才点得准 */
.pair-roster {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 3px;
  max-height: 110px; overflow-y: auto; overflow-x: hidden;
  padding: 1px 0;
}
.pair-chip {
  font: inherit; font-size: 11px; line-height: 1.3;
  padding: 1px 5px; border-radius: 5px;
  border: 1px solid var(--line); background: #fff; color: var(--ink-3);
  cursor: pointer; white-space: nowrap;
}
.pair-chip.online { color: var(--ink); border-color: var(--brand-2); background: var(--brand-tint); }
.pair-chip:hover { border-color: var(--brand); color: var(--brand-3); background: #eaf3ff; }
/* 第 124 轮（需求 1-2/3）：**正在说话 = 浅红**（原来是大红实心）。
   老师的原话是「接听后背景色修改浅红，再次点击后挂断」—— 浅红是「这一位此刻在说」，
   再点一下就是挂断；比大红实心温和，一排三四十枚按钮里也不会吵。 */
.pair-chip.on {
  color: #b3261e; font-weight: 700;
  background: #fdecec;
  border-color: #f0b4b0;
}
/* ============ 第 127 轮：**呼叫中 = 红白闪** ============
   需求原话：「呼叫后，学生提问选项卡中该学生的帐号按钮红-白闪烁」。
   第 112~126 轮这里一直是**绿白**闪（那时 `.ask` 表示「举手 / 申请发言」）；
   现在 `.ask` 只表示一件事 —— **他在呼你，等你接通**，所以按需求改成红白。
   · `.ask`    = 正在呼叫你 → **红白交替**（1 秒一格，实色切换，投影到大屏也一眼看见）；
   · `.ask.on` = 已经接通（通话中）→ 停闪，换成浅红实心；再点一下就是挂断；
   · 其余在线按钮（`.online`）保持浅蓝，一根手指都不动它们的样子。 */
.pair-chip.ask {
  color: #b3261e; font-weight: 700;
  border-color: #d94a42; background: #fff;
  animation: pairAskBlink 1.05s steps(1, end) infinite;
}
@keyframes pairAskBlink {
  0%, 49.9%   { background: #ffffff; color: #b3261e; border-color: #d94a42; }
  50%, 100%   { background: #e0372e; color: #ffffff; border-color: #b3261e; }
}
.pair-chip.ask:hover { border-color: #96201a; color: #96201a; background: #fdecec; }
.pair-chip.ask.on {
  animation: none;
  color: #b3261e; font-weight: 700;
  background: #fdecec;
  border-color: #f0b4b0;
}
.pair-chip.ask.on:hover { background: #fbdada; color: #96201a; }
/* 关掉动效的用户（系统设置里「减少动态效果」）：不闪，改成静态浅红底 —— 依然看得出「在等你」 */
@media (prefers-reduced-motion: reduce) {
  .pair-chip.ask { animation: none; background: #fdecec; color: #b3261e; }
  .pair-chip.ask.on { background: #fdecec; color: #b3261e; }
}
/* 「✋ 3 人提问中」—— 提问条删掉之后，仍要一眼看出还有几个人在等 */
.pair-ask-n {
  flex: none;
  font-size: 11px; font-weight: 700; color: #0f7a49;
  background: #e4f8ec; border-radius: 999px; padding: 1px 7px;
  white-space: nowrap;
}
.pair-ask-n[hidden] { display: none !important; }
.pair-empty { font-size: 11.5px; color: var(--ink-4); }

/* —— 语音自检详情弹窗 —— */
.dialog.lk-dlg {
  width: min(620px, 100%); max-height: calc(100vh - 32px);
  display: flex; flex-direction: column;
}
.dialog.lk-dlg h3 { font-size: 19px; margin-bottom: 2px; }
.dialog.lk-dlg .hint { margin: 0 0 10px; font-size: 12.5px; }
.lk-dlg-body { flex: 1 1 auto; min-height: 0; overflow: auto; padding-right: 2px; }
.lk-dlg-verdict {
  font-weight: 700; font-size: 12.5px; line-height: 1.5;
  margin: 0 0 8px; padding: 7px 10px; border-radius: 8px;
  background: #f4f9ff; color: var(--ink-2);
}
.lk-dlg-verdict.ok { color: #0e7548; background: #e9f8f0; }
.lk-dlg-verdict.bad { color: #b3261e; background: #fdeaea; }
.lk-dlg-sub {
  margin: 12px 0 5px; font-size: 12px; font-weight: 700;
  color: var(--ink-3); letter-spacing: .3px;
}
.lk-kv {
  display: grid; grid-template-columns: 84px minmax(0, 1fr);
  gap: 4px 10px; margin: 0; font-size: 12px; line-height: 1.5;
}
.lk-kv dt { color: var(--ink-4); }
.lk-kv dd { margin: 0; color: var(--ink-2); overflow-wrap: anywhere; }
.lk-dlg-note { font-size: 12px; line-height: 1.65; color: var(--ink-3); overflow-wrap: anywhere; }

/* ============ 第 110 轮（需求 1 / 2）：上课控制两处版面 ============
   ① 「班级」标签 + 下拉并进「上课」标题行（挂在「在线 N 人」右边）——
      原来它独占一整行、标签定宽 64px，面板本来就窄，白占一截高度；
   ② 「一对一语音」区：「和 XX 通话中」挪到「挂断」右侧，挂断位置固定不动。 */
.live-cls-pick {
  display: flex; align-items: center; gap: 6px;
  flex: 0 1 auto; min-width: 0;
}
.live-cls-pick > label {
  flex: none; margin: 0;
  font-size: 12.5px; font-weight: 700; color: var(--ink-2); letter-spacing: .02em;
}
/* 标题行里的下拉不再吃满一行：给个 74~168 的弹性区间，班名长了就省略 */
.live-card .live-card-h .live-cls-pick > select {
  flex: 0 1 auto; width: auto; min-width: 74px; max-width: 168px; margin: 0;
}
/* 168px 是量出来的：13px 字号下「实时在线学生（默认）」正好 130px，
   加上左右留白与箭头约 36px。窄屏（420px 以下）放不下「不换行」和「字不被截」两头：
   放到 168px 会把下拉挤到第二行（实测 375px 就是两行），所以窄屏让它缩回 128px
   把字截一点（下拉展开还是全的），保住「班级 + 在线人数」这一行。 */
@media (max-width: 420px) {
  body.is-embed .live-card .live-card-h .live-cls-pick > select { max-width: 128px; }
}
/* 「和 学*1 通话中」在挂断右边：不加左边距，间距交给标题行的 gap */
.live-card-h-pair .live-pair-who { margin: 0; }

/* ============================================================ 第 130 轮：AI 编程助手
   ① 按钮：挂在每块编程题面板「代码」两个字右边（活力橙 = 站里的 .btn.solid，
      但它站在左端、和右边那组「重置 / 运行」隔着一个 spacer，
      所以同样是橙色也不会跟「运行」混起来）
   ② 浮层：固定右上角的一张窄卡片，不铺遮罩 —— 学生一边看提示一边改代码，
      不用「关掉 → 改 → 再打开」。窄屏（≤640px）改成从底部弹起。
   换行说明：这个文件是 CRLF，追加时务必保持。
   ========================================================================= */
/* 浅绿：和「运行」那颗橙色按钮拉开距离 —— 两个都是主操作，
   但「问 AI」是问、不是跑，颜色分开学生一眼不会点错。
   注意选择器要写成 .btn.solid.aihint-btn（0,3,0）：只写 .aihint-btn 是 0,1,0，
   会被 .btn.solid 的橙色变量压回去，界面上一动不动。 */
.btn.solid.aihint-btn {
  gap: 4px;
  padding: 3px 9px 3px 7px;
  min-height: 26px;
  font-family: var(--sans);
  font-size: 11.5px;
  font-weight: 800;
  letter-spacing: 0;
  text-transform: none;
  --bg: linear-gradient(180deg, #a7e7c2, #6bd39b);
  --fg: #0b4a2e;
  --bd: #55c189;
  --bg-h: linear-gradient(180deg, #9ce2ba, #5dcb90);
  --fg-h: #0b4a2e;
  --bd-h: #47b57c;
  --bg-a: linear-gradient(180deg, #5dcb90, #46b47b);
  --fg-a: #0b4a2e;
  --bd-a: #39a46e;
  --sd: 0 5px 14px -9px rgba(34, 160, 100, .85);
}
.btn.solid.aihint-btn:hover { --sd: 0 9px 20px -11px rgba(34, 160, 100, .90); }
/* 小方块「AI」：白底半透明，像贴在橙牌上的一枚铭牌 */
.aihint-btn .aihint-tag {
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 800;
  letter-spacing: .04em;
  line-height: 1;
  padding: 2px 4px;
  border-radius: 4px;
  background: rgba(255, 255, 255, .55);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .55);
}
.aihint-btn.is-busy { opacity: .72; cursor: progress; }
.aihint-btn.is-busy .aihint-tag { animation: pulse 1s infinite; }

/* ---------------- 结果块（第 132 轮内嵌，第 135 轮改配色与宽度） ----------------
   位置：第 138 轮起直接插在**那台显示器**的后面（screen.nextSibling），
   也就是当前这道编程题的正下方，见 aihint.js 的 place()。
   配色：**活力橙描边 + 浅绿底** —— 框是站里的主色，底是「AI 助手」按钮那套浅绿，
   标题行上的橙按钮和橙框也能对上。
   宽度：fit-content —— 内容短就收窄成一张小卡，长到放不下才换行，
   不预先拉满整块面板（第 132~134 轮是撑满的，短提示两边空一大截）。
   这里没有 position / z-index / 阴影，就是一个普通的块级卡片。 */
.aihint-box {
  /* 「刚好一行显示完」：先按内容的最宽一行算，超过可用宽度才回落换行 */
  width: fit-content;
  width: -moz-fit-content;
  max-width: 100%;
  min-width: min(250px, 100%);
  margin: 0 0 10px;
  background: #eefaf3;
  border: 2px solid var(--orange);
  border-radius: var(--r);
  overflow: hidden;
  animation: aihint-in .16s ease-out;
}
.aihint-box[hidden] { display: none; }

/* 第 138 轮：卡片贴在显示器下面、**左对齐**。
   · display 保持块级 + margin-right:auto —— 父容器就算 text-align:center /
     align-items:center 也不会把它挪到中间。
   · 左边留 8px：显示器（.screen）自带 padding:8px，卡片缩进同样的 8px，
     左边缘正好跟屏幕边框对齐，不会比显示器更靠外。
   · margin-right:0 + max-width 减掉左边那 8px —— fit-content 撑满时不会顶出容器。 */
.aihint-box--under {
  display: block;
  margin: 10px auto 14px 8px;
  max-width: calc(100% - 8px);
}

/* 兼容：第 137 轮生成的卡片带这个类（线上缓存里的旧 aihint.js 还会用）。
   那条逻辑是把卡片 append 到块容器末尾，内缩得自己补。 */
.aihint-box--tail {
  --aihint-inset: clamp(12px, 1.6vw, 20px);
  margin: 12px var(--aihint-inset) 16px;
  max-width: calc(100% - var(--aihint-inset) * 2);
}
/* detail 页那块 .panel-body.tight 本来就是 padding 0（屏幕故意顶满），
   卡片贴着面板内边留一圈就够了，不用跟着 pane-host 那么宽的内缩。 */
.panel > .aihint-box--tail { --aihint-inset: 12px; }

/* 兼容：第 134~136 轮生成的卡片带这个类（线上缓存里的旧 aihint.js 还会用） */
.aihint-box--flush {
  margin: 12px clamp(12px, 1.6vw, 18px) 10px;
}
@keyframes aihint-in {
  from { opacity: 0; transform: translateY(-4px); }
  to { opacity: 1; transform: none; }
}

/* 头：第 132 轮内嵌之后这条色带会**横跨整个面板**（原来是 394px 的小浮层），
   再顶着一块饱和的绿就抢了正文的戏。所以压成很淡的绿底 + 深绿字，
   让「总评 / 第几行」成为第一眼看到的东西。
   跟按钮的浅绿仍是同一色系，只是分成「实心块」和「浅底」两档。

   第 133 轮：原来右边那枚「Agnes · agnes-2.5-flash」徽章去掉了（学生不看模型名，
   白占一截），空出来的位置放「再看一遍」—— 淡绿底上一颗活力橙，
   不抢正文又一眼找得到。头里现在只有：标题 / 再看一遍 / 弹性空档 / ×。

   第 135 轮：整张卡片改成浅绿底 + 橙框以后，头不再自己铺一块渐变
   （两截绿拼在一起会露出接缝），改成透明、让卡片底色透上来；
   分隔线换成淡橙虚线，跟边框同色系。 */
.aihint-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 10px 8px 14px;
  background: transparent;
  color: #0b4a2e;
  border-bottom: 1px dashed rgba(255, 122, 24, .42);
}
.aihint-head .spacer { flex: 1; }
.aihint-name { font-size: 13.5px; font-weight: 800; letter-spacing: .02em; }

/* 「再看一遍」：卡片里唯一的动作，固定在标题行、「AI 助手」右边。
   颜色直接用 .btn.solid 那套活力橙（不在这里另写一份，免得以后调主色对不上），
   这里只管尺寸 —— .btn 默认 38px 高、8px 18px 内边距，塞进 26px 的标题行
   会把整条顶高，看着像又一颗主按钮。 */
.aihint-again {
  flex: none;
  min-height: 24px;
  padding: 2px 11px;
  font-size: 11.5px;
  font-weight: 800;
  letter-spacing: 0;
  border-radius: 7px;
  --sd: 0 3px 10px -7px rgba(232, 99, 10, .90);
}
.aihint-again:hover { --sd: 0 5px 14px -7px rgba(232, 99, 10, .95); }
.aihint-x {
  flex: none;
  width: 26px;
  height: 26px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: #0b4a2e;
  font-size: 17px;
  line-height: 1;
  cursor: pointer;
}
.aihint-x:hover { background: rgba(11, 74, 46, .09); }

.aihint-body {
  padding: 12px 14px;
  font-family: var(--sans);
}

/* 一句话总评：像一句老师的话，不占地方。
   第 135 轮：底色从浅橙改成半透明白 —— 浅橙压在浅绿上会发浑（像脏了），
   换成白底反而把这句话衬出来；左边那道橙杠留着，跟边框呼应。 */
.aihint-sum {
  margin: 0 0 10px;
  padding: 8px 10px;
  font-size: 13px;
  font-weight: 700;
  line-height: 1.55;
  color: var(--ink);
  background: rgba(255, 255, 255, .82);
  border-left: 3px solid var(--orange);
  border-radius: 0 8px 8px 0;
}

.aihint-list { list-style: none; margin: 0; padding: 0; }
.aihint-row {
  display: grid;
  grid-template-columns: 58px 1fr;
  gap: 9px;
  padding: 9px 0;
  border-top: 1px dashed var(--line-2);
}
.aihint-row:first-child { border-top: 0; }

/* 「第 N 行」——按一下就跳到那一行并选中，学生不用自己数 */
.aihint-ln {
  align-self: start;
  display: block;
  width: 100%;
  padding: 3px 2px;
  border: 1px solid rgba(255, 122, 24, .32);
  border-radius: 7px;
  background: var(--orange-tint);
  color: #b9540a;
  font-family: var(--mono);
  font-size: 10.5px;
  font-weight: 700;
  line-height: 1.5;
  text-align: center;
  white-space: nowrap;
  cursor: pointer;
}
.aihint-ln:hover { background: rgba(255, 122, 24, .26); border-color: var(--orange); }
span.aihint-ln { cursor: default; opacity: .75; }

.aihint-msg { min-width: 0; }
.aihint-why {
  margin: 0 0 5px;
  font-size: 13px;
  font-weight: 700;
  line-height: 1.5;
  color: var(--ink);
}
/* 有问题的那段原代码：白底 + 左边一道红杠，一眼看出「说的是这句」
   （第 135 轮：卡片变浅绿后，半透明红压在绿上会发灰，所以底改白） */
.aihint-code {
  display: block;
  margin: 0 0 6px;
  padding: 4px 8px;
  font-family: var(--mono);
  font-size: 12px;
  line-height: 1.5;
  color: #a02a22;
  background: rgba(255, 255, 255, .78);
  border-left: 3px solid rgba(232, 68, 58, .62);
  border-radius: 0 6px 6px 0;
  white-space: pre-wrap;
  word-break: break-all;
}
.aihint-fix {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.55;
  color: #14663c;
}
.aihint-fix b {
  display: inline-block;
  margin-right: 6px;
  padding: 1px 5px;
  border-radius: 4px;
  background: rgba(22, 163, 74, .14);
  color: #14663c;
  font-size: 11px;
  font-weight: 800;
}

/* 知识点：一排小药丸，扫一眼就走 */
.aihint-know {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin-top: 10px;
  padding-top: 9px;
  border-top: 1px solid var(--line-2);
}
.aihint-know-l {
  font-family: var(--mono);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: .06em;
  color: var(--ink-4);
}
/* 知识点药丸：卡片整片浅绿以后，原来的蓝底+蓝字在里头显冷、显脏，
   改成白底 + 深绿字，跟这张卡同一套色（「第 N 行」仍是橙、改法仍是绿，一眼分得开）。 */
.aihint-k {
  padding: 3px 9px;
  border: 1px solid rgba(34, 160, 100, .26);
  border-radius: 999px;
  background: rgba(255, 255, 255, .92);
  color: #14663c;
  font-size: 11.5px;
  line-height: 1.5;
}

.aihint-gj, .aihint-empty {
  margin: 4px 0 10px;
  font-size: 13px;
  line-height: 1.6;
  color: var(--ink-2);
}
.aihint-err {
  margin: 4px 0 4px;
  font-size: 13px;
  font-weight: 700;
  line-height: 1.6;
  color: var(--bad);
}
.aihint-err-tip { margin: 0 0 8px; font-size: 12px; color: var(--ink-4); }

/* 等 AI：三个点轮流亮，比一根转圈更安静 */
.aihint-wait {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 0 12px;
  font-size: 12.5px;
  color: var(--ink-3);
}
.aihint-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #3aa871;
  animation: aihint-blink 1.1s infinite;
}
.aihint-dot:nth-child(2) { animation-delay: .18s; }
.aihint-dot:nth-child(3) { animation-delay: .36s; margin-right: 4px; }
@keyframes aihint-blink { 0%, 60%, 100% { opacity: .25; } 30% { opacity: 1; } }

/* 第 136 轮：正文末尾那句提示语（「AI 也会看错，改完自己跑一遍」）删了 ——
   卡片里只留「总评 / 第几行 / 改法 / 知识点」，学生扫一眼就能回去改代码。
   原来垫在它下面的 6px 内边距补到 12px，底部留白才和顶部一样。 */

/* 窄屏：结果块本来就铺满宽度，只把内边距收紧一点、按钮缩成「AI」，
   免得标题行被挤换行 */
@media (max-width: 640px) {
  .aihint-btn .aihint-txt { display: none; }   /* 只留「AI」两个字 */
  .aihint-body { padding: 10px 11px; }
  .aihint-row { grid-template-columns: 50px 1fr; gap: 8px; }
  .aihint-head { gap: 7px; padding: 7px 9px 7px 12px; }
}
