/* ===========================================================
   流体力学实验教学网站 · 组件与布局（移动优先）
   =========================================================== */

*,*::before,*::after{ box-sizing:border-box }

/* ===== iPhone 适配（学生多数用苹果手机，2026-08-22 专项） =====
   1) -webkit-text-size-adjust:100% —— 不写的话 iOS 横屏会自作主张放大正文，
      表格列宽全乱。
   2) -webkit-tap-highlight-color —— 去掉 iOS 点按时那层灰色闪块，
      在实验台边连点几十次会很晃眼。
   3) overscroll-behavior-y:none —— 关掉整页橡皮筋回弹：学生在数据卡片里
      上下划时，页面弹一下会让刚点中的输入框失焦。
   4) -webkit-appearance:none —— iOS Safari 会给 input/button 套一层原生圆角与
      内阴影，不清掉的话按钮和输入框在 iPhone 上和设计稿完全不是一个样。 */
html{
  -webkit-text-size-adjust:100%;
  -webkit-tap-highlight-color:rgba(0,0,0,0);
  overscroll-behavior-y:none;
  /* 滚到某段时别被粘顶的顶栏压住（第 144 轮 UI 专家团）：scrollIntoView 按规范不扣 sticky 头，
     问卷闸门把学生滚到反馈表时，标题正好藏在 56px 顶栏下面。教师端是两层粘顶，由 teacher.html 的 placeBar 另写。
     ⚠ 只影响 scrollIntoView / 锚点 / 焦点滚动，不影响 window.scrollTo —— 手算偏移的那几处不会被扣两次。 */
  scroll-padding-top:calc(56px + env(safe-area-inset-top) + 8px);
}
body{
  margin:0;
  font-family:var(--font);
  font-size:var(--f-md);
  line-height:var(--lh-body);
  color:var(--text);
  background:var(--bg);
  -webkit-font-smoothing:antialiased;
  overflow-wrap:break-word;
  /* 数字一律等宽（方向 D）：⑥ 段表格、⑦ 段读数、报告完成度里同一列的数
     位数不再左右跳动。只影响阿拉伯数字，中文与英文照旧按比例排。 */
  font-variant-numeric:tabular-nums;
}
img{ max-width:100%; height:auto; display:block }
/* [hidden] 必须压过任何 display 声明（2026-09-05 第 115 轮实测）：#testTools 是 .btn-row{display:flex}，
   UA 的 [hidden]{display:none} 与类选择器同优先级、又排在前面，于是 hidden=true 照样显示 ——
   「从头再测一遍」这颗测试身份专用按钮对每个真实学生都露着（点了不动，只是吓人）。
   同族：D62 的横竖屏按钮当时单独写过一条 [hidden] 规则，这里收敛成全站一条。 */
[hidden]{ display:none !important }

/* iOS 原生控件样式清除。注意 checkbox / radio / range 三者都要排除在外：
   · checkbox / radio —— 清掉 appearance 会让勾选框整个消失；
   · range —— 清掉之后原生滑块整个没了，而本站**一条 ::-webkit-slider-thumb 都没写**，
     等于抹掉了控件又不给替代品：轨道不可见、拇指极小、accent-color 也一并失效
     （accent-color 只对保留原生外观的控件生效）。教师 2026-08-23 实测
     「雷诺流量 Q 滑动不丝滑会卡住」就是它。原生滑块是 iOS 上最跟手的那一个，还回去。 */
input:not([type=checkbox]):not([type=radio]):not([type=range]),
select, textarea, button{
  -webkit-appearance:none; appearance:none; border-radius:0;
}
button, .btn, summary, label, a{ touch-action:manipulation }   /* 去掉 iOS 双击缩放的 300ms 等待 */
/* ⚠ 不要给所有 canvas 加 touch-action:none。图表画布没有任何手势监听，
   禁掉只会让手机上"手指落在图上就划不动页面"——三个实验页的结果分析段
   加数据中心各有一块 300px 高的死区（2026-08-22 检查团实测）。
   手写板由 sign.js 行内样式自己声明 touch-action:none；仿真画布见下方 canvas.sim。
   ⚠ pinch-zoom 必须显式写出来（2026-08-25 专家团实测）：touch-action 是**白名单**，
     只写 pan-y 等于连双指缩放一起排除掉。手机上学生想把 λ–Re 图放大看清某个点，
     两指落在图上毫无反应，得先把手指挪到图外的空白处 —— 而图几乎占满可视区。
     这与「输入框 ≥16px 防 iOS 聚焦缩放」相反：那条防**自动**放大，
     这条一度禁掉了**主动**放大，属 WCAG 1.4.4 回归。 */
canvas.chart{ touch-action:pan-y pinch-zoom }

/* 键盘弹出时把底栏收起：iOS 上 position:fixed 的底栏会浮在键盘上方，
   正好压住学生正在填的那一格。core.js 在 focusin/focusout 时切这个类。 */
body.kbd-open .bottombar, body.kbd-open .ask-fab{ display:none }
a{ color:var(--brand); text-decoration:none }
a:hover{ text-decoration:underline }

/* ---------- 顶栏 ---------- */
.topbar{
  position:sticky; top:0; z-index:50;
  display:flex; align-items:center; gap:var(--s-3);
  min-height:56px; padding:0 var(--s-4);
  background:var(--surface);
  border-bottom:1px solid var(--line);
  box-shadow:var(--sh-float);
  padding-top:env(safe-area-inset-top);
}
.topbar .back{
  display:flex; align-items:center; justify-content:center;
  width:36px; height:36px; margin-left:-8px;
  border-radius:var(--r-full); color:var(--text-2);
  font-size:20px; line-height:1; flex:none;
}
.topbar .back:hover{ background:var(--hover); text-decoration:none }
/* 标题两级（教师 2026-08-27）：一级＝平台名，二级＝页面名。
   **不并列** —— 二级另起一行、字号更小、颜色更弱，一眼能分出主次。
   顶栏高度不变：两行合起来仍在原来的行高预算内（20px + 13px ≈ 原 f-lg 单行 + 8px），
   移动端把二级再压一档，免得挤掉右边的工具按钮。 */
.topbar h1{
  flex:1; min-width:0;
  margin:0; font-weight:700;
  display:flex; flex-direction:column; justify-content:center; gap:1px;
  overflow:hidden;
}
.topbar h1 .app{
  font-size:var(--f-md); line-height:1.15; letter-spacing:.2px;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.topbar h1 .pg{
  font-size:var(--f-xs); font-weight:600; color:var(--muted); line-height:1.2;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
@media (min-width:900px){
  .topbar h1 .app{ font-size:var(--f-lg) }
  .topbar h1 .pg{ font-size:var(--f-sm) }
}
.topbar .tools{ display:flex; gap:var(--s-2); flex:none }
/* 顶栏的「教师端 ⇄ 学生端」是文字按钮，比图标按钮宽（2026-08-31，教师要回来的可见入口） */
.icon-btn.wide{ width:auto; padding:0 10px; font-size:var(--f-sm); white-space:nowrap }
/* ≤400px 字号不低于 12px（第 144 轮 UI 专家团：11px 是全站唯一低于 12px 的常驻文字）；宽度只多约 3px */
@media (max-width:400px){ .icon-btn.wide{ padding:0 6px; font-size:12px } }
.icon-btn{
  display:flex; align-items:center; justify-content:center;
  min-width:36px; height:36px; padding:0 var(--s-2);
  border:1px solid var(--line); border-radius:var(--r-1);
  background:var(--surface); color:var(--text-2);
  font-size:var(--f-sm); cursor:pointer;
}
.icon-btn:hover{ background:var(--hover) }
/* 顶栏的「🏛 学院门户」是 <a> 不是 <button>：<a> 自带下划线，要去掉 */
a.icon-btn{ text-decoration:none }
/* 触屏上把顶栏小按钮的**可点区域**扩到约 44px、视觉尺寸不变（第 144 轮 UI 专家团）：
   36px + 间距 8px，向外各扩 4px 两个相邻按钮的热区正好不重叠；整体把按钮放大到 44px 会把标题挤没。 */
@media (pointer:coarse){
  .icon-btn, .topbar .back{ position:relative }
  .icon-btn::after, .topbar .back::after{ content:''; position:absolute; top:-4px; right:-4px; bottom:-4px; left:-4px }   /* 不用 inset：老 iOS（14.5 以前）不认 */
}
a.icon-btn:hover{ text-decoration:none }

/* ---------- 容器 ---------- */
.wrap{
  max-width:var(--wrap-max); margin:0 auto;   /* 断点表在 tokens.css，别再写死字面量 */
  padding:var(--s-4) var(--s-4) calc(var(--bar-h) + var(--s-6) + env(safe-area-inset-bottom));
}
.wrap.wide{ max-width:var(--wrap-max-wide) }
/* 组间距（卡片之间 24px）必须 ≥ 组内 h3 的上留白（24px），
   否则同一张卡里的两小节看起来比两张卡之间还远。刻意不用 32px —— 
   页面已经 8600px，那是拿滑动次数换呼吸感。 */
.stack{ display:flex; flex-direction:column; gap:var(--s-5) }

/* ---------- 卡片 ---------- */
.card{
  background:var(--surface);
  border:1px solid var(--line-strong);
  border-radius:var(--r-3);
  box-shadow:var(--edge-hi);
  overflow:hidden;
}
.card > .hd{
  display:flex; align-items:center; gap:var(--s-3);
  padding:var(--s-4);
  border-bottom:1px solid var(--line-soft);
}
.card > .hd h2{ margin:0; font-size:var(--f-lg); font-weight:700; flex:1; line-height:var(--lh-head) }
.card > .bd{ padding:var(--s-4) }
.card > .bd > *:first-child{ margin-top:0 }
.card > .bd > *:last-child{ margin-bottom:0 }

/* ---------- 折叠段（8 段式模板的每一段） ---------- */
.sec{
  background:var(--surface);
  border:1px solid var(--line-strong);
  border-radius:var(--r-3);
  box-shadow:var(--edge-hi);
  overflow:hidden;
}
.sec > summary{
  /* 段标题从 15px 长到 22px（投影档 30px），容器要跟着长 —— 
     否则 34px 的序号圈与标题会把 summary 撑得上下不匀。 */
  min-height:56px;
  display:flex; align-items:center; gap:var(--s-3);
  min-height:var(--tap); padding:var(--s-3) var(--s-4);
  cursor:pointer; list-style:none; user-select:none;
}
.sec > summary::-webkit-details-marker{ display:none }
.sec > summary:hover{ background:var(--hover) }
/* 序号圈随段标题一起长大：22px 的标题配 26px 的圈会显得头重脚轻。
   ⚠ 别把新值插在规则开头 —— 同一条规则里后写的会盖掉先写的（本轮实测踩到）。 */
.sec .num{
  flex:none; display:flex; align-items:center; justify-content:center;
  width:34px; height:34px; border-radius:var(--r-full);
  background:linear-gradient(145deg, var(--brand-soft), var(--surface)); border:1px solid var(--brand-line); color:var(--brand);
  font-size:var(--f-sm); font-weight:700;
  font-variant-numeric:tabular-nums;
}
/* 段标题：改前 15px/650，和正文一模一样 —— 八个折叠段无法区分。
   现在 22px（投影档 30px），字距在这个字号上才真的看得出收紧效果。 */
.sec .ttl{ flex:1; min-width:0; font-size:var(--f-xl); font-weight:700;
           line-height:var(--lh-head); letter-spacing:-.015em }
.sec .ttl small{ display:block; font-weight:400; font-size:var(--f-sm); color:var(--text-2);
                 line-height:var(--lh-note); margin-top:2px; letter-spacing:0 }
/* 段头里的进度数字（「已问 3 / 9」这类）单独提出来 */
.sec .ttl small b{ color:var(--brand); font-weight:700; font-variant-numeric:tabular-nums }
.sec .chev{ flex:none; color:var(--muted); transition:transform .18s ease }
.sec[open] .chev{ transform:rotate(90deg) }
.sec[open] > summary{ border-bottom:1px solid var(--line-soft) }
.sec .body{ padding:0 var(--s-4) var(--s-4) }
.sec .body > *:first-child{ margin-top:0 }

/* ---------- 段内小折叠面板（仪器不确定度等） ---------- */
details.mini{
  border:1px solid var(--line);
  border-radius:var(--r-2);
  margin:var(--s-3) 0;
  background:var(--surface);
  overflow:hidden;
}
details.mini > summary{
  padding:10px var(--s-4);
  cursor:pointer; list-style:none; user-select:none;
  font-size:var(--f-sm); color:var(--muted);
  display:flex; align-items:center; gap:var(--s-2);
}
details.mini > summary::-webkit-details-marker{ display:none }
details.mini > summary::before{
  content:'＋'; font-size:12px; color:var(--brand);
  transition:transform .15s ease;
}
details.mini[open] > summary::before{ content:'－' }
details.mini > :not(summary){ margin-left:var(--s-4); margin-right:var(--s-4) }
details.mini[open] > :last-child{ margin-bottom:var(--s-4) }

/* ---------- 提交前自评量规 ---------- */
.eval-row{
  border:1px solid var(--line);
  border-left:3px solid var(--line);
  border-radius:var(--r-2);
  padding:10px var(--s-3);
  margin-bottom:var(--s-2);
  transition:border-color .18s ease, background .18s ease;
}
/* ⚠ 这三行的底色原来是写死的 rgba(40,194,129,.06) 一类 —— 来自旧表的深色调色板，
   浅色深色都不跟随主题。换成语义令牌。 */
.eval-row.ok{ border-left-color:var(--ok); background:var(--ok-bg) }
.eval-row.mid{ border-left-color:var(--warn); background:var(--warn-bg) }
.eval-row.no{ border-left-color:var(--bad); background:var(--bad-bg) }
.eval-row .edim{ font-size:var(--f-sm); font-weight:700; margin-bottom:6px }
.eval-row .eseg{ display:flex; gap:var(--s-2); flex-wrap:wrap }
.eval-row .eseg label{
  font-size:var(--f-xs); color:var(--muted);
  border:1px solid var(--line); border-radius:var(--r-full);
  padding:4px 10px; cursor:pointer;
  display:inline-flex; align-items:center; gap:5px;
  transition:all .15s ease;
}
.eval-row .eseg label:has(input:checked){ color:var(--brand); border-color:var(--brand); background:var(--brand-soft) }
/* 圆钮从 92×48 缩回 20×20 之后，可点区要由 label 自己扛住 —— 硬约束：手机上 ≥48px。
   桌面（真鼠标）才允许压到 38px，触屏平板不算桌面。 */
.eval-row .eseg label{ min-height:48px; padding:6px 12px }   /* 字号保持 --f-xs：改字号是设计决定，不混进 bug 修复 */
@media screen and (min-width:1200px) and (pointer:fine){
  .eval-row .eseg label{ min-height:38px }
}
.eval-tip{
  margin-top:var(--s-2); font-size:var(--f-sm); color:var(--muted);
  min-height:1.4em;
}

/* ---------- 课后反思 ---------- */
.refl-box textarea{
  width:100%; min-height:76px; resize:vertical;
  border:1px solid var(--line); border-radius:var(--r-2);
  background:var(--surface); color:var(--text);   /* 输入面用面板色：台面色会被读作禁用 */
  padding:10px var(--s-3); font-size:16px; line-height:1.6;
  font-family:inherit;
}
.refl-box textarea:focus{ outline:none; border-color:var(--brand); box-shadow:0 0 0 3px var(--brand-soft) }

/* ---------- 文本 ---------- */
h3{ margin:var(--s-5) 0 var(--s-3); font-size:var(--f-lg); font-weight:700; line-height:var(--lh-head) }
p{ margin:0 0 var(--s-3) }
.muted{ color:var(--muted); font-size:var(--f-sm) }

/* 教学目标的认知层次标签（布鲁姆）。教师 2026-08-25 给了行为动词表，
   要求目标动词一律可观察 —— 标签让"这三条覆盖了哪几层"一眼可查，
   也是一流课程评审要看的东西。学生侧刻意做得很轻，不抢正文。 */
.lv{
  display:inline-block; margin:0 4px 0 2px;
  font-size:var(--f-xs); font-weight:600; color:var(--brand);
  background:var(--brand-soft); border:1px solid var(--brand-line);
  border-radius:var(--r-full); padding:1px 8px; white-space:nowrap;
  vertical-align:1px;
}
@media print{ .lv{ background:none; border:0; padding:0; color:#333 } }
.lead{ color:var(--text-2) }
ul,ol{ margin:0 0 var(--s-3); padding-left:1.35em }
li{ margin-bottom:var(--s-2) }
code,kbd{ font-family:var(--mono); font-size:.92em }

/* 提示条 */
/* ⚠ 颜色是信息，不是装饰（教师 2026-08-25 批准）。
   改前每个 .note 都是浅蓝块，而站里绝大多数 note 是**教科书旁白**（恒显、与学生数据无关）——
   满屏浅蓝之后，真正在说"你这组数据有问题"的那几条反而淹掉了。
   现在：默认中性灰；绿/橙/红只留给对学生自己数据的判定；
   确实需要品牌蓝的地方显式加 .brand（本轮不改任何 HTML，留作以后按需加）。 */
.note{
  padding:var(--s-3) var(--s-4);
  border:1px solid var(--line);
  border-left:4px solid var(--line);
  border-radius:0 var(--r-1) var(--r-1) 0;
  background:var(--surface-2);
  font-size:var(--f-sm); line-height:var(--lh-note); color:var(--text-2);
  margin:0 0 var(--s-3);
}
.note.brand{ background:var(--brand-soft); border-color:var(--brand-line); border-left-color:var(--brand) }
.note.ok   { background:var(--lam-bg);    border-color:var(--lam);        border-left-color:var(--lam) }
.note.warn{ background:var(--trn-bg); border-color:var(--trn); border-left-color:var(--trn) }
.note.danger{ background:var(--tur-bg); border-color:var(--tur); border-left-color:var(--tur) }
.note strong{ color:var(--text) }

/* 公式卡 */
.formula{
  padding:var(--s-4);
  border:1px solid var(--brand-line);
  border-radius:var(--r-2);
  background:var(--brand-soft);
  text-align:center;
  font-family:var(--mono); font-size:var(--f-lg);
  overflow-x:auto; overscroll-behavior-x:contain; white-space:nowrap;
  margin:0 0 var(--s-3);
}
.formula .cap{
  display:block; margin-top:var(--s-2);
  font-family:var(--font); font-size:var(--f-xs);
  color:var(--muted); white-space:normal;
}
/* 分式：分子在上、分母在下（教师 2026-10-12：公式里的除法上下排，不用斜杠）。
   写法 <span class="frac"><span>分子</span><span>分母</span></span>；分数线用 currentColor，深浅两种主题都跟着字色走，不另配色。
   max-width 与 white-space:normal 让长分子在手机窄屏上自己折行，不撑出横向滚动。 */
.frac{ display:inline-block; vertical-align:middle; text-align:center; line-height:1.3;
       margin:0 .15em; max-width:100%; white-space:normal; font-size:.95em }
.frac > span{ display:block; padding:0 .2em }
.frac > span:first-child{ border-bottom:1px solid currentColor }
.frac .frac{ font-size:.9em }

/* 变量对照表 */
.vars{ display:flex; flex-direction:column; gap:var(--s-2) }
.vars div{
  display:flex; gap:var(--s-3); align-items:baseline;
  padding:var(--s-2) 0; border-bottom:1px solid var(--line-soft);
  font-size:var(--f-sm);
}
.vars div:last-child{ border-bottom:0 }
.vars b{ flex:none; min-width:4.5em; font-family:var(--mono); color:var(--brand) }

/* ---------- 表单 ---------- */
.field{ margin-bottom:var(--s-4) }
.field > label{
  display:block; margin-bottom:var(--s-2);
  font-size:var(--f-sm); font-weight:600; color:var(--text-2);
}
.field .hint{ margin-top:var(--s-1); font-size:var(--f-xs); color:var(--muted) }
input,select,textarea{
  width:100%; min-height:var(--tap);
  padding:var(--s-3); border:1px solid var(--line-field);
  border-radius:var(--r-2); background:var(--surface);
  color:var(--text); font-family:var(--font); font-size:16px; /* 16px 防 iOS 聚焦缩放 */
  outline:none; transition:border-color .15s, box-shadow .15s;
}
/* ===========================================================
   勾选控件的**唯一定义**（2026-08-25）
   ⚠ 上面那条 `input,select,textarea{ width:100%; min-height:var(--tap) }`
     没有排除 radio / checkbox —— 于是任何**没被单独打过补丁**的圆钮都会被撑成
     一个 92×48 的空白方块。实测：自评量规 `.eval-row .eseg` 的单选钮正是 92×48，
     整块 #selfEval 高达 1177px（约 1.7 屏），学生要多滚一屏半才够到提交按钮。
     站里为此散着 5 份"把它缩回去"的补丁（.checklist / .quiz .opts / .ea-chk /
     .pr-got-row / .poe .opts），唯独漏了自评量规这一处 —— 典型的"同类问题散落多处"。
   → 收敛成这一条，5 份补丁里的宽高/flex/accent 一律删掉。
     以后**不许**在任何组件里再写 radio/checkbox 的宽高与 accent-color。
   ⚠ 圆钮缩小后，包着它的 label 必须自己补足 48px 可点区（见 .eval-row .eseg label）。
   =========================================================== */
input[type=checkbox], input[type=radio]{
  width:20px; height:20px; min-height:0; flex:none; padding:0;
  accent-color:var(--brand);
}

input:focus,select:focus,textarea:focus{
  border-color:var(--brand);
  box-shadow:0 0 0 3px color-mix(in srgb, var(--brand) 18%, transparent);
}
input[readonly]{ background:var(--surface-2); color:var(--brand); font-weight:600 }
input[type=number]{ font-variant-numeric:tabular-nums }
input[type=number]::-webkit-outer-spin-button,
input[type=number]::-webkit-inner-spin-button{ -webkit-appearance:none; margin:0 }
input[type=number]{ -moz-appearance:textfield }
/* ⚠ touch-action 不写 = 横向拖动没人认领。实测本站滑块的 touch-action 是 auto，
   于是手指横着一划，浏览器就把这个手势自己拿去当页面手势 / iOS 的边缘返回手势；
   而 showView 已经接进 history，返回手势直接把学生弹到别的视图 ——
   教师 2026-08-23 实测「滑动卡住后就返回到其他界面了」，以及更早那条复现不出来的
   「左右滑动页面直接报错」，都是这一条。
   pan-y 的含义：纵向留给页面滚动，**横向一律归滑块**。
   （不能写 none —— 那样手指落在滑块上就划不动页面了，和 canvas 那条坑同一个道理。） */
input[type=range]{
  min-height:44px;              /* 44 是 Apple HIG 的最小可点尺寸，36 太细抓不住 */
  padding:0; border:0; background:transparent;
  accent-color:var(--brand);
  touch-action:pan-y;
}
/* 手机上让滑块独占一行：原先它和「流量 Q」「读数」挤在一行，实测只剩 253px 可拖，
   Q=2~250 每像素跳一个单位，手指稍抖就跳好几档。 */
@media (max-width:520px){
  .sim-bar{ row-gap:6px }
  .sim-bar > span{ order:1 }
  .sim-bar input[type=range]{ order:2; flex:1 0 100%; min-width:0 }
}

/* 仿真画布与读出条 */
/* pan-y pinch-zoom：纵向留给页面滚动，双指缩放要留着（见上面 canvas.chart 的说明）。
   仿真画布本身没有手势监听，流量靠下面那个滑块调，不必独占横向手势。 */
canvas.sim{
  display:block; width:100%; height:170px;
  border:1px solid var(--line); border-radius:var(--r-2);
  background:var(--surface-2); touch-action:pan-y pinch-zoom;
}
.sim-bar{
  display:flex; align-items:center; gap:var(--s-3); flex-wrap:wrap;
  margin-top:var(--s-2); font-size:var(--f-sm); color:var(--text-2);
  font-variant-numeric:tabular-nums;
}
.sim-bar .grow{ flex:1 }

/* POE 预测块 */
.poe{ border:1px dashed var(--brand-line); border-radius:var(--r-2); padding:var(--s-4); background:var(--brand-soft) }
.poe .q{ font-weight:700; margin-bottom:var(--s-3) }
.poe.locked .opts{ opacity:.75; pointer-events:none }
.row2{ display:grid; grid-template-columns:1fr 1fr; gap:var(--s-3) }

/* ---------- 按钮 ---------- */
.btn{
  display:inline-flex; align-items:center; justify-content:center; gap:var(--s-2);
  min-height:var(--tap); padding:0 var(--s-4);
  border:1px solid var(--line); border-radius:var(--r-2);
  background:var(--surface); color:var(--text);
  font-family:var(--font); font-size:var(--f-md); font-weight:600;
  cursor:pointer; user-select:none;
  transition:background .15s, border-color .15s, transform .06s;
}
.btn:active{ transform:scale(.985) }
.btn:hover{ background:var(--hover); text-decoration:none }
/* ⚠ 字色必须走 --on-state，不能写死 #fff。
   浅色下品牌是深蓝 #0a66d0，白字 5.49:1 没问题；
   **深色下品牌是浅蓝 #57a8ff，白字只有 2.49:1** —— 全站最严重的一处不合格，
   而且是既有问题（不是本轮引入）。--on-state 在深色下是 #0a0f1e，对浅蓝 7.68:1。 */
.btn.primary{ background:linear-gradient(180deg, var(--brand), var(--brand-strong)); border-color:var(--brand-strong); color:var(--on-state); box-shadow:var(--btn-sh) }
.btn.primary:hover{ background:var(--brand-strong); border-color:var(--brand-strong) }
.btn.block{ width:100% }
.btn.sm{ min-height:36px; padding:0 var(--s-3); font-size:var(--f-sm) }
.btn.danger{ color:var(--tur-ink); border-color:var(--tur) }
.btn:disabled{ opacity:.45; cursor:not-allowed }
.btn-row{ display:flex; gap:var(--s-3); flex-wrap:wrap }
.btn-row .btn{ flex:1 1 auto }

/* 分段控件 */
.seg{
  display:flex; padding:3px; gap:3px;
  border:1px solid var(--line); border-radius:var(--r-2);
  background:var(--surface-2);
}
.seg button{
  flex:1; min-height:40px; padding:0 var(--s-2);
  border:0; border-radius:var(--r-1); background:transparent;
  color:var(--text-2); font-family:var(--font); font-size:var(--f-sm); font-weight:600;
  cursor:pointer;
}
.seg button[aria-pressed=true]{ background:var(--surface); color:var(--brand); box-shadow:var(--edge-hi) }

/* ---------- 徽章 ---------- */
.badge{
  display:inline-flex; align-items:center; gap:4px;
  padding:2px 10px; border-radius:var(--r-full);
  font-size:var(--f-xs); font-weight:700; white-space:nowrap;
}
.badge.lam{ background:var(--lam-bg); color:var(--lam-ink) }
.badge.trn{ background:var(--trn-bg); color:var(--trn-ink) }
.badge.tur{ background:var(--tur-bg); color:var(--tur-ink) }
.badge.neutral{ background:var(--surface-2); color:var(--muted) }
.badge.brand{ background:var(--brand-soft); color:var(--brand) }

/* ---------- 数据卡（替代宽表格，手机端核心组件） ---------- */
.dlist{ display:flex; flex-direction:column; gap:var(--s-3) }
.dcard{
  border:1px solid var(--line); border-radius:var(--r-2);
  background:var(--surface-2); overflow:hidden;
}
.dcard > .dhd{
  display:flex; align-items:center; gap:var(--s-2);
  padding:var(--s-2) var(--s-3);
  background:var(--surface); border-bottom:1px solid var(--line-soft);
}
.dcard > .dhd .no{
  display:flex; align-items:center; justify-content:center;
  width:24px; height:24px; flex:none;
  border-radius:var(--r-full); background:var(--brand-soft);
  color:var(--brand); font-size:var(--f-xs); font-weight:700;
}
.dcard > .dhd .grow{ flex:1 }
.dcard > .dbd{ padding:var(--s-3); display:flex; flex-direction:column; gap:var(--s-3) }
.dcard .inputs{ display:grid; grid-template-columns:1fr 1fr; gap:var(--s-3) }
.dcard .inputs .field{ margin:0 }
/* 计算结果区 */
.dout{
  display:grid; grid-template-columns:repeat(2,1fr); gap:var(--s-2);
  padding:var(--s-3); border-radius:var(--r-1);
  background:var(--surface); border:1px dashed var(--line);
}
.dout div{ display:flex; flex-direction:column; gap:2px }
.dout .k{ font-size:var(--f-xs); color:var(--muted) }
.dout .v{ font-size:var(--f-md); font-weight:700; font-variant-numeric:tabular-nums }
.dout .v.big{ font-size:var(--f-num); color:var(--brand); line-height:var(--lh-tight); letter-spacing:-.02em }

/* ---------- 操作步骤 checklist ---------- */
.checklist{ display:flex; flex-direction:column; gap:var(--s-2); margin:0; padding:0; list-style:none }
.checklist li{ margin:0 }
.checklist label{
  display:flex; align-items:flex-start; gap:var(--s-3);
  min-height:var(--tap); padding:var(--s-3);
  border:1px solid var(--line); border-radius:var(--r-2);
  background:var(--surface-2); cursor:pointer;
  font-size:var(--f-sm); line-height:1.6;
}
.checklist input[type=checkbox]{ margin-top:2px }   /* 宽高与 accent 走唯一定义 */
.checklist input:checked + span{ color:var(--muted); text-decoration:line-through }
.progress{
  height:6px; border-radius:var(--r-full);
  background:var(--line-soft); overflow:hidden; margin-bottom:var(--s-3);
}
.progress > i{ display:block; height:100%; width:0; background:var(--brand); transition:width .25s }

/* ---------- 图表 ---------- */
.chart-box{ position:relative }
canvas.chart{
  display:block; width:100%; height:300px;
  border:1px solid var(--line); border-radius:var(--r-2);
  background:var(--surface);
}

/* ---------- 思考题 ---------- */
.quiz{ border:1px solid var(--line); border-radius:var(--r-2); padding:var(--s-4); background:var(--surface-2) }
.quiz + .quiz{ margin-top:var(--s-3) }
.quiz .q{ font-weight:600; margin-bottom:var(--s-3) }
.quiz .opts{ display:flex; flex-direction:column; gap:var(--s-2) }
.quiz .opts label{
  display:flex; align-items:center; gap:var(--s-3);
  min-height:44px; padding:var(--s-2) var(--s-3);
  border:1px solid var(--line); border-radius:var(--r-1);
  background:var(--surface); cursor:pointer; font-size:var(--f-sm);
}

/* 键盘焦点必须看得见：全局 outline:none 的替代品（18% 品牌色阴影）对白底约 1.3:1，
   达不到 WCAG 2.4.11 要求的 3:1。用实心描边补回来，只对键盘焦点生效不影响鼠标点击观感。 */
input:focus-visible, select:focus-visible, textarea:focus-visible,
button:focus-visible, summary:focus-visible, a:focus-visible{
  outline:2px solid var(--brand); outline-offset:2px;
}
/* 2026-09-04 教师：「选错了就选错了，不要提示学生选哪一个，把阴影部分去掉」——
   选项**不再按对错着色**（原先选中项绿/红、选错时正确项也标绿），只留一个中性的「已选」样式，
   让学生知道点中了；对错仍在 core.js 的 change 回调里算并落盘，grade.js / 教师端照旧判分。
   原生圆点本来就显示已选，这条样式纯装饰（用 JS 类而不用 :has —— 老 WebView 不认）。 */
.quiz .opts label.picked{ border-color:var(--brand); background:var(--brand-soft) }
/* 解析段永远不显示：它把正确答案讲出来了，就是提示。规则留着当兜底（谁再加 show 也无效）。 */
.quiz .fb{ margin-top:var(--s-3); font-size:var(--f-sm); color:var(--text-2); display:none }

/* ============ 课后即时反馈表（survey.js，2026-08-28 · D36）============
   学生在实验台边用手机填：可点区 ≥44px，输入框 ≥16px 防 iOS 聚焦缩放。
   ⚠ 选项样式沿用 .quiz .opts 的观感，但**不复用它的类名** —— 那套带对错着色
     （.ok/.no），问卷没有对错，套上去会让学生以为在被判分。 */
.sv-q{ border:1px solid var(--line); border-radius:var(--r-2); padding:var(--s-4);
  background:var(--surface-2); margin-top:var(--s-3); font-size:var(--f-sm) }
.sv-q > b{ display:inline-block; min-width:1.4em; color:var(--brand); font-size:var(--f-md) }
.sv-sub{ margin:var(--s-3) 0 var(--s-2); color:var(--text-2) }
.sv-opts{ display:flex; flex-direction:column; gap:var(--s-2); margin-top:var(--s-2) }
.sv-opts label{
  display:flex; align-items:center; gap:var(--s-3);
  min-height:44px; padding:var(--s-2) var(--s-3);
  border:1px solid var(--line); border-radius:var(--r-1);
  background:var(--surface); cursor:pointer;
}
.sv-opts input{ flex:none; width:20px; height:20px; accent-color:var(--brand) }
.sv-row{ display:flex; flex-wrap:wrap; gap:var(--s-3); margin:var(--s-3) 0 }
.sv-row label{ display:flex; flex-direction:column; gap:4px; font-size:var(--f-sm); color:var(--text-2) }
.sv-row input{ min-height:44px; width:9em; font-size:16px; padding:0 var(--s-3);
  border:1px solid var(--line); border-radius:var(--r-1); background:var(--surface); color:var(--text) }
.sv-q input[type=text], .sv-q input[type=number]{
  min-height:44px; font-size:16px; padding:0 var(--s-2); margin:0 4px;
  border:1px solid var(--line); border-radius:var(--r-1); background:var(--surface); color:var(--text);
  width:5.5em; vertical-align:middle }
.sv-q input.wide{ width:100%; margin:var(--s-2) 0 }
.sv-q textarea{ width:100%; margin-top:var(--s-2); font-size:16px; padding:var(--s-2);
  border:1px solid var(--line); border-radius:var(--r-1); background:var(--surface); color:var(--text) }
.sv-state{ margin-top:var(--s-3); padding:var(--s-3); border-radius:var(--r-1);
  background:var(--warn-bg, var(--surface-2)); color:var(--text-2); font-size:var(--f-sm) }
.sv-state.ok{ background:var(--lam-bg); color:var(--text) }

/* ---------- 底部固定操作条 ---------- */
.bottombar{
  position:fixed; left:0; right:0; bottom:0; z-index:60;
  display:flex; gap:var(--s-3); align-items:center;
  padding:var(--s-3) var(--s-4);
  padding-bottom:calc(var(--s-3) + env(safe-area-inset-bottom));
  /* 兜底不透明背景：老 iOS/安卓 WebView 不支持 color-mix() 时整条声明会作废，
     没有兜底会让正文滚到底部时透出来叠在"保存/提交"按钮上。 */
  background:var(--surface);
  background:color-mix(in srgb, var(--surface) 92%, transparent);
  backdrop-filter:blur(10px);
  border-top:1px solid var(--line);
}
.bottombar .status{ flex:1; min-width:0; font-size:var(--f-xs); color:var(--muted); line-height:1.35 }
.bottombar .btn{ flex:none }
/* 已经交给老师：按钮不再是实心主按钮，避免学生当成还要再点一次。仍可点，改了数据会变回「正式提交」。 */
.bottombar .btn[data-handed="1"],
.bottombar .btn[data-handed="1"]:hover{
  background:var(--surface); color:var(--brand); border-color:var(--brand); box-shadow:none;
}

/* ---------- 首页 ---------- */
/* 第 119 轮（教师 2026-09-06「首页信息主次」）：标题区改成**白色**，与浅灰页面背景靠一条线分开。
   为什么撤掉整块品牌渐变：蓝色是操作色 —— 整片蓝底会和「确认学号」「开始实验」那两个蓝按钮抢，
   学生第一眼看到的应该是「去哪儿点」，不是一张漂亮的招牌。层流/过渡/湍流三色含义不受影响。
   ⚠ 浅色白底、深色 --surface，两套都由令牌给；不再需要 --on-state（那是压在品牌色上才用的）。 */
.hero{
  position:relative;
  padding:var(--s-5) var(--s-4) var(--s-4);
  background:var(--surface);
  border-bottom:1px solid var(--line);
  color:var(--text);
}
/* 大标题兼作教师口令卡的连点手势热区：不许被长按选中（连点第 3 下手机会拉出选择柄）、
   不吃双击缩放（manipulation 去掉 300ms 双击等待，5 连点才数得准）。
   ⚠ 故意**不设 cursor:pointer** —— 手势是给教师记的，不向全班学生做视觉招摇。 */
.hero h1{ margin:0 0 var(--s-2); font-size:var(--f-xl); font-weight:700; line-height:1.3; letter-spacing:-.01em;
  -webkit-user-select:none; user-select:none; touch-action:manipulation }
.hero p{ margin:0; color:var(--text-2); font-size:var(--f-sm); line-height:1.6 }
/* 署名与课时改成整齐的文字行（教师 2026-09-06）：原先是四个胶囊标签，
   在白底上会被误认成可点的按钮。现在是纯文字，用竖线分隔、自动换行，安静地待在标题下面。 */
/* 署名：横排 + 自动换行，条与条之间靠较大的横向间距区分，**不加竖线**——
   换行时 ::before 的竖线会跑到行首，每行都以「｜」开头反而更乱（第 119 轮实测）。
   这样竖屏窄屏自然一行一条、横屏与桌面自动并排，不必为 844px 这类中间宽度另开断点。 */
.hero .meta{ display:flex; flex-wrap:wrap; gap:0 var(--s-5); margin-top:var(--s-3);
  font-size:var(--f-sm); color:var(--muted); line-height:1.55 }
@media (min-width:900px){
  .hero{ padding:var(--s-6) var(--s-4) var(--s-5) }
  .hero h1{ font-size:30px }
  .hero p{ font-size:var(--f-md) }
}
/* 手机一列（≥900 的两列在下面 @media 里，全站只此两处 —— 曾经三处打架，最后那处的 auto-fit 赢，桌面变成三列） */
.explist{ display:grid; grid-template-columns:1fr; gap:var(--s-3) }
/* 统一结构：图标 ＋ 名称 ＋ 一句说明 ＋ 状态（教师 2026-09-06）。
   ⚠ 用 grid 不用 flex-wrap：flex 换行的位置由文字长度决定 —— 实测「装置总览」的箭头留在右边、
     「实验报告」的箭头掉到了左下角。三列固定成 图标 / 文字 / 箭头，徽章占第二行的文字列，
     长实验名、长状态文字都不会把箭头挤走。 */
.expcard{
  display:grid; grid-template-columns:auto 1fr auto; align-items:center;
  column-gap:var(--s-3); row-gap:var(--s-1);
  padding:var(--s-4);
  background:var(--surface); border:1px solid var(--line);
  border-radius:var(--r-3); box-shadow:var(--edge-hi);
  color:inherit;
  transition:border-color .15s, transform .12s, background .15s;
}
/* 悬停/按下用位移与描边表达，不加阴影（量台纪律） */
.expcard:hover{ box-shadow:var(--edge-hi); border-color:var(--brand-line); background:var(--hover); text-decoration:none; transform:translateY(-1px) }
.expcard:active{ transform:translateY(0) }
.expcard .ic{
  flex:none; display:flex; align-items:center; justify-content:center;
  width:50px; height:50px; border-radius:14px;
  background:linear-gradient(145deg, var(--brand-soft), var(--surface));
  border:1px solid var(--brand-line); font-size:24px;
}
.expcard .ic{ grid-column:1; grid-row:1 / span 2; align-self:start }
.expcard .tx{ grid-column:2; grid-row:1; min-width:0 }
.expcard .tx b{ display:block; font-size:var(--f-md); line-height:1.35 }
/* 说明用 --f-sm（14px）而不是 12px：重要状态不用过小的字（教师 2026-09-06 第 4 条） */
.expcard .tx small{ display:block; color:var(--muted); font-size:var(--f-sm); line-height:1.5; margin-top:2px }
/* 状态徽章：第二行、文字列 —— 名称下方，不与箭头同行（教师 2026-09-06） */
.expcard > .badge{ grid-column:2; grid-row:2; justify-self:start; margin-top:2px }
.expcard .go{ grid-column:3; grid-row:1 / span 2; color:var(--muted); font-size:18px; align-self:center }
/* 没有徽章的卡（装置总览 / 实验报告）：文字与图标各占满两行，箭头照旧居中 */
.expcard:not(:has(> .badge)) .ic,
.expcard:not(:has(> .badge)) .tx{ grid-row:1 / span 2 }
.expcard.soon{ opacity:.55; pointer-events:none }


/* 教师首页概览（第 121 轮）：打开教师端第一眼看到今天的活儿 */
.tdash{ display:flex; flex-wrap:wrap; align-items:center; gap:var(--s-2) var(--s-5);
  padding:var(--s-3) var(--s-4); margin-bottom:var(--s-3);
  background:var(--brand-soft); border:1px solid var(--brand-line); border-radius:var(--r-2) }
.tdash-i{ display:flex; flex-direction:column; gap:1px }
.tdash-i small{ font-size:var(--f-xs); color:var(--text-2) }
.tdash-i b{ font-size:var(--f-lg); font-variant-numeric:tabular-nums }
.tdash-i b.warn{ color:var(--trn-ink) }
.tdash-i.bad b{ color:var(--tur-ink) }
.tdash-go{ margin-left:auto }

/* ================= 实验报告管理（第 121 轮，教师 2026-09-06） ================= */
/* 顶部筛选：一行放得下就并排，窄屏自动换行 */
.rpt-filter{ display:flex; flex-wrap:wrap; gap:var(--s-2); align-items:center; margin-bottom:var(--s-3) }
.rpt-filter select,.rpt-filter input{ min-height:38px; font-size:var(--f-md) }
/* 按班级看待批阅（第 168 轮）：班级按钮一排，「待批 N」用等宽数字 */
.rpt-clsbar{ display:flex; flex-wrap:wrap; gap:var(--s-2); align-items:center; margin:0 0 var(--s-3) }
.rpt-clsbar small{ margin-left:4px; font-variant-numeric:tabular-nums }

/* 数量概览：应提交 / 已提交 / 未提交 / 待批阅 / 待复核 / 已完成批阅 */
.rpt-sum{ display:flex; flex-wrap:wrap; gap:var(--s-2) var(--s-4);
  padding:var(--s-3); background:var(--surface-2); border:1px solid var(--line); border-radius:var(--r-2);
  font-size:var(--f-sm); color:var(--text-2) }
.rpt-sum .rpt-n b{ font-size:var(--f-lg); color:var(--text); margin-left:4px; font-variant-numeric:tabular-nums }

/* 名单 + 预览：手机一列，桌面两栏 */
.rpt-wrap{ display:grid; grid-template-columns:1fr; gap:var(--s-3) }
@media (min-width:1200px){ .rpt-wrap{ grid-template-columns:minmax(320px,380px) 1fr; align-items:start } }

.rpt-list{ border:1px solid var(--line); border-radius:var(--r-2); background:var(--surface);
  max-height:60vh; overflow:auto; overscroll-behavior:contain }
.rpt-row{ display:grid; grid-template-columns:auto 1fr auto; gap:2px var(--s-2);
  align-items:center; padding:var(--s-3); border-bottom:1px solid var(--line-soft); cursor:pointer }
.rpt-row:last-child{ border-bottom:0 }
.rpt-row:hover{ background:var(--hover) }
.rpt-row.on{ background:var(--brand-soft); box-shadow:inset 3px 0 0 var(--brand) }
.rpt-ck{ grid-row:1 / span 3; display:flex; align-items:center }
.rpt-ck input{ width:20px; height:20px }
.rpt-nm b{ display:block; font-size:var(--f-md); line-height:1.3 }
.rpt-nm small{ color:var(--muted); font-size:var(--f-sm) }
/* 提交状态与批阅状态各占一格 —— 两件事，不合并（教师 2026-09-06） */
.rpt-st{ grid-column:2; font-size:var(--f-sm) }
.rpt-rv{ grid-column:3; grid-row:1; justify-self:end }
.rpt-tot{ grid-column:3; grid-row:2; justify-self:end; font-variant-numeric:tabular-nums }
.rpt-at{ grid-column:3; grid-row:3; justify-self:end; color:var(--muted) }
.rpt-row:focus-visible{ outline:2px solid var(--brand); outline-offset:-2px }

.rpt-view{ border:1px solid var(--line); border-radius:var(--r-2); background:var(--surface); padding:var(--s-3) }
.rpt-view-hd{ display:flex; align-items:center; gap:var(--s-2); margin-bottom:var(--s-3) }
.rpt-view-hd .grow{ flex:1; min-width:0; font-size:var(--f-md) }
.rpt-view-grade{ margin-bottom:var(--s-3) }
.rpt-gs{ display:flex; flex-wrap:wrap; gap:var(--s-1) var(--s-3); font-size:var(--f-sm) }
.rpt-gs .rpt-g{ color:var(--text-2) }
.rpt-gs .rpt-g.auto{ color:var(--muted) }
.rpt-gs b{ font-variant-numeric:tabular-nums }
/* 报告预览：把 A4 那份缩进这个框里看，滚动条归它自己 */
.rpt-view-body{ max-height:56vh; overflow:auto; overscroll-behavior:contain;
  background:var(--surface-2); border:1px solid var(--line); border-radius:var(--r-2); padding:var(--s-3) }
/* 手机单列（< 1200，与 .rpt-wrap 同一断点，T2）（第 144 轮 UI 专家团）：
   ① 时间挪到第 3 行、跨两列 —— 原先它把第三列撑到约 113px，姓名那列被挤窄，一行 156~180px，60vh 的名单只放得下 3 人；
   ② 名单框与预览框不再挡滚动链 —— 手指落在框里滚到头时交还给页面，不再困住整页。桌面两栏照旧 contain。
   ⚠ 这一块必须写在 .rpt-list / .rpt-view-body 两条定义**之后**：媒体查询不加优先级，写在前面会被后写的 contain 盖掉（交付前审查坐实）。 */
@media (max-width:1199px){
  .rpt-at{ grid-column:2 / span 2; grid-row:3; justify-self:start }
  .rpt-list, .rpt-view-body{ overscroll-behavior:auto }
}
.rpt-paper{ background:#fff; color:#111; padding:8mm 6mm; border-radius:2px }
.rpt-paper table{ width:100%; border-collapse:collapse; font-size:8.5pt }
.rpt-paper td,.rpt-paper th{ border:1px solid #999; padding:2px 3px }
.rpt-paper img{ max-width:100% }

/* 正式归档区（第 121 轮）：跟在批量下载后面，视觉上明确是另一件事 */
.rpt-arch{ margin:var(--s-3) 0; padding:var(--s-3); border:1px solid var(--line);
  border-radius:var(--r-2); background:var(--surface-2) }
.rpt-arch .rpt-sum{ background:var(--surface) }
.rpt-arch .rpt-list{ max-height:34vh; margin-top:var(--s-2) }

.rpt-prog{ padding:var(--s-3); background:var(--surface-2); border:1px solid var(--line);
  border-radius:var(--r-2); font-size:var(--f-sm); line-height:1.7 }
.rpt-prog b{ font-variant-numeric:tabular-nums }
.rpt-fail{ margin-top:var(--s-2); color:var(--tur-ink) }

/* 评分保存状态条（第 120 轮）：颜色配文字，不只靠颜色区分（教师 2026-09-06「状态提示」那条） */
.gsync{ font-size:var(--f-sm); line-height:1.6; margin:-4px 0 10px }
.gsync-saving{ color:var(--text-2) }
.gsync-ok{ color:var(--lam-ink) }
.gsync-fail{ color:var(--tur-ink); font-weight:600 }

/* ---------- AI 答疑（DeepSeek，仅限流体力学话题） ---------- */
.ask-fab{
  position:fixed; right:16px;
  bottom:calc(var(--bar-h) + 20px + env(safe-area-inset-bottom));
  z-index:80;
  display:flex; align-items:center; gap:6px;
  min-height:48px; padding:0 18px;
  border:0; border-radius:var(--r-full);
  background:var(--brand); color:var(--on-state);
  font-family:var(--font); font-size:var(--f-sm); font-weight:700;
  box-shadow:var(--sh-modal); cursor:pointer;
}
.ask-fab:active{ transform:scale(.97) }
.ask-panel{
  position:fixed; left:0; right:0; bottom:0; z-index:120;
  display:flex; flex-direction:column;
  max-height:78vh;
  background:var(--surface);
  border:1px solid var(--line); border-bottom:0;
  border-radius:var(--r-3) var(--r-3) 0 0;
  box-shadow:var(--sh-modal);
  transform:translateY(105%); transition:transform .25s ease;
  padding-bottom:env(safe-area-inset-bottom);
}
.ask-panel.open{ transform:none }
.ask-hd{
  display:flex; align-items:center; gap:var(--s-2);
  padding:var(--s-3) var(--s-4);
  border-bottom:1px solid var(--line-soft);
}
.ask-hd b{ flex:1; font-size:var(--f-md) }
.ask-msgs{
  flex:1; overflow-y:auto; overscroll-behavior:contain; -webkit-overflow-scrolling:touch;
  display:flex; flex-direction:column; gap:var(--s-3);
  padding:var(--s-4); min-height:140px;
}
.ask-msgs .m{
  max-width:88%; padding:10px 13px; border-radius:var(--r-2);
  font-size:var(--f-sm); line-height:1.75;
  white-space:pre-wrap; overflow-wrap:break-word;
}
.ask-msgs .m.user{ align-self:flex-end; background:var(--brand); color:var(--on-state); border-bottom-right-radius:4px }
.ask-msgs .m.ai{ align-self:flex-start; background:var(--surface-2); border:1px solid var(--line-soft); border-bottom-left-radius:4px }
.ask-msgs .m.ai.warn{ border-color:var(--trn); background:var(--trn-bg) }
.ask-msgs .m.ai.err{ border-color:var(--tur); background:var(--tur-bg) }
.ask-chips{
  display:flex; gap:var(--s-2); overflow-x:auto; overscroll-behavior-x:contain;
  -webkit-overflow-scrolling:touch;
  padding:var(--s-2) var(--s-4) 0;
}
.ask-chips button{
  flex:none; min-height:34px; padding:0 12px;
  border:1px solid var(--brand-line); border-radius:var(--r-full);
  background:var(--brand-soft); color:var(--brand);
  font-family:var(--font); font-size:var(--f-xs); cursor:pointer;
}
.ask-think{ padding:var(--s-3) var(--s-4) 0 }
.ask-think label{ display:block; font-size:var(--f-xs); color:var(--text-2); font-weight:600; margin-bottom:var(--s-1) }
.ask-think-ta{ width:100%; box-sizing:border-box; min-height:52px; resize:vertical;
  border:1px solid var(--line); border-radius:var(--r-1); background:var(--surface);
  padding:var(--s-2) var(--s-3); font-size:16px; line-height:1.5;
  font-family:var(--font); color:var(--text) }
.ask-think-ta:focus{ outline:none; border-color:var(--brand); box-shadow:0 0 0 3px var(--brand-soft) }
.ask-in{ display:flex; gap:var(--s-2); padding:var(--s-3) var(--s-4); align-items:flex-end }
.ask-in textarea{ flex:1; min-height:48px; max-height:110px; resize:none }
.ask-in .btn{ flex:none }
.ask-note{ padding:0 var(--s-4) var(--s-3); font-size:var(--f-xs); color:var(--muted) }
.ask-keyrow{ display:none; padding:0 var(--s-4) var(--s-3) }
.ask-keyrow.show{ display:block }
@media (min-width:900px){
  .ask-panel{ left:auto; right:24px; width:440px }
}

/* ---------- Toast ---------- */
#toast{
  position:fixed; left:50%; bottom:calc(var(--bar-h) + 16px); transform:translateX(-50%);
  z-index:200; display:flex; flex-direction:column; gap:var(--s-2);
  pointer-events:none; width:max-content; max-width:88vw;
}
#toast div{
  padding:10px 16px; border-radius:var(--r-full);
  background:rgba(22,32,46,.94); color:#fff;
  font-size:var(--f-sm); box-shadow:var(--sh-modal);
  animation:tin .2s ease;
}
@keyframes tin{ from{ opacity:0; transform:translateY(8px) } }

/* ===========================================================
   桌面端增强（≥900px）：不改变移动端结构，只放宽栅格
   =========================================================== */
@media (min-width:900px){
  /* 这里原有一行 `:root{ --f-md:15px }` —— 把 15px 设成 15px，T1 档等于没有档，已删。 */
  .wrap{ padding:var(--s-5) var(--s-5) calc(var(--bar-h) + var(--s-6)) }
  .dcard .inputs{ grid-template-columns:repeat(4,1fr) }
  .dout{ grid-template-columns:repeat(4,1fr) }
  canvas.chart{ height:380px }
  .explist{ display:grid; grid-template-columns:repeat(2,1fr) }
  .row2{ grid-template-columns:repeat(3,1fr) }
}

/* ===========================================================
   误差溯源 + 回到前测（第 10 轮 · 判断链可视化）
   =========================================================== */
.ea-list{ display:flex; flex-direction:column; gap:var(--s-2); margin-top:var(--s-2) }
.ea-row{
  border:1px solid var(--line); border-radius:var(--r-2);
  background:var(--surface-2); padding:var(--s-3);
}
.ea-row.on{ border-color:var(--brand); background:var(--brand-soft) }
.ea-chk{
  display:flex; align-items:flex-start; gap:var(--s-3);
  min-height:var(--tap); padding:4px 0; cursor:pointer; font-weight:600;
}
.ea-chk input{ margin-top:0 }   /* 宽高与 accent 走唯一定义 */
.ea-exp{
  display:none; margin-top:var(--s-2); font-size:var(--f-sm); color:var(--text-2);
  border-left:3px solid var(--brand-line); padding-left:var(--s-3); line-height:1.6;
}
.ea-exp.show{ display:block }
textarea.ea-why, textarea.pr-explain{
  width:100%; box-sizing:border-box; border:1px solid var(--line);
  border-radius:var(--r-2); background:var(--surface);
  padding:10px var(--s-3); font-size:16px; line-height:1.6;
  font-family:var(--font); color:var(--text); resize:vertical;
}
textarea.ea-why{ margin-top:var(--s-3) }
textarea.ea-why:focus, textarea.pr-explain:focus{
  outline:none; border-color:var(--brand); box-shadow:0 0 0 3px var(--brand-soft);
}

.pr-list{ display:flex; flex-direction:column; gap:var(--s-3); margin-top:var(--s-2) }
.pr-item{
  border:1px solid var(--line); border-radius:var(--r-2);
  background:var(--surface-2); padding:var(--s-3);
}
.pr-q{ font-weight:600; margin-bottom:var(--s-2) }
.pr-pick{ font-size:var(--f-sm); color:var(--text-2); margin-bottom:var(--s-2); line-height:1.6 }
/* 「当时答对/答错」徽章已撤（2026-09-04）：前测在 ① 段仍可改选、计 5 分，印出结果就是排除法提示 */
.pr-got-row{
  display:flex; align-items:center; gap:var(--s-2); margin-top:var(--s-2);
  min-height:var(--tap); cursor:pointer; font-size:var(--f-sm); font-weight:600;
}
.pr-sum{ margin-top:var(--s-2); font-size:var(--f-sm); color:var(--muted); text-align:right }

/* 工程案例·在线视频（本地视频段已于 2026-08-22 按教师要求删除） */
.case-online{ margin:var(--s-3) 0; padding:var(--s-3); border:1px solid var(--line); border-radius:var(--r-2) }
.case-online .case-online-hd{ font-size:var(--f-sm); font-weight:600; color:var(--text-2); margin-bottom:var(--s-2) }
.case-online .case-online-hd span{ font-weight:400; color:var(--muted); font-size:var(--f-xs); margin-left:var(--s-1) }
.case-online .case-online-act{ display:flex; align-items:center; gap:var(--s-2); flex-wrap:wrap }
.case-online .case-online-act .muted{ font-size:var(--f-xs) }
.case-online .bili-slot{ margin-top:var(--s-3) }
/* 关闭条要显眼且始终在播放器正上方 —— 学生滚下去看视频时，
   上面那个「▲ 收起」按钮早已在屏幕外，只靠它就等于关不掉（教师 2026-08-23 实测）。 */
.case-online .bili-bar{ display:flex; justify-content:flex-end; margin-bottom:var(--s-2) }
.case-online .bili-close{ border-color:var(--tur); color:var(--tur-ink); background:var(--tur-bg) }
.case-online .bili-slot iframe{ width:100%; max-width:640px; aspect-ratio:16/9; border:0; border-radius:var(--r-2); background:#000; display:block }

/* ===========================================================
   打印：出实验报告
   =========================================================== */
@media print{
  /* 只在屏幕上才有意义的东西，一律不上纸。
     教师 2026-08-23 发来的导出 PDF 里，导航条、折叠箭头「›」、
     所有按钮、前测与思考题的单选钮全都印了出来 —— 纸上点不动，纯占地方。
     ⚠ 合订版的导航是 _build_single.js 自己生成的 <nav>，
        core.js 的 initTopbar 才给它加 .topbar；两种写法都要挡住。 */
  .topbar,.bottombar,#toast,.no-print,.ask-fab,.ask-panel,
  nav,header,.sec .chev,.btn,.btn-row,
  .quiz .opts input,.poe .opts input,.rubric input,
  .oldtbl-wrap iframe{ display:none !important }
  /* 选项文字保留（学生要能看到题目和选项），只去掉点不动的圆钮与外框 */
  .quiz .opts label,.poe .opts label{ border:0 !important; background:none !important; padding:1pt 0 !important }
  body{ background:#fff; color:#000; font-size:10.5pt; line-height:1.45 }
  .wrap{ max-width:none; padding:0 }
  .sec,.card{ border:1px solid #999; box-shadow:none; break-inside:avoid; page-break-inside:avoid }
  .sec .body{ display:block !important }
  .sec > summary{ background:#eee; page-break-after:avoid }
  .dcard{ break-inside:avoid; page-break-inside:avoid }
  input,select{ border:0; padding:0; min-height:0; font-size:10.5pt }
  a{ color:#000; text-decoration:none }
  /* ⑥ 段是 iframe 里的课堂计算表：A4 宽度装不下它十几列，
     打印时会整个塌成一列一列的孤零零文字（教师那份 PDF 第 9 页就是）。
     整页打印时改印一行提示，真要数据表请走「下载 PDF 报告」—— 那里是排过版的宽表。 */
  .oldtbl-wrap::after{
    content:"本段为课堂计算表（交互表格，不适合直接打印）。要带数据的表格请点第 ⑧ 段的「下载 PDF 报告」。";
    display:block; padding:8pt; border:1px dashed #999; font-size:9.5pt; color:#333;
  }
  /* 首页空白：@page 上留白 + 首个 section 顶部 margin 会挤出一张空页 */
  @page{ size:A4 portrait; margin:12mm }
  .wrap > :first-child{ margin-top:0 !important }
}

/* ===========================================================
   部件说明卡吸顶（rig.html ② 段）
   教师 2026-08-23：翻部件列表时希望说明一直看得见。
   30 多个部件，说明卡不吸顶的话，点一个要滚回去看一眼、再滚下来点下一个。
   ⚠ position:sticky 的三个前提，缺一个就完全不吸：
     ① 必须给 top 值；
     ② 任何一层祖先带 overflow:hidden/auto 都会让它失效 —— .sec .body 用的是
        display 切换、没有 overflow，安全；
     ③ 祖先高度不能塌，所以列表容器不能是 flex 的 stretch 子项。
   打印时不要吸顶（纸面上没有"滚动"，吸顶只会让它压住正文）。 */
/* 部件说明就地展开（教师 2026-08-23：「解释放在点击部件的下面」） */
.partbody{
  border:1px solid var(--brand-line); border-top:0;
  border-radius:0 0 var(--r-2) var(--r-2);
  background:var(--brand-soft);
  padding:var(--s-3) var(--s-4);
  font-size:var(--f-sm); line-height:1.75;
  margin:-6px 0 var(--s-3);      /* 贴住上面那条按钮 */
}
.partbody .src{ font-size:var(--f-xs); color:var(--muted) }
.partlist.hot{ border-bottom-left-radius:0; border-bottom-right-radius:0 }

/* 下面这块吸顶样式已不再使用（改成就地展开），保留定义以防旧缓存页面引用 */
.partcard-sticky{
  /* --sticky-top 由 rig.html 按真实固定头部高度实测后写入；
     取不到就退回 8px（合订版的 nav 是 static，本来就不用让） */
  position:sticky; top:var(--sticky-top, 8px); z-index:5;
  box-shadow:var(--sh-modal);
  max-height:44vh; overflow:auto; overscroll-behavior:contain;
  -webkit-overflow-scrolling:touch;
}
/* 顶栏是 fixed 的，吸顶位置要让开它，否则会被压住半截 */
/* ⚠ sticky 的杀手：.sec{overflow:hidden}（为裁 summary 灰底的圆角而设）。
   祖先只要有一层 overflow 非 visible，sticky 就完全不生效 ——
   写第一版时只查了 .sec .body，漏了 .sec 本身，实测卡片照样滚出屏幕。
   只给标了 .sec-sticky 的那一段开口，圆角补回 summary 自己身上。 */
.sec.sec-sticky{ overflow:visible }
.sec.sec-sticky > summary{ border-radius:var(--r-3) var(--r-3) 0 0 }
.sec.sec-sticky:not([open]) > summary{ border-radius:var(--r-3) }
@media print{ /* 部件说明就地展开（教师 2026-08-23：「解释放在点击部件的下面」） */
.partbody{
  border:1px solid var(--brand-line); border-top:0;
  border-radius:0 0 var(--r-2) var(--r-2);
  background:var(--brand-soft);
  padding:var(--s-3) var(--s-4);
  font-size:var(--f-sm); line-height:1.75;
  margin:-6px 0 var(--s-3);      /* 贴住上面那条按钮 */
}
.partbody .src{ font-size:var(--f-xs); color:var(--muted) }
.partlist.hot{ border-bottom-left-radius:0; border-bottom-right-radius:0 }

/* 下面这块吸顶样式已不再使用（改成就地展开），保留定义以防旧缓存页面引用 */
.partcard-sticky{ position:static; max-height:none; box-shadow:none } }
/* 选中部件后给一条左边线，和"还没选"的提示态区分开 */
.partcard-sticky.picked{ border-left:4px solid var(--brand) }

/* ===========================================================
   全屏手写层（教师 2026-08-23：「手写的签名换成整页签名」）
   手指在 96px 高的小格里写汉字，笔迹又挤又抖。点开这层占满屏幕横着写，
   写完回填缩略图。手机上是主要书写方式。
   =========================================================== */
.signfull{
  position:fixed; inset:0; z-index:9999;
  display:none; flex-direction:column;
  background:var(--surface);
}
.signfull.on{ display:block }   /* 列布局搬到了 .sf-rot 上（见下面「竖屏自动横排」） */
.signfull .sf-hd{
  flex:none; display:flex; align-items:center; gap:var(--s-3);
  padding:calc(env(safe-area-inset-top, 0px) + 10px) var(--s-4) 10px;
  border-bottom:1px solid var(--line); background:var(--surface-2);
}
.signfull .sf-hd b{ font-size:var(--f-md) }
.signfull .sf-hd .grow{ flex:1 }
.signfull .sf-tip{ font-size:var(--f-xs); color:var(--muted); padding:6px var(--s-4) 0 }
/* ⚠ min-height:0 是必须的：flex 子项默认 min-height:auto，内容（画布）比容器高时
   它不会收缩，而是把下面的 .sf-ft（放「完成」「清除」的页脚）挤出屏幕。
   横屏书写时正是这个情况 —— 学生写完找不到「完成」，笔迹只能丢。
   overflow:auto 再兜一层，万一高度算错也还能滚到按钮。 */
.signfull .sf-body{ flex:1; min-height:0; overflow:auto; -webkit-overflow-scrolling:touch;
  overscroll-behavior:contain; position:relative; padding:var(--s-3) var(--s-4) }
.signfull .sf-body canvas.sign-cv{
  width:100%; display:block;
  background:#fff;
  border:2px dashed var(--brand-line); border-radius:var(--r-2);
  touch-action:none;                 /* 不加这条，手指一动就变成页面滚动 */
}
.signfull .sf-ft{
  flex:none; display:flex; gap:var(--s-3);
  padding:10px var(--s-4) calc(env(safe-area-inset-bottom, 0px) + 12px);
  border-top:1px solid var(--line); background:var(--surface-2);
}
.signfull .sf-ft .btn{ flex:1; min-height:48px }
/* ---- 竖屏自动横排（教师 2026-09-04：「既可以竖屏填写，也可以横屏填写」「输入位置长一点，少数民族同学姓名字符较长」，D62）----
   页眉 / 提示 / 画布 / 页脚全部包进 .sf-rot（handwrite.js buildLayer 建的）。手机竖屏下 handwrite.js 给 .signfull
   加 .rot，.sf-rot 顺时针转 90°，书写条改用手机长边：390×844 上画布 358×672（0.53:1）→ 约 731×216（3.4:1）。
   学生把手机**逆时针**横过来（听筒朝左、Home 条朝右，平时横过来看视频的方向）看到的就是正的；
   系统旋转锁、微信/QQ 内置浏览器的竖屏锁都不影响。
   ⚠ .rot 只由 handwrite.js 按它的媒体查询加减，这里**不写媒体查询** —— 判据只有 JS 一处真源；
     横屏、桌面模态（下面 ≥900 那段）永远不带 .rot。
   ⚠ 不用 display:contents（Safari 的无障碍树会丢掉它）：.sf-rot 绝对定位铺满层，列布局原样搬过来。 */
.signfull .sf-rot{
  position:absolute; inset:0;
  display:flex; flex-direction:column;
  overflow:hidden;
  background:var(--surface);
}
.signfull.rot .sf-rot{
  inset:auto; top:50%; left:50%;
  width:100vh; height:100vw;             /* 不认 dvh 的老内核兜底 */
  width:100dvh; height:100dvw;           /* 随地址栏收放的真实视口；handwrite.js 还会写行内 px 盖掉它 */
  transform-origin:50% 50%;
  transform:translate(-50%,-50%) rotate(90deg);
  /* 系统仍认为是竖屏：刘海（safe-area-inset-top）在物理顶端 = 转后内容的**左端**（书写起点），
     Home 条（inset-bottom）= 右端。安全区挂在包裹层的左右，而不是页眉的上、页脚的下。 */
  padding-left:env(safe-area-inset-top, 0px);
  padding-right:env(safe-area-inset-bottom, 0px);
}
.signfull.rot .sf-hd{ padding-top:10px }        /* 页眉现在贴屏幕右边缘，竖屏下 inset-right 为 0 */
.signfull.rot .sf-ft{ padding-bottom:12px }     /* 页脚贴屏幕左边缘，同理 */
/* 转了 90° 的滚动容器手指方向与滚动轴对不上；fill 模式的画布本来就正好装满（cssH = clientHeight − 24），不需要滚 */
.signfull.rot .sf-body{ overflow:hidden }
/* 切换按钮「横着写 / 竖着写」：.btn 是 inline-flex，会盖掉 UA 的 [hidden]{display:none} —— 不写这条，b.hidden=true 照样显示 */
.signfull .sf-hd [data-sf="rot"][hidden]{ display:none }
/* 刚打开、手机还没转过来时唯一不跟着转的一句话；handwrite.js 2.5s 后摘掉。
   它是 .signfull 的直接子元素、在 .sf-rot 之后，压在转过的内容上，pointer-events:none 不挡笔。 */
.signfull .sf-turn{
  position:absolute; left:50%; top:50%; transform:translate(-50%,-50%);
  padding:10px 16px; border-radius:999px; background:rgba(12,18,28,.72); color:#fff;
  font-size:var(--f-sm); white-space:nowrap; pointer-events:none; z-index:1;
}
.signfull .sf-turn[hidden]{ display:none }
/* 打开时锁住背景滚动 */
body.signfull-open{ overflow:hidden }

/* 无障碍：尊重减少动效偏好 */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{ animation-duration:.01ms !important; transition-duration:.01ms !important }
}

/* ===========================================================
   雷诺仿真：读数卡 + 物理锚点预设（resim.js 配套）
   =========================================================== */
.simwrap{ position:relative }
.simwrap canvas.sim{ height:210px }
@media (min-width:900px){ .simwrap canvas.sim{ height:260px } }
/* 伯努利能量转化动画（第 109 轮，教师「视觉效果不是非常明显」）：画布加高，
   读数角标复用上面的 .sim-readout，三段式说明一行，动能徽章走 --ke 令牌 */
.simwrap-be canvas.sim{ height:230px }
@media (min-width:900px){ .simwrap-be canvas.sim{ height:300px } }
.sim-cap{ min-height:1.6em; margin-top:6px; font-variant-numeric:tabular-nums }
.badge.ke{ background:var(--ke-bg); color:var(--ke-ink) }

/* 教师 2026-08-27：「雷诺动画角标缩小」—— 原先 132px 宽 / 26px 数字，
   在手机上压掉了小半个仿真画面。缩到 96px / 18px，读数仍然看得清。 */
.sim-readout{
  position:absolute; top:10px; right:10px;
  min-width:96px; text-align:center;
  padding:4px 8px;
  background:color-mix(in srgb, var(--surface) 90%, transparent);
  background:var(--surface);
  border:1px solid var(--line); border-radius:var(--r-2);
  box-shadow:var(--sh-float);
  pointer-events:none;
}
/* ⚠ 这里原有一段 @supports 给 .sim-readout 加 backdrop-filter:blur(6px)，已删。
   它浮在 canvas.sim 上，而 resim.js 每帧重绘那张画布 ——
   合成器要每帧重采样 + 做高斯模糊，是全站唯一一处"持续"耗帧的效果，
   而且正好落在低端安卓已经在跑粒子仿真的那一页。
   读数条本来就有不透明底，去掉之后可读性反而更好。 */
.sim-readout .lbl{ font-size:9.5px; letter-spacing:.5px; color:var(--muted); line-height:1.3 }
.sim-readout .val{
  font-size:18px; font-weight:800; line-height:1.15; color:var(--brand);
  font-variant-numeric:tabular-nums;
}
.sim-readout .badge{ margin-top:2px }

.sim-presets{
  display:flex; gap:var(--s-2); flex-wrap:wrap; align-items:center;
  margin-top:var(--s-2);
}
.sim-presets button{
  min-height:36px; padding:0 12px;
  border:1px solid var(--brand-line); border-radius:var(--r-full);
  background:var(--brand-soft); color:var(--brand);
  font-family:var(--font); font-size:var(--f-xs); font-weight:700;
  cursor:pointer; white-space:nowrap;
}
.sim-presets button:hover{ border-color:var(--brand) }
.sim-presets button[aria-pressed="true"]{ background:var(--brand); color:var(--on-state); border-color:var(--brand) }
.sim-presets button[data-act]{
  background:var(--surface); color:var(--text-2); border-color:var(--line);
}
.sim-presets .grow{ flex:1 }
@media print{ .sim-presets, .sim-bar input[type=range]{ display:none } }

/* ===== 旧计算表原件嵌入框 =====
   教师 2026-08-22："三个实验完全移植不要改里面的内容"。
   三张旧表各带整套全局 CSS（沿程是浅色主题），只能用 iframe 隔离，否则会污染整页。 */
/* 嵌入框要"贴边"：外面再套边框底色，手机上就成了框中框、四周一圈白边。
   旧表自己已经有完整的页面外观（背景、圆角、卡片），这里一律不再加壳；
   并用负边距抵消 .sec .body 的左右内边距，让表格在手机上真正占满屏宽。 */
.oldtbl-wrap{
  border:0; border-radius:0; background:transparent; box-shadow:none;
  margin-left:calc(-1 * var(--s-4)); margin-right:calc(-1 * var(--s-4));
  overflow:hidden;
}
.oldtbl{
  display:block; width:100%; height:900px; border:0;   /* 仅首屏占位，oldtbl.js 会按内容撑开 */
  background:transparent;   /* 让 iframe 内页自己的底色透出来，不再压一层白 */
}
@media (max-width:640px){
  /* 手机上真正满屏：容器有 .wrap 和 .sec .body 两层内边距，逐层抵消容易漏。
     用视口满铺，与嵌套层数无关；移动端滚动条是覆盖式的，不会造成横向溢出。 */
  .oldtbl-wrap{ width:100vw; margin-left:50%; transform:translateX(-50%) }
}
@media print{ .oldtbl-wrap{ margin-left:0; margin-right:0 } }

/* ===== 雷诺各组流态图（随报告打印） ===== */
.regime-strip{ display:grid; gap:10px }
.regime-item{ margin:0; border:1px solid var(--line); border-radius:var(--r-2);
  overflow:hidden; background:var(--surface) }
.regime-item figcaption{ padding:6px 10px; font-size:var(--f-xs); color:var(--text-2);
  background:var(--surface-2); border-bottom:1px solid var(--line);
  display:flex; align-items:center; gap:8px }
.regime-item img{ display:block; width:100%; height:auto }

/* 合订版顶栏的版本号徽标（由 _build_single.js 注入，教师用来确认手里是哪一版） */
.ver-tag{ flex:none; margin-left:auto; padding:2px 8px; border-radius:var(--r-full);
  background:var(--surface-2); color:var(--text-2); font-size:var(--f-xs);
  font-family:var(--mono); letter-spacing:.02em; border:1px solid var(--line) }
@media print{ .ver-tag{ display:none } }



/* 下载卡上的数据现状：数据门槛已撤，这行只是提示，不能长得像警告 */
.dlcard .dl-n{ display:inline-block; margin-top:4px; font-size:var(--f-xs); color:var(--text-2) }
.dlcard .dl-n.ok{ color:var(--lam-ink) }   /* 4.36 → 5.35 */

/* ===========================================================
   桌面自适应 · 主体（断点表见 tokens.css）
   =========================================================== */

/* ---- ⑥ 段旧计算表：桌面上整张表放得下，就不该让人左右拖 ----
   实测：三张表分别要 1117 / 1292 / 1348px 可视宽，而正文栏只给到 688~952px。

   ⚠ 放宽的对象是**整张 ⑥ 段卡片**，不是卡片里的表格。
     我第一版只放宽 .oldtbl-wrap，结果 .sec{overflow:hidden}（为裁 summary 圆角而设）
     把伸出去的两截整个裁掉 —— 教师截图里「测点」列只剩一个「点」字、右边「操作」列也没了。
     更糟的是裁掉之后表格内部反而不再溢出，**连横向滚动条都没有**，整列消失且滚不出来。
     现在放宽卡片本身、卡片的 overflow:hidden 原样保留：表格老实待在边框圆角里，
     不会两端浮在页面底色上被边框横穿。
   ⚠ CLAUDE.md 早记过 .sec{overflow:hidden} 是 position:sticky 的杀手 ——
     我知道那一条，却没想到同一个属性同样会杀掉**横向挣脱**。同一个祖先，另一种后果。 */
@media (min-width:900px){
  .sec.sec-oldtbl{
    width:min(calc(100vw - 32px), 1460px);
    margin-left:50%;
    transform:translateX(-50%);
  }
  /* 卡片已经变宽，表格老实待在卡片里：只保留抵消 .sec .body 内边距的负 margin */
  .sec.sec-oldtbl .oldtbl-wrap{
    width:auto;
    margin-left:calc(-1 * var(--s-4));
    margin-right:calc(-1 * var(--s-4));
    transform:none;
  }
}
@media print{
  .sec.sec-oldtbl{ width:auto; margin-left:0; transform:none }
}

/* ---- 答疑面板 ----
   ⚠ 桌面上面板展开会盖住底栏的「保存 / 提交」（专家团实测，属实）。
     我试过两种 CSS 改法（抬高 bottom + :not(.open) / 低特异度覆盖 transform），
     两次都把面板推到视口外、等于"桌面上答疑打不开"，比原问题更糟。
     追下去发现：手动给元素加 .open 类**根本不是 ask.js 真正的开关方式** ——
     基础的 .ask-panel{transform:translateY(105%)} 在带 .open 时依然生效，
     说明显隐由 ask.js 用别的手段控制，光在 CSS 里抢 transform 是错的路子。
   → 本轮**原样保留**，只留位置微调（右下角浮起、限宽）。
     正确的修法是改 ask.js：展开时把 bottom 设到底栏之上，或给底栏加 padding 让位。
     没查清 ask.js 的开关路径之前不动它 —— 这是既有问题，不是本轮的回归。 */
@media (min-width:900px){
  .ask-panel{ left:auto; right:24px; width:440px }
}
@media (min-width:1600px){
  .ask-panel{ right:32px; width:520px }
  .ask-fab{ right:32px }
}

/* ---- 顶栏 / 底栏的内容对齐到内容列 ----
   栏的背景仍整屏横贯（它本来就该是一条完整的面），但**内部内容**收进中央列。
   1440 实测：底栏宽 1425，左端身份、右端按钮，中间 1400px 空白，
   返回箭头贴在屏幕最左、与正文左边缘差 220px，视觉上完全不成一列。
   ⚠ 用 100% 而不是 100vw：100vw 含经典滚动条宽度，会让内容比 .wrap 偏左约 7px。
   ⚠ 这段必须排在 .topbar/.bottombar 的 padding 简写之后，否则被吃掉。 */
@media (min-width:1200px){
  .topbar, .bottombar{
    padding-left: max(var(--s-4), calc((100% - var(--wrap-max)) / 2));
    padding-right:max(var(--s-4), calc((100% - var(--wrap-max)) / 2));
  }
}

/* ---- 合订版导航条 ----
   ⚠ 必须写 header.appnav，不能写 .appnav：
     _build_single.js 把 navCss 注入在 app.css **之后**，同特异度它会赢，
     写成 .appnav 的话规则被静默吃掉，控制台一个字都不会报。 */
@media (min-width:1200px){
  header.appnav{
    gap:var(--s-4);
    padding-left: max(12px, calc((100% - var(--wrap-max)) / 2));
    padding-right:max(12px, calc((100% - var(--wrap-max)) / 2));
  }
  header.appnav nav{ gap:var(--s-2) }
}
@media (min-width:1600px){
  header.appnav .brand{ font-size:var(--f-lg) }
  header.appnav nav button{ min-height:44px; padding:0 18px; font-size:var(--f-sm) }
}

/* ---- 整页手写层：桌面上收成居中模态 ----
   手机占满屏横写是对的，不动。桌面 1440×900 实测：画布 1408×722、
   三个按钮各 461px 宽，提示语还写着「把手机横过来写」——
   学生要用鼠标在 1408px 宽的画布上写名字，笔宽还是按高度算的 43px 超粗笔。
   这是报告页在电脑上唯一一处"打开就没法用"的地方。
   ⚠ 带 screen 前缀，打印规则一概不碰。 */
@media screen and (min-width:900px) and (min-height:560px){
  body.signfull-open::before{
    content:""; position:fixed; inset:0; z-index:9998;
    background:rgba(12,18,28,.55);
  }
  .signfull{
    inset:auto; top:50%; left:50%; transform:translate(-50%,-50%);
    width:min(780px, 92vw); height:min(560px, 86vh);
    border:1px solid var(--line); border-radius:var(--r-3);
    box-shadow:var(--sh-modal);
  }
  .signfull .sf-ft{ justify-content:flex-end }
  .signfull .sf-ft .btn{ flex:0 0 auto; min-width:120px }
}

/* ---- 投影档（T3 ≥1600）：屏幕变大 = 字变大 ---- */
@media (min-width:1600px){
  :root{ --f-md:17px; --f-sm:15px; --f-lg:20px }
  canvas.chart{ height:440px }
}

/* ===========================================================
   桌面自适应 · 第二批（来自桌面专家团 27 条里的 P1/P2）
   断点表见 tokens.css：T1 900 / T2 1200 / T3 1600
   =========================================================== */

/* ##### 桌面信息密度：--tap 48→40，但必须用 (pointer:fine) 挡住 iPad ##### */
/* [必须] 带 (pointer:fine)：iPad Pro 横屏 1366px、Windows 触摸本也在 1200+ 区间，
   它们是手指操作，压到 40px 会直接变难点。只有真·鼠标设备才提密度。 */
@media (min-width:1200px) and (pointer:fine){
  :root{ --tap:40px }
  .sec > summary{ min-height:52px; padding:var(--s-3) var(--s-4) }
  .checklist label{ min-height:40px; padding:var(--s-2) var(--s-3) }
  .quiz .opts label{ min-height:38px }
  .ea-chk, .pr-got-row{ min-height:40px }
  .btn.sm{ min-height:32px }
}

/* ##### .row2 / .explist 写死列数，2 个子项时空一整格 ##### */
/* 替换 app.css:593-594 这两行 */
@media (min-width:900px){
  /* 教师 2026-09-06 定「电脑两列、手机一列」。
     ⚠ 这里原是 auto-fit minmax(260px,1fr) —— 当年为「三个实验排成 2+1 右下角空一格」写的，
     而现在是四个实验，auto-fit 在 1280px 下给出**三列**、第四张单独换行，正是它要避免的那种空格。
     四张卡两列刚好两行，改回定值。 */
  .explist{ display:grid; grid-template-columns:repeat(2,1fr); gap:var(--s-3) var(--s-4) }
  /* 原 repeat(3,1fr)：.row2 的子项数是 2/3/4/6 不等（逐处数过，见 now），
     写死 3 列会让 2 子项那几处右边空一整格。auto-fit 让空轨道折叠。 */
  .row2{ grid-template-columns:repeat(auto-fit,minmax(200px,1fr)) }
}

/* ##### 五块手写板在桌面各 900×96 的长条，纵向白吃 863px ##### */
/* ---- 桌面：五块手写板改多列 ----
   窄屏一列不变。1440 实测改前：画布 900×96（9.4:1 长条）、#hwGrid 863px、整页 2565px；
   改后：画布 280×96、#hwGrid 338px、整页 2040px。
   ⚠ 画布**高度**由 sign.js 的 fit() 写成行内 cv.style.height='96px'，
     CSS 不要用 !important 去改 —— 位图仍按 96×dpr 生成，只会被拉糊。 */
@media screen and (min-width:900px){
  .hwgrid{ grid-template-columns:repeat(2, minmax(0,1fr)) }
  .hwpad:last-child{ grid-column:1 / -1 }   /* 同组人写好几个名字，给整行 */
  /* 卡头的小字（「点下面的格子手写 / 点格子可修改」）桌面上照样显示：
     2026-09-04 起卡头只剩一个「清除」按钮，不再挤成两行（原先三个按钮时才藏它）。 */
}
@media screen and (min-width:1200px){
  .hwgrid{ grid-template-columns:repeat(3, minmax(0,1fr)) }
  .hwpad:last-child{ grid-column:span 2 }   /* 3 列时同组人跨 2 列 = 590px（1920 下 670px） */
  .hwpad > .hd button{ padding:0 10px }     /* 卡头只剩「清除」一个按钮（2026-09-04 起） */
}

/* ##### 手写提示全写着「用手指」，电脑上学生以为这页用不了（鼠标其实早就能画） ##### */
/* ---- 桌面：把「用手指」的话换成鼠标的话 ----
   sign.js 走 Pointer 分支（sign.js:151），桌面鼠标本来就画得出来（已实测出墨迹与 PNG），
   缺的只是文案。(pointer:fine) 排除纯触屏；再叠 min-width:900 排除带触控笔的手机。 */
@media screen and (min-width:900px) and (pointer:fine){
  .hwtip::after{
    content:"电脑上：点任意一格会弹出一块大画布，按住鼠标左键在里面写；写完点「完成」。";
    display:block; margin-top:4px;
  }
  /* .sf-tip 的文案在 handwrite.js（buildLayer）里直接写成设备中立的说法，
     不用 font-size:0 + ::after 那种硬换文本的写法 —— 太脆，且屏幕阅读器会读到空。 */
}

/* ##### 完成度看板 4 张卡永远断成 3+1，投影时第三个实验被甩到第二行 ##### */
/* ---- 桌面：完成度看板四张一行 ----
   4 张卡（个人信息 + 三个实验）在 auto-fit 下永远是 3+1，
   而「三个实验做到哪」正是要一眼看全的东西。 */
@media screen and (min-width:1200px){
  .prog{ grid-template-columns:repeat(4, minmax(0,1fr)) }
}

/* ---- 投影（1920×1080）：卡里的字要在教室后排看得见 ----
   实测卡高只从 78px 涨到 81px，不会引起重排。 */
@media screen and (min-width:1600px){
  .prog .item b{ font-size:17px }
  .prog .item span{ font-size:14px }
  .prog .item .mark{ font-size:20px }
  .dlcard h4{ font-size:17px }
  .dlcard p{ font-size:13.5px }
}

/* ##### ⑦ 段误差溯源清单：桌面上 7 张卡竖排 918×659，改成双栏 ##### */
@media (min-width:1200px){
  /* ⑦ 段误差溯源：7 个候选来源要能一次摆在眼前做比较，竖排 659px 一屏放不下。
     ⚠ .ea-list 原本是 flex column，gap 沿用，改成 grid 后间距不变。 */
  .ea-list{
    display:grid;
    grid-template-columns:1fr 1fr;
    align-items:start;   /* 展开 .ea-exp 时不把同行另一张卡拉高 */
  }
  .ea-list > textarea.ea-why{ grid-column:1 / -1 }
  .ea-list > p{ grid-column:1 / -1 }   /* 「已选 N 项」说明挪进 #errAudit 之后占满整行（第 151 轮 SHARED-02） */
}

/* ##### ⑦ 段思考题与前测回看：884px 宽的选项条、3 项竖排的回看卡 ##### */
@media (min-width:1200px){
  /* 思考题选项只有十来个字，884px 一条纯属浪费，还把题干和末选项拉得很远。
     minmax 下限给 300px：最长的选项（如「真实流体有黏性，机械能沿程损耗为热」）
     实测约 296px 含圆钮与内边距，不够时自动落回两列，不会挤成两行错位。 */
  .quiz .opts{
    display:grid;
    grid-template-columns:repeat(auto-fit, minmax(300px, 1fr));
    align-items:start;
  }
  /* 「回到前测·看自己的变化」三张卡并排，一屏对照 */
  .pr-list{
    display:grid;
    grid-template-columns:repeat(3, 1fr);
    align-items:start;
  }
  .pr-list > .pr-sum{ grid-column:1 / -1; text-align:right }
}

/* ##### ⑦ 段结果卡：内联 style 把桌面分栏挡死，6 个格子一直挤在 2 列 ##### */
/* HTML 两处改动：exp/reynolds.html 的 summary 容器与 exp/friction.html 的 egOut 容器，
   删掉写死两列的内联 style、改加 cols-3 类。
   ⚠ 注释里**故意不写出 id= 的字面形式** —— 构建期的"重复 id"扫描会连注释一起数，
     我第一版把 HTML 片段原样贴进来，当场被判成"合订版里有 2 个重复 id"。
     又一次"模式匹配到了不该匹配的地方"，这回是我自己的注释。 */

/* 六格的结果区：窄屏 2 列（.dout 默认值），桌面 3 列 —— 正好两行、不留孤儿格。
   ⚠ 别写 #summary / #egOut 这类 id 选择器：合订版会给 id 加页前缀（r_summary），
     选择器静默失效。一律走类名。
   特异度 (0,2,0) 高于 app.css:591 的 .dout (0,1,0)，与书写顺序无关。 */
@media (min-width:1200px){
  .dout.cols-3{ grid-template-columns:repeat(3, 1fr) }
}

/* ##### ⑦ 段雷诺各组流态图：918px 一张竖着排，桌面上应该铺成缩略图墙 ##### */
@media (min-width:900px){
  /* 7 组流态图竖排 918px 一张，滚半天才能比完；桌面铺成缩略图墙。
     ⚠ 与报告里那份三列网格互不相干：报告是 report.js 自带的内联样式，
       全站 .regime-strip 只出现在 exp/reynolds.html:1038。 */
  .regime-strip{ grid-template-columns:repeat(auto-fill, minmax(240px, 1fr)) }
}

/* ##### ⑧ 段自评量规：桌面上排成两栏，投影时就是一张评分表 ##### */
@media (min-width:1200px){
  /* 五个维度当成一张评分量规读：维度名一列，三档选项在右边一行排开。
     实测三档选项合计约 460px（207/150/84 + 间距），右列 ≥756px 够用；
     真不够时 .eseg 本来就是 flex-wrap:wrap，会自己折行，不会溢出。 */
  .eval-row{
    display:grid;
    grid-template-columns:150px 1fr;
    align-items:center;
    column-gap:var(--s-3);
  }
  .eval-row .edim{ margin-bottom:0 }
}

/* ##### 没有底栏的三页仍留着 96px 空底 ##### */
@media (min-width:900px){
  /* rig / data / teacher 三页没有 .bottombar，却照样留了 64+32=96px 空底。
     用兄弟选择器判断而不是 body:has(.bottombar)：合订版是一份文档，
     按 body 判会因为「别的视图里有底栏」而整份误判。
     .bottombar 在每页都是 main.wrap 的后继兄弟，合订版里也同在一个 section 内。 */
  .wrap:not(:has(~ .bottombar)){ padding-bottom:var(--s-6) }
}

/* ##### 下载卡的两个按钮在桌面上竖着堆，每张卡白高 56px ##### */
/* ---- 桌面：下载卡两个按钮并排 ----
   .dlcard 里没有按钮的包裹容器（report.html:437-438 是平级的两个 button），
   所以让 .dlcard 自己变 grid、标题与说明跨列，两个按钮自然落进两格。
   实测卡高 260→204，主按钮 194px、「预览」64px。 */
@media screen and (min-width:1200px){
  .dlcard{
    display:grid; grid-template-columns:1fr auto;
    align-content:start; column-gap:8px;
  }
  .dlcard h4, .dlcard p{ grid-column:1 / -1 }
  .dlcard .btn{ width:auto }
  .dlcard .btn + .btn{ margin-top:0 }
}

/* ##### ⑧ 段课后反思：两个 918px 文本域竖排，桌面上应该并排 ##### */
@media (min-width:1200px){
  /* 「学到的」与「没弄懂的」是一对，本来就该对照着写 */
  .refl-box{
    display:grid;
    grid-template-columns:1fr 1fr;
    column-gap:var(--s-4);
    align-items:start;
  }
  /* 第二个 label 带行内 margin-top:10px（并排后不需要），只能用 !important 抹掉。
     ⚠ 必须排除 .one-col：那种盒子本来就是竖排，抹掉行内 margin 会让题与题贴死。
       别指望用 margin-top:revert 去救 —— 实测 revert 回到的是 UA 的 0，不是行内的 10px。 */
  .refl-box:not(.one-col) > label{ margin-top:0 !important }
  .refl-box > label:nth-of-type(1){ grid-column:1; grid-row:1 }
  .refl-box > textarea:nth-of-type(1){ grid-column:1; grid-row:2 }
  .refl-box > label:nth-of-type(2){ grid-column:2; grid-row:1 }
  .refl-box > textarea:nth-of-type(2){ grid-column:2; grid-row:2 }
  /* ⚠⚠ 第三、四对必须**显式**占满两栏（第 133 轮，教师 2026-09-15 实报「第三题错位」）。
     上面那四条是当初只为「学到的 / 没弄懂的」**两对**写的。后来 D69（2026-09-06）给四个
     实验页的课后反思加了第 3 格「把它用到工程上」，机械页的三道思考题更是三对 ——
     没有显式定位的那一对会被 grid **按 DOM 顺序横向填格**：题干落进第 3 行左栏、
     它的答题框落进第 3 行右栏，两者并排同高。学生会把它读成上一题的第二个框。
     1280 与 1600 实测：exp/{bernoulli,reynolds,friction,pump} 与 mech/planar 的 #refl
     无一幸免，合订版（id 被前缀改写成 b_refl / r_refl…）同样中招 —— 所以修法只能走**类**，
     按 id 写的规则在合订版里一条都打不到（族 7）。
     ⚠ 补位要连 grid-row 一起写死：只写 grid-column 挡不住自动放置。
     ⚠ <1200px 没有这条媒体查询，本来就是竖排，不受影响。 */
  .refl-box > label:nth-of-type(3){    grid-column:1 / -1; grid-row:3 }
  .refl-box > textarea:nth-of-type(3){ grid-column:1 / -1; grid-row:4 }
  .refl-box > label:nth-of-type(4){    grid-column:1 / -1; grid-row:5 }
  .refl-box > textarea:nth-of-type(4){ grid-column:1 / -1; grid-row:6 }
  /* 修饰类 .one-col：这个盒子一律竖排（题一→答一→题二→答二…），桌面也不分栏。
     机械页的三道指导书思考题用它 —— 那三题题干长、各自独立，不是「一对对照着写」的关系
     （教师 2026-09-15：「每道思考题下面紧跟自己的答题框」）。
     ⚠ 类名别用 .stack：app.css 上面已有全局 .stack{display:flex;gap:var(--s-5)}，
       它在 <1200px 也生效，会把手机上的间距一起改掉。 */
  .refl-box.one-col{ grid-template-columns:1fr }
  .refl-box.one-col > label,
  .refl-box.one-col > textarea{ grid-column:1 !important; grid-row:auto !important }
}

/* ##### ⑧ 段导出按钮：桌面上被拉成 300px 宽，红色「清空」还占满 918px ##### */
@media (min-width:1200px){
  /* 桌面上按钮不该按文字长短被拉成 351/260/283 —— 主次分不出来；
     破坏性的「清空本实验数据」更不该是横贯 918px 的红条。 */
  .sec .btn-row{ justify-content:flex-start }
  .sec .btn-row > .btn{ flex:0 0 auto; min-width:140px }
  .sec .btn-row > .btn.block{ width:auto; min-width:200px }
}

/* ===========================================================
   桌面自适应 · 第三批 P0（专家团补跑带回来的）
   =========================================================== */

/* ---- ⑥ 段说明块被 grid 甩进右邻格（V0.06 我自己造成的回归） ----
   rig.html 用 insertAdjacentHTML('afterend') 把部件说明插成按钮的**兄弟节点**。
   窄屏 .explist 是 flex column，它自然落在被点那一条的正下方；
   而我在 V0.06 把 .explist 改成 grid 之后，兄弟节点 = 下一个格子 = **右邻列** ——
   教师 2026-08-23 明确要求的「解释出现在点击那一条的正下方」在 ≥900px 全部失效。
   让它横跨整行就回到正下方，同时行内只剩按钮、高度不再参差。
   ⚠ 教训：把一个纵向列表改成 grid 之前，先问一句"有没有人往它里面 insertAdjacentHTML"。 */
@media screen and (min-width:900px){
  .explist > .partbody{ grid-column:1 / -1 }
}

/* ---- ③ 段（原理段）POE 预测块：单选钮被全局 input{width:100%} 拉成整行宽 ----
   根因是 app.css 那条 input,select,textarea{width:100%;min-height:var(--tap)} 没排除 radio。
   .quiz / .ea-chk / .pr-got-row 三处都各自改回来了，**唯独 POE 漏了**，
   实测圆钮宽度 1920→1064px、1440→924px，手机上同样坏。
   ⚠ 这几条**故意不放进媒体查询** —— 它是修 bug，不是桌面增强：
     手机上圆钮从 275×48 变回 20×20、44px 可点行保留，是净改善。 */
.poe .opts{ display:flex; flex-direction:column; gap:var(--s-2) }
.poe .opts label{
  display:flex; align-items:center; gap:var(--s-3);
  min-height:44px; padding:var(--s-2) var(--s-3);
  border:1px solid var(--line); border-radius:var(--r-1);
  background:var(--surface); cursor:pointer; font-size:var(--f-sm);
}
@media screen and (min-width:1200px){
  .poe .opts{ display:grid; grid-template-columns:repeat(auto-fit, minmax(300px, 1fr)); align-items:start }
}
@media screen and (min-width:1200px) and (pointer:fine){
  .poe .opts label{ min-height:38px }
}

/* 下载卡上的手写状态行（教师 2026-08-24：报告上要有学生的手写信息） */
.dlcard .dl-hw{ display:inline-block; margin-bottom:4px; font-size:var(--f-xs) }
.dlcard .dl-hw.ok{ color:var(--lam-ink) }   /* 4.36 → 5.35 */
.dlcard .dl-hw.warn{ color:var(--trn-ink) }  /* 4.44 → 6.16 */



/* ===========================================================
   材质 · 台面（bezel）
   ⚠ 必须放在文件靠后：这些选择器前面都已经写过 background 简写。
   只给"凹进去的读数窗"性质的面：仿真画布、数据卡、前测框、误差溯源行、
   分段控件槽。悬停态与输入面**不在此列**（那两类要显得能按、能填，不是凹陷）。
   =========================================================== */
canvas.sim, .dcard, .quiz, .ea-row, .pr-item, .seg{
  box-shadow:var(--bezel);
}
.seg button[aria-pressed=true]{ box-shadow:var(--edge-hi) }

/* ===========================================================
   卡顶 2px 品牌渐变条（方向 D 的唯一装饰）
   ⚠ **必须放在文件靠后的位置**。第一版插在 .sec 规则之前，而 .sec 里写的是
     `background:var(--surface)` 这个**简写** —— 简写会把 background-image 重置成 none，
     于是 .card 上生效、.sec 上不生效，而且不报任何错（实测背景图读出 "none"）。
   ⚠ 有意不用 ::before：那需要给 .sec 加 position:relative，而 .sec 是 <details>，
     加定位上下文会改变它内部任何绝对定位元素的参照系。
   =========================================================== */
.card, .sec{
  background-image:var(--accent-bar);
  background-repeat:no-repeat;
  background-size:100% 2px;
  background-position:0 0;
}

/* ===========================================================
   方向 D 的打印收尾（2026-08-25）
   双层阴影与卡顶渐变条都是屏幕装饰：打印机会把渐变条印成一道灰杠，
   阴影则被渲染成边缘发灰。学生交的是纸，这两样一律去掉。
   ⚠ 放在文件最末，确保覆盖前面所有规则。
   =========================================================== */
@media print{
  .card, .sec, .dcard, .kpi, .note{ background-image:none !important; box-shadow:none !important }
  .btn.primary{ box-shadow:none !important }
}

/* 教师登录按钮已撤（教师 2026-08-28「把教师入口去掉」）：口令卡改由连点首页大标题 5 下唤出。 */

/* ============ 手写板（handwrite.js，2026-08-26 从 report.html 上移到共享层：首页也要用） ============ */
.hwgrid{ display:grid; gap:10px; margin-top:8px }
.hwpad{ border:1px solid var(--line); border-radius:var(--r-2); background:var(--surface); overflow:hidden }
.hwpad > .hd{ display:flex; align-items:center; gap:8px; padding:8px 12px;
  border-bottom:1px dashed var(--line); background:var(--surface-2) }
.hwpad > .hd b{ flex:none }
.hwpad > .hd .tag{ font-size:var(--f-xs); color:var(--muted); font-weight:400 }
.hwpad > .hd .sp{ flex:1 }
.hwpad > .hd button{ min-height:34px; padding:0 12px; font-size:var(--f-xs) }
.hwpad .area{ position:relative; padding:6px 8px 8px }
.hwpad .area::after{ content:attr(data-ph); position:absolute; inset:0; display:flex;
  align-items:center; justify-content:center; color:var(--muted); opacity:.5;
  pointer-events:none; font-size:var(--f-sm); transition:opacity .15s }
.hwpad .area[data-empty="no"]::after{ opacity:0 }
/* 2026-09-04 教师「点一下签字页面就弹出全屏」：小板只预览，整块 .area 就是那个"点一下"的目标。
   要有可点的暗示（鼠标 hover / 手指 active / 键盘 focus），否则桌面上卡头只剩「清除」、
   学生不知道格子能点。 */
.hwpad .area{ cursor:pointer; transition:background .12s }
.hwpad .area:hover{ background:var(--surface-2) }
.hwpad .area:active{ background:var(--brand-soft) }
.hwpad .area:focus-visible{ outline:2px solid var(--brand); outline-offset:-2px }
.hwtip{ font-size:var(--f-xs); color:var(--muted); margin-top:8px; line-height:1.7 }

/* 名单点名（首页身份卡） */
/* 整班名单点名的样式已随轻门禁（2026-08-28，D34）退役：点名改为输学号查自己那一条。 */
.roster-grid{ display:grid; grid-template-columns:repeat(auto-fill,minmax(104px,1fr)); gap:8px; margin-top:8px }
.roster-name{ min-height:48px; border:1px solid var(--line); border-radius:var(--r-2);
  background:var(--surface); font-family:var(--font); font-size:var(--f-md); font-weight:600;
  color:var(--text); display:flex; flex-direction:column; align-items:center; justify-content:center; gap:1px }
.roster-name small{ font-size:var(--f-xs); color:var(--muted); font-weight:400 }
.roster-name:active{ background:var(--surface-2) }

/* 报告页的手写只读预览 */
.hwprev{ display:grid; grid-template-columns:repeat(auto-fill,minmax(150px,1fr)); gap:8px; margin-top:8px }
.hwprev-it{ border:1px solid var(--line); border-radius:var(--r-2); padding:6px 10px;
  background:var(--surface); display:flex; align-items:center; gap:8px; min-height:48px }
.hwprev-it b{ font-size:var(--f-xs); color:var(--muted); font-weight:600; flex:none }
.hwprev-it img{ max-height:34px; max-width:100%; width:auto; height:auto; object-fit:contain; flex:0 0 auto }
.hwprev-it .miss{ color:var(--tur); font-size:var(--f-xs) }

/* ============ 工程案例：解说 + 视频配对（教师 2026-08-27：「视频要在文字解说下面」） ============
   解说从原来的 .vars 网格搬进了各自的 .case-online 卡；playBili 把播放器 append 到卡底部，
   所以视觉顺序天然是「解说 → 播放按钮 → 播放器」。 */
.case-tx{ font-size:var(--f-sm); color:var(--text); line-height:1.75;
  padding:8px 12px 0; }

/* 首页班级下拉：教师 2026-08-27「数据导入下拉空白过多」—— 原先撑满整行，
   班级名只有七八个字，右边一大片空白。限宽到 220px。 */
#rosterCls{ max-width:220px; width:100% }

/* ============ 实验模块（一级）→ 实验（二级）============
   教师 2026-08-27：「把我们流体力学的实验作为二级标题，一级标题里面包含了
   流体力学实验基础实验……可以让我增加其他实验的模块」。
   只有一个模块时也照样出一级标题 —— 后期加第二个模块，界面不用再改一次。
   ⚠ 层次靠**填充色台阶**，不靠阴影（方向「量台」的第 ① 条纪律）。 */
.modlist{ display:flex; flex-direction:column; gap:var(--s-5) }
/* 第 119 轮（教师 2026-09-06「减少一个框里面再套几个框」）：分组本身不再是一个带底色的框 ——
   它只是一个标题 + 一列卡片。台面色留给真正的「凹陷面」（数据读数区），不用来分组。 */
.expmod{ border:0; border-radius:0; background:transparent;
  padding:var(--s-3) }
.expmod-hd{ display:flex; align-items:center; gap:var(--s-2); flex-wrap:wrap;
  margin:0 0 2px; font-size:var(--f-md); font-weight:700; color:var(--text) }
.expmod-desc{ margin:0 0 var(--s-3); font-size:var(--f-sm); color:var(--muted); line-height:1.6 }
/* 模块内的卡片坐在"面板"色上，与模块底色（台面）拉开一级台阶 */
.expmod .expcard{ background:var(--surface) }

/* ============ 仿真的播放/暂停（resim.js 与伯努利动画共用）============
   教师 2026-08-27 实报「动图在别的电脑/手机上不动」：根因是系统「减少动效」
   被当成"冻结时间"。现在动画照放，并给出这个按钮 ——
   它既是无障碍规范要求的「可暂停」机制（本站此前一个都没有），
   也让对动效敏感的人能立刻停下。
   放右下角：读数卡在右上角，两者不打架。 */
.sim-play{
  position:absolute; right:10px; bottom:10px; z-index:2;
  min-height:38px; padding:0 14px; border-radius:999px;
  border:1px solid var(--line); background:var(--surface); color:var(--text);
  font-size:var(--f-sm); font-weight:600; cursor:pointer;
  font-variant-numeric:tabular-nums; line-height:1;
  /* 手指落在按钮上不要被当成页面手势（与滑块那条同族） */
  touch-action:manipulation; -webkit-tap-highlight-color:transparent;
}
.sim-play:hover{ background:var(--hover) }
.sim-play:focus-visible{ outline:2px solid var(--brand); outline-offset:2px }
/* 「减少动效」时的说明条：如实告诉用户我们为什么仍然播放 */
.sim-rm-tip{
  margin-top:8px; padding:8px 10px; border-radius:var(--r-1);
  background:var(--trn-bg); border:1px solid var(--trn);
  color:var(--trn-ink); font-size:var(--f-xs); line-height:1.6;
}
/* 伯努利的动画条里那颗按钮：跟着 .sim-bar 走，不用绝对定位 */
.sim-bar .sim-play{ position:static; min-height:34px; padding:0 12px }

/* ============ 截图模式（教师 2026-08-27，操作说明书用）============
   `?shot=1` 或 Shift+S 打开。把这些区块整块藏掉，避免第三方名称、台标、
   国徽进入说明书截图：
     · 「工程应用经典案例（视频）」整段与页内播放器
     · 答疑浮球与答疑面板（面板标题带模型服务商名字）
     · 首页设置里那一栏 API Key（同样带服务商名字）
   ⚠ 只是**显示层**的临时隐藏，功能一个都没删；学生正常访问不受影响。 */
body.shot-mode .case-online,
body.shot-mode .bili-slot,
body.shot-mode [data-shot-hide],
body.shot-mode .ask-fab,
body.shot-mode .ask-panel,
body.shot-mode .ask-keyrow{ display:none !important }
/* 截图模式下给个占位说明，免得说明书里出现一块莫名的空白 */
body.shot-mode [data-shot-hide]::before{ content:none }
body.shot-mode .shot-note{ display:block }
.shot-note{ display:none; padding:10px 12px; border:1px dashed var(--line);
  border-radius:var(--r-2); background:var(--surface-2); color:var(--muted);
  font-size:var(--f-sm); line-height:1.7 }
/* ============ 实验室安全须知 · 准入确认遮罩（D45，App.safety 建的 DOM） ============
   全屏遮罩：第一次登记身份后必读必确认。不做淡入动画（低端安卓 + 减动效偏好都省心）。 */
.safety-ovl{
  position:fixed; inset:0; z-index:9998;           /* 盖在 #toast（z-index 200）之上：「已记录」那句 toast 是遮罩收起之后才弹的（第 149 轮订正：原注释写「低于 toast（9999）」是错的） */
  background:rgba(10,15,30,.55);
  display:flex; align-items:center; justify-content:center;
  padding:16px; overflow-y:auto; overscroll-behavior:contain;
}
.safety-card{
  background:var(--surface); color:var(--text);
  border:1px solid var(--line); border-radius:var(--r-2);
  max-width:560px; width:100%; max-height:88vh; overflow-y:auto;
  overscroll-behavior:contain;   /* 划到尽头不外溢成 iOS 边缘返回（构建断言当场逮的） */
  padding:20px 18px; box-shadow:var(--sh-modal);
}
.safety-card h2{ margin:0 0 8px; font-size:var(--f-lg) }
.safety-card .who{ margin:0 0 10px; font-size:var(--f-sm); color:var(--muted) }
.safety-card .dz{
  padding:10px 12px; border-radius:var(--r-1); margin-bottom:10px;
  background:var(--tur-bg); border:1px solid var(--tur); color:var(--tur-ink);
  font-size:var(--f-sm); font-weight:600;
}
.safety-card ol{ margin:0 0 14px; padding-left:22px }
.safety-card ol li{ margin:6px 0; font-size:var(--f-sm) }
.safety-card .agree{
  display:flex; align-items:flex-start; gap:8px; margin-bottom:12px;
  font-size:var(--f-sm); font-weight:600; cursor:pointer;
}
.safety-card .agree input{ margin-top:3px; flex:none }
.safety-card .btn{ width:100% }
.safety-card .fine{ margin:10px 0 0; font-size:var(--f-xs); color:var(--muted) }

/* ---- 教师口令弹窗（第 130 轮，D83）：core.js 的 openAdminModal 建的，全站唯一 ----
   [hidden]{display:none!important} 那条全局规则负责藏它；z-index 高过顶栏(50)与底栏(120)。 */
.admodal{ position:fixed; inset:0; z-index:10000;   /* 在安全须知遮罩（9998）与全屏手写层（9999）之上（第 151 轮 SHARED-01）：遮罩上的「我是老师」要能把口令窗叫到眼前 */
   display:flex; align-items:center; justify-content:center;
          padding:var(--s-4); background:rgba(10,15,30,.45) }
.admodal-card{ width:min(440px, 100%); background:var(--surface); border:1px solid var(--line-strong);
               border-radius:var(--r-3); box-shadow:var(--sh-modal); padding:var(--s-5) var(--s-4) }
.admodal-card h2{ margin:0 0 var(--s-2); font-size:var(--f-lg) }
.admodal-card p{ margin:0 0 var(--s-2) }
.admodal-card input{ width:100%; min-height:48px; font-size:16px; padding:0 12px; border:1px solid var(--line-field);
                     border-radius:var(--r-1); background:var(--surface); color:var(--text) }
.admodal-hint{ min-height:1.4em; font-size:var(--f-sm); color:var(--tur-ink); margin:6px 0 0 }
.admodal-card .btn-row{ margin-top:var(--s-3) }
@media print{ .admodal{ display:none } }

/* ---- 没登记学号就进了实验页（第 145 轮，学生全流程专家团坐实）：core.js 的 idBanner 在顶栏下插这一条 ----
   不遮挡、不拦人，只把「预习只存在这台手机」说清楚；登记后自动收起。 */
.idwarn{ display:flex; flex-wrap:wrap; align-items:center; gap:8px;
         padding:8px var(--s-4); background:var(--warn-bg); color:var(--text);
         border-bottom:1px solid var(--line); font-size:var(--f-sm); line-height:1.5 }
.idwarn span{ flex:1 1 16em; min-width:0 }
@media print{ .idwarn{ display:none } }
/* 教师测试身份那一条（第 152 轮，D111）：与「还没登记」同形，换品牌浅底 —— 一眼分得出是测试，不是警告 */
.idwarn.testbar{ background:var(--brand-soft) }

/* ---- 横屏安全区（第 145 轮，上轮疑点核实坐实）：全站 viewport-fit=cover，原先只处理了上下安全区 ——
   iPhone 横屏（旧表那段主动劝学生横屏填）时正文、旧表钉边列、顶栏返回键会有约 30px 落进刘海带。
   竖屏时 inset 为 0，版式不变；不认 max() 的老 WebView 整条丢弃这一条，退回原来的值。
   ⚠ 必须站在 .wrap / .topbar / .bottombar 原有 padding 简写之后（本文件末尾），否则被简写盖掉。 */
.wrap, .idwarn{
  padding-left:max(var(--s-4), env(safe-area-inset-left));
  padding-right:max(var(--s-4), env(safe-area-inset-right));
}
/* 顶栏 / 底栏只在 <1200 时管：≥1200 那段「内容对齐到内容列」的 padding 不能被这里盖掉 */
@media (max-width:1199px){
  .topbar, .bottombar{
    padding-left:max(var(--s-4), env(safe-area-inset-left));
    padding-right:max(var(--s-4), env(safe-area-inset-right));
  }
}
@media (min-width:900px){
  .wrap{ padding-left:max(var(--s-5), env(safe-area-inset-left)); padding-right:max(var(--s-5), env(safe-area-inset-right)) }
}
/* 提示条也要让开底部安全区（底栏实际高度 = --bar-h + 底部 inset；答疑浮球早就加了，toast 漏了） */
#toast{ bottom:calc(var(--bar-h) + 16px + env(safe-area-inset-bottom)) }
/* 上面几条没带媒体条件、又排在打印块之后 —— 打印时把 @media print 里的 .wrap{padding:0} 盖掉了（第 145 轮交付前审查，CDP 实测左右各多 16~24px）。补一条复位 */
@media print{ .wrap{ padding-left:0; padding-right:0 } }

/* ============ 学生操作流程（第 149 轮，教师「帮我审核整个的操作流程……有没有重复填写的繁杂的，或者说是设计不合理的」）============ */
/* POE：选了还没锁定 —— 锁定按钮醒目一点（审核 FN-03②，core.js 的 initPoeNudge 加类） */
.poe-armed:not(:disabled){ box-shadow:0 0 0 3px var(--brand-soft); border-color:var(--brand) }
/* 回到前测：写够字还没勾（审核 FN-07④ / WR-8；只提醒、不自动勾，D59） */
.pr-got-row.pr-nudge{ padding:0 var(--s-2); border-radius:var(--r-2); outline:2px solid var(--trn); outline-offset:1px; background:var(--trn-bg) }
.pr-got-row.pr-nudge::after{ content:'写好了？勾上'; margin-left:auto; font-size:var(--f-xs); color:var(--trn-ink); font-weight:700 }
/* ⑧ 段「交之前核对一下」（审核 FN-07 / FN-06，core.js 的 initSubmitCheck 画的 DOM） */
.wc-box{ margin:var(--s-3) 0 }
.wc-stat{ margin:0 0 var(--s-2); padding:var(--s-2) var(--s-3); border:1px solid var(--line); border-radius:var(--r-2); background:var(--surface-2); font-weight:600; line-height:1.6 }
.wc-stat.wc-done{ border-color:var(--lam); background:var(--lam-bg); color:var(--lam-ink) }
.wc-stat.wc-stale, .wc-stat.wc-rejected{ border-color:var(--trn); background:var(--trn-bg); color:var(--trn-ink) }
.wc-head{ margin:var(--s-2) 0; font-size:var(--f-sm); color:var(--text-2) }
.wc-list{ list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:6px }
.wc-row{ display:flex; flex-wrap:wrap; align-items:baseline; gap:2px 8px; padding:8px 10px; border:1px solid var(--line); border-radius:var(--r-2); font-size:var(--f-sm); line-height:1.6 }
.wc-row.todo{ border-color:var(--trn) }
.wc-ic{ flex:none; width:1.1em; font-weight:700 }
.wc-row.ok .wc-ic{ color:var(--lam-ink) }
.wc-row.todo .wc-ic{ color:var(--trn-ink) }
.wc-row.opt .wc-ic{ color:var(--muted) }   /* 选填没写（第 169 轮）：中性边框、灰点，不挂待办的橙色 */
.wc-t{ font-weight:700; color:var(--text) }
.wc-d{ flex:1 1 12em; color:var(--muted) }
.wc-go{ margin-left:auto; white-space:nowrap; font-weight:600; min-height:32px; display:inline-flex; align-items:center }
/* ⑧ 段「带入 ⑦ 段的判断」按钮与标签同一行（审核 FN-08） */
.err-pull-row{ display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between; gap:6px }
/* ⑧ 段报告会自动带上什么（审核 FN-10） */
.auto-inc{ margin:var(--s-3) 0 0; font-size:var(--f-sm); color:var(--muted); line-height:1.7 }
/* 段落目录浮钮（审核 FN-13，core.js 的 initSecToc）：左下角，右下角是答疑浮球 */
.sec-toc-fab{
  position:fixed; left:16px; bottom:calc(var(--bar-h) + 20px + env(safe-area-inset-bottom)); z-index:80;
  min-height:44px; padding:0 14px; border:1px solid var(--line-strong); border-radius:var(--r-full);
  background:var(--surface); color:var(--text); font-family:var(--font); font-size:var(--f-sm); font-weight:700;
  box-shadow:var(--sh-float); cursor:pointer;
}
.sec-toc-pop{
  position:fixed; left:16px; bottom:calc(var(--bar-h) + 72px + env(safe-area-inset-bottom)); z-index:81;
  width:min(300px, calc(100vw - 32px)); max-height:60vh; overflow:auto; overscroll-behavior:contain; padding:6px;
  display:flex; flex-direction:column; gap:2px;
  border:1px solid var(--line); border-radius:var(--r-3); background:var(--surface); box-shadow:var(--sh-float);
}
.sec-toc-pop button{ min-height:44px; padding:0 12px; border:0; border-radius:var(--r-2); background:transparent; color:var(--text); font:inherit; font-size:var(--f-sm); text-align:left; cursor:pointer }
.sec-toc-pop button:hover{ background:var(--hover) }
.sec-toc-fab[hidden], .sec-toc-pop[hidden]{ display:none }
body.kbd-open .sec-toc-fab, body.kbd-open .sec-toc-pop{ display:none }
body.shot-mode .sec-toc-fab, body.shot-mode .sec-toc-pop{ display:none !important }
/* 横屏打开软键盘：可视高度只剩一两百像素，吸顶的顶栏占去一大块（审核 FN-16 / EX-9）—— 只在「横屏且矮屏且键盘开着」时让它跟着滚走 */
@media (orientation:landscape) and (max-height:500px){ body.kbd-open .topbar{ position:static } }
/* 触屏上 .btn.sm 的可点区域上下各扩 4px 到 44px、视觉尺寸不变（审核 FN-16，与 .icon-btn 同一做法；左右不扩，相邻按钮热区不重叠） */
@media (pointer:coarse){
  .btn.sm{ position:relative }
  .btn.sm::after{ content:''; position:absolute; top:-4px; bottom:-4px; left:0; right:0 }
}
/* 课后反馈表：没填的题直接标出来（审核 FN-11，survey.js 的 focusMissing）；短选项手机上排两列 */
.sv-need{ outline:2px solid var(--trn); outline-offset:2px; border-radius:var(--r-2) }
.sv-need-tip{ display:block; margin-top:4px; color:var(--trn-ink); font-size:var(--f-xs); font-weight:700 }
@media (max-width:640px){ .sv-opts.two{ display:grid; grid-template-columns:1fr 1fr } }
details.sv-fold > summary{ cursor:pointer; min-height:var(--tap); display:flex; align-items:center; font-weight:600 }
/* 课后反馈表：组号旁「用我上次填的」排在输入框下面；第 1 题旁那行只读小字单独占一行（第 150 轮，D105） */
.sv-grp{ display:flex; flex-direction:column; align-items:flex-start; gap:4px }
.sv-dn{ display:block; margin:2px 0 6px; font-size:var(--f-xs) }
/* AI 答疑没开时如实降级（审核 FN-18）：不再是第二个品牌蓝主按钮（D71） */
.ask-fab.ask-off{ background:var(--surface); color:var(--muted); border:1px solid var(--line); box-shadow:none }
.ask-offnote{ margin:0 0 var(--s-2); padding:var(--s-2) var(--s-3); border:1px solid var(--line); border-radius:var(--r-2); background:var(--surface-2); color:var(--text-2); font-size:var(--f-sm) }
/* ⑧ 段封面摘要（审核 FN-24） */
.cover-sum{ margin:var(--s-3) 0 0; font-size:var(--f-sm); color:var(--text-2); line-height:1.7 }
@media print{ .sec-toc-fab, .sec-toc-pop, .wc-box{ display:none !important } }
