/* ==========================================================
   法硕主观题批改台
   令牌沿用「抽背」的字体栈 / 圆角 / systemBlue，两个产品看起来同源。
   ========================================================== */
:root{
  color-scheme: light dark;

  --label:        #0b0b0c;
  --label-2:      rgba(60,60,67,.62);
  --label-3:      rgba(60,60,67,.34);

  --bg:           #f4f4f6;
  --bg-card:      #ffffff;
  --bg-sunken:    #ebebed;

  --separator:    rgba(60,60,67,.16);
  --separator-2:  rgba(60,60,67,.28);
  --fill:         rgba(120,120,128,.10);
  --fill-press:   rgba(120,120,128,.18);

  --blue:         #007aff;
  --green:        #2fa84a;
  --orange:       #e08300;
  --red:          #e0342a;
  --purple:       #5e5ce6;

  --r-card:       12px;
  --r-ctl:        8px;

  /* ease-in 不用在 UI 上 */
  --ease-out:     cubic-bezier(.23,1,.32,1);
  --ease-drawer:  cubic-bezier(.32,.72,0,1);

  --font: -apple-system, BlinkMacSystemFont, "SF Pro Text",
          "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
  --font-read: "Songti SC", "Source Han Serif SC", Georgia, serif;

  --nav-h: 48px;
}

@media (prefers-color-scheme: dark){
  :root{
    --label:       #f5f5f7;
    --label-2:     rgba(235,235,245,.62);
    --label-3:     rgba(235,235,245,.34);
    --bg:          #000000;
    --bg-card:     #1c1c1e;
    --bg-sunken:   #2c2c2e;
    --separator:   rgba(84,84,88,.48);
    --separator-2: rgba(84,84,88,.72);
    --fill:        rgba(118,118,128,.18);
    --fill-press:  rgba(118,118,128,.32);
    --blue:        #0a84ff;
    --green:       #30d158;
    --orange:      #ff9f0a;
    --red:         #ff453a;
    --purple:      #5e5ce6;
  }
}
/* ---------- 基础 ---------- */
*{box-sizing:border-box;-webkit-tap-highlight-color:transparent}
html,body{margin:0;height:100%}
body{
  background:var(--bg);color:var(--label);
  font-family:var(--font);font-size:14px;line-height:1.5;
  letter-spacing:-.01em;-webkit-font-smoothing:antialiased;
  display:flex;flex-direction:column;
}
button,input,textarea,select{font:inherit;letter-spacing:inherit;color:inherit}
button{cursor:pointer;border:0;background:none}
a{color:inherit;text-decoration:none}
[hidden]{display:none!important}
.sr-only{position:absolute;width:1px;height:1px;margin:-1px;overflow:hidden;clip-path:inset(50%)}

#view{flex:1;min-height:0;overflow:auto;outline:none}
#view.no-scroll{overflow:hidden;display:flex;flex-direction:column}

.pressable{transition:transform 160ms var(--ease-out)}
.pressable:active{transform:scale(.97)}

@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{
    animation-duration:.01ms!important;animation-iteration-count:1!important;
    transition-duration:.01ms!important;
  }
}

/* ---------- 顶栏 ---------- */
.topnav{
  flex:none;display:flex;align-items:center;gap:18px;
  height:var(--nav-h);padding:0 16px;
  background:var(--bg-card);border-bottom:1px solid var(--separator);
}
.brand{font-size:14px;font-weight:650;letter-spacing:-.02em;display:flex;align-items:baseline;gap:7px}
.brand small{font-size:11px;font-weight:400;color:var(--label-3);letter-spacing:0}
.tabs{display:flex;gap:2px;margin-left:auto}
.tabs a{
  display:flex;align-items:center;gap:5px;height:28px;padding:0 11px;border-radius:7px;
  font-size:13px;color:var(--label-2);
  transition:background 150ms ease,color 150ms ease;
}
.tabs a:hover{background:var(--fill)}
.tabs a[data-on]{background:var(--fill-press);color:var(--label);font-weight:550}
.tab-n{
  font-size:10.5px;font-weight:700;font-variant-numeric:tabular-nums;
  min-width:16px;height:16px;padding:0 4px;border-radius:99px;
  display:grid;place-items:center;background:var(--blue);color:#fff;
}
.tab-n:empty{display:none}

/* 谁登录着 + 退出。tabs 已经吃掉了 margin-left:auto，所以这一格只要
   flex:none 就会稳稳落在最右。本地后端下它是空的（:empty 收掉自己的 gap，
   否则顶栏右边会莫名多出一段 18px 的空隙）。 */
.nav-who{flex:none;display:flex;align-items:center;gap:8px}
.nav-who:empty{display:none}
.nav-me{font-size:12.5px;color:var(--label-3);max-width:120px;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

/* 手机宽度上七个 tab 挤不下一行。默认的收缩会让每个 tab 里的中文逐字换行，
   顶栏变成三行高、每个标签两个字上下摞着 —— 那比横向滑一下难用得多。
   所以副标题收掉、标签不许收缩、整条 tabs 自己横滚（滚动条隐掉，
   顶栏本来就靠 tab 高亮告诉你在哪一页）。
   教师端是桌面工作台，这条只是别让窄屏看起来是坏的，不是把它当主场景。 */
@media (max-width:620px){
  .topnav{gap:10px;padding:0 10px}
  .brand small{display:none}
  .brand{flex:none;white-space:nowrap}
  .tabs{overflow-x:auto;scrollbar-width:none}
  .tabs::-webkit-scrollbar{display:none}
  .tabs a{flex:none;white-space:nowrap;padding:0 9px}
}

/* 进不去的时候（登录屏 / 「还没准备好」）把整排 tab 藏掉。
   不藏的话那排链接看着能点，点下去 hashchange 把登录屏冲掉，换成一屏
   「这一页出错了」—— 老师就再也找不到输入框了。app.js 里 render() 那句
   `if (gated) return` 是同一件事的另一半：CSS 管看不见，JS 管点不着。 */
body[data-gate] .tabs{display:none}

/* ---------- 登录屏（app.js loginScreen）----------
   它不是一页，是盖在 #view 上的一屏：登录状态是整台软件能不能用的前提，
   做成路由的话浏览器后退能退到一个读不到数据的工作台。 */
.signin{max-width:340px;margin:8vh auto 0}
.signin-h{font-size:19px;font-weight:650;letter-spacing:-.02em;margin:0}
.signin-sub{font-size:12.5px;color:var(--label-3);line-height:1.6;margin:5px 0 4px}
/* 错误要在按钮**上面**：在下面的话它一出现就把按钮往下推，
   老师刚要点的第二下会落到空处。 */
.signin-err{
  margin:12px 0 0;padding:8px 10px;border-radius:8px;
  background:rgba(224,52,42,.12);color:var(--red);font-size:12px;line-height:1.55;
}
/* 底下那句不能用通用 .hint 的灰（--label-3 是「0 字」那种可以掠过的说明文字）。
   这一屏的那句是**唯一一条能让他进去的指令**（「先在 Supabase 里手工建老师那一行」
   / 「已退出」），而这一屏上没有别的字可看 —— 掠过它就等于卡在这儿。 */
.signin .hint{color:var(--label-2);font-size:12px;margin-top:10px}

/* ---------- 通用控件 ---------- */
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:6px;
  height:30px;padding:0 13px;border-radius:var(--r-ctl);
  font-size:13px;font-weight:500;background:var(--fill);color:var(--label);
  transition:background 150ms ease,transform 160ms var(--ease-out),opacity 150ms ease;
}
.btn:hover{background:var(--fill-press)}
.btn:active{transform:scale(.97)}
.btn-primary{background:var(--blue);color:#fff}
.btn-primary:hover{filter:brightness(1.06);background:var(--blue)}
.btn-danger{color:var(--red)}
.btn[disabled]{opacity:.4;pointer-events:none}
/* busy() 压住的那一下（写库往返）。跟 disabled 同一个样子 ——
   对老师来说「点不动」就是一件事，不该有两种长相。 */
.btn[data-busy]{opacity:.4;pointer-events:none}
.btn-sm{height:26px;padding:0 9px;font-size:12px}
.btn-lg{height:38px;padding:0 18px;font-size:14px}

.navbtn{
  display:grid;place-items:center;width:26px;height:26px;border-radius:7px;
  color:var(--label-2);background:var(--fill);
  transition:background 150ms ease,transform 160ms var(--ease-out),opacity 150ms ease;
}
.navbtn:hover{background:var(--fill-press)}
.navbtn:active{transform:scale(.94)}
.navbtn[disabled]{opacity:.3;pointer-events:none}
.navbtn kbd{font:inherit;font-size:10px}

/* type=password 必须在这条选择器里列出来：不列的话密码框走浏览器默认样式，
   跟上面的登录名框宽窄、圆角、边框全不一样 —— 同一屏两个框长得不一样，
   看起来像页面坏了。（migrate.html 那份独立样式表踩过同一个坑。） */
input[type=text],input[type=password],input[type=number],textarea,select{
  width:100%;padding:8px 10px;border-radius:var(--r-ctl);
  border:1px solid var(--separator-2);background:var(--bg-card);color:var(--label);
  font-size:13.5px;line-height:1.55;
  transition:border-color 150ms ease,box-shadow 150ms ease;
}
textarea{resize:vertical;line-height:1.65}
/* 占位符要明显比真值浅。浏览器默认那个灰在 13.5px 下深得像已经填好了 ——
   登录屏上最要命（那一屏除了两个框没别的字），老师会以为登录名已经填成
   `teacher` 了，直接去点「进去」。学生端 student.html 那份独立样式表里
   有一条一模一样的。 */
input::placeholder,textarea::placeholder{color:var(--label-3);opacity:1}
input:focus,textarea:focus,select:focus{
  outline:none;border-color:var(--blue);box-shadow:0 0 0 3px rgba(0,122,255,.16);
}
/* 不限定 label —— 「加入哪些组」是一排 chip，没有单个可关联的控件，
   那一栏的标题必须是 span（label 指向不存在的 for 反而是无障碍错误）。 */
.fl{display:block;font-size:11px;font-weight:600;letter-spacing:.04em;
  color:var(--label-3);margin:0 0 6px}
.hint{font-size:11.5px;color:var(--label-3);line-height:1.55;margin:5px 0 0}
/* 选中了「这道题没开给他」时的那一句（决策 13）。它是后果警告，不是说明文字 ——
   跟下面的「0 字」同一个灰色的话，老师会当成填空提示直接掠过。 */
.hint[data-warn]{color:var(--orange)}
.hint[data-warn] b{font-weight:650}

.badge{
  font-size:10.5px;font-weight:600;padding:1.5px 7px;border-radius:99px;
  background:var(--fill);color:var(--label-2);white-space:nowrap;
}
.badge-todo{background:rgba(0,122,255,.14);color:var(--blue)}
.badge-done{background:rgba(47,168,74,.16);color:var(--green)}
.badge-hold{background:rgba(224,131,0,.16);color:var(--orange)}
/* 「链接过期了」跟「待接受」是两种处置：一个等学生点，一个必须老师重发。
   都用橙的话老师扫一列扫不出那几个已经卡死的人，所以过期走红。 */
.badge-warn{background:rgba(224,52,42,.14);color:var(--red)}
.badge-draft{background:var(--fill-press);color:var(--label-3)}

.total{display:flex;align-items:baseline;gap:3px}
.total b{font-size:22px;font-weight:650;font-variant-numeric:tabular-nums;letter-spacing:-.02em}
.total span{font-size:12px;color:var(--label-3)}
.total-lbl{font-size:11px;color:var(--label-3);margin-right:2px}
.num{font-variant-numeric:tabular-nums}
/* ---------- 页面外壳 ---------- */
.page{max-width:1080px;margin:0 auto;padding:22px 20px 64px}
.page-narrow{max-width:720px}
.page-h{display:flex;align-items:center;gap:12px;margin:0 0 18px}
.page-h h1{font-size:21px;font-weight:650;letter-spacing:-.03em;margin:0}
.page-h-meta{display:flex;align-items:center;gap:12px;font-size:12px;color:var(--label-3)}
.page-h-meta b{font-weight:600;color:var(--label-2)}
.page-h-btns{margin-left:auto;display:flex;gap:8px}

.panel{
  background:var(--bg-card);border:1px solid var(--separator);
  border-radius:var(--r-card);padding:16px;margin:0 0 14px;
}
.panel-quiet{background:none;border-style:dashed}
.panel-h{
  display:flex;align-items:center;gap:8px;margin:0 0 12px;
  font-size:11px;font-weight:600;letter-spacing:.05em;color:var(--label-3);
}
.fld{margin-top:12px}
.grid-2{display:grid;grid-template-columns:1fr 160px;gap:12px}
.grid-3{display:grid;grid-template-columns:1fr 190px 140px;gap:12px}
.form-foot{display:flex;justify-content:flex-end;gap:10px;margin-top:18px}
.btn-row{display:flex;gap:8px;flex-wrap:wrap}
/* 登录屏那个「进去」：一屏只有一个动作，把它铺满就不用去找它。 */
.btn-wide{width:100%;justify-content:center;height:34px}

.prose{font-size:13.5px;line-height:1.75;white-space:pre-wrap;color:var(--label)}
.prose-sunken{background:var(--bg-sunken);border-radius:var(--r-ctl);padding:12px;color:var(--label-2)}
.prose i{color:var(--label-3)}

.empty{
  display:grid;place-items:center;gap:12px;text-align:center;
  padding:70px 20px;color:var(--label-3);
}
.empty h2{font-size:17px;font-weight:600;color:var(--label-2);margin:0}
.empty p{margin:0;font-size:13px;line-height:1.7;max-width:420px}
.empty-btns{display:flex;gap:8px;margin-top:4px}

/* ---------- 列表行 ---------- */
.card-list{
  background:var(--bg-card);border:1px solid var(--separator);
  border-radius:var(--r-card);overflow:hidden;
}
.row{
  display:flex;align-items:center;gap:12px;width:100%;
  padding:11px 14px;text-align:left;
  border-bottom:1px solid var(--separator);
  transition:background 130ms ease;
}
.row:last-child{border-bottom:0}
.row:hover{background:var(--fill)}
.row:active{background:var(--fill-press)}
.row-static:hover{background:none}
.row-main{flex:1;min-width:0;display:block}
.row-t{
  display:block;font-size:13.5px;font-weight:550;letter-spacing:-.015em;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.row-t i{font-weight:400;font-style:normal;color:var(--label-3);margin-left:8px}
.row-sub{display:block;font-size:11.5px;color:var(--label-3);margin-top:2px}
.row-score{font-size:15px;font-weight:650;letter-spacing:-.02em;white-space:nowrap}
.row-score i{font-size:11px;font-weight:400;font-style:normal;color:var(--label-3);margin-left:2px}
.row-dur{font-size:11.5px;color:var(--label-3);white-space:nowrap}
.row-arrow{color:var(--label-3);font-size:17px;line-height:1}

/* ---------- 骨架 ----------
   异步读数据期间的占位。高度按 .row 算（11px×2 + 一行 13.5px + 一行 11.5px），
   所以真内容到了原地替换、布局不跳。呼吸而不转圈：spinner 会把注意力
   吸到「在等」，灰条只是说「这里将要有几行东西」。 */
.sk-list{
  background:var(--bg-card);border:1px solid var(--separator);
  border-radius:var(--r-card);overflow:hidden;
}
.sk-row{
  height:52px;border-bottom:1px solid var(--separator);
  animation:sk 1.4s ease-in-out infinite;
}
.sk-row:last-child{border-bottom:0}
.sk-row:nth-child(2){animation-delay:.12s}
.sk-row:nth-child(3){animation-delay:.24s}
.sk-row:nth-child(n+4){animation-delay:.36s}
@keyframes sk{0%,100%{background:var(--fill)}50%{background:transparent}}
@media (prefers-reduced-motion:reduce){
  .sk-row{animation:none;background:var(--fill)}
}

/* ---------- 采分点：只读 / 编辑 ---------- */
.rub-view{border-top:1px solid var(--separator);padding:10px 0}
.rub-view:first-of-type{border-top:0;padding-top:0}
.rub-view-h{display:flex;align-items:baseline;gap:8px;font-size:13px}
.rub-view-h b{font-weight:600;letter-spacing:-.01em}
.rub-view-h span{margin-left:auto;font-size:11.5px;color:var(--label-3)}
.rub-view-hint{
  font-size:12px;line-height:1.6;color:var(--label-2);
  white-space:pre-wrap;margin-top:5px;
}
/* 「本该怎么写」在只读页要跟 hint 分得开 —— 两段字体一样的话，
   老师扫一眼看不出哪段会被学生看见。所以它缩进一格、带一条竖线。 */
.rub-view-std{
  font-size:12px;line-height:1.6;color:var(--label-2);
  white-space:pre-wrap;margin-top:6px;
  padding-left:9px;border-left:2px solid var(--separator);
}
.rub-view-std i{
  display:block;font-style:normal;font-size:10.5px;font-weight:600;
  color:var(--label-3);letter-spacing:.02em;margin-bottom:1px;
}
/* 没填的点标橙：这一格空着学生就看不到参考答案（决策 20 的风险要能数出来）。 */
.rub-view-std[data-none]{border-left-color:var(--orange);color:var(--orange)}
.rub-view-std[data-none] i{color:var(--orange)}

.rub-edit{border:1px solid var(--separator);border-radius:10px;padding:10px;margin-bottom:8px}
.rub-edit-h{display:flex;align-items:center;gap:8px}
.rub-edit-no{
  flex:none;width:20px;font-size:11px;font-weight:700;color:var(--label-3);text-align:center;
}
/* 这两条必须带上父类选择器 —— 光靠 .rub-name / .rub-max 权重低于
   input[type=text|number]{width:100%}，名称框会被压成一条缝，分值框反而铺满整行。 */
.rub-edit-h .rub-name{flex:1;min-width:0;width:auto}
.rub-edit-h .rub-max{flex:none;width:64px;text-align:center;padding:8px 6px}
.rub-unit{flex:none;font-size:11.5px;color:var(--label-3)}
.rub-hint{margin-top:8px;font-size:12.5px;resize:none;overflow:hidden}
/* 编辑态两个框挨着，必须有一行字说清第二个是给谁看的 ——
   不然它读起来就是「提示的第二段」。 */
.rub-std-h{display:flex;align-items:baseline;gap:6px;margin:9px 0 0}
.rub-std-h .fl{margin:0}
.rub-std-h span{font-size:10.5px;color:var(--label-3)}
.rub-std{margin-top:3px;font-size:12.5px;resize:none;overflow:hidden}

/* ---------- 统计 ---------- */
.stat-row{display:flex;flex-wrap:wrap;gap:10px}
.stat{
  flex:1;min-width:96px;background:var(--bg-sunken);
  border-radius:10px;padding:11px 12px;
}
.stat b{display:block;font-size:19px;font-weight:650;letter-spacing:-.02em}
.stat span{display:block;font-size:11px;color:var(--label-3);margin-top:2px}

.loss{display:flex;align-items:center;gap:12px;padding:8px 0;border-top:1px solid var(--separator)}
.loss:first-child{border-top:0}
.loss-t{flex:1;min-width:0;font-size:13px;font-weight:550}
.loss-t i{
  display:block;font-weight:400;font-style:normal;
  font-size:11px;color:var(--label-3);margin-top:2px;
}
.loss-bar{flex:0 0 120px;height:6px;border-radius:99px;background:var(--fill-press);overflow:hidden}
.loss-bar span{
  display:block;height:100%;border-radius:99px;background:var(--orange);
  transition:width 400ms var(--ease-out);
}
.loss-bar span[data-hot]{background:var(--red)}
.loss-n{flex:0 0 auto;font-size:11.5px;color:var(--label-3);text-align:right;min-width:74px}

/* ---------- 照片用量（数据页 · 待定 3）----------
   一个数 + 一条槽。不做成 .stat 那样的格子，因为它要表达的是「占了多大一份」，
   而分子分母并排 + 一条槽是唯一不用读文字就看得出比例的形状。
   三档颜色（绿 / 橙 / 红）在 span 上用 data 属性切 —— 状态一律 data 属性，
   不用 modifier class（仓库惯例）。 */
.use{margin:0 0 10px}
.use-n{display:flex;align-items:baseline;gap:6px}
.use-n b{font-size:24px;font-weight:650;letter-spacing:-.02em}
.use-n span{font-size:12px;color:var(--label-3)}
.use-bar{
  margin-top:8px;height:6px;border-radius:99px;
  background:var(--fill-press);overflow:hidden;
}
.use-bar span{
  display:block;height:100%;border-radius:99px;background:var(--green);
  /* 一进页面从 0 长到位会让人以为在加载。这个数是静态的，只在重画时变，
     所以只给宽度一个短过渡，不做入场动画。 */
  transition:width 300ms var(--ease-out);
  /* 有量但不到 1% 时也要看得见一条 —— 「有，只是很少」和「没有」不一样。 */
  min-width:2px;
}
.use-bar span[data-warm]{background:var(--orange)}
.use-bar span[data-hot]{background:var(--red)}
/* ==========================================================
   批改工作台 —— 三栏：学生答案 | 标准答案+我的批改 | AI 批改及建议
   栏与栏之间用 1px 的底色缝，不用 border，三栏等高对齐更干净。
   ========================================================== */
.wb{display:flex;flex-direction:column;flex:1;min-height:0;overflow:hidden}
.wb-top{
  flex:none;display:flex;align-items:center;gap:12px;
  height:50px;padding:0 14px;
  background:var(--bg-card);border-bottom:1px solid var(--separator);
}
.wb-title{
  font-size:13.5px;font-weight:600;letter-spacing:-.015em;
  min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
/* 顶栏是单行 50px 高的固定条，里面的东西一律不许换行 ——
   窄屏上按钮文字会被挤成竖排，把整条撑破。 */
.wb-top>.btn,.wb-top .navbtn,.wb-top .stu{flex:none;white-space:nowrap}
.wb-title small{
  display:block;font-size:11px;font-weight:400;color:var(--label-3);letter-spacing:0;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.wb-spacer{flex:1}
.stu{display:flex;align-items:center;gap:6px}
.stu-name{font-size:13px;font-weight:600;min-width:72px;text-align:center}
.stu-meta{font-size:11px;color:var(--label-3);text-align:center}
.wb-timer{
  font-size:12px;color:var(--label-3);min-width:56px;text-align:right;
  font-feature-settings:"tnum";
}
.wb-top kbd{
  font:inherit;font-size:10px;opacity:.55;margin-left:3px;
}
/* 桌面上按键提示 [ ] 就够了，箭头是给手机备的（见 620px 断点） */
.nav-gl{display:none;font-size:16px;line-height:1;color:var(--label-2)}

/* ---------- 三栏 ---------- */
.wb-body{
  flex:1;display:flex;min-height:0;
  gap:1px;background:var(--separator);
}
.wb-col{background:var(--bg-card);display:flex;flex-direction:column;min-width:0}
.wb-col-a{flex:1.05}
.wb-col-b{flex:1.3}
.wb-col-c{flex:none;width:300px}
.wb-col-c[data-collapsed]{width:44px}
.col-h{
  flex:none;display:flex;align-items:center;gap:8px;height:34px;padding:0 14px;
  font-size:11px;font-weight:600;letter-spacing:.04em;color:var(--label-3);
  border-bottom:1px solid var(--separator);
}
.col-h .badge{margin-left:auto;letter-spacing:0}
.col-h .navbtn{margin-left:auto;width:22px;height:22px}
.col-h .badge+.navbtn{margin-left:0}
.col-h-s{margin-left:auto;font-size:10.5px;font-weight:400}
.col-scroll{flex:1;overflow:auto;padding:14px}

/* ---------- A 栏：学生答案 ---------- */
.ans-meta{display:flex;gap:14px;font-size:11px;color:var(--label-3);margin-bottom:10px}

/* 同一学生同一题的提交版本。旧版必须找得到，但不能长得像另一个待批答案。 */
.version-strip{
  display:flex;align-items:center;flex-wrap:wrap;gap:6px;
  margin:8px 0 12px;padding:7px 9px;border-radius:8px;
  background:var(--fill);font-size:12px;color:var(--label-2);
}
.version-strip b{margin-right:2px;color:var(--label);font-weight:600}
.version-strip a,.version-strip span{
  padding:3px 7px;border-radius:99px;background:var(--bg-card);border:1px solid var(--separator);
}
.version-strip a{color:var(--blue)}
.version-strip [data-current]{border-color:var(--blue);color:var(--blue);font-weight:600}
.version-strip [data-old]{color:var(--label-2)}
.history-warn{
  margin:0 0 12px;padding:10px 12px;border-radius:10px;
  background:rgba(224,131,0,.12);color:var(--orange);font-size:13px;
}
.history-grid{display:grid;grid-template-columns:minmax(0,1.35fr) minmax(280px,.65fr);gap:12px}
.history-grid .ans-shots{grid-template-columns:repeat(auto-fill,minmax(150px,1fr))}
@media(max-width:850px){.history-grid{grid-template-columns:1fr}}
.ans-q{
  font-size:12.5px;line-height:1.65;color:var(--label-2);margin:0 0 14px;
  padding-bottom:12px;border-bottom:1px solid var(--separator);
}
/* 答案是要通读的：衬线、宽行距。三栏下栏宽有限，字号比原来的单栏小一号。 */
.ans-text{
  font-family:var(--font-read);font-size:15px;line-height:1.95;
  white-space:pre-wrap;letter-spacing:0;
  -webkit-user-select:text;user-select:text;
}
.ans-text::selection,.ans-text *::selection{background:rgba(255,214,10,.42)}
.ans-text[data-none]{font-family:var(--font);font-size:12.5px;color:var(--label-3)}

/* 「学生报有几页没传上来」（跟学生端 .gap 同一件事的另一头）。
   红而不是橙，而且比 .ans-note 重：橙的 .ans-note 说的是「这个功能这里没有」，
   老师掠过它只是少一个便利；这一条说的是**手上这份答案不全**，掠过它就会
   把没传上来的判成没写 —— 那是这条路上唯一真会改分数的错。
   所以给左边一道实色竖条 + 有色底，摆在题干和照片之间那道视觉缝里，
   不缩成一个 badge：badge 大小的东西读起来像状态，读不出「先别判」。
   三段各自一行：b 是事实，span 是原因（决定找谁），p 是动作。 */
.ans-gap{
  margin:0 0 14px;padding:9px 11px;border-radius:8px;
  border-left:3px solid var(--red);
  background:rgba(224,52,42,.10);
  font-size:12px;line-height:1.7;color:var(--label-2);
}
.ans-gap b{display:block;color:var(--red);font-weight:600;font-size:12.5px}
/* 原因是学生浏览器报的九种之一，翻成了中文。比正文小半号、灰一点：
   它是线索不是结论（客户端自报的，学生想写什么都能写）。 */
.ans-gap span{display:block;margin-top:2px;font-size:11px;color:var(--label-3)}
.ans-gap p{margin:4px 0 0}

/* ---------- A 栏：照片提交（决策 3）----------
   缩略图是「拍了几页、每页大概什么样」，判分要点开看大图。
   两列固定：A 栏只有 300 上下的宽度，三列之后每张窄到看不出是哪一页；
   一份答案常是两页纸，两列正好一行放完。
   纵横比按 A4 竖版（1:1.414）留，object-fit:cover 保证边角对齐 ——
   不 cover 的话每张高度不同，两页纸看起来像两种东西。 */
.ans-shots{display:grid;grid-template-columns:1fr 1fr;gap:8px}
.ans-shot{
  position:relative;display:block;padding:0;width:100%;aspect-ratio:1/1.414;
  border:1px solid var(--separator);border-radius:8px;overflow:hidden;
  background:var(--bg-sunken);
  transition:border-color 160ms ease,transform 160ms var(--ease-out);
}
.ans-shot:hover{border-color:var(--blue);transform:translateY(-1px)}
.ans-shot img{display:block;width:100%;height:100%;object-fit:cover}
.ans-shot-off{
  position:absolute;inset:0;display:grid;place-items:center;
  font-size:11px;color:var(--label-3);
}
.ans-shot-n{
  position:absolute;left:5px;bottom:5px;padding:1px 5px;border-radius:5px;
  background:rgba(0,0,0,.56);color:#fff;font-size:9.5px;font-weight:600;
}
/* 「这一份不能选中引用」。橙色是因为它是**能力缺失**的通知，不是说明文字 ——
   跟题干同一个灰的话老师会掠过，然后自己在照片上拖选半天。 */
.ans-note{
  margin:10px 0 0;padding:8px 10px;border-radius:8px;
  background:rgba(224,131,0,.09);
  font-size:11.5px;line-height:1.65;color:var(--label-2);
}
.ans-note b{color:var(--orange);font-weight:600}

/* ---------- 看大图 ----------
   复用 .mask（render() 只清 .drw-mask / .mask / .quote-pop），
   data-photo 把底色压到几乎全黑：34% 的灰底下白纸糊成一片看不清笔画，
   而 90% 又还能透出底下的三栏 —— 判分要看清一笔一画，背后浮着一整页
   可读的标准答案就是在跟这张图抢眼睛。这里要的是暗房，不是蒙层。 */
.mask[data-photo]{background:rgba(0,0,0,.975);padding:0}
.pv{width:100%;height:100%;display:flex;flex-direction:column}
.pv-bar{
  flex:none;display:flex;align-items:center;gap:8px;height:52px;padding:4px 16px 0;
  color:#fff;font-size:12px;
}
.pv-bar .navbtn{color:#fff;font-size:18px}
.pv-bar .navbtn:disabled{opacity:.3}
.pv-bar .btn{background:rgba(255,255,255,.16);color:#fff}
.pv-bar .btn:hover{background:rgba(255,255,255,.26)}
.pv-body{flex:1;min-height:0;display:grid;place-items:center;padding:0 20px 20px}
.pv-body img{
  max-width:100%;max-height:100%;width:auto;height:auto;
  border-radius:6px;background:#fff;object-fit:contain;
}
.pv-off{
  display:grid;gap:6px;justify-items:center;text-align:center;
  color:rgba(255,255,255,.82);font-size:13px;
}
.pv-off small{font-size:11px;color:rgba(255,255,255,.5);word-break:break-all}

/* 手打引文框（照片提交，决策 10）。跟 .item-ev 一样带蓝左竖线 ——
   它俩是同一个 evidence 字段的两种填法，看起来必须是同一个东西。 */
.item-ev-box{
  margin:9px 11px 0;width:calc(100% - 22px);
  font-family:var(--font-read);font-size:12.5px;line-height:1.6;
  border-left:2px solid var(--blue);border-radius:0 6px 6px 0;
  background:var(--bg-sunken);
}

/* 选中原文后浮出的「引用」按钮 */
.quote-pop{
  position:fixed;z-index:60;transform:translate(-50%,-100%) scale(.94);
  height:28px;padding:0 11px;border-radius:8px;
  background:var(--label);color:var(--bg-card);
  font-size:12px;font-weight:500;white-space:nowrap;
  box-shadow:0 4px 14px rgba(0,0,0,.22);
  opacity:0;transition:opacity 140ms ease,transform 180ms var(--ease-out);
}
.quote-pop[data-in]{opacity:1;transform:translate(-50%,-100%) scale(1)}

/* ---------- B 栏：标准答案 + 我的批改 ---------- */
.std-h{
  display:flex;align-items:baseline;gap:8px;
  font-size:11px;font-weight:600;letter-spacing:.04em;color:var(--label-3);
  margin:16px 0 7px;
}
.std-h:first-child{margin-top:0}
.std-h kbd{font:inherit;font-size:10px;font-weight:400;opacity:.7}
.std-h-k{margin-left:auto;font-weight:400;font-size:10px;letter-spacing:0;text-align:right}
.std{
  font-size:12.5px;line-height:1.7;color:var(--label-2);white-space:pre-wrap;
  padding:11px 12px;background:var(--bg-sunken);border-radius:var(--r-ctl);
}
.std i{color:var(--label-3)}
.cmt-box{width:100%;font-size:12.5px}

/* 采分点卡片。全部展开，当前项靠左边框和底色标出来，不做折叠动画 ——
   老师是对着上面的标准答案逐条比对的，一次只露一项反而要来回滚。 */
.item{
  border:1px solid var(--separator);border-left:2px solid transparent;
  border-radius:10px;margin-bottom:8px;overflow:hidden;
  transition:border-color 180ms ease,background 180ms ease;
}
.item[data-cur]{border-left-color:var(--blue);background:rgba(0,122,255,.03)}
.item[data-done]:not([data-cur]) .item-name{color:var(--label-2)}
.item-h{display:flex;align-items:baseline;gap:7px;padding:9px 11px 6px}
.item-no{font-size:10.5px;font-weight:700;color:var(--label-3);min-width:14px}
.item-name{font-size:13px;font-weight:600;letter-spacing:-.01em}
.item-vd{font-size:10.5px;font-weight:600;color:var(--label-3)}
.item-vd[data-v=hit]{color:var(--green)}
.item-vd[data-v=part]{color:var(--orange)}
.item-vd[data-v=err]{color:var(--red)}
.item-max{margin-left:auto;font-size:11px;color:var(--label-3)}
.item-hint{
  font-size:11.5px;line-height:1.6;color:var(--label-2);
  white-space:pre-wrap;margin:0 11px 8px;
}
.item-ctl{
  display:flex;align-items:center;gap:8px;padding:7px 11px;
  background:var(--bg-sunken);
}
.keys{display:flex;gap:4px;min-width:0}
.keycap{
  display:flex;align-items:center;gap:4px;height:26px;padding:0 8px;border-radius:7px;
  font-size:11.5px;background:var(--bg-card);color:var(--label-2);
  box-shadow:0 0 0 1px var(--separator);
  transition:background 150ms ease,color 150ms ease,transform 160ms var(--ease-out);
}
.keycap:hover{background:var(--fill-press)}
.keycap:active{transform:scale(.96)}
.keycap b{
  display:grid;place-items:center;width:14px;height:14px;border-radius:4px;
  background:var(--fill);
  font-size:9.5px;font-weight:700;color:var(--label-3);
}
.keycap[data-on=hit]{background:rgba(47,168,74,.16);color:var(--green)}
.keycap[data-on=part]{background:rgba(224,131,0,.16);color:var(--orange)}
.keycap[data-on=miss]{background:var(--fill-press);color:var(--label-2)}
.keycap[data-on=err]{background:rgba(224,52,42,.14);color:var(--red)}

/* 得分框和批注按钮都必须 flex:none —— 否则 input[type=number] 的
   width:100% 会赢过 .score-in（属性选择器权重高于类），把整行挤变形。 */
.item-ctl .score-in{
  flex:none;width:48px;height:26px;margin-left:auto;
  padding:0 4px;text-align:center;font-size:12px;
}
.item-note-btn{flex:none}
.item-ev{
  display:flex;align-items:flex-start;gap:6px;
  margin:9px 11px 0;padding:7px 9px;
  background:var(--bg-sunken);border-left:2px solid var(--blue);border-radius:0 6px 6px 0;
  font-size:11.5px;line-height:1.6;color:var(--label-2);
}
.item-ev span{flex:1;min-width:0}
.ev-x{flex:none;font-size:11px;color:var(--label-3);padding:0 2px;line-height:1}
.ev-x:hover{color:var(--red)}
.item-note{margin:9px 11px;width:calc(100% - 22px);font-size:12px}

.pips{display:flex;gap:4px;margin-left:8px}
.pip{
  width:7px;height:7px;padding:0;border-radius:99px;background:var(--separator-2);
  transition:background 200ms ease,transform 200ms var(--ease-out);
}
.pip[data-s=hit]{background:var(--green)}
.pip[data-s=part]{background:var(--orange)}
.pip[data-s=miss]{background:var(--label-3)}
.pip[data-s=err]{background:var(--red)}
.pip[data-cur]{transform:scale(1.5)}

/* ---------- C 栏：AI 批改及建议 ----------
   默认折叠成一条竖排标签。折叠不为省地方，是防锚定。 */
.ai-rail{
  flex:1;display:flex;align-items:center;justify-content:center;
  writing-mode:vertical-rl;white-space:nowrap;
  font-size:11px;letter-spacing:.12em;color:var(--label-3);
  transition:color 150ms ease,background 150ms ease;
}
.ai-rail:hover{color:var(--label-2);background:var(--fill)}
.ai-off p{margin:0 0 10px;font-size:12.5px;line-height:1.7;color:var(--label-2)}
.ai-off b{color:var(--label)}
.ai-card{border:1px solid var(--separator);border-radius:10px;padding:10px;margin-bottom:8px}
.ai-ev{
  font-size:12px;line-height:1.6;color:var(--label-2);
  border-left:2px solid var(--separator-2);padding-left:8px;margin:5px 0;
}
.ai-note{
  font-size:10.5px;color:var(--label-3);line-height:1.6;text-align:center;
  padding:10px 4px 0;border-top:1px dashed var(--separator);margin-top:6px;
}

/* ---------- 底部结算条：完成 与 发布 永远是两步 ---------- */
.wb-foot{
  flex:none;display:flex;align-items:center;gap:12px;
  min-height:52px;padding:0 16px;
  background:var(--bg-card);border-top:1px solid var(--separator);
}
.rail-left{font-size:11.5px;color:var(--label-3)}
.rail-saved{
  font-size:10.5px;color:var(--label-3);
  opacity:.5;transition:opacity 200ms ease;
}
/* 存盘失败：唯一会丢批改结果的地方，所以给它整条底栏里最重的颜色。
   文案是「正在重试」而不是「失败」—— 老师不需要做任何事，只需要知道
   现在还没落地，别关页面。 */
.rail-saved[data-s=fail]{color:var(--red);font-weight:600;opacity:1}
.wb-acts{display:flex;align-items:center;gap:8px}
/* 状态跃迁在飞的时候整组按钮不可点：连点两次「发布」在真后端上是两条写。 */
.wb-acts[data-busy]{opacity:.5;pointer-events:none}
.wb-acts kbd{font:inherit;font-size:11px;opacity:.6}
.acts-note{font-size:11px;line-height:1.5;color:var(--label-2)}
.acts-quiet{color:var(--label-3)}
.acts-ok{color:var(--green)}
/* ==========================================================
   浮层：toast / 确认框 / 抽屉
   ========================================================== */
.toast{
  position:fixed;left:50%;bottom:28px;z-index:80;
  display:flex;align-items:center;justify-content:center;gap:10px;
  transform:translate(-50%,0);
  max-width:min(460px,88vw);padding:10px 16px;border-radius:11px;
  background:var(--label);color:var(--bg-card);
  font-size:13px;line-height:1.5;text-align:center;
  box-shadow:0 8px 28px rgba(0,0,0,.24);
  transition:opacity 200ms ease,transform 240ms var(--ease-out);
}
.toast[data-enter]{opacity:0;transform:translate(-50%,10px) scale(.97)}
.toast-warn{background:var(--red);color:#fff}
/* 撤销键。加进组 / 开放给某个组不拦确认（决策 8），撤销就是这个动作
   唯一的退路，所以它必须在 toast 里够显眼、够好点。
   底色跟着 currentColor 走而不是写死白色半透明 —— 深色模式下 toast 自己
   是浅底深字，写死白色的话这个键会整个消失。 */
.toast button{
  flex:none;padding:2px 9px;border-radius:6px;
  background:color-mix(in srgb, currentColor 18%, transparent);
  color:inherit;font-size:12px;font-weight:550;
  transition:background 140ms ease;
}
.toast button:hover{background:color-mix(in srgb, currentColor 32%, transparent)}

.mask{
  position:fixed;inset:0;z-index:70;display:grid;place-items:center;padding:20px;
  background:rgba(0,0,0,.34);
  transition:opacity 200ms ease;
}
.mask[data-enter]{opacity:0}
.mask[data-enter] .dlg{transform:scale(.94)}
.dlg{
  width:100%;max-width:352px;padding:20px;border-radius:14px;
  background:var(--bg-card);
  box-shadow:0 20px 60px rgba(0,0,0,.3);
  transition:transform 260ms var(--ease-out);
}
.dlg h3{margin:0;font-size:16px;font-weight:650;letter-spacing:-.02em}
.dlg p{margin:8px 0 0;font-size:13px;line-height:1.65;color:var(--label-2)}
/* 后果的下半句：既要安抚（已交答案不受影响）又要警告（草稿会找不到入口）。
   它必须跟主句分开一块底色 —— 混在一段里老师只会读第一句。 */
.dlg-note{
  margin-top:12px;padding:10px 12px;border-radius:9px;background:var(--fill);
  font-size:12.5px;line-height:1.7;color:var(--label-2);
}
.dlg-note b{color:var(--label);font-weight:600}
.dlg .fld:first-of-type{margin-top:14px}
.dlg-foot{display:flex;gap:8px;margin-top:18px}
.dlg-foot .btn{flex:1}
.btn-danger-solid{background:var(--red);color:#fff}
.btn-danger-solid:hover{background:var(--red);filter:brightness(1.06)}

.drw-mask{
  position:fixed;inset:0;z-index:65;
  background:rgba(0,0,0,.30);
  transition:opacity 240ms ease;
}
.drw-mask[data-enter]{opacity:0}
.drw-mask[data-enter] .drw{transform:translateX(100%)}
.drw{
  position:absolute;top:0;right:0;bottom:0;width:min(440px,92vw);
  display:flex;flex-direction:column;
  background:var(--bg-card);border-left:1px solid var(--separator);
  box-shadow:-12px 0 40px rgba(0,0,0,.16);
  transition:transform 320ms var(--ease-drawer);
}
.drw-h{
  flex:none;display:flex;align-items:center;gap:8px;
  height:48px;padding:0 14px;border-bottom:1px solid var(--separator);
}
.drw-h b{font-size:13.5px;font-weight:650}
.drw-scroll{flex:1;overflow:auto;padding:14px}
.drw-scroll textarea{font-size:13.5px}

.seg-tabs{
  flex:none;display:flex;gap:2px;padding:8px 14px 0;
}
.seg-tabs button{
  height:28px;padding:0 12px;border-radius:7px;
  font-size:12.5px;color:var(--label-2);
  transition:background 150ms ease,color 150ms ease;
}
.seg-tabs button:hover{background:var(--fill)}
.seg-tabs button[data-on]{background:var(--fill-press);color:var(--label);font-weight:550}

.code-eg{
  margin:0 0 12px;padding:10px 12px;border-radius:8px;
  background:var(--bg-sunken);color:var(--label-2);
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
  font-size:11.5px;line-height:1.65;white-space:pre-wrap;
}
.parse-out{margin:10px 0}
.parse-h{font-size:11px;font-weight:600;letter-spacing:.04em;color:var(--label-3);margin-bottom:7px}
.parse-warn{color:var(--orange)}
.parse-row{
  display:flex;flex-wrap:wrap;align-items:baseline;gap:8px;padding:6px 0;
  border-top:1px solid var(--separator);font-size:12px;
}
.parse-row b{font-weight:600;flex:none}
.parse-row .num{color:var(--label-3);font-size:11px;flex:none}
.parse-pv{
  flex:1;min-width:0;color:var(--label-3);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
/* 「这一份挂到谁」占满第二行 —— 决策 13 的整个重点是这一行看得清，
   挤在预览右边被 ellipsis 切掉就等于没写。data-none = 还挂不到人，标橙。 */
.parse-who{
  flex-basis:100%;font-size:11.5px;color:var(--label-2);
  display:flex;align-items:center;gap:6px;
}
.parse-who[data-none]{color:var(--orange)}
.parse-who select{width:auto;flex:1;min-width:0;padding:3px 6px;font-size:11.5px}

.ai-off{padding:2px 0}

/* ---------- 窄屏：三栏叠成上下 ----------
   三栏并列要 ~900px 才不挤。窄屏改成纵向：答案在上（限高，可滚），
   批改在下（占满剩下的），AI 收成一条横条。 */
@media (max-width:900px){
  .wb-body{flex-direction:column;gap:1px}
  /* 答案区让位给批改区 —— 采分点被底部结算条压住的话键盘流就断了 */
  .wb-col-a{flex:none;max-height:34vh}
  .wb-col-b{flex:1;min-height:0}
  .col-scroll{padding:12px}
  /* AI 那栏竖排标签在横条里会变成一列字，改回横排 */
  .wb-col-c{width:auto}
  .wb-col-c[data-collapsed]{width:auto;flex:none}
  .wb-col-c[data-collapsed] .ai-rail{
    writing-mode:horizontal-tb;height:38px;flex:none;
    letter-spacing:.06em;
  }
  .wb-col-c:not([data-collapsed]){flex:1;min-height:0}
  .wb-timer{display:none}
  .grid-2{grid-template-columns:1fr}
  .page{padding:16px 14px 48px}

  /* 结算条竖过来：分数一行，动作按钮一行铺满。
     横排在手机上会把「发布给学生」挤成两个字宽。 */
  .wb-foot{
    display:grid;grid-template-columns:auto auto 1fr;align-items:center;
    gap:4px 8px;padding:8px 14px calc(8px + env(safe-area-inset-bottom));
  }
  .wb-foot .total-lbl{grid-area:1/1}
  .wb-foot .total{grid-area:1/2}
  .wb-foot .rail-left{grid-area:1/3;justify-self:start}
  .wb-foot .wb-spacer,.rail-saved{display:none}
  .wb-acts{grid-column:1 / -1;flex-wrap:wrap}
  .wb-acts .btn{flex:1;min-width:120px}
  .acts-note{flex:1 0 100%;text-align:center}
}

/* 矮屏（横放的手机、SE 这类小屏）：答案区再让一点，
   否则批改区凑不出一个采分点的高度。 */
@media (max-width:900px) and (max-height:760px){
  .wb-col-a{max-height:24vh}
  .item-hint{font-size:11px;line-height:1.5}
}

/* 手机宽度上顶栏放不下题目名 —— 它跟 A 栏里的题干本来就重复，直接收掉，
   把宽度让给「上一份 / 下一份」，保住单行。 */
@media (max-width:620px){
  .wb-top{gap:8px;padding:0 10px}
  .wb-title{display:none}
  /* 手机上没键盘，[ ] 提示要收掉；但收掉之后按钮里就空了 ——
     必须让出箭头字形，否则上一份/下一份是两个空方块。 */
  .wb-top kbd{display:none}
  .wb-top .nav-gl{display:block}
  .stu-name{min-width:0}
  /* 判定档位四个挤不下一行，让它们换行，得分框独占一行右对齐 */
  .item-ctl{flex-wrap:wrap}
  .keys{flex:1 0 100%}
  .item-ctl .score-in{margin-left:0}
  .item-note-btn{margin-left:auto}
  .std-h-k{display:none}
}

/* ==========================================================
   学生页（两栏 · 左组右人，决策 18）
   ========================================================== */
/* ---------- 组 chip：开放页用的是同一个类，不是长得像 ----------
   只是主语换了：这边「点亮 = 这个人在这个组」，那边「点亮 = 这个组能做这个题集」。
   两页共用一个原子不是省事 —— 老师点的是同一张表的两头
   （student_groups / set_grants），手感必须一样，否则他会以为其中一个有别的后果。
   所以开放页没有第二套 chip 样式，直接用这一个。
   小勾的位子始终留着，切换只改透明度：不留位的话每次点击整排 chip 都要重排。 */
.gchip{
  display:inline-flex;align-items:center;gap:5px;height:27px;padding:0 9px 0 7px;
  border-radius:99px;font-size:12.5px;color:var(--label-2);background:var(--fill);
  box-shadow:0 0 0 1px transparent inset;
  transition:background 160ms ease,color 160ms ease,box-shadow 160ms ease,transform 160ms var(--ease-out),opacity 160ms ease;
}
.gchip:hover{background:var(--fill-press)}
.gchip:active{transform:scale(.96)}
.gchip[data-on]{
  background:rgba(0,122,255,.10);color:var(--blue);font-weight:550;
  box-shadow:0 0 0 1px rgba(0,122,255,.30) inset;
}
.gchip-tk{width:11px;display:grid;place-items:center;color:var(--blue);opacity:0;transition:opacity 160ms ease}
.gchip[data-on] .gchip-tk{opacity:1}
.gchip-n{font-size:10.5px;color:var(--label-3);font-variant-numeric:tabular-nums}
.gchip[data-on] .gchip-n{color:rgba(0,122,255,.62)}
/* 写库那一下。真实实现是 insert/delete student_groups 一行。 */
.gchip[data-busy]{opacity:.5;pointer-events:none}

/* ---------- 搜索框：12 个人以上「找到张伟」就开始靠扫 ---------- */
.srch{
  display:flex;align-items:center;gap:7px;height:30px;padding:0 10px;
  border-radius:var(--r-ctl);background:var(--bg-card);
  box-shadow:0 0 0 1px var(--separator-2) inset;
  transition:box-shadow 150ms ease;
}
.srch:focus-within{box-shadow:0 0 0 2px var(--blue) inset}
/* 全局 input 规则给了 border + padding，搜索框的壳自己画，要覆盖掉 */
.srch input{
  border:0;outline:0;background:none;padding:0;width:100%;
  font-size:13px;box-shadow:none;
}
.srch input:focus{border:0;box-shadow:none}

/* ---------- 页头下的一句话：这一页在改什么、哪个记号是异常 ----------
   分组这件事的后果隔着三跳才看得见（组 → 题集 → 题 → 学生首屏），
   所以这一页必须自己说一句，不能指望老师从界面推出来。 */
.page-lead{
  font-size:12.5px;line-height:1.7;color:var(--label-2);
  margin:-6px 0 16px;max-width:640px;
}

/* ---------- 两栏骨架 ---------- */
.tw{display:grid;grid-template-columns:212px 1fr;gap:16px;align-items:start}
.tw-side{
  background:var(--bg-card);border:1px solid var(--separator);
  border-radius:var(--r-card);padding:6px;position:sticky;top:0;
}
.tw-si{
  display:flex;align-items:center;gap:8px;width:100%;padding:8px 10px;border-radius:9px;
  font-size:13px;text-align:left;color:var(--label);transition:background 140ms ease;
}
.tw-si:hover{background:var(--fill)}
.tw-si[data-on]{background:var(--fill-press);font-weight:600}
.tw-si b{font-weight:inherit;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.tw-si i{
  font-style:normal;margin-left:auto;font-size:11.5px;color:var(--label-3);
  font-variant-numeric:tabular-nums;
}
/* 「不在任何组」那一条：橙色的组名 —— 跟行左的橙条是同一个记号。 */
.tw-si[data-orphan] b{color:var(--orange)}
.tw-si-add{color:var(--label-3);font-size:12.5px}
/* 分隔线在承担「主语换了」：上面按组分，下面按状态分（决策 18）。 */
.tw-sep{height:1px;margin:5px 8px;background:var(--separator)}

.tw-main{
  background:var(--bg-card);border:1px solid var(--separator);
  border-radius:var(--r-card);overflow:hidden;
}
.tw-hd{
  display:flex;align-items:center;gap:9px;min-height:44px;padding:7px 15px;
  border-bottom:1px solid var(--separator);
}
.tw-hd b{font-size:14px;font-weight:650;letter-spacing:-.02em}
.tw-hd i{font-style:normal;font-size:11.5px;color:var(--label-3)}
.tw-hd-r{margin-left:auto;display:flex;align-items:center;gap:8px}
.tw-hd-r .srch{width:170px}

/* 四列：姓名/备注 · 组 chip · 邀请状态 · 链接。
   姓名给死宽度、chip 那列吃掉剩下的 —— chip 数量跟组数一起长，
   给它弹性列才不会在第五个组出现时把姓名挤断。 */
.tw-row{
  display:grid;grid-template-columns:176px 1fr 118px 48px;align-items:center;gap:12px;
  padding:6px 15px;min-height:51px;border-top:1px solid var(--separator);
}
.tw-row:first-child{border-top:0}
/* 一道橙条，不用读字：他一道题都看不到。跟开放页的孤儿题集同一个记号。 */
.tw-row[data-orphan]{box-shadow:inset 3px 0 0 var(--orange)}
.tw-empty{padding:34px 15px;text-align:center;font-size:13px;color:var(--label-3);margin:0}

.rs-nm{display:flex;flex-direction:column;gap:1px;min-width:0}
.rs-nm b{font-size:13.5px;font-weight:600;letter-spacing:-.01em}
/* 备注不是装饰：决策 13 的代录下拉要靠它区分两个同名的「张伟」。 */
.rs-nm i{font-style:normal;font-size:11.5px;color:var(--label-3)}
.rs-nm i[data-dup]{color:var(--orange)}
/* 登录名摆在名字下面而不是只在对话框里：老师在微信上被问「我的账号是啥」
   的时候不该为了念一个字符串去点开一个对话框。等宽是因为它要被念出来 ——
   linzhiyao 里那个 l 和 1 在正文字体里分不开。 */
.rs-lg{
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
  font-size:10.5px;color:var(--label-3);letter-spacing:-.01em;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.rs-gs{display:flex;flex-wrap:wrap;gap:5px}
.rs-none{font-size:12px;color:var(--orange)}
.rs-act{display:flex;justify-content:flex-end}

/* ---------- 邀请链接（决策 6：老师自己粘到微信发，没有 SMTP） ----------
   所以这一块的产物是**一段能选中的文本**，不是一个「已发送」状态。
   已设密码之后不再显示 URL —— 留一个作废的链接在屏上只会让老师又发一次。 */
.inv{margin-top:14px;padding:12px;border-radius:11px;background:var(--bg-sunken)}
.inv-h{
  display:flex;align-items:center;gap:7px;
  font-size:11.5px;font-weight:600;color:var(--label-2);margin-bottom:8px;
}
.inv-h .badge{margin-left:auto}
.inv-url{
  display:block;width:100%;padding:9px 10px;border-radius:8px;background:var(--bg-card);
  box-shadow:0 0 0 1px var(--separator-2) inset;
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;line-height:1.55;
  color:var(--label-2);word-break:break-all;text-align:left;
}
.inv-f{display:flex;align-items:center;gap:8px;margin-top:9px}
.inv-note{font-size:11px;line-height:1.55;color:var(--label-3);margin:8px 0 0}
.inv-note b{color:var(--label-2);font-weight:600}

/* 登录名单独一块，跟 .inv 分开：链接是一次性的（「重新生成」会把 .inv 整块
   换掉），登录名是这个人永久的身份，不该跟着一起重画。 */
.inv-login{margin-top:14px;padding:12px;border-radius:11px;background:var(--bg-sunken)}
.inv-login .inv-h{margin-bottom:0}
.inv-login-v{
  flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
  font-size:12.5px;font-weight:400;color:var(--label-1);letter-spacing:-.01em;
}

/* 已设密码 = 绿；待接受 = 橙（badge-hold 现成）。
   两个都要跟小勾并排，所以自己声明 inline-flex，不动全局 .badge。 */
.badge-on{
  display:inline-flex;align-items:center;gap:3px;
  background:rgba(47,168,74,.16);color:var(--green);
}

/* ---------- 录入抽屉里的组 chip 和后果警告 ---------- */
.fld-chips{display:flex;flex-wrap:wrap;gap:6px}
/* 一个组都不选也能录（决策 7 不禁止），但后果要当场说 ——
   否则老师录完 12 个人，其中 3 个进去一道题都看不到，一周后才发现。 */
.fld-warn{
  margin:8px 0 0;padding:8px 10px;border-radius:8px;
  background:rgba(224,131,0,.10);font-size:11.5px;line-height:1.6;color:var(--orange);
}
.fld-warn b{font-weight:650}
/* 空名单那条警告里带一条去名单页的路（决策 13：代录之前必须先录人）——
   死胡同里放一句「你得先去别处做件事」还不给链接，等于让老师自己找路。 */
.fld-warn a{color:inherit;font-weight:650;text-decoration:underline;
  text-underline-offset:2px;display:inline-block;margin-top:4px}
.drw-n{margin-left:auto;font-size:11.5px;color:var(--label-3)}
.drw-f{
  flex:none;display:flex;gap:8px;padding:12px 14px;
  border-top:1px solid var(--separator);
}
.drw-f .btn{flex:1;justify-content:center}

/* ---------- 窄屏：左栏塌成顶部一排横向可滚的 chip ----------
   决策 18 明写这是选两栏必须一起付的账。右边的表照旧，只是列收一收。 */
@media (max-width:900px){
  .tw{grid-template-columns:1fr;gap:10px}
  .tw-side{
    position:static;display:flex;gap:4px;padding:6px;
    overflow-x:auto;scrollbar-width:none;
  }
  .tw-side::-webkit-scrollbar{display:none}
  .tw-si{width:auto;flex:none;border-radius:99px;padding:6px 11px;font-size:12.5px}
  /* 横排之后计数不能再靠 margin-left:auto 顶到右边 —— 那会把 chip 撑满整行。 */
  .tw-si i{margin-left:0}
  .tw-sep{height:auto;width:1px;margin:4px 3px;flex:none}
  /* 姓名和 chip 上下叠，状态和链接并到右边一列 */
  .tw-row{grid-template-columns:1fr auto;gap:4px 10px;padding:9px 13px}
  .tw-row .rs-nm{grid-area:1/1}
  .tw-row .rs-gs{grid-area:2/1}
  .tw-row .rs-act{grid-area:1/2}
  .tw-row .rs-st{grid-area:2/2;justify-self:end}
  .tw-hd{flex-wrap:wrap}
  .tw-hd-r{width:100%;margin-left:0}
  .tw-hd-r .srch{flex:1;width:auto}
}

/* ==========================================================
   题目页（决策 22 之后只有一份列表，没有二级 tab）
   ==========================================================
   删掉的是 .st（二级 tab）/ .sc*（题集卡）/ .ochip（只读组 chip）/
   .pk*（勾题清单）四组，一共一百来行。题集那一层不存在了，
   它们没有任何选择器还能匹配到东西 —— 留着的话下一个人会以为
   还有个面能进去。 */

/* ---------- 工具条：搜索 + 常驻的异常计数 ---------- */
.tbar{display:flex;align-items:center;gap:8px;margin:0 0 12px;flex-wrap:wrap}
.tbar .srch{width:200px;flex:none}
.q-filter{
  width:auto;min-width:116px;height:32px;padding:0 28px 0 10px;
  border:1px solid var(--separator);border-radius:8px;background-color:var(--bg-card);
  color:var(--label);font-size:12.5px;
}
.q-filter:focus{border-color:var(--blue);box-shadow:0 0 0 2px color-mix(in srgb,var(--blue) 18%,transparent)}
.q-match{font-size:11.5px;color:var(--label-3);white-space:nowrap}
/* 异常计数。「N 道谁都做不了」是一条去开放页的链接（a.flt），
   开放页那颗是纯读数（span）—— 长得一样但一个能按一个不能，
   靠 :hover 区分不够，所以只读那颗压掉 hover。 */
.flt{
  display:inline-flex;align-items:center;gap:5px;height:27px;padding:0 10px;
  border-radius:99px;font-size:12.5px;color:var(--orange);
  background:rgba(224,131,0,.12);
  transition:background 150ms ease;
}
a.flt:hover,button.flt:hover{background:rgba(224,131,0,.2)}
/* 标题旁边那颗题数。原来是二级 tab 上的角标，tab 删了它跟着搬到 h1 里 ——
   「库里有几道题」这个数在这一页只有一处该出现。 */
.st-n{font-size:10.5px;opacity:.55;margin-left:4px;font-variant-numeric:tabular-nums}

/* ---------- 多出来的那一格：开给了哪些组 ----------
   只读的一行淡字，不是 chip —— 长得像可点的东西而点了没反应比不显示更糟。
   要改开放去开放页。高频面只加信息，不加控件。 */
.row-sets{
  flex:0 1 250px;min-width:0;font-size:11.5px;color:var(--label-3);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.row-sets em{font-style:normal;color:var(--orange)}
/* 一个组都没开的题：行左一道 3px 橙条 —— 跟学生页的孤儿学生、开放页的
   孤儿题同一个记号，「这条橙色的意思是有东西到不了人」只学一次。
   决策 22 之前这道条要同时表示「不在任何题集」和「题集没开给组」两件事，
   现在只表示一件。 */
.row[data-orphan]{box-shadow:inset 3px 0 0 var(--orange)}

@media (max-width:760px){
  .page-h{flex-wrap:wrap}
  /* 「开给了哪些组」换到第二行。.row 本来不换行，只给 .row-sets 一个
     flex-basis:100% 不会让它掉下去 —— 它会把同一行里的 .row-main 挤成
     每行一个字。所以要同时开 wrap，而 wrap 只开给题目那份列表：
     .row 是全站共用的（队列、答案卷），别的列表在窄屏是好的，
     不能为了这一格把它们一起改成会换行的。 */
  .q-rows .row{flex-wrap:wrap;gap:2px 10px}
  .q-rows .row-sets{flex:1 0 100%;order:9;white-space:normal}
  .tbar .srch{flex:1;width:auto}
  .grid-3{grid-template-columns:1fr}
  .q-filter{flex:1;min-width:130px}
}

/* ==========================================================
   开放页（一题一卡 · 组是 chip，决策 17 + 22）
   ========================================================== */
/* 这一页的卡没有展开态：一张卡上要显示的东西一屏就够，藏起来只会多一次点击。
   chip 用的是学生页那个 .gchip，不另立一套：老师点的是
   grants 和 student_groups，同一种两列表的两头，手感必须一样。 */
.bs{display:flex;flex-direction:column;gap:10px}
.bs-card{
  background:var(--bg-card);border:1px solid var(--separator);
  border-radius:var(--r-card);padding:12px 14px 11px;
  display:flex;flex-direction:column;gap:10px;
}
/* 没开给任何组的题：左边一道 3px 橙条。决策 22 之后这是整条分发链上
   **唯一**的异常状态（原来是三个），值得一个不用读字就看见的记号 ——
   跟孤儿学生、题目列表里那道橙条是同一个记号，只学一次。 */
.bs-card[data-orphan]{box-shadow:inset 3px 0 0 var(--orange)}
/* 骨架也得是一排卡，不是列表灰条 —— 灰条换成卡片时整页会重排一次。
   高度按真卡算（12+14×1.4+10+27+11）。 */
.bs-sk{height:94px;animation:sk 1.4s ease-in-out infinite}
.bs-sk:nth-child(2){animation-delay:.12s}
.bs-sk:nth-child(n+3){animation-delay:.24s}
@media (prefers-reduced-motion:reduce){.bs-sk{animation:none;background:var(--fill)}}
/* 还是草稿的题：卡顶一句橙字，不抢橙条。橙条留给「谁都做不了」——
   两个异常抢同一道左边条的话，橙条就不再是一句确定的话了。 */
.bs-h{display:flex;align-items:baseline;gap:9px;flex-wrap:wrap}
.bs-h b{font-size:14px;font-weight:600;letter-spacing:-.015em}
.bs-h i{font-style:normal;font-size:11.5px;color:var(--label-3)}
.bs-h em{font-style:normal;color:var(--orange)}
/* 「N 个组 · M 人能看到」常驻。按题这个形状答不了「这个组能做几道题」
   （得把每张卡上找一遍同一个 chip），这一格是把那半个问题补上的地方 ——
   决策 17 明写它是选这个形状必须一起付的账，不能省。 */
.bs-st{margin-left:auto;font-size:11.5px;color:var(--label-3);white-space:nowrap}
.bs-st b{font-weight:600;color:var(--label-2)}
.bs-chips{display:flex;flex-wrap:wrap;gap:6px;align-items:center}
/* 「全开」只做开不做关（决策 17），所以它长得不像一个开关：
   放在 chip 排的最后、字号更小、没有点亮态。 */
.bs-all{
  margin-left:2px;font-size:11.5px;color:var(--blue);
  padding:0 4px;height:27px;border-radius:99px;
}
.bs-all:hover{background:var(--fill)}
.bs-all[data-busy]{opacity:.5;pointer-events:none}
/* 一个组都没有的时候整页只说一句话 —— 这一页什么都做不了，
   给一条去学生页建组的路，别让老师对着一排空卡点。 */
.bs-none{font-size:12px;color:var(--orange)}

@media (max-width:760px){
  /* 「N 个组 · M 人能看到」换到第二行：卡顶三段里它最长，
     挤在窄屏上会把题目名压断，而题目名是这张卡的主语。 */
  .bs-st{margin-left:0;flex:1 0 100%}
}
