/* pages-classroom.css —— 班级（#/class 列表 / #/class?class=X 班内）与班级练习房间

   两层（2026-09-20 第 33 轮，老师定）：
     第一层 班级主页 —— 只摆「我有哪些班级模块」，点进去才有内容
     第二层 班内     —— 公开/隐蔽房间 + 我的状态 + 右栏折叠的本班名单
   房间分**公开**与**隐蔽**，两种都能配密码（密码框见 .cls-pass）。
   ★ 这一页上**不出现任何成绩**：没有正确率、没有词汇量、没有等级、没有排名。
     房间成员的排序是「加入时间」（服务端定的），连交卷快慢都不显示 ——
     顺序本身就是排名。样式上也别偷偷用颜色深浅去表达「谁更靠前」。
   ★ 依赖 theme.css 的令牌与 components.css 的 .panel / .btn*，不重复定义。
*/

/* ---------------- 顶部：我的班级 + 三个数字 ---------------- */

.cls-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 10px;
}

.cls-head-left {
  display: flex;
  align-items: baseline;
  gap: 10px;
}

.cls-head-left .lbl {
  font-size: var(--fs-sm);
  color: var(--t-muted);
  letter-spacing: 0.6px;
}

.cls-name {
  font-size: var(--fs-xl);
  color: var(--t-bright);
}

.cls-head-right {
  display: flex;
  gap: 18px;
}

.cls-stat {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-width: 52px;
}

.cls-stat b {
  font-size: var(--fs-xl);
  font-weight: 500;
  color: var(--t-bright);
  line-height: 1.15;
}

.cls-stat span {
  font-size: var(--fs-xs);
  color: var(--t-dim);
}

.cls-pick {
  flex-basis: 100%;
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  border-top: 1px solid var(--c-line-soft);
  padding-top: 10px;
}

.cls-pick .lbl {
  font-size: var(--fs-sm);
  color: var(--t-dim);
  margin-right: 2px;
}

.cls-pick .chip {
  background: var(--c-input);
  border: 1px solid var(--c-line);
  border-radius: var(--r-sm);
  color: var(--t-muted);
  padding: 4px 10px;
  cursor: pointer;
}

.cls-pick .chip:hover {
  border-color: var(--c-accent-dim);
  color: var(--t-primary);
}

.cls-pick .chip.is-on {
  background: var(--c-accent-deep);
  border-color: var(--c-accent-dim);
  color: var(--t-bright);
}

.cls-nobody h2 {
  margin-bottom: 6px;
}

/* ---------------- 我自己现在在练什么 ---------------- */

.cls-me {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}

.cls-me-who {
  display: flex;
  align-items: center;
  gap: 8px;
}

.cls-me-name {
  color: var(--t-bright);
  font-size: var(--fs-lg);
}

.cls-me-line {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  color: var(--t-primary);
}

/* 在线/离线的小圆点。隐身的人对同学必须**长得和真离线一模一样** ——
   所以离线的点不能做成灰色虚线之类「暗示他有状态」的样子。 */
.dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #3c4a58;
  flex: none;
}

.dot.is-on {
  background: var(--c-green-a);
  box-shadow: 0 0 0 3px rgba(117, 176, 34, 0.16);
}

.cls-tag {
  font-size: var(--fs-xs);
  color: var(--c-accent);
  background: var(--c-accent-deep);
  border-radius: var(--r-sm);
  padding: 1px 6px;
}

.cls-tag.is-dim {
  color: var(--t-dim);
  background: rgba(143, 152, 160, 0.12);
}

/* ---------------- 公开房间 ---------------- */

.cls-room {
  border-left: 3px solid var(--c-accent);
}

.cls-room-top {
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
}

/* 房间号要大 —— 老师是站在讲台上用嘴念这个号的，字小了后排看不清。 */
.cls-room-code {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  background: var(--c-input);
  border: 1px solid var(--c-line);
  border-radius: var(--r-md);
  padding: 8px 16px;
  min-width: 128px;
}

.cls-room-code .lbl {
  font-size: var(--fs-xs);
  color: var(--t-dim);
}

.cls-room-code b {
  font-size: var(--fs-2xl);
  font-weight: 500;
  color: var(--t-bright);
  letter-spacing: 3px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

.cls-room-meta {
  flex: 1;
  min-width: 200px;
}

.cls-room-title {
  color: var(--t-primary);
  margin: 4px 0 0;
}

.cls-room-sub {
  color: var(--t-dim);
  font-size: var(--fs-sm);
  margin: 2px 0 0;
}

.cls-room-acts {
  margin-top: 12px;
}

.cls-room-acts .btn.is-warn {
  background: linear-gradient(180deg, var(--c-red-a), var(--c-red-b));
  color: var(--t-bright);
}

.cls-room-empty h2 {
  margin-bottom: 6px;
}

/* 谁进过这间房。只报「在 / 练完 / 离开了」三个状态，不报时间、不报快慢。 */
.cls-members {
  margin-top: 12px;
  border-top: 1px solid var(--c-line-soft);
  padding-top: 10px;
}

.cls-members-head {
  font-size: var(--fs-sm);
  color: var(--t-muted);
  margin-bottom: 6px;
}

.cls-mrow {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 30px;
  padding: 2px 0;
}

.cls-mrow.is-me .nm {
  color: var(--t-bright);
}

.cls-mrow .nm {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.cls-mrow .st {
  font-size: var(--fs-sm);
  color: var(--t-dim);
}

.cls-mrow .st.is-live {
  color: var(--c-green-a);
}

/* ★ 「已准备好」= 备课阶段老师真正盯着的那一栏（老师 2026-09-21）。
   它和「正在练」同一个绿 —— 都是"这个人已经在这局里了"，不必分两种颜色；
   真正要区分的是它和底下那句「还没准备」（灰的）。 */
.cls-mrow .st.is-ready {
  color: var(--c-green-a);
}

.cls-mrow .st.is-done {
  color: var(--c-accent);
}

/* 按房间号进房 */
.cls-code-entry {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

.cls-code-entry .lbl {
  color: var(--t-muted);
}

.cls-code-input {
  background: var(--c-input);
  border: 1px solid var(--c-line);
  border-radius: var(--r-sm);
  color: var(--t-bright);
  padding: 8px 10px;
  width: 140px;
  letter-spacing: 3px;
  text-transform: uppercase;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

.cls-code-input::placeholder {
  color: var(--t-dim);
  letter-spacing: 1px;
}

/* ---------------- 教师：开房表单 ---------------- */

.cls-form-note {
  color: var(--t-dim);
  font-size: var(--fs-sm);
  margin: 4px 0 10px;
}

.cls-frow {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 8px;
  flex-wrap: wrap;
}

.cls-frow .lbl {
  flex: none;
  width: 104px;
  color: var(--t-muted);
  font-size: var(--fs-sm);
}

.cls-sel,
.cls-inp {
  flex: 1;
  min-width: 180px;
  background: var(--c-input);
  border: 1px solid var(--c-line);
  border-radius: var(--r-sm);
  color: var(--t-primary);
  padding: 8px 10px;
}

.cls-sel:disabled,
.cls-inp:disabled {
  color: var(--t-dim);
}

.cls-form-hint {
  margin: 2px 0 10px;
  font-size: var(--fs-sm);
}

/* ---------------- 本班同学名单（个人房间） ---------------- */

.cls-roster-head {
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 8px;
}

.cls-roster-note {
  font-size: var(--fs-sm);
}

.cls-prow {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 0;
  border-top: 1px solid var(--c-line-soft);
}

.cls-prow:first-of-type {
  border-top: 0;
}

.cls-prow.is-me {
  background: rgba(102, 192, 244, 0.06);
  border-radius: var(--r-sm);
  padding-left: 6px;
  padding-right: 6px;
}

.cls-av svg,
.cls-av {
  width: 34px;
  height: 34px;
  border-radius: var(--r-sm);
  flex: none;
}

.cls-pmid {
  flex: 1;
  min-width: 0;
}

.cls-pname {
  display: flex;
  align-items: center;
  gap: 6px;
  color: var(--t-primary);
}

.cls-prow.is-me .cls-pname {
  color: var(--t-bright);
}

.cls-pstate {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  font-size: var(--fs-sm);
  color: var(--t-primary);
}

/* ---------------- 房间开局屏（练习窗口里那一屏） ----------------
   学生从班级页点进来先看到的就是它：大字房号 + 「全班同一份题」+ 一个开始。 */

.cls-room-start {
  text-align: center;
  padding: 22px 14px;
}

/* 房号要很大：这是要跟同桌对一对的那个号，也是老师在上面念的那个。 */
.cls-code {
  font-size: 40px;
  line-height: 1.1;
  color: var(--t-bright);
  letter-spacing: 6px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

.cls-code-note {
  font-size: var(--fs-sm);
  color: var(--t-dim);
  margin-bottom: 14px;
}

.cls-room-start h2 {
  margin-bottom: 8px;
}

/* 「全班做的是同一份题」—— 这一屏存在的理由，给它一点分量。 */
.cls-room-same {
  color: var(--c-accent);
}

.cls-room-start .btn {
  margin-top: 14px;
  min-width: 180px;
}

/* ---------------- 窄屏 ---------------- */

@media (max-width: 720px) {
  .cls-head {
    align-items: flex-start;
  }

  .cls-head-right {
    width: 100%;
    justify-content: space-between;
  }

  .cls-frow .lbl {
    width: auto;
    flex-basis: 100%;
  }

  .cls-room-code {
    min-width: 0;
    width: 100%;
  }
}

/* ==========================================================================
   ★ 2026-09-20 第 33 轮：两层结构 / 房间公开·隐蔽 / 房间密码 / 右侧可折叠名单
   ========================================================================== */

/* ---------------- 第一层：班级主页（只有班级模块） ---------------- */

.cls-list-head {
  margin-bottom: 4px;
}

.cls-list-head h2 {
  margin-bottom: 6px;
}

.cls-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: var(--gap);
}

.cls-card {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.cls-card-name {
  font-size: var(--fs-lg);
  color: var(--t-bright);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ---------------- 第二层：班内（主区 + 最右一栏） ---------------- */

.cls-body {
  display: flex;
  align-items: flex-start;
  gap: var(--gap);
}

.cls-main {
  flex: 1 1 auto;
  min-width: 0;
}

/* ★ 名单收在最右一栏、固定宽度：老师说「不影响使用，但是一样能看到」——
     它不该占中间的黄金位置，贴边放、需要时再点开。 */
.cls-side {
  flex: 0 0 300px;
  width: 300px;
  position: sticky;
  top: 8px;
}

.cls-back {
  padding: 0;
  background: none;
  border: 0;
  color: var(--c-accent);
  font-size: var(--fs-md);
  cursor: pointer;
}

.cls-back:hover {
  color: #8fd0ff;
}

/* ---------------- 班级同学：默认折叠 ---------------- */

.cls-roster {
  padding: 0;
  overflow: hidden;
}

.cls-roster-toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  width: 100%;
  min-height: var(--tap);
  padding: 12px 14px;
  background: none;
  border: 0;
  text-align: left;
  cursor: pointer;
}

.cls-roster-toggle:hover {
  background: var(--c-card-hi);
}

.cls-roster-title {
  font-size: var(--fs-lg);
  color: var(--t-bright);
}

.cls-roster-caret {
  flex: none;
  font-size: var(--fs-sm);
  color: var(--c-accent);
}

/* ★★ 默认收起 —— 老师定的：名单要「点开下拉菜单才会弹出」。
      收起时整块不占地方，页面看着就是「这一班今天练什么」。 */
.cls-roster:not(.is-open) .cls-roster-body {
  display: none;
}

.cls-roster-body {
  padding: 0 14px 14px;
  border-top: 1px solid var(--c-line-soft);
}

.cls-roster-note {
  margin: 10px 0;
}

/* ---------------- 房间：隐蔽标记 + 密码 ---------------- */

.cls-vis-tag {
  display: inline-block;
  margin-left: 8px;
  padding: 1px 7px;
  border-radius: var(--r-sm);
  background: var(--c-accent-deep);
  color: var(--c-accent);
  font-size: var(--fs-xs);
  vertical-align: middle;
}

.cls-pass {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
}

.cls-pass-hint {
  flex-basis: 100%;
  font-size: var(--fs-sm);
}

.cls-pass-input {
  flex: 1 1 160px;
  min-width: 0;
  min-height: var(--tap);
  padding: 0 12px;
  background: var(--c-input);
  border: 1px solid var(--c-line);
  border-radius: var(--r-md);
  color: var(--t-primary);
}

.cls-pass-input:focus {
  border-color: var(--c-accent-dim);
  outline: none;
}

/* ---------------- 教师开房：公开/隐蔽二选一 + 要不要密码 ---------------- */

.cls-vis {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.cls-vis .chip.is-on {
  background: var(--c-accent-deep);
  border-color: var(--c-accent-dim);
  color: var(--c-accent);
}

.cls-check {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex: none;
  white-space: nowrap;
  cursor: pointer;
}

.cls-check input {
  width: 16px;
  height: 16px;
  margin: 0;
  accent-color: var(--c-accent-dim);
}

/* ---------------- 窄屏：右栏落到下面、宽度放开 ---------------- */

@media (max-width: 899px) {
  .cls-body {
    flex-direction: column;
  }

  .cls-side {
    flex: none;
    width: 100%;
    position: static;
  }

  .cls-list {
    grid-template-columns: 1fr;
  }
}

/* ---------------- 房间页（2026-09-21） ----------------
   开完房 / 进房之后待的整页。里面那块 `.cls-room` 是现成的样式，
   这里只负责把整页摆正、别让它拉到屏幕两端去。 */

.cls-roompage {
  max-width: 860px;
  margin: 0 auto;
  padding: 4px 0 40px;
}

/* 房间页里的「准备名单」跟着房间一起居中，不要左右贴边 */
.cls-roompage .cls-members {
  max-width: 100%;
}

/* ★ 房间号：这一页最大的字（老师 2026-09-21「房间号和实际号码应该最醒目」）。
   它是老师站在讲台上要喊、学生在手机上要敲的那串字 —— 别把它做成小字。
   ★ 同一份规则在 pages-practice.css 里也有一份：练习设置屏的房间屏
     （`.cls-code`）是另一个页面，两边的 CSS 不会互相串。 */
.cls-code-num {
  font-size: 40px;
  font-weight: 700;
  letter-spacing: 4px;
  font-variant-numeric: tabular-nums;
}

/* 房间信息五行（老师 2026-09-21 指定）：一行一个语义，行距收紧一点，
   让它读起来像一张"比赛须知"，而不是散落的句子。 */
.cls-room-info {
  margin-top: 8px;
}

.cls-room-info .cls-room-sub {
  margin: 2px 0;
}

/* ---------------- 全班进度 · 名次（联机裁决，2026-09-21 / S14 2026-09-22） ----------------
   只有教师看得到（正在做的学生不画这块）。
   ★ 这里的数字来自**服务端判题**，不是客户端上报的 —— 伪造不了。
   ★★ S14 起这一块**按名次排**（旧注释写着"别改成按正确数排"，那条在
      老师定下排名开关与 §5.3 口径之后已经作废）：名次由服务端算、服务端下发
      （`members[].rank`），前端只负责把顺序画出来。口径 = 正确数优先、同分比用时。
   ★ 开关（`settings.showRank`）关掉时服务端一个 `rank` 都不发 → 这一列自动退回
      "按进度排"，不需要第二套样式。
   ★ 学生那边**看不到这块**，他有自己的一块 `.cls-board`（只有做完 / OB 位才出现）。 */

.cls-prog {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px dashed var(--c-line);
}

.cls-prog-head {
  margin-bottom: 8px;
  font-size: var(--fs-sm);
  color: var(--t-muted);
}

.cls-prog-note {
  margin: 0 0 8px;
  font-size: var(--fs-xs);
  color: var(--t-dim);
}

.cls-prog-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-height: 280px;
  overflow-y: auto;
}

.cls-prog-row {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: var(--fs-md);
}

.cls-prog-rank {
  flex: none;
  min-width: 22px;
  padding: 1px 5px;
  border-radius: var(--r-sm);
  background: var(--c-accent);
  color: var(--t-ink);
  font-size: var(--fs-sm);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  text-align: center;
}

/* 还没上榜（没做完 / 开关关掉）—— 一个淡点，**不是 0**：
   0 看着像"最后一名"，而他的真实状态是"还没做完"。 */
.cls-prog-rank.is-none {
  background: transparent;
  color: var(--t-dim);
  font-weight: 400;
}

.cls-prog-row .nm {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--t-primary);
}

.cls-prog-row.is-me .nm {
  color: var(--c-accent);
}

.cls-prog-num {
  flex: none;
  color: var(--t-bright);
  font-variant-numeric: tabular-nums;
}

.cls-prog-ok {
  flex: none;
  font-size: var(--fs-sm);
  color: var(--t-muted);
}

/* ---------------- 学生端的看板（S14，2026-09-22） ----------------
   只有「做完了 / 在 OB 位 / 本局已结算」的学生看得到。
   里面**没有别人的 `correct`**（接口里就没有那一列），所以「对 N」只有
   自己那一行会出现；名次同理，只有自己那一个。 */

.cls-board {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px dashed var(--c-line);
}

.cls-board-head {
  margin-bottom: 8px;
  font-size: var(--fs-sm);
  color: var(--t-muted);
}

.cls-board-mine {
  margin: 0 0 8px;
  font-size: var(--fs-md);
  color: var(--t-primary);
}

.cls-board-mine.is-me {
  color: var(--c-accent);
}

/* 班级页（学生、结算后）那句「你排第 N 名」（老师 2026-10-01 报的缺口）。
   ★ 用金色 —— 与看板里「名次」那一格同一档视觉权重：这是一句结论，
     不该和旁边的说明文字（`--t-dim`）混成一片。 */
.cls-myrank {
  margin: 10px 0 0;
  font-size: var(--fs-md);
  font-weight: 600;
  color: var(--c-gold);
}

.cls-board-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 3px 0;
  font-size: var(--fs-md);
}

.cls-board-row .nm {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--t-primary);
}

.cls-board-row.is-me .nm {
  color: var(--c-accent);
}

.cls-board-num {
  flex: none;
  color: var(--t-bright);
  font-variant-numeric: tabular-nums;
}

.cls-board-ok {
  flex: none;
  font-size: var(--fs-sm);
  color: var(--t-muted);
}

/* 班级看板每行的**逐题对错点**（老师 2026-10-01：「连逐题对错都要」）。
   ★ 复用 `.vs-lane-dots` 那套点（`.vs-dot` / `.is-ok` / `.is-bad` / `.is-done`），
     只加一条：让它在看板行里靠右收尾，别把"做完了"那个标签顶出屏幕。
   ★ 不加 `.is-seat`（那是选手卡的窄版），看板行比选手卡宽，
     题号写出来更有用（`vsDots` 里 `tot <= 20` 才写题号）。 */
.cls-board-dots {
  flex: 1 1 auto;
  justify-content: flex-end;
  min-width: 0;
}

.cls-board-tag {
  flex: none;
  font-size: var(--fs-sm);
  color: var(--t-dim);
}

.cls-board-tag.is-done {
  color: var(--c-green-a);
}

/* ---------------- 教师：开房表单 · 挡位化改版（2026-09-25） ----------------
   下拉框全部改成按钮；词本 / Word list 两块位置固定；Word list 每页 12 个翻页；
   挡位（上一次 / 内置 / 我的）+ 设置码分享。服务端接口与字段一字未动。 */

.cls-gears {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
}

.cls-gear-last {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  background: var(--c-accent-deep);
  border: 1px solid var(--c-accent-dim);
  border-radius: var(--r-sm);
  color: var(--t-bright);
  padding: 8px 14px;
  cursor: pointer;
  text-align: left;
}

.cls-gear-last:hover {
  border-color: var(--c-accent);
}

.cls-gear-last-t {
  font-size: var(--fs-md);
}

.cls-gear-last-s {
  font-size: var(--fs-xs);
  color: var(--t-muted);
}

.cls-gears-actions {
  margin-left: auto;
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}

.cls-mygears {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
  margin: 2px 0 10px;
}

.cls-mygears .lbl {
  font-size: var(--fs-xs);
  color: var(--t-dim);
}

.cls-mygears-empty {
  font-size: var(--fs-xs);
  color: var(--t-dim);
}

.cls-gear-item {
  font-size: var(--fs-sm);
}

.cls-gear-x {
  margin-left: 6px;
  color: var(--t-dim);
}

.cls-gear-x:hover {
  color: var(--t-bright);
}

.cls-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

/* 这些场景共用同一颗 chip：挡位预设 / 我的挡位 / 设置按钮组 / 翻页 / 导入 */
.cls-chips .chip,
.cls-gears-actions .chip,
.cls-mygears .chip,
.cls-import .chip,
.cls-unitpage .chip {
  background: var(--c-input);
  border: 1px solid var(--c-line);
  border-radius: var(--r-sm);
  color: var(--t-muted);
  padding: 5px 12px;
  cursor: pointer;
  font-size: var(--fs-sm);
}

.cls-chips .chip:hover,
.cls-gears-actions .chip:hover,
.cls-mygears .chip:hover,
.cls-import .chip:hover,
.cls-unitpage .chip:hover {
  border-color: var(--c-accent-dim);
  color: var(--t-bright);
}

.cls-chips .chip.is-on,
.cls-unitpage .chip.is-on {
  background: var(--c-accent-deep);
  border-color: var(--c-accent-dim);
  color: var(--t-bright);
}

.cls-chips .chip.is-locked {
  opacity: 0.45;
}

.cls-import.is-hidden {
  display: none;
}

.cls-pickgrid {
  display: flex;
  gap: 10px;
  align-items: stretch;
  flex-wrap: wrap;
  margin: 2px 0 12px;
}

.cls-pickbox {
  flex: 1 1 260px;
  min-width: 230px;
  border: 1px solid var(--c-line-soft);
  border-radius: var(--r-sm);
  padding: 10px;
}

.cls-pickbox-t {
  font-size: var(--fs-sm);
  color: var(--t-dim);
  margin-bottom: 8px;
}

.cls-bookgrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 6px;
}

.cls-book {
  text-align: left;
  background: var(--c-input);
  border: 1px solid var(--c-line);
  border-radius: var(--r-sm);
  color: var(--t-primary);
  padding: 8px 10px;
  cursor: pointer;
}

.cls-book:hover {
  border-color: var(--c-accent-dim);
}

.cls-book.is-on {
  border-color: var(--c-accent);
  color: var(--t-bright);
  background: var(--c-accent-deep);
}

.cls-book .t {
  display: block;
  font-size: var(--fs-sm);
}

.cls-book .m {
  display: block;
  font-size: var(--fs-xs);
  color: var(--t-dim);
}

.cls-unitgrid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 5px;
}

.cls-unit {
  display: flex;
  flex-direction: column;
  align-items: center;
  background: var(--c-input);
  border: 1px solid var(--c-line);
  border-radius: var(--r-sm);
  color: var(--t-primary);
  padding: 6px 2px;
  cursor: pointer;
}

.cls-unit:hover {
  border-color: var(--c-accent-dim);
}

.cls-unit.is-on {
  border-color: var(--c-accent);
  background: var(--c-accent-deep);
}

.cls-unit .cls-unit-no {
  font-size: var(--fs-md);
  color: var(--t-primary);
}

.cls-unit .cls-unit-ct {
  font-size: 11px;
  color: var(--t-dim);
}

.cls-unit.is-on .cls-unit-no {
  color: var(--t-bright);
}

.cls-unit.is-on .cls-unit-ct {
  color: var(--t-muted);
}

.cls-unitpage {
  display: flex;
  gap: 6px;
  align-items: center;
  flex-wrap: wrap;
  margin-top: 8px;
}

.cls-unitpage .chip {
  padding: 3px 10px;
}

.cls-unitpage .chip:disabled {
  opacity: 0.4;
  cursor: default;
}

.cls-unitpage-info {
  font-size: var(--fs-xs);
  color: var(--t-dim);
}

/* 词本搜索栏（2026-09-25）：默认只列通用词本，关键字搜出原版教材 / 专业词本 */
.cls-booksearch {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
  flex-wrap: wrap;
}

.cls-booksearch .cls-inp {
  flex: 1;
  min-width: 180px;
}

.cls-booksearch-n {
  font-size: var(--fs-xs);
  color: var(--t-dim);
}


/* ---------- 桌子（S26）：候选池 → 抢位 → OB ----------
   这一块是独立自习室页的主角，所以放在房间卡**之前**。
   全部沿用 theme.css 令牌。 */
.cls-table-wrap {
  margin: 0 0 var(--gap);
  padding: 14px 16px 16px;
  background: var(--c-card);
  border: 1px solid var(--c-line);
  border-radius: var(--r-md);
}
.cls-table-head {
  display: flex; align-items: center; gap: 10px; margin-bottom: 12px; flex-wrap: wrap;
}
.cls-table-mode { color: var(--t-bright); font-weight: 700; font-size: var(--fs-lg); }
.cls-table-seat { color: var(--t-muted); font-size: var(--fs-sm); }
.cls-table-err { color: var(--c-red-a); font-size: var(--fs-sm); min-height: 18px; margin: 6px 0 0; }
.cls-table-loading { margin: 0; }

/* 座位：等宽一排，空位虚线，坐着的实线 */
.cls-seats { display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 14px; }
.cls-seat {
  width: 132px; min-height: 128px;
  display: flex; flex-direction: column; align-items: center; justify-content: flex-start;
  gap: 6px; padding: 12px 10px;
  background: var(--c-card-hi); border: 1px solid var(--c-line); border-radius: var(--r-md);
}
.cls-seat.is-empty { background: var(--c-input); border-style: dashed; justify-content: center; }
.cls-seat.is-me { border-color: var(--c-accent); box-shadow: 0 0 0 1px var(--c-accent) inset; }
.cls-seat-name { color: var(--t-bright); font-size: var(--fs-md); font-weight: 600; text-align: center; }
.cls-seat-empty-mark { color: var(--t-dim); font-size: var(--fs-sm); }
.cls-seat-locked { color: var(--t-dim); font-size: var(--fs-xs); }
.cls-seat-btn {
  min-height: 30px; padding: 0 14px; border-radius: var(--r-md);
  border: 1px solid var(--c-line); background: var(--c-card); color: var(--t-primary);
  font-size: var(--fs-sm); font-family: var(--font); cursor: pointer;
}
.cls-seat-btn:hover { border-color: var(--c-accent); background: var(--c-hover); }
.cls-seat-btn.is-go {
  background: linear-gradient(var(--c-green-a), var(--c-green-b));
  color: #0d1405; border-color: transparent; font-weight: 700;
}
.cls-seat-rank { color: var(--c-gold); font-size: var(--fs-xs); text-align: center; }

/* ---------- 桌位改版（2026-09-30）：对战席 ----------
   参考图 = Dota 风格房间：顶部一排是房里所有人，
   下面 左一队 / 右二队，中间空白区 = 本次比赛的设置 + 干扰词云。
   队伍只是**展示层**的分法（前一半座位左、后一半右），
   抢位 / 判分仍归服务端，座位没有队伍概念。全部沿用 theme.css 令牌。 */
.vs-roster {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 8px 10px; margin-bottom: 12px;
  background: var(--c-input); border: 1px solid var(--c-line-soft); border-radius: var(--r-md);
}
.vs-roster-cap { color: var(--t-dim); font-size: var(--fs-xs); flex: none; }
.vs-chip {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 2px 8px 2px 3px; border-radius: 999px;
  background: var(--c-card); border: 1px solid var(--c-line);
}
.vs-chip.is-seated { border-color: var(--c-accent); }
.vs-chip.is-me { box-shadow: 0 0 0 1px var(--c-accent) inset; }
.vs-chip-name { color: var(--t-primary); font-size: var(--fs-xs); }
.vs-arena {
  display: grid; grid-template-columns: 1fr minmax(220px, 1.2fr) 1fr;
  gap: 12px; align-items: start; margin-bottom: 14px;
}
.vs-team { display: flex; flex-direction: column; gap: 8px; }
.vs-team-cap { color: var(--t-dim); font-size: var(--fs-xs); margin: 0; letter-spacing: 2px; }
/* 参考图里的队伍识别色：左队绿、右队红（只上在队标与座位卡侧边上，克制度优先） */
.vs-team.is-left .vs-team-cap { color: var(--c-green-a); }
.vs-team.is-right .vs-team-cap { color: var(--c-red-a); text-align: right; }
.vs-team.is-left .cls-seat { border-left: 3px solid var(--c-green-a); }
.vs-team.is-right .cls-seat { border-right: 3px solid var(--c-red-a); }

/* ---------- 选手框体固定 + 动作按钮挪到两侧（2026-09-30 晚，老师）----------
   · 「框体固定」= 1v1 与 2v2 用**同一套宽高**，空位与有人也一样大 ——
     来了人、换了模式，队形一个像素都不跳（原来 1v1 是 168、2v2 缩成 132）。
   · 「下桌一类的按钮安排到左右两侧」= 卡里只留信息，按钮挂进 `.vs-acts`，
     贴到**队伍外侧**：左队的按钮在左（row-reverse 把视觉顺序翻过来），
     右队的按钮在右 —— 卡片始终挨着中间，两队面对面。 */
.vs-arena .cls-seat {
  width: 168px; height: 190px;
  justify-content: center; gap: 8px; padding: 14px 10px;
}
.vs-slot { display: flex; align-items: center; gap: 8px; }
.vs-slot.is-left { flex-direction: row-reverse; }
.vs-acts { display: flex; flex-direction: column; gap: 6px; }
.vs-center { display: flex; flex-direction: column; gap: 10px; min-height: 220px; }
.vs-set, .vs-cloud {
  padding: 10px 12px; background: var(--c-input);
  border: 1px solid var(--c-line-soft); border-radius: var(--r-md);
}
.vs-set-title { color: var(--t-bright); font-size: var(--fs-sm); font-weight: 700; margin: 0 0 6px; }
.vs-set-row { color: var(--t-muted); font-size: var(--fs-xs); margin: 0 0 3px; }
.vs-cloud { flex: 1; }  /* 词云吃掉中间剩余的空白（参考图中央是大块区域） */
/* 窄屏：两队在上一排，设置 + 词云整条垫底 */
@media (max-width: 767px) {
  .vs-arena { grid-template-columns: 1fr 1fr; }
  .vs-arena .vs-team.is-left { grid-column: 1; grid-row: 1; }
  .vs-arena .vs-team.is-right { grid-column: 2; grid-row: 1; }
  .vs-arena .vs-center { grid-column: 1 / -1; grid-row: 2; min-height: 0; }
}

/* 候选池 / OB */
.cls-pool { border-top: 1px solid var(--c-line-soft); padding-top: 10px; margin-bottom: 12px; }
.cls-pool-title { color: var(--t-bright); font-size: var(--fs-md); font-weight: 600; margin: 0 0 4px; }
.cls-pool-note { font-size: var(--fs-xs); margin: 0 0 8px; }

/* 最近 3 局 */
.cls-games { border-top: 1px solid var(--c-line-soft); padding-top: 10px; }
.cls-game {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 6px 8px; border-radius: var(--r-sm); background: var(--c-input);
  margin-bottom: 5px; font-size: var(--fs-sm);
}
.cls-game-no { color: var(--t-dim); font-size: var(--fs-xs); flex: none; }
.cls-game-p { color: var(--t-primary); }
.cls-game-p.is-win { color: var(--c-gold); font-weight: 700; }


/* ---------- 「现在在班里」（老师 2026-09-26） ----------
   ★ 在班 ≠ 在线：这一块只统计**打开了这个班页面**的人（页面级心跳）。 */
.cls-inclass-box { margin: 0 0 var(--gap); padding: 12px 16px 14px; }
.cls-inclass-head {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 4px;
}
.cls-inclass-title { color: var(--t-bright); font-weight: 700; font-size: var(--fs-lg); }
.cls-inclass-count { color: var(--t-muted); font-size: var(--fs-sm); }
.cls-inclass-new {
  margin-left: auto; min-height: 32px; padding: 0 14px; border-radius: var(--r-md);
  border: 1px solid var(--c-accent); background: var(--c-accent-deep);
  color: var(--t-bright); font-size: var(--fs-sm); font-family: var(--font); cursor: pointer;
}
.cls-inclass-new:hover { background: var(--c-hover); }
.cls-inclass-note { font-size: var(--fs-xs); margin: 0 0 8px; }
.cls-inclass-list { display: flex; flex-direction: column; gap: 2px; }

/* ---------- 「现在开着的自习室」（2026-09-26） ----------
   ★ 这一段的用意：让「老师不在，房间照样在」看得见 —— 房主一栏写「房主不在」，
     「0 人在里头」照样列出来。这是未来大厅 20 间房的验收样本。 */
.cls-inclass-sep {
  margin: 10px 0 8px; height: 0; border-top: 1px solid var(--c-line-soft);
}
.cls-inclass-sub {
  margin: 0 0 6px; font-size: var(--fs-sm); color: var(--t-muted);
}
.cls-rrow {
  display: flex; align-items: center; gap: 10px;
  padding: 7px 0; border-top: 1px solid var(--c-line-soft);
}
.cls-rrow:first-of-type { border-top: 0; }
.cls-rrow .cls-pname { flex: 1 1 auto; min-width: 0; flex-wrap: wrap; }
.cls-room-code {
  font-family: ui-monospace, "Cascadia Mono", Consolas, monospace; font-weight: 700;
  letter-spacing: 1px; color: var(--t-bright);
}
.cls-room-meta { font-size: var(--fs-xs); }
.cls-room-go { flex: 0 0 auto; min-height: 30px; padding: 0 12px; font-size: var(--fs-sm); }

/* ---------- 联机大厅（2026-09-26） ----------
   ★ 10 间固定房：**没人时静默、有人点进才亮**。
     两种长相共用一套骨架，只差一颗灯 + 按钮的主次色 ——
     所以「静默」看上去不是「坏了」，而是「还空着」。 */
.cls-inclass-sub-line {
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; margin: 0 0 6px;
}
.cls-inclass-sub-line .cls-inclass-sub { margin: 0; }
.cls-hrow {
  display: flex; align-items: center; gap: 10px;
  padding: 7px 0; border-top: 1px solid var(--c-line-soft);
}
.cls-hrow:first-of-type { border-top: 0; }
.cls-hrow .cls-hmid { flex: 1 1 auto; min-width: 0; }
.cls-hrow .cls-pname { flex-wrap: wrap; }
.cls-hname { font-weight: 600; color: var(--t-bright); }
/* 那颗灯：亮 = 有人在里头；暗 = 空着（不是「关闭」）。 */
.cls-hlamp {
  flex: 0 0 auto; width: 8px; height: 8px; border-radius: 50%;
  background: var(--c-line); box-shadow: none;
}
.cls-hrow.is-live .cls-hlamp {
  background: var(--c-green-a); box-shadow: 0 0 0 3px var(--c-green-b);
}
.cls-hgo { flex: 0 0 auto; min-height: 30px; padding: 0 12px; font-size: var(--fs-sm); }
/* ★★ 2026-09-30：静默大厅房那排「形态」chip（1v1 · 两个人 / Free For All · 不限人数）。
   复用表单那套 `.cls-chips .chip`（底色/边框/is-on 都不用重写），只把它压小一号 ——
   大厅一行有 10 行，chip 按表单尺寸摆会把整个列表撑胖。
   ★ 它挂在 `.cls-hmid` 里（`mid` 是 flex:1 那一格），所以跟着行的左边对齐。 */
.cls-hrow .cls-hmode { margin-top: 6px; }
.cls-hrow .cls-hmode .chip {
  padding: 3px 10px;
  font-size: var(--fs-xs);
  min-height: 0;
}

/* ---------- 学生开房弹窗里的「练什么」一行 ---------- */
.ot-what {
  padding: 8px 10px; border: 1px solid var(--c-line); border-radius: var(--r-md);
  background: var(--c-input); color: var(--t-bright); font-size: var(--fs-md);
}

/* ---------- 踢人按钮（S27：只在班级房、只有 host 看得到） ---------- */
.cls-kick {
  margin-left: auto; flex: none;
  min-height: 28px; padding: 0 12px;
  border-radius: var(--r-md);
  border: 1px solid var(--c-line);
  background: var(--c-card);
  color: var(--t-muted);
  font-size: var(--fs-xs); font-family: var(--font); cursor: pointer;
}
.cls-kick:hover { border-color: var(--c-red-a); color: var(--c-red-a); }

/* ★★ 2026-09-30：桌子下方的**动作区**。
   老师原话：「把 1v1 桌子空桌里的『开始比赛』和『退出这间自习室』放到
   『回班级』这附近，让他们挨在一起。」
   表头只留「这张桌子是什么 / 我能改什么」，三颗"接下来去哪"的按钮统一收到这里。
   ★ 用一条浅分隔线跟上面的战报分开 —— 它是**页面的出口**，不是战报的一部分。 */
/* 「开始比赛」旁边那句「还差谁」（2026-10-03）。
   老师定的口径：人数不够时**按钮置灰 + 写清楚原因**，而不是把按钮藏起来 ——
   藏起来房主只会觉得"坏了、找不到开始键"。
   ★ 与按钮同一行、字号小一档、颜色用告警色（不是正文色）：
     它是"还没就绪"的说明，不是普通文字。 */
.cls-startwhy {
  align-self: center;
  margin-left: 8px;
  font-size: var(--fs-sm);
  color: var(--c-gold);
}

.cls-table-foot {

  display: flex; flex-wrap: wrap; gap: 10px; align-items: center;
  margin: 18px 0 2px; padding-top: 14px;
  border-top: 1px solid var(--c-line);
}
.cls-table-foot .btn { flex: 0 0 auto; }

/* ---------------- 桌页完善（2026-09-30 第 2 批） ---------------- */

/* 房间号胶囊：自建房唯一的邀请入口，排表头第一位、点一下整串复制 */
.cls-code {
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
  padding: 4px 12px;
  background: var(--c-input);
  border: 1px solid var(--c-gold);
  border-radius: var(--r-md);
  cursor: pointer;
}

.cls-code-k {
  font-size: var(--fs-xs);
  color: var(--t-muted);
}

.cls-code-v {
  font-size: var(--fs-lg);
  letter-spacing: 2px;
  color: var(--c-gold);
  font-family: Consolas, monospace;
}

/* VS 徽记：两队中间的那个锚 —— 开局后换成「进行中」当灯用 */
.vs-mark {
  align-self: center;
  padding: 6px 18px;
  border: 2px solid var(--c-gold);
  border-radius: 50%;
  color: var(--c-gold);
  font-weight: 700;
  font-size: var(--fs-lg);
  letter-spacing: 2px;
}

.vs-mark.is-live {
  border-color: var(--c-green-a);
  color: var(--c-green-a);
  border-radius: var(--r-md);
  font-size: var(--fs-sm);
  letter-spacing: 4px;
}

/* 干扰词云本体：字号/色相由词哈希决定（重画也不闪），悬停看释义 */
.vs-cloud-body {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 12px;
  min-height: 64px;
  padding: 8px 10px;
  background: var(--c-input);
  border: 1px solid var(--c-line);
  border-radius: var(--r-md);
}

.vs-cloud-w {
  line-height: 1.5;
  cursor: default;
}

/* 开局后的座位进度条 —— **只在服务端没下发逐题对错时用**。
   ① 班级房 / 大厅房：「别人的 correct 一律不下发」是裁决定的口径；
   ② `judge_mode='once'`（完成以后统计对错）：那一局在做的时候什么都不说。
   对战胜负看得见的时候，卡里画的是 `.vs-prog-dots` 那排绿红点
   （见本文件末尾「旁观者视角」那一段）。 */
.vs-prog {
  width: 100%;
  height: 6px;
  background: var(--c-input);
  border-radius: 3px;
  overflow: hidden;
}

.vs-prog-fill {
  display: block;
  height: 100%;
  width: 0;
  background: var(--c-green-a);
  transition: width 0.4s ease;
}

.vs-prog-t {
  font-size: var(--fs-xs);
  color: var(--t-muted);
}

/* 队伍色顶条 + 空位呼吸感：对抗感落在卡上，不只落在队标上 */
.vs-slot.is-left .cls-seat {
  border-top: 3px solid var(--c-green-a);
}

.vs-slot.is-right .cls-seat {
  border-top: 3px solid var(--c-red-a);
}

.vs-arena .cls-seat.is-empty {
  border-style: dashed;
}

@keyframes vsPulse {
  0%, 100% { opacity: 0.55; }
  50% { opacity: 1; }
}

.vs-arena .cls-seat.is-empty .cls-seat-empty-mark {
  animation: vsPulse 2.2s ease-in-out infinite;
}

/* ============================================================================
   1v1 对战可视化（第 3 批 · 2026-09-30）：开局 VS 门 · 双赛道 · 结算图
   ----------------------------------------------------------------------------
   老师原话：「房间里准备对战的时候，按照今天约定的来。但是一旦开始了比赛，
   就用屏 1 做倒数五秒的准备图。进入以后对战双方和所有 OB 都可以看到屏 2
   里上方的进度，结算以后也可以看到结算图。」
   观感口径见 `1v1可视化样板_20260925\index.html` 的 .gate / .lanes / .result。

   ★★ 这三个件**跨两个页面**（练习页的答题屏 + 班级页的桌子），所以样式只写在
      这一份里。分两处写就会出现"玩家看到的赛道和旁观者看到的不是一套"——
      而这一屏的说服力恰恰来自「我俩看到的是同一份」。
   ============================================================================ */

/* ---------- 屏 1：开局 VS 门（倒数 5 秒） ---------- */
.vs-gate {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 24px;
  padding: 6px 0 14px;
  flex-wrap: wrap;
}

.vs-fighter {
  width: 200px;
  text-align: center;
  background: var(--c-card);
  border: 1px solid var(--c-line);
  border-radius: var(--r-md);
  padding: 12px 10px 10px;
}

.vs-fighter.is-me { border-color: var(--c-accent); }
.vs-fighter.is-op { border-color: var(--c-red-a); }

.vs-fighter-av img,
.vs-fighter-av svg {
  width: 84px;
  height: 84px;
  object-fit: contain;
  display: block;
  margin: 0 auto 6px;
}

.vs-fighter-nm {
  font-size: var(--fs-lg);
  font-weight: 700;
  color: var(--t-bright);
}

.vs-fighter-rk {
  font-size: var(--fs-xs);
  color: var(--c-gold);
  margin-top: 2px;
}

.vs-fighter-cl {
  font-size: var(--fs-xs);
  color: var(--t-dim);
  margin-top: 3px;
}

.vs-fighter-me {
  font-size: var(--fs-xs);
  color: var(--c-accent);
  margin-top: 4px;
  letter-spacing: 1px;
}

.vs-vs {
  font-size: 40px;
  font-weight: 800;
  color: var(--t-bright);
  letter-spacing: 2px;
  text-shadow: 0 0 22px rgba(102, 192, 244, 0.55);
}

.vs-vs-en {
  display: block;
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--c-accent);
  letter-spacing: 3px;
  text-align: center;
  margin-top: 2px;
}

.vs-gate-info {
  text-align: center;
  border-top: 1px solid var(--c-line-soft);
  padding-top: 12px;
}

.vs-gate-line {
  font-size: var(--fs-lg);
  color: var(--t-bright);
  margin: 0 0 6px;
}

.vs-gate-sub {
  font-size: var(--fs-xs);
  color: var(--t-muted);
  margin: 0;
}

.vs-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--c-accent-deep);
  border: 1px solid var(--c-accent);
  color: var(--t-bright);
  font-weight: 700;
  font-size: 16px;
  margin-left: 8px;
  vertical-align: middle;
}

.vs-count.is-go {
  width: auto;
  padding: 0 14px;
  border-radius: 999px;
  background: linear-gradient(var(--c-green-a), var(--c-green-b));
  border-color: var(--c-green-a);
  color: var(--t-ink);
}

/* ---------- 屏 2 上方：双赛道（我 / 对手，逐题的对错点） ---------- */
/* 答题屏顶部那个容器：只负责"占位 + 被清空重填"，样式全在 .vs-lanes 里 */
.vs-lanes-slot { margin: 0 0 4px; }

.vs-lanes {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 0 0 12px;
}

.vs-lane {
  display: flex;
  align-items: center;
  gap: 10px;
  background: var(--c-card);
  border: 1px solid var(--c-line);
  border-radius: var(--r-md);
  padding: 7px 10px;
}

.vs-lane.is-me { border-left: 4px solid var(--c-accent); }
.vs-lane.is-op { border-left: 4px solid var(--c-red-a); }
.vs-lane.is-done { opacity: 0.92; }

.vs-lane-who {
  width: 150px;
  flex: none;
  display: flex;
  align-items: center;
  gap: 8px;
}

.vs-lane-av { flex: none; display: inline-flex; }

.vs-lane-av img,
.vs-lane-av svg {
  width: 26px;
  height: 26px;
  object-fit: contain;
  display: block;
}

.vs-lane-tx { min-width: 0; }

.vs-lane-tx b {
  display: block;
  font-size: var(--fs-md);
  color: var(--t-bright);
  font-weight: 600;
  line-height: 1.2;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.vs-lane-sub {
  display: block;
  font-size: var(--fs-xs);
  color: var(--t-dim);
  line-height: 1.2;
}

.vs-lane-dots {
  display: flex;
  gap: 4px;
  flex: 1;
  min-width: 0;
  flex-wrap: nowrap;
  overflow: hidden;
}

/* 点：绿=对 / 红=错 / 中性=答过但对错不公布 / 高亮=正在做的那一题 / 灰=没答 */
.vs-dot {
  flex: 0 1 22px;
  min-width: 10px;
  height: 22px;
  border-radius: var(--r-sm);
  background: var(--c-input);
  border: 1px solid var(--c-line);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  font-style: normal;
  color: var(--t-dim);
}

.vs-dot.is-ok {
  background: var(--c-green-b);
  border-color: var(--c-green-a);
  color: var(--t-bright);
}

.vs-dot.is-bad {
  background: var(--c-red-b);
  border-color: var(--c-red-a);
  color: var(--t-bright);
}

.vs-dot.is-done {
  background: var(--c-hover);
  border-color: var(--c-line);
  color: var(--t-primary);
}

.vs-dot.is-now {
  border-color: var(--c-accent);
  color: var(--c-accent);
  box-shadow: 0 0 0 2px rgba(102, 192, 244, 0.28);
}

.vs-lanes.is-compact .vs-lane { padding: 5px 8px; }
.vs-lanes.is-compact .vs-lane-who { width: 118px; }
.vs-lanes.is-compact .vs-lane-av img,
.vs-lanes.is-compact .vs-lane-av svg { width: 20px; height: 20px; }
.vs-lanes.is-compact .vs-dot { height: 18px; flex-basis: 18px; font-size: 10px; }

/* ---------- 屏 3：结算 ---------- */
.vs-result { text-align: center; padding: 4px 0 12px; }

.vs-win {
  font-size: 40px;
  font-weight: 800;
  letter-spacing: 4px;
  color: var(--c-gold);
  text-shadow: 0 0 24px rgba(212, 175, 55, 0.5);
}

.vs-win.is-lose { color: var(--t-muted); text-shadow: none; }

.vs-win.is-draw {
  color: var(--c-accent);
  text-shadow: 0 0 24px rgba(102, 192, 244, 0.45);
}

.vs-result-sub {
  color: var(--t-muted);
  font-size: var(--fs-xs);
  margin-top: 6px;
}

.vs-recap {
  display: flex;
  gap: 14px;
  justify-content: center;
  margin: 14px 0 4px;
  flex-wrap: wrap;
}

.vs-recap-col {
  width: 260px;
  background: var(--c-card);
  border: 1px solid var(--c-line);
  border-radius: var(--r-md);
  padding: 12px;
  text-align: left;
}

.vs-recap-col.is-me { border-color: var(--c-accent); }

.vs-recap-top {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 8px;
}

.vs-recap-top img,
.vs-recap-top svg { width: 40px; height: 40px; object-fit: contain; flex: none; }

.vs-recap-nm { font-size: var(--fs-lg); font-weight: 700; color: var(--t-bright); }
.vs-recap-rk { font-size: var(--fs-xs); color: var(--c-gold); }

.vs-stat {
  display: flex;
  justify-content: space-between;
  font-size: var(--fs-xs);
  padding: 4px 0;
  border-bottom: 1px dashed var(--c-line-soft);
}

.vs-stat:last-child { border-bottom: 0; }
.vs-stat span:last-child { color: var(--t-bright); font-weight: 600; }

/* =====================================================================
   1v1 对战可视化（第 3 批 · 续 · 2026-09-30 晚）—— **旁观者（OB）视角**
   ---------------------------------------------------------------------
   老师原话：「进入以后**对战双方和所有 OB** 都可以看到屏 2 里上方的进度，
             **结算以后也可以看到结算图**」。
   ★ 图纸与对战双方**共用**（`ui.vsLanes()` / `ui.vsResult()`），
     这里只补三处"搬进桌子页之后"才需要的间距与尺寸 —— 一份图纸，两处安放。
     **不新开一套 `ob-*` 类名**：那等于承认两处会画得不一样。
   ===================================================================== */

/* 桌上那两条赛道：夹在表头与「房间里 N 人」之间，上下留一口气 */
.vs-lanes-slot.is-table { margin: 10px 0 4px; }

/* 桌上的结算图：与 `.cls-table-wrap` 里其它块同款外边距 */
.cls-result-box { margin: 10px 0 4px; }

/* 选手卡里那一排点 —— 卡片只有 132px 宽，所以不写题号（纯靠颜色与位置，
   与 `ui.vsDots(..., 'seat')` 里那句 `showNum = false` 是同一件事的两半）。
   题多了一个格子缩到 6px、换行继续排（`.cls-seat` 是 min-height，卡会长高）。 */
.vs-lane-dots.is-seat {
  gap: 2px;
  justify-content: center;
  flex-wrap: wrap;
  overflow: visible;
}

.vs-lane-dots.is-seat .vs-dot {
  flex: 0 1 12px;
  min-width: 6px;
  height: 12px;
  border-radius: 3px;
  font-size: 0;
}

/* 卡里那排点与它下面「3 / 10 题」之间留一口气 */
.cls-seat .vs-prog-dots { margin: 4px 0 2px; }

/* 结算图那张卡（`ui.vsResult()` 返回的根节点）—— **两个消费方共用**：
   答题屏的结算屏 与 桌子页（OB 视角）。两侧 260px 的成绩卡 + 间距约 548px，
   在宽屏上不设上限会摊成很长一条，所以给个上限并居中。
   ★ `_selftest.py` 有一条「JS 用到的 class 必须在 CSS 里有定义」——
     这个类名就是那时补出来的：光有钩子没有样式 = 一次红。 */
.vs-result-box { max-width: 720px; margin: 0 auto; }

/* ============================================================
   学习时长（老师 2026-09-30）—— 教师专属面板，挂在班级页最下
   ============================================================ */
.cls-stime { margin-top: 14px; padding: 14px 16px; }
.cls-stime h3 { margin: 0 12px 0 0; font-size: 15px; display: inline; }
.cls-stime-head { display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 12px; }
.cls-stime-note { color: var(--t-muted); font-size: 12px; }
.cls-stime-day {
  background: var(--c-input);
  border: 1px solid var(--c-line);
  border-radius: 3px;
  color: inherit;
  padding: 3px 8px;
  font-size: 13px;
}
.cls-stime-list { margin-top: 10px; }
.cls-stime-row {
  padding: 6px 4px;
  border-top: 1px solid var(--c-line-soft);
}
.cls-stime-row:first-child { border-top: 0; }
.cls-stime-top { display: flex; justify-content: space-between; gap: 12px; }
.cls-stime-top .n { font-size: 14px; }
.cls-stime-top .t { font-size: 14px; color: var(--c-accent); white-space: nowrap; }
.cls-stime-sub { margin-top: 1px; font-size: 12px; color: var(--t-muted); }
.cls-stime-empty { padding: 10px 4px; color: var(--t-muted); font-size: 13px; }
