:root {
  /* 底部导航的【实测高度】，由 app.js 的 syncBottomNavMetrics() 写入。
     iPhone 上 .bottom-nav 还带 env(safe-area-inset-bottom)，实际可达 94px，
     所以留白一律用 calc(var(--bottom-nav-h) + Npx)，不再写死 60/68。
     默认 0：桌面端没有底部导航，不该产生任何留白。 */
  --bottom-nav-h: 0px;

  /* 软键盘遮挡高度，由 app.js 的 syncKeyboardInset() 经 visualViewport 写入。
     手机上填申诉理由/作答会弹出键盘，而 100vh 不随键盘变化（iOS 尤其明显），
     弹窗底部的「提交」就会被顶到键盘下面。默认 0。 */
  --kbd-inset: 0px;

  /* ===== 层级契约：改这里，别再到处硬写 z-index =====
     底部导航 1000 < 弹窗 1100 < 全屏页 1200 < toast 10001
     ★ 血泪：以前 .modal-overlay 是 200、低于 .bottom-nav(1000)，
       于是「作业申诉」等所有标准弹窗的底部按钮被导航条压住点不到，
       而且无论怎么加 padding-bottom / 改高度都没用 —— 那是【层级】问题，
       不是【高度】问题。弹窗打开时应当独占屏幕（遮罩压住导航），
       这也是移动端通行做法。
     ★ 2026-10-22 toast 1300 → 10001：通知面板（inline 9999）与推送引导弹窗
       （inline 10000）是历史遗留的高层遮罩，toast 原在 1300 会被它们整个盖住 ——
       面板里点「开启通知」失败时提示根本看不见（用户报「一闪而过，什么都没有」）。
       toast 是转瞬即逝的反馈，必须压在所有遮罩之上。 */
  --z-bottom-nav: 1000;
  --z-modal: 1100;
  --z-fullpage: 1200;
  --z-toast: 10001;
}

/* 有底部导航时，页面内容统一为它让位（手机 ≤768 / 平板 769–1366 触摸 都命中），
   避免底部按钮、最后一张卡片被压在导航条下面点不到。
   注意：这条只对【文档流】生效；弹窗是 fixed 层，靠上面的层级契约解决。 */
body.has-bottom-nav {
  padding-bottom: calc(var(--bottom-nav-h) + 16px);
}

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html, body {
  margin: 0;
  padding: 0;
  width: 100%;
  overflow-x: hidden;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
  background: #f5f7fa;
  color: #333;
  line-height: 1.6;
}

/* 导航栏 */
.navbar {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
  padding: 0 24px;
  height: 60px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
  position: sticky;
  top: 0;
  z-index: 100;
}

.nav-brand {
  font-size: 18px;
  font-weight: 600;
}

.nav-menu {
  display: flex;
  gap: 8px;
  flex: 1;
  margin-left: 40px;
}

.nav-menu a {
  color: rgba(255,255,255,0.85);
  text-decoration: none;
  padding: 8px 16px;
  border-radius: 6px;
  cursor: pointer;
  transition: all 0.2s;
  font-size: 14px;
}

.nav-menu a:hover, .nav-menu a.active {
  background: rgba(255,255,255,0.2);
  color: white;
}

.nav-user {
  display: flex;
  align-items: center;
  gap: 12px;
  font-size: 14px;
}

/* 主内容 */
.main-content {
  max-width: 1200px;
  margin: 0 auto;
  padding: 24px;
  min-height: calc(100vh - 60px);
}

/* ============================================================
 * 进入平台时的加载骨架（2026-10-14）
 *
 * 背景：initApp() 要先等 getCourseMode()、再等 loadTeacherNavConfig()，
 * 接口慢时用户对着空白主区干等（最长 30s），观感就是"卡住"。
 * 现在先渲染一层骨架占位，让"正在加载"可见。
 *
 * 设计约束：
 *   · 纯静态灰块，不用 shimmer 动画 —— 避免与 tablet.js 的 rAF 争抢主线程；
 *   · 不挂 body.layout-tablet 前缀（骨架只在登录后极短时间存在，且与档位无关）；
 *   · 所有后代渲染（renderTeacherHome / renderStudentHome）都是整块写
 *     mainContent.innerHTML，会自动覆盖骨架，无需手动清理。
 * ============================================================ */
.app-skeleton {
  padding: 4px 0;
}
.app-skeleton-title {
  height: 22px;
  width: 180px;
  background: #eef0f3;
  border-radius: 6px;
  margin-bottom: 20px;
}
.app-skeleton-row {
  height: 14px;
  background: #eef0f3;
  border-radius: 4px;
  margin-bottom: 12px;
}
.app-skeleton-row.w-60 { width: 60%; }
.app-skeleton-row.w-45 { width: 45%; }
.app-skeleton-row.w-80 { width: 80%; }
.app-skeleton-block {
  height: 120px;
  background: #eef0f3;
  border-radius: 8px;
  margin: 20px 0;
}

/* 卡片 */
.card {
  background: white;
  border-radius: 12px;
  padding: 24px;
  margin-bottom: 20px;
  box-shadow: 0 2px 12px rgba(0,0,0,0.06);
}

.card-title {
  font-size: 18px;
  font-weight: 600;
  margin-bottom: 16px;
  color: #1a1a1a;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

/* 按钮 */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 10px 20px;
  border: none;
  border-radius: 8px;
  font-size: 14px;
  cursor: pointer;
  transition: all 0.2s;
  text-decoration: none;
  font-weight: 500;
}

.btn-primary {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
}

.btn-primary:hover {
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(102,126,234,0.4);
}

.btn-success {
  background: #52c41a;
  color: white;
}

.btn-warning {
  background: #faad14;
  color: white;
}

.btn-danger {
  background: #ff4d4f;
  color: white;
}

.btn-outline {
  background: transparent;
  border: 1px solid rgba(255,255,255,0.5);
  color: white;
}

.btn-outline:hover {
  background: rgba(255,255,255,0.1);
}

.btn-sm {
  padding: 6px 12px;
  font-size: 12px;
}

.btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  transform: none;
}

/* 表单 */
.form-group {
  margin-bottom: 16px;
}

.form-label {
  display: block;
  margin-bottom: 6px;
  font-size: 14px;
  font-weight: 500;
  color: #555;
}

.form-input, .form-textarea, .form-select {
  width: 100%;
  padding: 10px 12px;
  border: 1px solid #d9d9d9;
  border-radius: 8px;
  font-size: 14px;
  transition: border-color 0.2s;
  font-family: inherit;
}

.form-input:focus, .form-textarea:focus, .form-select:focus {
  outline: none;
  border-color: #667eea;
  box-shadow: 0 0 0 3px rgba(102,126,234,0.1);
}

.form-textarea {
  min-height: 120px;
  resize: vertical;
}

.form-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
}

/* 登录注册页 */
.auth-container {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  padding: 20px;
}

.auth-card {
  background: white;
  border-radius: 16px;
  padding: 40px;
  width: 100%;
  max-width: 420px;
  box-shadow: 0 20px 60px rgba(0,0,0,0.2);
}

.auth-title {
  text-align: center;
  font-size: 24px;
  font-weight: 700;
  margin-bottom: 8px;
  color: #1a1a1a;
}

.auth-subtitle {
  text-align: center;
  color: #888;
  margin-bottom: 32px;
  font-size: 14px;
}

.auth-footer {
  text-align: center;
  margin-top: 20px;
  font-size: 14px;
  color: #666;
}

.auth-footer a {
  color: #667eea;
  text-decoration: none;
  cursor: pointer;
}

.password-group {
  position: relative;
}

.password-toggle {
  position: absolute;
  right: 12px;
  top: 50%;
  transform: translateY(-50%);
  cursor: pointer;
  color: #999;
  user-select: none;
  font-size: 18px;
}

/* 表格 */
.table {
  width: 100%;
  border-collapse: collapse;
  font-size: 14px;
}

.table th, .table td {
  padding: 12px;
  text-align: left;
  border-bottom: 1px solid #f0f0f0;
}

.table th {
  background: #fafafa;
  font-weight: 600;
  color: #555;
}

.table tr:hover {
  background: #f9f9f9;
}

/* 标签 */
.tag {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 4px;
  font-size: 12px;
  font-weight: 500;
}

.tag-success { background: #f6ffed; color: #52c41a; border: 1px solid #b7eb8f; }
.tag-warning { background: #fffbe6; color: #faad14; border: 1px solid #ffe58f; }
.tag-danger { background: #fff2f0; color: #ff4d4f; border: 1px solid #ffccc7; }
.tag-info { background: #e6f7ff; color: #1890ff; border: 1px solid #91d5ff; }

/* 作业卡片 */
.assignment-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
  gap: 20px;
}

.assignment-card {
  background: white;
  border-radius: 12px;
  padding: 20px;
  box-shadow: 0 2px 12px rgba(0,0,0,0.06);
  transition: transform 0.2s, box-shadow 0.2s;
  cursor: pointer;
}

.assignment-card:hover {
  transform: translateY(-2px);
  box-shadow: 0 8px 24px rgba(0,0,0,0.1);
}

.assignment-card h3 {
  font-size: 16px;
  margin-bottom: 8px;
  color: #1a1a1a;
}

.assignment-meta {
  font-size: 13px;
  color: #888;
  margin-bottom: 12px;
}

.assignment-content {
  font-size: 14px;
  color: #555;
  margin-bottom: 12px;
  max-height: 80px;
  overflow: hidden;
}

/* LaTeX 渲染容器 */
/* 块级公式改为左对齐：KaTeX 默认居中 + white-space:nowrap，超宽公式会被居中布局
   把左侧（题号、行首文字）挤出可视区且无法回滚（overflow 只能向右滚）。左对齐后
   超宽部分只向右溢出，在公式自身框内横向滚动，行首内容不再丢失。
   ★ 2026-10-22 考试AI评语超出左边界：考试结果页/教师批改页的 AI评语、参考答案、
   评分标准盒子用的是 class="comment-content"（没有 katex-content），这条规则管不到，
   评语里的宽公式（矩阵、长对齐式）照样居中溢出。故选择器一并覆盖 .comment-content。 */
.katex-content .katex-display,
.comment-content .katex-display {
  text-align: left;
  margin: 0.5em 0;
  overflow-x: auto;
  overflow-y: hidden;
  padding: 2px 0;
}

.katex-content .katex-display > .katex,
.comment-content .katex-display > .katex {
  text-align: left;
}

/* 列表题号（marked 把"1. / (1)"转成的 ol/li）用 inside 定位，防止序号落在内容框外被裁切。
   同样覆盖 .comment-content：考试AI评语等盒子没有 katex-content 类（2026-10-22）。 */
.katex-content ol,
.katex-content ul,
.comment-content ol,
.comment-content ul {
  list-style-position: inside;
  padding-left: 0.5em;
}

.katex-block-wrapper {
  display: block;
  overflow-x: auto;
  overflow-y: hidden;
  padding: 8px 0;
  margin: 8px 0;
}

.katex-block-inner {
  flex-shrink: 0;
}

.katex-content {
  font-size: 14px;
  line-height: 1.8;
}

/* 上传区域 */
.upload-area {
  border: 2px dashed #d9d9d9;
  border-radius: 12px;
  padding: 40px;
  text-align: center;
  cursor: pointer;
  transition: all 0.2s;
  background: #fafafa;
}

.upload-area:hover, .upload-area.dragover {
  border-color: #667eea;
  background: #f0f3ff;
}

.upload-area input[type="file"] {
  display: none;
}

.upload-icon {
  font-size: 48px;
  margin-bottom: 12px;
}

.upload-text {
  color: #666;
  font-size: 14px;
}

.preview-image {
  max-width: 100%;
  max-height: 400px;
  border-radius: 8px;
  margin: 16px 0;
}

/* 成绩展示 */
.score-display {
  text-align: center;
  padding: 24px;
}

.score-number {
  font-size: 64px;
  font-weight: 700;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
}

.score-label {
  color: #888;
  font-size: 14px;
}

/* 加载 */
.loading {
  text-align: center;
  padding: 40px;
  color: #888;
}

.spinner {
  display: inline-block;
  width: 32px;
  height: 32px;
  border: 3px solid #f0f0f0;
  border-top-color: #667eea;
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}

@keyframes spin {
  to { transform: rotate(360deg); }
}

/* Toast */
.toast {
  position: fixed;
  top: 80px;
  left: 50%;
  transform: translateX(-50%);
  background: rgba(0,0,0,0.8);
  color: white;
  padding: 12px 24px;
  border-radius: 8px;
  z-index: var(--z-toast, 1300);
  font-size: 14px;
  animation: fadeIn 0.3s;
}

.toast.error { background: #ff4d4f; }
.toast.success { background: #52c41a; }

@keyframes fadeIn {
  from { opacity: 0; transform: translateX(-50%) translateY(-10px); }
  to { opacity: 1; transform: translateX(-50%) translateY(0); }
}

/* 空状态 */
.empty-state {
  text-align: center;
  padding: 60px 20px;
  color: #999;
}

.empty-state-icon {
  font-size: 64px;
  margin-bottom: 16px;
}

/* 提示条 */
.alert {
  padding: 12px 16px;
  border-radius: 8px;
  margin-bottom: 16px;
  font-size: 14px;
}

.alert-warning {
  background: #fffbe6;
  border: 1px solid #ffe58f;
  color: #d48806;
}

.alert-info {
  background: #e6f7ff;
  border: 1px solid #91d5ff;
  color: #096dd9;
}

/* 模态框
   ★ 层级必须高于底部导航（见 :root 的层级契约），否则底部按钮被压住点不到；
   ★ bottom 用 --kbd-inset 让位软键盘，否则输入时「提交」被顶到键盘下面。 */
.modal-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: var(--kbd-inset, 0px);
  background: rgba(0,0,0,0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: var(--z-modal, 1100);
  padding: 20px;
}

/* 手搓的自制遮罩（原先内联 z-index:9999/10000 那几处）加这个 class，
   即可复用同一套「高于底部导航 + 让位软键盘」规则，避免再次各写一套打架。
   内联 cssText 里请只保留 display/align/background 之类外观，别再写定位。 */
.app-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: var(--kbd-inset, 0px);
  z-index: var(--z-modal, 1100);
}

.modal {
  background: white;
  border-radius: 12px;
  padding: 24px;
  width: 100%;
  max-width: 600px;
  max-height: 90vh;
  overflow-y: auto;
}

.modal-title {
  font-size: 18px;
  font-weight: 600;
  margin-bottom: 20px;
}

.modal-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
}

.modal-header h3 {
  margin: 0;
  font-size: 18px;
  font-weight: 600;
}

.modal-close {
  background: none;
  border: none;
  font-size: 24px;
  cursor: pointer;
  color: #999;
  padding: 0;
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  transition: all 0.2s;
  line-height: 1;
}

.modal-close:hover {
  background: #f5f5f5;
  color: #333;
}

/* 导航角标 */
.nav-badge {
  position: absolute;
  top: -6px;
  right: -10px;
  background: #ff4d4f;
  color: white;
  font-size: 11px;
  font-weight: 600;
  min-width: 18px;
  height: 18px;
  border-radius: 9px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0 5px;
  line-height: 1;
}

.modal-footer {
  display: flex;
  justify-content: flex-end;
  gap: 12px;
  margin-top: 20px;
}

/* ============================================================
 * 通知面板（铃铛弹出的通知列表）
 *
 * 独立于通用 .modal：手机档 .modal 被强制成 100vw/100vh 全屏无圆角，
 * 用它做「浮层卡片」会导致① 满屏铺满 ② 标题行按钮被 flex 挤掉
 * ③ 内容只占一小截、其余全是空白。
 *
 * 结构：.notif-overlay（遮罩）> .notif-panel（卡片）> .notif-head + #notifList
 * ============================================================ */
.notif-overlay {
  padding: 16px;
  align-items: flex-start;
}

.notif-panel {
  background: #fff;
  border-radius: 12px;
  width: 100%;
  max-width: 480px;
  /* 手机上不占满屏：留出上下空间，看得出这是一个浮层 */
  max-height: calc(100vh - 120px);
  margin-top: 56px;
  display: flex;
  flex-direction: column;
  box-shadow: 0 12px 40px rgba(0, 0, 0, .18);
  overflow: hidden;
}

.notif-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
  padding: 14px 16px;
  border-bottom: 1px solid #f0f0f0;
  flex: none;
}

.notif-title {
  font-size: 16px;
  font-weight: 600;
  color: #1a1a1a;
}

.notif-actions {
  display: flex;
  gap: 8px;
  flex: none;
}

/* 遮罩内的关闭/已读按钮：白底上要能看见（.btn-outline 是白字） */
.notif-panel .btn-outline {
  border-color: #d9d9d9;
  color: #555;
}

.notif-panel .btn-outline:hover {
  background: #f5f5f5;
  color: #333;
}

/* 列表区独立滚动，标题行始终可见 */
#notifList {
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 12px 16px;
  flex: 1 1 auto;
  min-height: 0;
}

/* 空状态不需要撑高 */
#notifList > div:not([style*="display:flex"]) {
  min-height: 0;
}

/* 空状态：紧凑一些，别让卡片空荡荡 */
.notif-empty {
  text-align: center;
  padding: 24px 12px;
  color: #999;
}

/* 面板脚注：当前浏览器收不到关页通知的诚实说明（2026-10-21）
   只在被 pushCapability() 判为不可用的学生端出现；浅色底不与列表抢注意力 */
.notif-gate-note {
  flex: 0 0 auto;
  padding: 10px 16px 14px;
  border-top: 1px solid #f0f0f0;
  background: #fffbe6;
  color: #8c6d1f;
  font-size: 12px;
  line-height: 1.7;
}

.notif-gate-note b {
  color: #6b5310;
}

/* 常驻「开启通知」状态行（学生端，2026-10-22）
   放在列表上方、测试推送下方：状态比历史消息更该先看到。
   is-disabled = 已开启/不可用/已拒绝等终态，整体降饱和避免误以为还能点；
   is-error    = 开启失败/授权弹窗没弹出，红字 + 可重试（文案可能多行，故顶部对齐） */
.notif-push-toggle {
  flex: 0 0 auto;
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 10px 16px;
  border-bottom: 1px solid #f0f0f0;
  background: #f6f9ff;
  font-size: 13px;
  color: #333;
}

.notif-push-toggle-icon {
  flex: none;
  font-size: 16px;
  line-height: 1.45;
}

.notif-push-toggle-text {
  flex: 1 1 auto;
  min-width: 0;
  line-height: 1.6;
}

.notif-push-toggle-text b {
  color: #1f4e9c;
}

.notif-push-toggle .btn {
  flex: none;
  margin-top: 1px;
}

.notif-push-toggle.is-disabled {
  background: #fafafa;
  color: #999;
}

.notif-push-toggle.is-disabled .btn {
  color: #999;
  border-color: #e0e0e0;
  cursor: default;
}

.notif-push-toggle.is-error {
  background: #fff7f6;
}

.notif-push-toggle.is-error .notif-push-toggle-text,
.notif-push-toggle.is-error .notif-push-toggle-text b {
  color: #a8071a;
}

/* 诊断信息折叠块（2026-10-22 晚）：空订阅/订阅失败时给用户一键复制现场数据，
   替代「让你反复刷新」——没有现场数据只能靠猜。 */
.notif-push-diag {
  margin-top: 8px;
  font-size: 12px;
  line-height: 1.5;
}

.notif-push-diag summary {
  cursor: pointer;
  color: #888;
  outline: none;
}

.notif-push-diag pre {
  white-space: pre-wrap;
  word-break: break-all;
  background: #f6f8fa;
  border: 1px solid #e5e7eb;
  border-radius: 6px;
  padding: 8px;
  margin: 6px 0;
  font-size: 11px;
  line-height: 1.55;
  color: #333;
  max-height: 170px;
  overflow: auto;
}

.notif-push-diag .btn {
  font-size: 12px;
}

/* 测试推送区块（教师/管理员，2026-10-21）：验证 Web Push 长链路 */
.notif-testpush {
  flex: 0 0 auto;
  padding: 10px 16px;
  border-bottom: 1px solid #f0f0f0;
  background: #f6f9ff;
}

.notif-testpush-row {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

.notif-testpush-label {
  font-size: 13px;
  font-weight: 600;
  color: #1f4e9c;
  white-space: nowrap;
}

.notif-testpush-input {
  flex: 1 1 120px;
  min-width: 100px;
  max-width: 220px;
  padding: 6px 10px;
  border: 1px solid #d9d9d9;
  border-radius: 6px;
  font-size: 13px;
}

.notif-testpush-result {
  margin-top: 8px;
  font-size: 12px;
  line-height: 1.7;
  color: #666;
}

/* ============================================================
 * 推送引导弹窗（开启作业通知）
 *
 * 同样被手机档 .modal 的 100vw/100vh + padding:0 坑到：
 * 文字顶到屏幕边、底部两个按钮被挤出可视区（「以后再说」点不到）。
 * 保持居中卡片，但高度自适应内容 + 还原内边距。
 * ============================================================ */
.push-guide-panel {
  max-width: 380px;
  height: auto;
  max-height: calc(100vh - 80px);
  text-align: center;
  padding: 0;
}

.push-guide-body {
  padding: 4px 20px 20px;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}

.push-guide-actions {
  margin-top: 16px;
  display: flex;
  gap: 12px;
  justify-content: center;
}

/* 白底上的 .btn-outline：.btn-outline 默认是白字（为深色导航设计），
   这里必须显式改成深色，否则按钮看不见 —— 手机端已存在的隐性bug。 */
.push-guide-panel .btn-outline,
.push-guide-overlay .btn-outline {
  border-color: #d9d9d9;
  color: #555;
}

.push-guide-panel .btn-outline:hover {
  background: #f5f5f5;
  color: #333;
}

/* ============================================================
 * 底部导航栏
 * 基础样式放在这里（不再关在手机档内），使手机（≤768）与平板（769–1024）
 * 共用同一套DOM；仅在桌面档（≥1025）由下方 @media 隐藏。
 * ============================================================ */
.bottom-nav {
  display: flex;
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  height: 60px;
  background: white;
  border-top: 1px solid #e8e8e8;
  box-shadow: 0 -2px 10px rgba(0,0,0,0.05);
  z-index: var(--z-bottom-nav, 1000);
  padding-bottom: env(safe-area-inset-bottom);
}

.bottom-nav a {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-decoration: none;
  color: #999;
  font-size: 11px;
  gap: 2px;
  transition: color 0.2s;
}

.bottom-nav a .nav-icon {
  font-size: 20px;
  line-height: 1;
}

.bottom-nav a.active {
  color: #667eea;
}

.bottom-nav a:active {
  background: #f5f5f5;
}

/* ============================================================
 * 响应式
 * ============================================================ */
@media (max-width: 768px) {
  body {
    /* ★ 底部留白一律交给上面那条 body.has-bottom-nav（实测高度，含 iPhone
       安全区），这里不再写死 60px：
       ① 写死值在 iPhone 上会被安全区吃掉一截；
       ② 考试答题页会临时隐藏底部导航，写死后那里底部空出 60px。 */
    font-size: 15px;
  }

  .navbar {
    padding: 0 16px;
    height: 52px;
    flex-wrap: nowrap;
  }

  .nav-brand {
    font-size: 15px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 60%;
  }

  /* 手机端隐藏顶部导航菜单 */
  .nav-menu {
    display: none;
  }

  .nav-user {
    font-size: 13px;
    gap: 8px;
  }

  .nav-user #userInfo {
    display: inline;
    max-width: 120px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .nav-user .btn-sm {
    padding: 6px 12px;
    font-size: 13px;
    white-space: nowrap;
  }

  /* 手机端按钮更大，方便点击 */
  .btn {
    padding: 12px 20px;
    font-size: 15px;
    border-radius: 8px;
  }

  .btn-sm {
    padding: 8px 14px;
    font-size: 14px;
  }

  /* 手机端作业卡片按钮占满宽度 */
  .assignment-card .btn {
    width: 100%;
    margin-top: 12px;
  }

  /* 手机端数学公式区域可滚动，避免被截断 */
  .katex-content {
    overflow-x: auto;
    overflow-y: hidden;
    max-height: 180px;
    -webkit-overflow-scrolling: touch;
  }

  /* 手机端AI评语区域不受高度限制 */
  .katex-content.comment-content {
    max-height: none;
    overflow-y: auto;
    overflow-x: auto;
  }

  /* 手机端作业卡片中的公式预览限制高度 */
  .assignment-preview .katex-content {
    max-height: 120px;
    overflow: hidden;
    position: relative;
  }

  .assignment-preview .katex-content::after {
    content: '';
    position: absolute;
    bottom: 0;
    left: 0;
    right: 0;
    height: 30px;
    background: linear-gradient(transparent, #fafafa);
    pointer-events: none;
  }

  .main-content {
    padding: 0;
    padding-top: 0;
    padding-bottom: 16px; /* 让位交给 body.has-bottom-nav（实测高度，含安全区），这里只留呼吸空间 */
    min-height: calc(100vh - 52px - var(--bottom-nav-h));
    width: 100vw;
  }

  .card {
    padding: 16px;
    border-radius: 0;
    margin-bottom: 10px;
  }

  .card-title {
    font-size: 16px;
    margin-bottom: 12px;
  }

  .form-row {
    grid-template-columns: 1fr;
    gap: 12px;
  }

  .form-group {
    margin-bottom: 14px;
  }

  .form-label {
    font-size: 14px;
    margin-bottom: 6px;
  }

  .form-input, .form-select, .form-textarea {
    padding: 12px;
    font-size: 15px;
    border-radius: 8px;
  }

  .btn {
    padding: 12px 20px;
    font-size: 15px;
    border-radius: 8px;
    min-height: 44px;
  }

  .auth-card {
    padding: 24px 20px;
    margin: 20px auto;
  }

  .assignment-grid {
    grid-template-columns: 1fr;
    gap: 12px;
  }

  .assignment-card {
    padding: 16px;
  }

  .table {
    font-size: 13px;
  }

  .table th, .table td {
    padding: 10px 8px;
  }

  .score-display {
    padding: 20px;
  }

  .score-number {
    font-size: 48px;
  }

  .upload-area {
    padding: 30px 20px;
    min-height: 150px;
  }

  .upload-icon {
    font-size: 36px;
  }

  .upload-text {
    font-size: 14px;
  }

  .preview-image {
    max-height: 300px;
  }

  .modal-overlay {
    padding: 0;
  }

  /* 通知面板例外：不要满屏，保持浮层卡片观感。
     上一条 .modal-overlay{padding:0} 是通配，这里需要显式覆盖回来。 */
  .notif-overlay {
    padding: 16px;
    align-items: flex-start;
  }

  .notif-panel {
    margin-top: 52px;
    border-radius: 12px;
    max-height: calc(100vh - 96px);
  }

  /* 手机上按钮是触摸目标，按钮加大；标题行窄时允许换行 */
  .notif-actions .btn {
    min-height: 40px;
    padding: 8px 14px;
    font-size: 14px;
  }

  .notif-head {
    flex-wrap: wrap;
    padding: 12px 14px;
  }

  #notifList {
    padding: 12px 14px;
  }

  .push-guide-body {
    padding: 4px 18px 18px;
  }

  /* 弹窗内的按钮是触摸目标 */
  .push-guide-actions .btn {
    min-height: 44px;
    padding: 10px 18px;
  }

  /* 手机档遮罩去掉留白，让弹窗贴合可视区。
     ★ 原先 .modal 写死 100vh：① 遮罩有 padding:20px 时上下各被切掉 20px，
       底部「提交/保存」行正好少一截；② 100vh 不随软键盘变化，
       键盘一弹出，整条按钮行被顶到屏幕外。改成 100% 跟随遮罩实际高度。 */
  .modal-overlay {
    padding: 0;
  }

  /* 遮罩里不是 .modal 的居中卡片（铃铛面板、内联卡片等）补回边距，别贴死屏幕边缘 */
  .modal-overlay > *:not(.modal) {
    margin: 20px;
  }

  .modal {
    max-width: 100vw;
    max-height: 100%;
    width: 100vw;
    height: 100%;
    margin: 0;
    border-radius: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
  }

  /* 推送引导弹窗：必须写在上面 .modal 全屏规则【之后】。
     同等特异性下 CSS 后写的赢，写在前面会被 .modal 覆盖掉。 */
  .push-guide-panel {
    width: auto;
    height: auto;
    max-width: calc(100vw - 32px);
    max-height: calc(100vh - 96px);
    border-radius: 12px;
  }

  .modal-title {
    font-size: 16px;
    padding: 14px 16px;
    margin-bottom: 0;
    border-bottom: 1px solid #f0f0f0;
  }

  .modal-header {
    margin-bottom: 0;
    padding: 14px 16px;
    border-bottom: 1px solid #f0f0f0;
    flex-shrink: 0;
  }

  .modal-body {
    padding: 16px;
    flex: 1;
    overflow-y: auto;
  }

  /* iPhone 的 Home Indicator 是【系统层】，压在任何 z-index 之上，
     必须给底部按钮行留出安全区，否则横屏/非全面屏机型上手柄会压住按钮。 */
  .modal-footer {
    flex-shrink: 0;
    border-top: 1px solid #f0f0f0;
    padding: 12px 16px;
    padding-bottom: calc(12px + env(safe-area-inset-bottom, 0px));
    margin-top: 0;
  }

  .tag {
    font-size: 11px;
    padding: 2px 8px;
  }

  h2 {
    font-size: 18px;
    margin-bottom: 14px;
  }

  h3 {
    font-size: 16px;
  }

  h4 {
    font-size: 15px;
  }

  /* 班级标签栏横向滚动 */
  .class-filter-bar {
    overflow-x: auto;
    flex-wrap: nowrap;
    -webkit-overflow-scrolling: touch;
    padding-bottom: 4px;
  }

  .class-filter-bar::-webkit-scrollbar {
    display: none;
  }
}

/* 桌面端隐藏底部导航。
   iPad Pro 13" 横屏逻辑宽 1366，与 1366 宽的笔记本窗口同宽；
   区分靠「是否触摸设备」这个 body 类（tablet.js 打的 touch-device），
   所以这里必须写成「无触摸才隐藏」，否则 iPad 会漏掉底部导航。 */
@media (min-width: 1281px) {
  body:not(.touch-device) .bottom-nav {
    display: none;
  }
}

/* ============================================================
 * 平板档（769–1280px）——由 body.layout-tablet 驱动（tablet.js 判定）
 *
 * 问题背景：此前只有「手机≤768」和「桌面>768」两档，平板落进桌面档，
 * 顶部 9 个导航项被压成竖排单字，表格右侧「删除」按钮被裁出屏幕点不到。
 *
 * 平板档 = 宽度命中769–1366 且设备支持触摸。宽度上界用 JS 判定更准
 *（iPad 1194 / 1366 与笔记本同宽），CSS 段只覆盖 ≤1280；
 * 1281–1366 的触摸设备由下面的 .touch-device 补丁段补齐。
 * ============================================================ */
@media (min-width: 769px) and (max-width: 1280px) {
  body.layout-tablet {
    padding-bottom: 68px; /* 底部导航高度 */
  }

  /* 顶栏只留品牌 + 用户区，菜单整体隐藏交给底部导航 */
  body.layout-tablet .nav-menu {
    display: none;
  }

  body.layout-tablet .navbar {
    padding: 0 20px;
    height: 56px;
  }

  body.layout-tablet .nav-brand {
    font-size: 17px;
    white-space: nowrap;
  }

  body.layout-tablet .nav-user {
    font-size: 14px;
    /* 顶栏菜单在平板档是隐藏的（display:none 不占位），若不给 nav-user 推右边，
       它会紧贴左侧、看起来像顶栏空了一大片。margin-left:auto 把它顶到右侧。 */
    margin-left: auto;
    padding-left: 16px;
  }

  /* 主内容给底部导航让出空间，卡片保留圆角不铺满 */
  body.layout-tablet .main-content {
    padding: 20px;
    min-height: calc(100vh - 56px - var(--bottom-nav-h));
  }

  body.layout-tablet .card {
    padding: 18px;
    margin-bottom: 16px;
  }

  body.layout-tablet .card-title {
    font-size: 17px;
  }

  /* 触控目标统一 44px（平板是触摸设备，29px 的按钮手指点不准） */
  body.layout-tablet .btn {
    min-height: 44px;
    padding: 11px 18px;
    font-size: 15px;
  }

  body.layout-tablet .btn-sm {
    min-height: 44px;
    padding: 11px 16px;
    font-size: 14px;
  }

  /* 顶栏「退出登录」同样是触摸目标，不设例外 */
  body.layout-tablet .nav-user .btn-sm {
    min-height: 44px;
    padding: 10px 16px;
    font-size: 14px;
  }

  body.layout-tablet .form-input,
  body.layout-tablet .form-select,
  body.layout-tablet .form-textarea {
    font-size: 16px; /* ≥16px 可避免 iOS/安卓聚焦时自动放大页面 */
    padding: 12px;
    min-height: 44px;
  }

  body.layout-tablet .assignment-grid {
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: 16px;
  }

  /* 表格：字号略放大，单元格留足呼吸 */
  body.layout-tablet .table {
    font-size: 14px;
  }

  body.layout-tablet .table th,
  body.layout-tablet .table td {
    padding: 12px 10px;
  }

  body.layout-tablet .table th {
    font-size: 14px;
    white-space: nowrap;
  }

  /* ---------- 表格整行左滑 ---------- */
  /* 结构（由 tablet.js 生成）：
       <tr>  <td class="row-slide-cell"><div class="row-slide">…内容…</div></td> × N-1
             <td class="row-swipe-cell"><div class="row-slide">查看提交</div>
                                    <div class="row-swipe-actions">编辑 统计 删除</div></td>
     滑动时各格内的 .row-slide 统一左移 → 视觉上整行一起滑，
     右侧操作列腾出空间，底衬里的其余操作键露出。

     为什么不给 <tr> 直接加 transform：会破坏 table 内部布局（列宽塌陷）。
     所以改成「每格内容各包一层、各自位移」，等效且不破布局。

     宽度问题：这些表格是 table-layout:fixed，列宽由 <colgroup> 百分比锁死
     （作业列表操作列固定 22% ≈ 164px），放不下 4 个 44px 按钮。
     注意 fixed 布局下 min-width 对单元格无效，只能改 col 宽度。 */
  body.layout-tablet table.table col:last-child,
  body.layout-tablet table.table th:last-child,
  body.layout-tablet table.table td:last-child {
    width: 26% !important;
  }
  /* 滑开行的整体底色：与参考图一致，用中性浅灰而非淡蓝，
     避免和操作色块抢视觉。 */
  body.layout-tablet .table tbody tr.swiped > td {
    background: #f5f5f5;
  }

  /* 滑动层：位移载体 */
  body.layout-tablet .row-slide {
    transition: transform .24s cubic-bezier(.4, 0, .2, 1);
    will-change: transform;
    /* 允许纵向滚动页面不误触发横滑 */
    touch-action: pan-y;
  }

  /* 各数据格：溢出裁掉，左滑时内容从右侧推出格内 */
  body.layout-tablet td.row-slide-cell {
    overflow: hidden;
  }

  /* 操作列：滑动层宿主。padding 归零，让色块能贴到行边缘 */
  body.layout-tablet td.row-swipe-cell {
    position: relative;
    overflow: hidden;
    padding: 0 !important;
  }

  /* 操作区：滑开时从右端露出的并排色块，铺满整个操作列宽度。
     收起态必须 visibility:hidden —— 它是 absolute 定位且可能超出格宽，
     仅靠 pointer-events:none 挡不住它盖在「查看提交」上面。 */
  body.layout-tablet .row-swipe-actions {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    z-index: 1;
    display: flex;
    align-items: stretch;
    /* 铺满整列：色块按列宽均分，不留右侧空白。
       table 有 border-spacing:2px，不抵消的话色块上下各差 1px 露白边。 */
    width: 100%;
    margin: -1px -2px -1px 0;
    opacity: 0;
    visibility: hidden;
    transition: opacity .2s;
  }

  body.layout-tablet tr.swiped .row-swipe-actions {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
  }

  body.layout-tablet tr:not(.swiped) .row-swipe-actions {
    pointer-events: none;
  }

  /* 色块按钮：铺满整行高度、等宽并排、白字居中。
     背景色沿用各按钮原有的语义色（编辑蓝/统计紫/批改橙/删除红），
     不在这里另指定颜色，以免和 teacher.js 的按钮配色两处维护。 */
  body.layout-tablet .row-swipe-actions .btn {
    flex: 1 1 0;
    min-width: 0;
    min-height: 100%;
    height: 100%;
    margin: 0;
    padding: 0 14px;
    border: none;
    border-radius: 0;
    color: #fff;
    font-size: 15px;
    font-weight: 500;
    white-space: nowrap;
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: none;
  }

  /* 色块之间用分隔线而非留缝，避免看起来像散落的按钮 */
  body.layout-tablet .row-swipe-actions .btn + .btn {
    border-left: 1px solid rgba(255, 255, 255, .3);
  }
}

/* ============================================================
 * 平板档补丁段（1281–1366px 且为触摸设备）
 *
 * 覆盖 iPad Pro 13" 横屏（1366×1024）。这段与上面平板档的规则集相同，
 * 单独成段是因为宽度与笔记本窗口重叠，只能靠 body.touch-device 区分。
 * 维护提示：新增平板样式时记得同步到这里。
 * ============================================================ */
@media (min-width: 1281px) and (max-width: 1366px) {
  body.layout-tablet.touch-device {
    padding-bottom: 68px;
  }

  body.layout-tablet.touch-device .nav-menu {
    display: none;
  }

  body.layout-tablet.touch-device .navbar {
    padding: 0 20px;
    height: 56px;
  }

  body.layout-tablet.touch-device .nav-brand {
    font-size: 17px;
    white-space: nowrap;
  }

  /* 与主段一致：顶栏菜单隐藏后，把铃铛+用户信息+退出推到右侧 */
  body.layout-tablet.touch-device .nav-user {
    margin-left: auto;
    padding-left: 16px;
  }

  body.layout-tablet.touch-device .main-content {
    padding: 20px;
    min-height: calc(100vh - 56px - var(--bottom-nav-h));
  }

  body.layout-tablet.touch-device .btn {
    min-height: 44px;
    padding: 11px 18px;
    font-size: 15px;
  }

  body.layout-tablet.touch-device .btn-sm {
    min-height: 44px;
    padding: 11px 16px;
    font-size: 14px;
  }

  body.layout-tablet.touch-device .form-input,
  body.layout-tablet.touch-device .form-select,
  body.layout-tablet.touch-device .form-textarea {
    font-size: 16px;
    padding: 12px;
    min-height: 44px;
  }

  body.layout-tablet.touch-device table.table col:last-child,
  body.layout-tablet.touch-device table.table th:last-child,
  body.layout-tablet.touch-device table.table td:last-child {
    width: 26% !important;
  }

  /* 与主段一致：滑开行用淡蓝底，空白读起来是选中而非内容丢失 */
  body.layout-tablet.touch-device .table tbody tr.swiped > td {
    background: #f5f5f5;
  }



  /* 与主段一致的整行左滑样式（此段仅因宽度 >1280 而单列） */
  body.layout-tablet.touch-device .row-slide {
    transition: transform .24s cubic-bezier(.4, 0, .2, 1);
    will-change: transform;
    touch-action: pan-y;
  }

  body.layout-tablet.touch-device td.row-slide-cell {
    overflow: hidden;
  }

  body.layout-tablet.touch-device td.row-swipe-cell {
    position: relative;
    overflow: hidden;
    padding: 0 !important;
  }

  body.layout-tablet.touch-device .row-swipe-actions {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    z-index: 1;
    display: flex;
    align-items: stretch;
    width: 100%;
    margin: -1px -2px -1px 0;
    opacity: 0;
    visibility: hidden;
    transition: opacity .2s;
  }

  body.layout-tablet.touch-device tr.swiped .row-swipe-actions {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
  }

  body.layout-tablet.touch-device tr:not(.swiped) .row-swipe-actions {
    pointer-events: none;
  }

  /* 与主段一致：铺满行高的等宽色块 */
  body.layout-tablet.touch-device .row-swipe-actions .btn {
    flex: 1 1 0;
    min-width: 0;
    min-height: 100%;
    height: 100%;
    margin: 0;
    padding: 0 14px;
    border: none;
    border-radius: 0;
    color: #fff;
    font-size: 15px;
    font-weight: 500;
    white-space: nowrap;
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: none;
  }

  body.layout-tablet.touch-device .row-swipe-actions .btn + .btn {
    border-left: 1px solid rgba(255, 255, 255, .3);
  }
}

/* ===== 考试作答：内嵌输入框 / 辨析题判断控件（2026-10-18）===== */
/* 填空题把题干里的下划线换成「无边框、只有下划线」的输入框，尺寸与基线贴着正文 */
.exam-blank-input {
  border: 0;
  border-bottom: 1px solid #bfbfbf;
  background: transparent;
  padding: 0 2px;
  margin: 0 2px;
  font: inherit;
  color: inherit;
  height: 1.5em;
  line-height: 1.5;
  vertical-align: baseline;
  outline: none;
  min-width: 3em;
  max-width: 100%;
}
.exam-blank-input:focus {
  border-bottom-color: #1890ff;
}
/* 辨析题判断圆点（外观对齐判断题的选中态） */
.exam-judge-label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 8px 16px;
  border: 1px solid #d9d9d9;
  border-radius: 8px;
  cursor: pointer;
  font-size: 15px;
  background: #fff;
  color: #333;
  user-select: none;
}
.exam-judge-label.checked {
  border-color: #1890ff;
  background: #e6f7ff;
}
