/**
 * 各页专有样式。通用的在 app.css，这里只放某一屏独有的东西。
 */

/* ── P0 登录 ────────────────────────────────────────── */
.login-aside {
  width: 380px;
  flex: none;
  background: var(--accent);
  border-radius: var(--r-card) 0 0 var(--r-card);
  padding: 40px;
  color: #eefaf9;
  display: flex;
  flex-direction: column;
}

.login-mark {
  width: 34px;
  height: 34px;
  border-radius: 10px;
  flex: none;
  display: block;
}

.login-brand {
  font-weight: 800;
  font-size: 17px;
  color: #fff;
}

.login-slogan {
  font-weight: 800;
  font-size: 24px;
  line-height: 1.4;
  margin-top: 36px;
  color: #fff;
}

.login-points {
  list-style: none;
  padding: 0;
  margin: 24px 0 0;
  display: flex;
  flex-direction: column;
  gap: 12px;
  font-size: 13.5px;
  color: rgba(255, 255, 255, 0.85);
}

.login-points li {
  display: flex;
  align-items: center;
  gap: 10px;
}

.login-foot {
  margin-top: auto;
  font-size: 11.5px;
  color: rgba(255, 255, 255, 0.55);
}

.login-card {
  width: 400px;
  border-radius: 0 var(--r-card) var(--r-card) 0;
  padding: 36px 40px;
  display: flex;
  flex-direction: column;
}

.login-tabs {
  display: flex;
  gap: 22px;
  border-bottom: 1px solid var(--line-2);
}

.login-tab {
  padding-bottom: 10px;
  font-size: 14px;
}

.login-tab.is-on {
  font-weight: 700;
  border-bottom: 2px solid var(--accent);
  color: var(--accent-dark);
}

.login-tab.is-off {
  color: var(--muted-2);
  cursor: not-allowed;
}

.pw-wrap {
  position: relative;
}

.pw-eye {
  position: absolute;
  right: 12px;
  top: 50%;
  transform: translateY(-50%);
  background: none;
  border: none;
  cursor: pointer;
  padding: 4px;
  display: flex;
}

/* ── P0b 绑定微信 ───────────────────────────────────── */
.bind-card {
  width: 520px;
  padding: 40px;
  text-align: center;
}

.bind-steps {
  display: flex;
  justify-content: center;
  gap: 20px;
  align-items: center;
  font-size: 12.5px;
  color: var(--muted);
}

.bind-line {
  width: 24px;
  height: 1px;
  background: var(--line-strong);
}

.bind-title {
  font-weight: 800;
  font-size: 20px;
  margin-top: 24px;
}

/* 二维码容器。尺寸跟着里面的 SVG 走，不写死 ——
   原来 180px 是照着设计稿那张 158px 装饰图定的，
   换成服务端真生成的 220px 二维码就会溢出来压住上下文字。 */
.bind-qr {
  display: inline-flex;
  border: 1px solid rgba(18, 42, 41, 0.1);
  border-radius: 12px;
  margin: 22px auto 0;
  padding: 10px;
  background: #fff;
  line-height: 0;
}

.dot-ok {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--ok);
  flex: none;
}

/* ── 空状态卡 ───────────────────────────────────────── */
.empty-card {
  width: 640px;
  max-width: 100%;
  padding: 48px;
  text-align: center;
}

.empty-title {
  font-weight: 800;
  font-size: 22px;
  margin-top: 18px;
}

.empty-sub {
  font-size: 14px;
  color: var(--ink-2);
  margin-top: 8px;
}

/* ── 名册 / 搜索 ────────────────────────────────────── */
.search-wrap {
  display: flex;
  align-items: center;
  gap: 8px;
  border: 1px solid var(--line-3);
  border-radius: var(--r-btn);
  padding: 7px 12px;
  background: var(--surface);
  min-width: 240px;
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}

.search-wrap:focus-within {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(15, 111, 122, 0.1);
}

.search-in {
  border: none;
  outline: none;
  background: none;
  font-size: 13.5px;
  flex: 1;
  min-width: 0;
}

/* 表格滚动容器：表头 sticky 需要这一层 */
.table-scroll {
  overflow: auto;
  max-height: 66vh;
}

.is-done {
  text-decoration: line-through;
  color: var(--muted);
}

/* ── 分段控件 ───────────────────────────────────────── */
.seg-group {
  display: inline-flex;
  background: var(--neutral-bg);
  border-radius: var(--r-btn);
  padding: 3px;
  gap: 2px;
}

.seg {
  padding: 6px 14px;
  border-radius: 9px;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-2);
  transition: background var(--dur) var(--ease);
}

.seg-on {
  background: var(--surface);
  color: var(--accent-dark);
  box-shadow: 0 1px 2px rgba(18, 42, 41, 0.08);
}

/* ── P2 导入映射 ────────────────────────────────────── */
.row-attention {
  background: var(--warn-soft);
}

/* ── P3 校验预览 ────────────────────────────────────── */
.cell-bad {
  display: inline-block;
  border: 1.5px solid var(--warn);
  border-radius: 8px;
  padding: 4px 10px;
  background: var(--surface);
  color: var(--warn);
  font-size: 13px;
}

.fix-link {
  font-size: 12px;
  margin-left: 6px;
  font-weight: 600;
}

/* ── 名册网格（浏览 / 编辑同一张表）──────────────────
   用 CSS Grid 而不是 <table>：列宽要能 minmax() 自适应，
   还要能整列定位（Tab 跳格、整列粘贴都是按 data-c 找的）。
   行用 display:contents 包一层，才能做整行的斑马纹、hover 和选中态。 */
.page-body {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  padding: 0 var(--gutter) var(--s5);
}

.fill-card {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.grid-wrap {
  flex: 1;
  min-height: 0;
  overflow: auto;
}

.grid-head,
.grid-body {
  display: grid;
  min-width: 100%;
}

.grid-head {
  background: var(--surface-sunken);
  border-bottom: 1px solid var(--line-2);
  position: sticky;
  top: 0;
  z-index: 2;
}

.grid-row {
  display: contents;
}

/* 斑马纹 + hover + 选中，三态优先级从低到高 */
.grid-body .grid-row:nth-child(even) > .cell {
  background: var(--zebra);
}

.grid-body.is-browsing .grid-row:hover > .cell {
  background: var(--accent-bg);
  cursor: pointer;
}

.grid-body .grid-row.is-picked > .cell {
  background: var(--accent-bg);
}

.cell {
  padding: var(--row-pad-y) var(--row-pad-x);
  border-right: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  font-size: var(--cell-font);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-height: var(--row-h);
  display: flex;
  align-items: center;
  gap: 4px;
}

.grid-head .cell {
  border-bottom: none;
  height: 32px;
  min-height: 0;
}

.cell:last-child {
  border-right: none;
}

/* 行号列：靠右、压灰，眼睛扫的是姓名不是序号 */
.grid-body .cell.is-index {
  color: var(--muted-2);
  justify-content: flex-end;
  font-size: 12px;
}

.cell.td-pick {
  justify-content: center;
  padding: 0;
}

.cell.is-bold {
  font-weight: 600;
  color: var(--ink);
}

.cell.num {
  font-variant-numeric: tabular-nums;
}

/* 只读格在编辑模式下要看得出「这里点了没用」 */
.cell.is-readonly {
  color: var(--muted);
  background: var(--surface-sunken);
}

.cell.is-editable {
  cursor: text;
  outline: none;
}

.cell.is-editable:hover {
  box-shadow: inset 0 0 0 1px var(--line-strong);
}

.cell.is-editable:focus {
  box-shadow: inset 0 0 0 2px var(--accent);
  background: var(--surface);
}

.cell.is-editing {
  padding: 0;
  box-shadow: inset 0 0 0 2px var(--accent);
}

/* 粘贴进来的格子亮一下再褪掉 —— 整列贴 46 行，得看见到底改了哪些 */
.cell.is-pasted {
  animation: pasted 1.2s var(--ease);
}

@keyframes pasted {
  from { background: rgba(15, 111, 122, 0.18); }
  to { background: transparent; }
}

.cell-input {
  width: 100%;
  height: 100%;
  border: none;
  outline: none;
  background: var(--surface);
  padding: 0 var(--row-pad-x);
  font-size: var(--cell-font);
  font-family: inherit;
}

/* 脱敏小锁 */
.cell .lock,
.th .lock {
  display: inline-block;
  width: 10px;
  height: 11px;
  position: relative;
  opacity: 0.5;
  margin-left: 2px;
}

.lock::before {
  content: '';
  position: absolute;
  inset: 4px 0 0 0;
  border-radius: 2px;
  background: currentColor;
}

.lock::after {
  content: '';
  position: absolute;
  top: 0;
  left: 2px;
  right: 2px;
  height: 6px;
  border: 1.5px solid currentColor;
  border-bottom: none;
  border-radius: 4px 4px 0 0;
}

/* ── 学生档案 ───────────────────────────────────────── */
/* 左列整列滚动；卡片本身不许被压扁 —— flex 子项默认可收缩，
   card-clip 又是 overflow:hidden，两者一撞就是「卡片从中间被切掉」 */
.detail-col {
  display: flex;
  flex-direction: column;
  gap: var(--s3);
  overflow: auto;
  min-height: 0;
  padding-bottom: 4px;
}

.detail-col > * { flex: none; }

.detail-grid {
  display: grid;
  grid-template-columns: 360px 1fr;
  gap: var(--s3);
  align-items: stretch;
  min-height: 0;
}

@media (max-width: 1180px) {
  .detail-grid {
    grid-template-columns: 1fr;
    overflow: auto;
  }
}

.stu-ava {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: var(--accent-bg);
  color: var(--accent-dark);
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 800;
  font-size: 16px;
  flex: none;
}

/* 键值表。原来是 CSS Grid 两列，改成一行一条：
   值那一格要能整块变成输入框，两列 grid 里做这件事会把行高撑变形。 */
.kv-list {
  display: flex;
  flex-direction: column;
}

.kv-row {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: 5px var(--s4);
  border-bottom: 1px solid var(--line);
  font-size: 13px;
  min-height: 30px;
}

.kv-row:last-child { border-bottom: none; }

.kv-k {
  width: 96px;
  flex: none;
  color: var(--muted);
  font-size: 12.5px;
}

.kv-v {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* 可就地改的值：平时看不出是控件，鼠标进去才显出边框 —— 
   档案是「读」为主的屏，十几个输入框的框线会把它变成表单 */
.kv-edit {
  cursor: text;
  border-radius: 6px;
  padding: 2px 6px;
  margin: -2px -6px;
  outline: none;
}

.kv-edit:hover { box-shadow: inset 0 0 0 1px var(--line-strong); }
.kv-edit:focus { box-shadow: inset 0 0 0 2px var(--accent); }

.kv-input {
  width: 100%;
  border: none;
  outline: none;
  background: var(--surface);
  font: inherit;
  padding: 0;
}

/* 时间线 */
.tl-scroll {
  flex: 1;
  min-height: 0;
  overflow: auto;
  padding: var(--s2) var(--s4);
}

.tl-row {
  display: flex;
  gap: var(--s3);
  padding: 9px 0;
  border-bottom: 1px solid var(--line);
}

.tl-row:last-child {
  border-bottom: none;
}

.tl-time {
  width: 92px;
  flex: none;
}

/* 记一次沟通：钉在时间线顶部的一行，不再单独占一张卡 */
.comm-bar {
  display: flex;
  gap: var(--s2);
  align-items: center;
  padding: var(--s2) var(--s4);
  border-bottom: 1px solid var(--line-2);
  background: var(--surface-sunken);
}

.comm-bar .field {
  margin-top: 0;
  padding: 6px 10px;
  font-size: 13px;
}

/* ── P7 评语审阅 ────────────────────────────────────── */
.review-bar {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 16px 24px;
  background: var(--surface);
  box-shadow: 0 1px 0 var(--line);
  flex: none;
}

.review-grid {
  flex: 1;
  display: grid;
  grid-template-columns: 1fr 400px;
  gap: 14px;
  padding: 16px 24px;
  overflow: hidden;
}

@media (max-width: 1180px) {
  .review-grid {
    grid-template-columns: 1fr;
    overflow: auto;
  }
}

.cm-card {
  padding: 24px 28px;
  overflow: auto;
  display: flex;
  flex-direction: column;
}

.cm-body {
  font-size: 15px;
  line-height: 2;
  text-wrap: pretty;
  outline: none;
  flex: 1;
}

.cm-body:focus {
  outline: none;
}

.cm-p {
  margin: 0 0 10px;
  border-radius: 4px;
  transition: background var(--dur) var(--ease);
}

.cm-p.is-lit {
  background: var(--accent-bg);
  box-shadow: inset 0 -2px 0 var(--accent);
}

.cm-sup {
  color: var(--accent);
  font-weight: 700;
  cursor: pointer;
  padding: 0 2px;
  border-radius: 3px;
}

.cm-sup:hover,
.cm-sup.is-on {
  background: var(--accent);
  color: #fff;
}

.cm-foot {
  font-size: 12px;
  color: var(--muted);
  margin-top: 16px;
  border-top: 1px solid var(--line);
  padding-top: 12px;
}

.cite-card {
  cursor: pointer;
  transition: box-shadow var(--dur) var(--ease), transform var(--dur) var(--ease);
}

.cite-card:hover {
  transform: translateX(-2px);
}

.cite-weak {
  font-size: 11.5px;
  color: var(--warn);
  margin-top: 3px;
}

/* ── P9 归档 / 设置 ─────────────────────────────────── */
.archive-ic {
  width: 44px;
  height: 44px;
  border-radius: 12px;
  background: var(--accent-bg);
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
}

/* ── 账号与设置 ─────────────────────────────────────
   设置页是一张表单，不是一张表：整页收到一栏宽。
   原来它用的是通栏 .main —— 2560px 的屏上，「微信服务通知」在最左、
   开关在最右，中间一米宽的空白。要改一个开关得先横扫一趟确认它属于哪一行。 */
.main-form > * {
  max-width: 760px;
}

/* 遮罩是 position:fixed + inset:0，靠左右都为 0 铺满视口。上面那条
   max-width 会把它压成 760px 贴在左边，于是「居中」的弹窗居到了屏幕左侧。
   浮层不属于这一栏的版心，排除掉。 */
.main-form > .modal-mask,
.main-form > .drawer-mask,
.main-form > .drawer {
  max-width: none;
}

.set-row {
  display: flex;
  align-items: center;
  gap: var(--s4);
  padding: 14px 18px;
  border-bottom: 1px solid var(--line);
}

.set-row:last-child {
  border-bottom: none;
}

/* 操作列定宽：几行的控件左缘对齐成一条线，扫一眼就知道每行能做什么。
   不定宽的话按钮长短不一，右对齐反而参差 */
.set-ctl {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--s2);
  min-width: 104px;
}

/* 开关。原来是个写着「已开启」的按钮 —— 那行字到底是当前状态还是按下去的结果，
   看不出来。轨道 + 圆点没有这个歧义。 */
.switch {
  flex: none;
  position: relative;
  width: 44px;
  height: 26px;
  padding: 0;
  border: 1px solid var(--line-3);
  border-radius: var(--r-pill);
  background: var(--neutral-bg);
  cursor: pointer;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease);
}

.switch::after {
  content: '';
  position: absolute;
  top: 2px;
  left: 2px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: #fff;
  box-shadow: var(--e1);
  transition: transform var(--dur) var(--ease);
}

.switch[aria-pressed='true'] {
  background: var(--accent);
  border-color: var(--accent);
}

.switch[aria-pressed='true']::after {
  transform: translateX(18px);
}

.switch:disabled {
  opacity: 0.5;
  cursor: default;
}

/* 一行一个方向。改完点别处就存，所以没有一排「保存」按钮 */
.dim-row {
  display: grid;
  grid-template-columns: 1fr 92px 32px;
  align-items: center;
  gap: var(--s2);
  padding: var(--s2) 0;
  border-bottom: 1px solid var(--line);
}

.dim-row:last-child {
  border-bottom: none;
}

.dim-row .field {
  width: 100%;
}

/* 流水里指向已停用分项的那一段：划掉。这一笔真发生过，所以留着；
   但它现在不进总分，不标出来老师会以为流水加错了 */
.is-off-item {
  text-decoration: line-through;
  color: var(--muted-2);
}

/* 已停用的那几行：压灰、不给编辑框。它们在这儿只为一件事 —— 能恢复 */
.dim-row.is-off {
  color: var(--muted);
}

.dim-off-name {
  font-size: 13.5px;
}

/* 标记那几行：名字 · 说明 · 删除。中间一列比行为分的初始分宽，要放得下
   「内置 · 座位表按名字认它」这句 */
.mark-row {
  grid-template-columns: 1fr auto 32px;
}

.mark-row.is-off .dim-off-name {
  color: var(--ink-2);
}

/* 流水与沟通记录行尾的「撤销 / 删除」——
   平时几乎看不见，鼠标移到那一行才显出来。
   这类操作要够得着，但不该在一屏十几条记录里抢注意力。 */
.undo-score {
  font-size: 11.5px;
  color: var(--muted-2);
  cursor: pointer;
  opacity: 0;
  transition: opacity .15s ease, color .15s ease;
  white-space: nowrap;
}
.row:hover > .undo-score { opacity: 1; }
.undo-score:hover { color: var(--warn); text-decoration: underline; }

/* 多行输入：与 .field 同一套外观，只是能换行 */
textarea.field { resize: vertical; min-height: 46px; line-height: 1.55; font-family: inherit; }

/* 绑定码：8 位一次性码的手输兜底。等宽 + 分组显示，
   老师是照着屏幕念给自己听然后在手机上敲的，字距要够松。 */
.bind-code-line { margin-top: 14px; text-align: center; }
.bind-code {
  margin: 6px 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 27px;
  font-weight: 700;
  letter-spacing: .18em;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
/* 服务端返回的是 <svg>，给它一个白底容器，暗色主题下也扫得出来 */
.bind-qr svg { display: block; width: 220px; height: 220px; }
.bind-qr img { display: block; width: 220px; height: 220px; border-radius: 6px; }
/* 出错或加载中时容器里是普通文字，不能是 line-height:0 */
.bind-qr .center { line-height: 1.7; width: 220px; }

/* 只在纸上出现的抬头 */
.print-head { display: none; }

/* ── 座位表 ─────────────────────────────────────────
   照着老师手里那张纸：讲台在最下方通栏，第一排贴着讲台，
   一张桌（同桌两人）是一个视觉单元；组长的底纹只打他自己那一格。 */
.seating-body {
  display: grid;
  grid-template-columns: 1fr 220px;
  gap: var(--s3);
  min-height: 0;
}

@media (max-width: 1180px) {
  .seating-body { grid-template-columns: 1fr; }
}

.room-scroll {
  flex: 1;
  min-height: 0;
  overflow: auto;
  padding: var(--s4);
}

.room {
  /* 排号那一列的宽度。讲台的左边距也读它 —— 讲台的框要和表格一样宽，
     两处各写一个数字迟早对不上，打印时把它置 0 就能一次收掉整列 */
  --row-no-w: 32px;
  /* 每个座位分组之间留一条真正的空隙；打印时换成物理尺寸，预览和纸面一致。 */
  --group-gap: 20px;
  display: flex;
  flex-direction: column;
  min-width: 640px;
}

.room-row {
  display: grid;
  grid-template-columns: var(--row-no-w) repeat(var(--cols), minmax(0, 1fr));
  column-gap: var(--group-gap);
  align-items: stretch;
}

/* 排号：帮老师说「第几排」，只在屏幕上显示。
   它和第一组之间也沿用 group-gap，让排号不会贴住座位外框。 */
.row-no {
  padding-right: 10px;
  font-size: 11px;
  color: var(--muted-2);
  display: flex;
  align-items: center;
  justify-content: flex-end;
  font-variant-numeric: tabular-nums;
}

/* 一张桌 = 同桌的几个座位。每组现在是独立的小表格：
   · 同桌座位之间 1px --line-2；
   · 排与排之间 1px --line-3；
   · 组与组之间由 .room-row 的真实空白 gap 分开。
   每张桌保留左右边框，空隙两边都有清楚的组边界；下边框仍由下一排的上边框、
   最后一排则由组名行的 border-top 补上，避免横线叠成 2px。 */
.desk {
  display: grid;
  grid-template-columns: repeat(var(--seats), 1fr);
  border: 1px solid var(--line-3);
  border-bottom: none;
  background: var(--surface);
}

.seat {
  position: relative;
  min-height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 4px 6px;
  font-size: 13px;
  border-right: 1px solid var(--line-2);
  cursor: pointer;
  user-select: none;
  transition: background var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}

.seat:last-child { border-right: none; }

/* 组长本人那一格打底纹。只填底色、不加内描边 —— 一张桌里单独给半格描个框，
   会和桌子自己的边框打架，也会撞上拖拽落点的那圈 .is-over */
.seat.is-leader { background: var(--neutral-bg); }

/* 下面这两条要盖过底纹，所以排在它后面（同权重，后写的赢） */
.seat:hover { background: var(--accent-bg); }

.seat.is-empty {
  background: repeating-linear-gradient(
    -45deg, transparent, transparent 5px, var(--surface-sunken) 5px, var(--surface-sunken) 10px
  );
}

.seat.is-over { box-shadow: inset 0 0 0 2px var(--accent); }
.seat.is-dragging { opacity: .4; }

/* 选中：点一个座位再点另一个就换位置 */
.seat.is-picked,
.pool-item.is-picked {
  background: var(--accent);
  color: #fff;
  box-shadow: inset 0 0 0 2px var(--accent-dark);
}

.seat.is-picked .seat-star,
.pool-item.is-picked .seat-star { color: #fff; }

.seat-name { text-align: center; line-height: 1.25; }

/* 内宿生下划线 —— 和老师手写那张纸同一套符号 */
.seat.is-boarding .seat-name,
.pool-item.is-boarding { text-decoration: underline; text-underline-offset: 3px; }

.seat-star { color: var(--accent-dark); margin-left: 1px; font-size: 11px; }

.seat-front-mark {
  position: absolute;
  top: 2px;
  right: 3px;
  font-size: 9px;
  font-style: normal;
  color: var(--warn);
  border: 1px solid currentColor;
  border-radius: 3px;
  padding: 0 2px;
  line-height: 1.3;
}

.group-name {
  text-align: center;
  font-size: 12px;
  font-weight: 700;
  color: var(--ink-2);
  padding: 4px 0;
  border-top: 1px solid var(--line-3); /* 兼任整片网格的下边框 */
}

.podium {
  margin-top: 12px;
  /* 屏幕上与第一组左边缘对齐：排号轨道之后还有一段 group-gap。 */
  margin-left: calc(var(--row-no-w) + var(--group-gap));
  border: 1px solid var(--line-3);
  border-radius: var(--r-inner);
  background: var(--surface-sunken);
  text-align: center;
  font-weight: 700;
  letter-spacing: .3em;
  padding: 8px;
  font-size: 13px;
  color: var(--ink-2);
}

.legend {
  display: flex;
  gap: var(--s4);
  flex-wrap: wrap;
  padding: var(--s2) var(--s4);
  border-top: 1px solid var(--line);
  font-size: 12px;
  color: var(--muted);
}

.legend .seat-front-mark { position: static; }

.note-wrap {
  padding: var(--s2) var(--s4) var(--s3);
  border-top: 1px solid var(--line);
}

.note-wrap .field { margin-top: 4px; font-size: 13px; padding: 8px 10px; }

/* 待入座池 */
.seating-side { min-height: 0; }

.pool {
  flex: 1;
  min-height: 0;
  overflow: auto;
  padding: var(--s2);
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.pool.is-over { background: var(--accent-bg); }

.pool-item {
  position: relative;
  padding: 6px 10px;
  border: 1px solid var(--line-3);
  border-radius: var(--r-inner);
  background: var(--surface);
  font-size: 13px;
  cursor: grab;
}

.pool-item:hover { border-color: var(--accent); }

/* 备注抽屉里的附图。小程序拍的，PC 上只读 —— 缩略图铺开，点开走新标签页 */
.note-pic-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
}

.note-pic {
  width: 92px;
  height: 92px;
  border-radius: var(--r-inner);
  overflow: hidden;
  background: var(--neutral-bg);
  flex: none;
}

.note-pic img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.note-pic:hover { box-shadow: 0 0 0 2px var(--accent); }

/* ── 存档 ──────────────────────────────────────────
   只读态靠三样东西同时说话：工具条里改动的按钮消失、通栏横幅、座位不给拖。
   少了任何一样，老师都会在一张改不动的表上白拖半天。 */
.is-archive .edit-only { display: none; }

.archive-bar {
  display: flex;
  align-items: center;
  gap: var(--s3);
  margin: 0 var(--gutter) var(--s3);
  padding: var(--s2) var(--s3);
  border-radius: var(--r-card);
  background: var(--accent-bg);
  font-size: 13px;
}

.archive-bar[hidden] { display: none; }

.archive-tag {
  flex: none;
  padding: 1px 8px;
  border-radius: var(--r-pill);
  background: var(--accent);
  color: #fff;
  font-size: 11px;
  font-weight: 700;
}

/* 存档里已转学的人。位置和名字留着（那天的纸上就有他），只是点不开档案了 */
.is-archive .seat.is-gone .seat-name { color: var(--muted); }

/* 只读态右栏放的是这一版的信息，不是一列可拖的名牌 —— 收掉池子那套排布 */
.is-archive .pool { display: block; padding: 0; }

/* 只读态的座位不给任何「可以动」的暗示 */
.is-archive .seat { cursor: default; }
.is-archive .seat:hover { background: none; }
.is-archive .seat.is-leader:hover { background: var(--neutral-bg); }

/* 工具条上的存档份数。没有存档时是空字符串，靠 :empty 收掉那圈底 */
.pill-n {
  display: inline-block;
  min-width: 16px;
  padding: 0 5px;
  border-radius: var(--r-pill);
  background: var(--neutral-bg);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
}

.pill-n:empty { display: none; }

/* 存档列表 */
.plan-list {
  max-height: 46vh;
  overflow: auto;
  border-top: 1px solid var(--line);
}

.plan-row {
  display: flex;
  align-items: center;
  gap: var(--s2);
  padding: var(--s2) var(--s4);
  border-bottom: 1px solid var(--line);
  font-size: 13px;
}

.plan-row:last-child { border-bottom: none; }
.plan-row:hover { background: var(--accent-bg); }
.plan-row.is-open { box-shadow: inset 3px 0 0 var(--accent); }
.plan-row .hint { margin-top: 1px; }

/* 下拉菜单（一键初排）*/
.menu-wrap { position: relative; }

.menu {
  position: absolute;
  right: 0;
  top: calc(100% + 6px);
  z-index: 50;
  background: var(--surface);
  border-radius: var(--r-btn);
  box-shadow: var(--e2);
  padding: 6px;
  min-width: 220px;
}

.menu-item {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1px;
  width: 100%;
  padding: 8px 10px;
  border: none;
  background: none;
  border-radius: var(--r-inner);
  cursor: pointer;
  text-align: left;
  font-size: 13px;
}

.menu-item:hover { background: var(--accent-bg); }

/* ── 课程表 ─────────────────────────────────────────
   行是节次、列是周一到周五，和老师手里印的那张一样。
   上午和下午之间插一条分段横杠 —— 那是午休；合成一串连续的节次就读不出来了。
   格子有三态：排了课 / 空闲（老师明确不排）/ 还没排，三种必须一眼分得开。 */
.tt-scroll {
  flex: 1;
  min-height: 0;
  overflow: auto;
  padding: var(--s4);
}

.tt {
  /* 左边那一列写节次和钟点。它的宽度只写在这一处，分段横杠的缩进也读它 */
  --when-w: 92px;
  display: flex;
  flex-direction: column;
  min-width: 720px;
}

.tt-row {
  display: grid;
  grid-template-columns: var(--when-w) repeat(var(--days), minmax(0, 1fr));
  align-items: stretch;
  border-left: 1px solid var(--line-3);
}

/* 第一行是星期。整张表的上边框由它兼任，横线不会和下面那排叠成 2px */
.tt-head {
  border-top: 1px solid var(--line-3);
  background: var(--surface-sunken);
}

.tt-day {
  padding: 7px 6px;
  text-align: center;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--ink-2);
  border-right: 1px solid var(--line-3);
  border-bottom: 1px solid var(--line-3);
}

.tt-head .tt-when { border-bottom: 1px solid var(--line-3); }

/* 节次那一格：第几节在上，钟点在下（没填就只有第几节） */
.tt-when {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 2px;
  padding: 6px 10px;
  font-size: 12px;
  color: var(--ink-2);
  background: var(--surface-sunken);
  border-right: 1px solid var(--line-3);
  border-bottom: 1px solid var(--line-3);
}

.tt-time-text {
  font-size: 11px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* 上午 / 下午的分段横杠。通栏，正是纸上那道午休的线 */
.tt-band {
  padding: 5px 10px;
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.08em;
  color: var(--muted);
  background: var(--neutral-bg);
  border: 1px solid var(--line-3);
  /* 上边线由上一行的下边框出（表头或上一节），这里再来一条就叠成 2px 的双线 */
  border-top: none;
}

.tt-cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  min-height: 52px;
  padding: 4px 6px;
  border: none;
  border-right: 1px solid var(--line-3);
  border-bottom: 1px solid var(--line-3);
  background: var(--surface);
  cursor: pointer;
  text-align: center;
  transition: background var(--dur) var(--ease);
}

.tt-cell:hover { background: var(--accent-bg); }

.tt-subject {
  font-size: 13.5px;
  font-weight: 600;
  line-height: 1.25;
}


/* 一节课都没排时摆在表上面的一句话：告诉人课在哪儿配。
   排过课之后前端就不渲染它了 */
.tt-tip {
  margin-bottom: var(--s3);
  padding: var(--s2) var(--s3);
  border-radius: var(--r-inner);
  background: var(--accent-bg);
  color: var(--accent-dark);
  font-size: 12.5px;
}

/* 还没排：一个淡「＋」，只在鼠标扫过时才明显 —— 35 个空格子同时喊
   「填我」的话，整张表看上去就只剩噪音。但压得太狠又会让空表看着像
   「这儿没东西可点」，0.45 就是那样，抬到 0.7 */
.tt-add {
  font-size: 15px;
  color: var(--muted-2);
  opacity: 0.7;
  transition: opacity var(--dur) var(--ease);
}

.tt-cell:hover .tt-add { opacity: 1; }

/* 空闲：老师明确「先不排课」。用斜纹和座位表的空座位同一套符号，
   一眼就和「还没排」的空白分得开 */
.tt-cell.is-free {
  background: repeating-linear-gradient(
    -45deg, transparent, transparent 5px, var(--surface-sunken) 5px, var(--surface-sunken) 10px
  );
}

.tt-free {
  font-size: 12px;
  color: var(--muted);
}

/* 图注里代表「空闲」的那一小块，和格子里的斜纹同一套 */
.tt-dot {
  display: inline-block;
  width: 14px;
  height: 10px;
  vertical-align: -1px;
  border: 1px solid var(--line-3);
  border-radius: 2px;
  background: repeating-linear-gradient(
    -45deg, transparent, transparent 3px, var(--neutral-bg) 3px, var(--neutral-bg) 6px
  );
}

/* 正在选课的那一格：浮层贴着它弹出来，格子本身也得亮着，
   否则浮层飘在半空，看不出改的是哪一格 */
.tt-cell.is-picking {
  background: var(--accent-bg);
  box-shadow: inset 0 0 0 2px var(--accent);
}

/* ── 选课浮层 ──
   十来门课一次全摊开，点一下就完事。复用 .menu 的外观（阴影、圆角、菜单项），
   只把宽度收窄、加一个抬头和一道分隔线：课程在上，「空闲 / 清空」在下 ——
   它们不是课，混在一串课名里会被当成两门叫这个名字的课。 */
.tt-picker {
  min-width: 168px;
  max-width: 240px;
  max-height: 60vh;
  overflow: auto;
  z-index: 130;
}

.tt-picker-head {
  padding: 4px 10px 6px;
  font-size: 11.5px;
  color: var(--muted);
}

.tt-picker-sep {
  margin: 4px 0;
  border-top: 1px solid var(--line-2);
}

/* 当前这一格已经选着的那一项 */
.tt-pick.is-on {
  background: var(--accent-bg);
  color: var(--accent-dark);
}

/* ── 课程设置：本班开哪几门课 ── */
.tt-sub-add {
  display: flex;
  gap: var(--s2);
  align-items: center;
}

.tt-sub-add .field { margin-top: 0; }

.tt-subs {
  margin-top: var(--s3);
  max-height: 320px;
  overflow: auto;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* 课名 / 排了几节 / 删除。中间那栏定宽，删除按钮才会对齐成一列 */
.tt-sub {
  display: grid;
  grid-template-columns: 1fr 72px auto;
  align-items: center;
  gap: var(--s2);
}

.tt-sub .field {
  margin-top: 0;
  padding: 6px 10px;
  font-size: 13px;
}

.tt-sub-n {
  font-size: 11.5px;
  color: var(--muted);
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* 还没设过节次时整屏就一句话 —— 这时候没有表可看，只有一件事该做 */
.tt-blank {
  padding: 48px var(--s5);
  text-align: center;
}

/* 节次设置弹窗：两个数字并排（座位表那个是三个） */
.modal-nums-2 { grid-template-columns: repeat(2, 1fr); }

/* 每节的起止时间。名字定宽，两个时间框才会上下对齐成一列 */
.tt-times {
  margin-top: var(--s3);
  max-height: 320px;
  overflow: auto;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.tt-time {
  display: grid;
  grid-template-columns: 92px 1fr auto 1fr;
  align-items: center;
  gap: var(--s2);
}

.tt-time-name { font-size: 12.5px; color: var(--ink-2); }
.tt-time-dash { color: var(--muted-2); }

.tt-time .field {
  margin-top: 0;
  padding: 6px 10px;
  font-size: 13px;
}

/* ── 打印 ───────────────────────────────────────────
   这张表的最终形态是一张贴在墙上的纸，A4 横向。纸上只留三样东西：
   班级和日期、教室网格、写了内容的附注。屏幕上的一切工作态信息 ——
   导航、工具条、待入座池、排号、鼠标操作提示、「需坐前排」——
   到了墙上都不是给看表的人用的，一律不上纸。 */
@media print {
  @page { size: A4 landscape; margin: 8mm; }

  .side, .toolbar, .seating-side, .menu-wrap, .btn, .route-bar, .toast-wrap, .archive-bar,
  .modal-mask { display: none !important; }

  html, body, #root, .app, .view, .main-flush { height: auto; overflow: visible; }

  /* 页面底色是浅灰。平时打印会被浏览器丢掉，但只要老师为了印出组长底纹
     勾上「背景图形」，页眉那一条就会变成一道灰带 —— 明确刷白 */
  html, body, .app, .view { background: #fff; }

  /* ── 页眉：班级居中放大，日期右对齐 ──
     1fr auto 1fr 的两侧等宽，所以标题是相对整张纸居中的，不会被日期推偏。
     外层 .row-between 的 padding 是内联样式写死的，只能 !important 压掉，
     否则页眉会比表格窄一圈、日期也对不齐表格的右边线 */
  .seating-page .h1 { display: none; }
  .seating-page > .row-between { display: block; padding: 0 0 6px !important; }
  .print-head {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: baseline;
    margin-bottom: 8px;
  }
  .print-title { grid-column: 2; font-size: 22px; font-weight: 800; }
  .print-date { grid-column: 3; text-align: right; font-size: 12px; color: #333; }

  /* ── 撑满一页 ──
     屏幕上那条「卡片吃掉剩余高度」的伸缩链，在打印里被 display:block 打断过。
     这里把它接回来，并以 100vh（打印下就是一页的可用高度）作为底座，
     再让每一排平分剩余高度 —— 4 排的班和 12 排的班都能把纸用满，
     不会出现下半张全白。.seat 的 min-height 仍是下限，排数太多时照常翻页。 */
  .seating-page { min-height: 100vh; }
  .seating-body { display: flex; flex-direction: column; gap: 0; }
  .page-body { padding: 0; }
  .card, .fill-card { box-shadow: none; border-radius: 0; }
  .room-scroll { flex: 1; overflow: visible; padding: 0; }
  .room { min-width: 0; height: 100%; }
  .room-row { flex: 1; }
  .room-row.room-foot, .podium { flex: none; }

  /* 排号是给老师说「第几排」的工作信息，纸上不需要。
     光把 .row-no 藏掉不够 —— 它一从网格里消失，第一组就会被自动排进
     原本留给它的那条轨道，整张表挤成一团。轨道本身也得去掉。
     --row-no-w 归零是给讲台用的：左边距没了，框就和表格一样宽。 */
  .row-no { display: none; }
  .room-row { grid-template-columns: repeat(var(--cols), 1fr); }
  /* 右边缘恰好贴住 A4 内容框时，Chrome 会裁掉约 1px 边框；向内收 1px，
     座位表和讲台仍同宽，同时左右外框都能完整打印。 */
  .room { --row-no-w: 0px; --group-gap: 5mm; padding-right: 1px; }

  /* ── 线：纸上每一组都是独立表格 ──
     所有线都用黑色：同桌内部线 1px，每组左右外框与整表上下边界 2px。
     组间不再靠粗竖线区分，而是保留 5mm 真空白，打印预览和最终纸面一致。 */
  .desk {
    border-color: #000;
    border-left-width: 2px;
    border-right-width: 2px;
  }
  .seat { border-right-color: #000; }

  /* 每组之间保留 5mm 真空白，而不是用一根粗线假装分组。
     @media print 同时作用于浏览器打印预览和最终打印/PDF。 */
  .room-row:first-child .desk { border-top-width: 2px; }
  .group-name { border-top: 2px solid #000; font-size: 12px; }
  .podium { margin-left: 0; border: 2px solid #000; }

  .seat { min-height: 34px; font-size: 14px; transition: none; }

  /* ── 网页操作态归零 ──
     打印只认数据语义，不认打开打印框那一刻的鼠标状态：点选、悬停、
     拖拽中和拖拽落点都不能改变纸上的底色、文字、透明度或描边。
     语义样式（组长底纹、空座斜纹、内宿生下划线）在归零后重新声明。 */
  .seat:hover,
  .seat.is-picked {
    background: transparent;
    color: var(--ink);
    box-shadow: none;
  }
  .seat.is-over { box-shadow: none; }
  .seat.is-dragging { opacity: 1; }
  .seat.is-picked .seat-star { color: var(--accent-dark); }

  .seat.is-empty {
    background: repeating-linear-gradient(
      -45deg, transparent, transparent 5px, var(--surface-sunken) 5px, var(--surface-sunken) 10px
    );
  }

  /* 打印默认会丢掉背景色，组长那格的底纹得明确要回来 */
  .seat.is-leader {
    background: #e8e8e8;
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }

  /* 「需坐前排」是排座时的工作信息 —— 排完了它的任务就完了，
     贴到墙上只会让被标的学生当众多一个记号。座位上的「前」和图注里
     那一条一起撤，不然纸上会有一条解释不存在符号的图注。
     「右键座位可打标记」同理：鼠标操作说明印在纸上没有意义。 */
  .seat-front-mark,
  .legend-front,
  .legend-tip { display: none !important; }

  /* 附注在纸上是正文，不是输入框 */
  .note-wrap .field {
    border: none;
    padding: 0;
    resize: none;
    height: auto;
    font-size: 12px;
  }
  .note-wrap .kick { display: none; }

  /* 没写附注时，占位提示（「例：…不上晚自习。」）绝不能上纸 ——
     那是给屏幕上的老师看的举例，印到墙上会被当成真的通知。
     两道保险：整块收起来，同时把占位文字本身也变透明，
     万一 :has() 不认（旧内核）也不会漏出来。 */
  .note-wrap:has(.field:placeholder-shown) { display: none; }
  .note-wrap .field::placeholder { color: transparent; opacity: 0; }

}

/* ── 备注详情抽屉 ───────────────────────────────────
   看完一条要接着翻下一条，跳走再回来会丢筛选和滚动位置，所以是抽屉不是页面 */
.drawer-mask,
.modal-mask {
  position: fixed;
  inset: 0;
  background: rgba(18, 42, 41, 0.2);
  z-index: 120;
}

.drawer {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  width: min(460px, 92vw);
  background: var(--surface);
  box-shadow: var(--e2);
  z-index: 121;
  overflow: auto;
  animation: drawer-in 0.18s var(--ease);
}

@keyframes drawer-in {
  from { transform: translateX(16px); opacity: 0.6; }
  to { transform: none; opacity: 1; }
}

.drawer .sec-head { position: sticky; top: 0; z-index: 1; }

/* ── 居中弹窗 ───────────────────────────────────────
   给「改完就走」的小配置用。和抽屉的分工：抽屉是留在原地翻资料（能一条接一条看），
   弹窗是打断一下、确认了再回去 —— 教室布局属于后者。 */
.modal-mask {
  display: grid;
  place-items: center;
  padding: var(--s5);
}

/* hidden 的默认 display:none 会被上面的 grid 盖掉，得写回来 */
.modal-mask[hidden] { display: none; }

.modal {
  width: min(400px, 100%);
  max-height: 100%;
  overflow: auto;
  background: var(--surface);
  border-radius: var(--r-card);
  box-shadow: var(--e2);
  z-index: 121;
  animation: modal-in 0.16s var(--ease);
}

@keyframes modal-in {
  from { transform: translateY(8px) scale(0.98); opacity: 0; }
  to { transform: none; opacity: 1; }
}

.modal .sec-head { border-radius: var(--r-card) var(--r-card) 0 0; }

/* 行为分方向的弹窗比布局弹窗宽一档：一行里要放下名字、初始分和删除。
   必须写在 .modal 后面 —— 同权重时后定义的赢，写在前面等于没写。 */
.modal-wide {
  width: min(560px, 100%);
}

/* 事件类型再宽一档：一行里要放下类型名、判定说明、待沟通、兜底和停用/删除。
   判定说明是给模型看的一句话，560px 下这个输入框只剩十几个字符宽，
   写到一半就开始横向滚，等于没法写。 */
.modal-xwide {
  width: min(780px, 100%);
}

/* 三个数字并排，每个顶上一行小字。比工具条里挤成一行好认 —— 
   「组 / 排 / 每桌」三个词单看都像量词，靠位置区分不了 */
.modal-nums {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--s3);
}

.modal-nums label { display: grid; gap: var(--s1); }
.modal-nums .field { width: 100%; }

.modal-foot {
  display: flex;
  justify-content: flex-end;
  gap: var(--s2);
  padding: var(--s3) var(--s4);
  border-top: 1px solid var(--line-2);
}

/* 缩小布局会挤掉人，这句是按下「应用」前最该看见的话，所以按警示色处理 */
.modal-warn {
  margin-top: var(--s3);
  padding: var(--s2) var(--s3);
  border-radius: var(--r-card);
  background: var(--warn-bg);
  color: var(--warn);
  font-size: 12px;
  line-height: 1.6;
}
