/* ============================================================
 *  生成物，请勿手改：由 tools/build-css.js 从以下源文件拼接
 *  app.css
 *  重新生成：node tools/build-css.js
 * ============================================================ */

/* ---- app.css ---- */
/* ============================================================
   数字校园 · 主样式
   版式参考：Taskly 仪表盘（左侧栏 + 顶栏 + 卡片网格 + 右侧栏）
   配色：本校校徽（见 tokens.css）
   ============================================================ */

* { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0;
  font-family: var(--font);
  color: var(--ink);
  /* 2026-10-01 配色升级：纯浅灰绿底 → 极淡的绿/蓝双色光晕打底，
     白卡片"浮"起来，整屏不至于一片素白（都是极浅阶，不影响文字对比度） */
  background:
    radial-gradient(1100px 420px at 8% -8%, var(--acc-green-s) 0%, rgba(255, 255, 255, 0) 62%),
    radial-gradient(900px 400px at 96% 2%, var(--acc-teal-s) 0%, rgba(255, 255, 255, 0) 58%),
    var(--bg);
  background-attachment: fixed;
  -webkit-font-smoothing: antialiased;
}
a { color: inherit; text-decoration: none; }
button { font: inherit; cursor: pointer; }
:focus-visible {
  outline: 3px solid var(--focus-ring);
  outline-offset: 2px;
  border-radius: 6px;
}

/* ============ 骨架布局 ============ */
.app {
  display: grid;
  grid-template-columns: var(--side-w) 1fr;
  min-height: 100vh;
  transition: grid-template-columns .22s ease;
}
.app.mini { grid-template-columns: var(--side-w-mini) 1fr; }

/* ============ 左侧栏 ============ */
.side {
  background: var(--surface);
  border-right: 1px solid var(--line);
  padding: 18px 14px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  position: sticky;
  top: 0;
  height: 100vh;
  overflow: hidden;
}
.side-nav { display: flex; flex-direction: column; gap: 3px; flex: 1; overflow-y: auto; }
.nav-sec {
  font-size: 11px; color: var(--ink-3);
  padding: 14px 10px 6px;
  letter-spacing: .06em;
}
.nav-item {
  display: flex; align-items: center; gap: 11px;
  padding: 11px 12px;
  border-radius: var(--r-sm);
  color: var(--ink-2);
  font-size: 14px;
  transition: background .16s ease, color .16s ease, box-shadow .16s ease;
  white-space: nowrap;
  position: relative;
}
.nav-item svg { width: 19px; height: 19px; flex: none; }
.nav-item:hover { background: var(--green-050); color: var(--green-900); }
.nav-item.on {
  background: var(--green-800);
  color: var(--surface);
  font-weight: 600;
  box-shadow: var(--sh-1);
}
.nav-item .badge {
  margin-left: auto;
  background: var(--amber-100); color: var(--amber-900);
  font-size: 11px; font-weight: 700;
  padding: 2px 7px; border-radius: var(--r-pill);
  font-family: var(--font-num);
}
.nav-item.on .badge { background: rgba(255,255,255,.22); color: var(--surface); }

/* 侧栏底部：收起按钮 + 升级卡位（这里放"当前用户"更有用） */
/* 侧栏底部：身份块与品牌块已移到顶栏身份卡（2026-09-27），此处只剩「收起侧栏」。
   品牌块/身份块规则只服务管理后台，已在 admin.css 内声明。 */
.side-foot { border-top: 1px solid var(--line); padding-top: 12px; display: flex; flex-direction: column; gap: 10px; }
.mini .side-foot { align-items: center; }
.mini .collapse { width: auto; justify-content: center; padding: 8px 14px; }
.mini .nav-item span.t, .mini .nav-sec, .mini .side-foot .collapse-lbl { display: none; }
.mini .nav-item { justify-content: center; padding: 12px 0; }
/* 收起态：身份卡只留头像（居中），铃铛收起（展开侧栏可用） */
.mini .user-chip { justify-content: center; padding: 8px 0; margin: 0 0 12px; }
.mini .user-chip .uc, .mini .user-chip .bell-in { display: none; }

.collapse {
  display: flex; align-items: center; gap: 9px;
  background: none; border: 0; color: var(--ink-3);
  font-size: 13px; padding: 9px 12px; border-radius: var(--r-sm);
  width: 100%;
}
.collapse:hover { background: var(--line-soft); color: var(--ink-2); }

/* ============ 主区 ============ */
.main { min-width: 0; display: flex; flex-direction: column; }

/* 顶栏：只放搜索 + 通知 + 用户（导航桂教通自带，这里不做菜单） */
.top {
  height: var(--top-h);
  display: flex; align-items: center; gap: 16px;
  padding: 0 var(--pad);
  position: sticky; top: 0; z-index: 20;
  background: rgba(243,246,242,.86);
  -webkit-backdrop-filter: saturate(1.4) blur(10px);
  backdrop-filter: saturate(1.4) blur(10px);
  border-bottom: 1px solid var(--line-soft);
}
.top-context { font-size: 14px; font-weight: 700; color: var(--ink); letter-spacing: .2px; flex: none; margin-right: auto; }
body.in-app .top-context { display: none; }
@media (max-width: 640px) { .top-context { display: none !important; } }
.search {
  flex: 1; max-width: 520px;
  display: flex; align-items: center; gap: 10px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  padding: 10px 16px;
  box-shadow: var(--sh-1);
}
/* 顶栏校名 logo（2026-10-01 用户定：搜索框左侧、与搜索框垂直居中）
   ① height 写死 26px、width:auto —— 顶栏仍是 64px（--top-h），搜索框尺寸一点不变；
   ② flex:none —— 不参与伸缩，搜索框的 max-width/flex 规则照旧；
   ③ 与搜索框的间距直接吃 .top 既有的 gap:16px，不新增间距规则；
   ④ 小屏隐藏：手机上顶栏本来就窄，多一个 logo 会把搜索框压短。 */
.top .top-brand {
  flex: none;
  height: 26px; width: auto;
  display: block;
  user-select: none;
  -webkit-user-drag: none;
}
@media (max-width: 900px) {
  .top .top-brand { display: none; }
}
.search svg { width: 17px; height: 17px; color: var(--ink-3); flex: none; }
.search input {
  border: 0; outline: 0; background: none; width: 100%;
  font-size: 14px; color: var(--ink);
}
.search input::placeholder { color: var(--ink-3); }

.top-right { margin-left: auto; display: flex; align-items: center; gap: 10px; }
.icon-btn {
  width: 40px; height: 40px; border-radius: 50%;
  border: 1px solid var(--line); background: var(--surface);
  display: grid; place-items: center; color: var(--ink-2);
  box-shadow: var(--sh-1); position: relative;
}
.icon-btn:hover { color: var(--green-800); border-color: var(--green-100); }
.icon-btn svg { width: 18px; height: 18px; }
.icon-btn .dot {
  position: absolute; top: 8px; right: 9px;
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--brand-amber); border: 1.5px solid var(--surface);
}

/* 身份卡（2026-09-27）：位于**侧栏顶部**（首页导航之上），浅绿卡 + 右内嵌通知铃铛，
   整卡点击=退出登录。原先在顶栏右上角，按要求移到侧栏。 */
.user-chip {
  display: flex; align-items: center; gap: 10px;
  background: var(--green-050);
  border: 1px solid var(--green-100);
  border-radius: var(--r-md);
  padding: 8px;
  margin: 0 0 14px;
  cursor: pointer;
  transition: background .15s ease, border-color .15s ease;
}
.user-chip:hover { background: var(--green-100); }
.user-chip .av {
  width: 34px; height: 34px; border-radius: 50%;
  background: var(--green-800); color: var(--surface);
  display: grid; place-items: center; font-weight: 700; font-size: 13px; flex: none;
}
.user-chip .uc { display: flex; flex-direction: column; line-height: 1.3; min-width: 0; flex: 1; }
.user-chip .uc b { font-size: 13px; color: var(--green-900); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.user-chip .uc small { font-size: 11px; color: var(--ink-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 卡内铃铛：去掉独立按钮的描边与阴影，融进卡里靠右 */
.icon-btn.bell-in {
  width: 30px; height: 30px; flex: none;
  margin-left: auto;
  border: 0; background: transparent; box-shadow: none;
}
.icon-btn.bell-in:hover { background: var(--surface); color: var(--green-800); }
/* 未读数字徽标（2026-09-28：各应用通知统一汇聚到侧栏铃铛） */
.icon-btn .bell-badge {
  position: absolute; top: -6px; right: -7px;
  min-width: 16px; height: 16px; padding: 0 4px;
  border-radius: 999px; background: var(--brand-amber);
  color: var(--surface); font-size: 11px; font-weight: 700; line-height: 16px;
  text-align: center; border: 1.5px solid var(--surface);
  font-style: normal;
}
.icon-btn .bell-badge.hidden { display: none; }
.icon-btn.bell-in .bell-badge { top: -5px; right: -6px; }

/* 通知面板（点侧栏铃铛弹出，挂在身份卡下方） */
.notif-panel {
  position: fixed; z-index: 80;
  width: min(360px, calc(100vw - 24px));
  max-height: 60vh; overflow: auto;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-md, 12px);
  box-shadow: 0 14px 40px rgba(15, 30, 10, .18);
  padding: 12px;
}
.notif-panel .np-hd {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: 8px;
}
.notif-panel .np-hd h3 { margin: 0; font-size: 14px; color: var(--green-900); }
/* 2026-10-31：通知面板加关闭钮（原来只能点遮罩），跟「全部已读」排在一行 */
.notif-panel .np-close {
  margin-left: 8px; width: 26px; height: 26px; flex: 0 0 auto;
  border: 0; border-radius: 8px; background: transparent; color: var(--ink-3);
  font-size: 18px; line-height: 1; cursor: pointer;
}
.notif-panel .np-close:hover { background: var(--line-soft); color: var(--ink); }
.notif-panel .np-item { padding: 9px 6px; border-bottom: 1px dashed var(--line-soft); }
.notif-panel .np-item:last-child { border-bottom: 0; }
.notif-panel .np-item.unread { background: var(--green-050); border-radius: 8px; }
.notif-panel .np-item .n-t { font-size: 13px; color: var(--ink); word-break: break-all; }
.notif-panel .np-item .n-m { font-size: 11px; color: var(--ink-3); margin-top: 2px; display: flex; gap: 6px; align-items: center; }
.notif-panel .np-item .n-m .src { color: var(--green-800); }
.notif-panel .np-empty { padding: 18px 6px; text-align: center; color: var(--ink-3); font-size: 13px; }

/* ============ 内容区 ============ */
/* 2026-09-26 修：去掉 max-width:var(--max-w) —— 1560px 上限会把内嵌应用 iframe
   卡在这个宽度且左对齐，收起侧栏后右侧空白反而更大。改为完全随 .main 铺满。 */
.wrap {
  padding: 10px var(--pad) 40px;
  width: 100%;
}
/* ---- 首页网格：间距标准 / 主次比例 / 对齐方式（2026-09-29 重新设计）----
   · 间距标准：首页所有卡片间隙统一 14px（列间与卡间同一个值，不再混用 20px）；
   · 主次比例：主列 1fr（值日安排、文明班级积分两张主卡全宽）:
               次列 clamp(300px, 23vw, 360px)（天气、通知、课程表三张次卡，约主列 40%）；
   · 对齐方式：两列 stretch 等高，列内最后一张卡 flex:1 吸收高度差 → 左右底边对齐，
     消除原先两列各自收缩造成的底部参差。 */
.cols {
  display: grid;
  grid-template-columns: minmax(0, 1fr) clamp(300px, 23vw, 360px);
  gap: 14px;
  align-items: stretch;
}
.col-main { display: flex; flex-direction: column; gap: 14px; min-width: 0; }
.col-side { display: flex; flex-direction: column; gap: 14px; min-width: 0; }
/* 底边对齐：最后一张卡伸展占满列内剩余高度 */
.col-main .work-card { flex: 1; }
.col-side .notif-card { flex: 1; }

/* ---- 首页卡片：强调色体系 + 交互动效（2026-10-01 用户定：配色/动效统一升级）----
   设计原则：**结构完全一致**（同圆角、同描边、同阴影、同卡头、同运动节奏），
   只用「强调色」区分卡片身份 —— 顶部 3px 渐变条 + 卡头小色块 + 极浅顶部底色 + 悬停描边。
   强调色由 index.html 上的 acc-* 类给出，未标记的卡片回落到品牌绿。
   · 悬停：上浮 3px + 阴影加深 + 描边染色 + 色条加粗；
   · 按压：回落到接近原位并微缩；
   · 入场：淡入 + 上移 10px，依次错峰（间隔约 60-70ms）；
   · 系统开启「减少动态效果」时全部关闭。 */
@keyframes cardIn {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}
.cols .card {
  /* 默认（未标 acc-*）= 品牌绿 */
  --a-ink: var(--acc-green); --a-mid: var(--acc-green-m);
  --a-soft: var(--acc-green-s); --a-faint: var(--acc-green-f);
  position: relative;
  overflow: hidden;                                  /* 顶部色条跟着圆角裁剪 */
  background: linear-gradient(180deg, var(--a-faint) 0%, var(--surface) 44%);
  transition: transform var(--t-fast), box-shadow var(--t-fast),
              border-color var(--t-fast), background var(--t-fast);
  /* ⚠️ 填充只能用 backwards：用 both/forwards 的话，动画结束后会一直"填"着 transform: none，
     而动画样式优先级高于普通声明 —— 结果 :hover 的 translateY(-3px) 永远不生效（悬停不上浮）。
     2026-10-01 实测发现并修正。 */
  animation: cardIn var(--t-slow) ease backwards;
}
/* ★ 2026-10-19：入场动画只放**第一次**。
   `#homeCols` 切应用时会 display:none → block，CSS 动画会**重播**一遍，
   于是"从应用回首页"看着就是又一次跳转动画（与 iframe 的加载动画叠在一起）✗
   JS 在首屏动画跑完后给 `.cols` 加 `cards-done`，之后不再重播 ✓ */
.cols.cards-done .card { animation: none; }
.cols .card::before {
  content: ''; position: absolute; left: 18px; right: 18px; top: 0; height: 3px;
  border-radius: 0 0 4px 4px;
  background: linear-gradient(90deg, var(--a-mid), var(--a-soft));
  transition: height var(--t-fast);
}
.cols .card:hover {
  transform: translateY(-3px);
  box-shadow: var(--sh-hover);
  border-color: var(--a-mid);
}
.cols .card:hover::before { height: 4px; }
.cols .card:active { transform: translateY(-1px) scale(.998); }
/* 卡头标题前的小色块：一眼分辨是哪张卡，又不喧宾夺主 */
.cols .card .card-h h2 { display: flex; align-items: center; gap: 8px; }
.cols .card .card-h h2::before {
  content: ''; width: 4px; height: 15px; border-radius: 2px; flex: none;
  background: var(--a-mid);
}
/* 六张卡各自的强调色（放在默认规则之后，同特异性按顺序生效） */
.cols .acc-green  { --a-ink: var(--acc-green);  --a-mid: var(--acc-green-m);  --a-soft: var(--acc-green-s);  --a-faint: var(--acc-green-f); }
.cols .acc-teal   { --a-ink: var(--acc-teal);   --a-mid: var(--acc-teal-m);   --a-soft: var(--acc-teal-s);   --a-faint: var(--acc-teal-f); }
.cols .acc-blue   { --a-ink: var(--acc-blue);   --a-mid: var(--acc-blue-m);   --a-soft: var(--acc-blue-s);   --a-faint: var(--acc-blue-f); }
.cols .acc-violet { --a-ink: var(--acc-violet); --a-mid: var(--acc-violet-m); --a-soft: var(--acc-violet-s); --a-faint: var(--acc-violet-f); }
.cols .acc-amber  { --a-ink: var(--acc-amber);  --a-mid: var(--acc-amber-m);  --a-soft: var(--acc-amber-s);  --a-faint: var(--acc-amber-f); }
.cols .acc-rose   { --a-ink: var(--acc-rose);   --a-mid: var(--acc-rose-m);   --a-soft: var(--acc-rose-s);   --a-faint: var(--acc-rose-f); }
/* 卡片内的强调元素一律跟随本卡强调色 */
.cols .card .tag.n { background: var(--a-soft); color: var(--a-ink); }
.cols .card .duty-table thead th,
.cols .card .work-table thead th { background: var(--a-soft, var(--surface-2)); color: var(--a-ink, var(--ink-2)); }
.cols .card .duty-table tbody tr:hover td,
.cols .card .work-table tbody tr:hover td { background: var(--a-faint); }
.cols .card .course-select:focus {
  border-color: var(--a-mid); box-shadow: 0 0 0 3px var(--a-soft); outline: none;
}
.cols .card .btn {
  transition: background var(--t-fast), border-color var(--t-fast),
              color var(--t-fast), transform var(--t-fast);
}
.cols .card .btn:hover { border-color: var(--a-mid); transform: translateY(-1px); }
.cols .card .btn:active { transform: translateY(0); }
/* 选中态按钮跟随本卡强调色（比 .course-card .card-h .btn.on 多一层，确保生效） */
.cols .card .card-h .btn.on { background: var(--a-ink); border-color: var(--a-ink); color: var(--surface); }
/* 喊话卡的图标块与它自己的强调色一致（橙色=行动） */
.cols .card.shout-card .sh-ico { background: var(--a-soft, var(--green-050)); color: var(--a-ink, var(--green-900)); }
/* 通知条目悬停：淡淡的本卡强调色 + 左侧色条 */
.notif-card .nc-item { transition: background var(--t-fast), border-left-color var(--t-fast); }
.notif-card .nc-item:hover { background: var(--a-faint); border-left-color: var(--a-mid); }
.col-main .card:nth-of-type(1) { animation-delay: .05s; }
.col-main .card:nth-of-type(2) { animation-delay: .12s; }
.col-side .card:nth-of-type(1) { animation-delay: .02s; }
.col-side .card:nth-of-type(2) { animation-delay: .09s; }
.col-side .card:nth-of-type(3) { animation-delay: .16s; }

/* ---- 加载骨架屏（2026-10-01）：比一行「正在加载…」更像"内容马上到" ---- */
.sk-wrap { display: flex; flex-direction: column; gap: 10px; padding: 6px 2px 2px; }
.sk {
  height: 14px; border-radius: 8px;
  background: linear-gradient(90deg, var(--line-soft) 25%, var(--line-soft) 37%, var(--line-soft) 63%);
  background-size: 220% 100%;
  animation: skShimmer 1.25s linear infinite;
}
.sk-line.w80 { width: 80%; }
.sk-line.w60 { width: 60%; }
@keyframes skShimmer {
  from { background-position: 120% 0; }
  to   { background-position: -60% 0; }
}

/* ---- 内容切换过渡（切周次 / 刷新后重新渲染）：淡入 + 轻微上移 ---- */
@keyframes fadeSlideIn {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}
.fade-in { animation: fadeSlideIn var(--t-mid) both; }

@media (prefers-reduced-motion: reduce) {
  .cols .card { animation: none; transition: none; }
  .cols .card:hover { transform: none; }
  .cols .card::before { transition: none; }
  .fade-in, .sk { animation: none; }
}

/* 问候语 */
.hello { padding: 12px 2px 4px; display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; }
.hello h1 {
  margin: 0; font-size: 24px; font-weight: 800;
  color: var(--green-900); letter-spacing: .01em;
}
.hello h1 .em { font-size: 22px; }
.hello p { margin: 6px 0 0; color: var(--ink-2); font-size: 14px; }
/* 2026-11-07：问候区右侧补当天日期/周次，避免右侧整片空 */
.hello-side { display: flex; flex-direction: column; align-items: flex-end; gap: 3px; flex: none; padding-top: 4px; text-align: right; }
.hello-side .hd { font-size: 14px; font-weight: 700; color: var(--ink); }
.hello-side .hw { font-size: 12px; color: var(--ink-3); }
/* 每周寄语可能是一句完整的话（比原来那句问候语长）：最多两行，超出省略，
   不让长寄语把下面的卡片顶下去（2026-10-01） */
#helloSlogan {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; line-height: 1.5; max-width: 46em;
}

/* 卡片 */
.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-card, 14px);
  box-shadow: var(--sh-1);
  padding: 20px;
}
.card-h {
  display: flex; align-items: center; gap: 10px;
  margin-bottom: 16px;
}
.card-h h2 {
  margin: 0; font-size: 16px; font-weight: 700; color: var(--ink);
}
.card-h .sp { margin-left: auto; display: flex; align-items: center; gap: 8px; }
/* 首页卡片内边距标准（2026-09-29）：上下 16 / 左右 18，卡头与内容间距 12——
   只作用于首页 .cols 内的卡片（原 20px 内边距 + 16px 卡头距留白偏大），
   其它页面的卡片维持原样。 */
.cols .card { padding: 16px 18px; }
.cols .card-h { margin-bottom: 12px; }

/* 统计小卡（参考图顶部四宫格）—— 已随首页改版移除渲染，样式暂留备用 */
.stats {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--gap);
}

/* ---------- 本周值日卡片（数据源：智能导入） ---------- */
.duty-card { margin-top: 0; }
/* 2026-11-07：值日未发布提示：文字 + 操作按钮同行，手机端上下堆叠 */
#dutyCurNote { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
#dutyCurNote .btn { margin-left: 0 !important; }
@media (max-width: 640px) {
  #dutyCurNote { flex-direction: column; align-items: flex-start; }
  #dutyCurNote .btn { width: 100%; }
}   /* 间距统一交给 .col-main 的 gap（2026-09-29 排版重构） */
.duty-card .sp { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.duty-scroll { overflow-x: auto; }
/* 仿纸质值日表：首列固定行名，列头为星期 */
.duty-table {
  width: 100%; border-collapse: collapse; font-size: 13px;
  border: 1px solid var(--line);
}
.duty-table th, .duty-table td {
  border: 1px solid var(--line);
  padding: 9px 10px; text-align: center; vertical-align: middle;
}
.duty-table thead th {
  background: var(--surface-2, var(--surface));
  font-weight: 700; color: var(--ink); white-space: nowrap;
}
.duty-table .duty-k {
  background: var(--surface-2, var(--surface));
  font-weight: 700; color: var(--ink-2); white-space: nowrap; font-size: 12px;
}
.duty-table td.duty-many { font-size: 12px; line-height: 1.6; color: var(--ink-2); }
/* 「下周星期一」预告列（2026-10-01）：与本周各天隔一道虚线，一眼看出这是下周的 */
.duty-table th.duty-next-h, .duty-table td.duty-next {
  background: var(--surface-2, var(--surface));
  border-left: 2px dashed var(--line-strong, var(--line));
  white-space: nowrap;
}
.duty-next-h .duty-next-t { display: block; font-weight: 700; }
.duty-next-h .duty-next-d {
  display: block; margin-top: 2px; font-weight: 400; font-size: 11px; color: var(--ink-3, var(--ink-3));
}
.duty-table .duty-none { color: var(--line-strong, var(--line)); }
.duty-hint { margin: 8px 0 0; font-size: 12px; line-height: 1.6; }
/* 2026-10-18：「本周工作要点」折叠块整块删除（DOM 已不再生成），样式一并清掉 */
/* 窄屏：横向滚动已由 .duty-scroll 兜底，这里只收紧字号 */
@media (max-width: 640px) {
  .duty-table { font-size: 12px; }
  .duty-table th, .duty-table td { padding: 7px 6px; }
}

/* ---------- 值日卡片 · 周次选择（2026-09-28 用户定：周次选择并入值日卡片） ---------- */
.course-weekbar { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.course-select {
  padding: 5px 8px; font-size: 13px; border-radius: var(--r-sm, 6px);
  border: 1px solid var(--line); background: var(--surface); color: var(--ink);
  font-family: inherit;
  /* 触控目标下限：内容短（如"第 6 周"）时这个下拉只有 38px 宽，卡在 40px 门槛上，
     e2e-mobile 的"独立控件 ≥40px"断言会**偶发**报红（2026-10-08 遇到一次）。给个下限一劳永逸。 */
  min-width: 44px;
}

/* ---------- 首页 · 通知中心卡片（2026-09-28 用户定；点击跳转对应应用） ---------- */
.notif-card { margin: 0; }
.notif-card .card-h .tag { font-weight: 700; }
.nc-item {
  padding: 8px 6px; border-bottom: 1px solid var(--line-soft, var(--line-soft));
  cursor: pointer; border-left: 3px solid transparent;
}
.nc-item:last-child { border-bottom: 0; }
.nc-item:hover { background: var(--bg); }
.nc-item.unread { border-left-color: var(--green, #1e7a4f); background: var(--surface-2); }
.nc-item .nc-t { font-size: 13px; line-height: 1.5; }
.nc-item.unread .nc-t { font-weight: 700; }
/* 三行排版的第二行：「标题」（面向：…）——弱化色、最多两行截断 */
.nc-item .nc-t2 {
  font-size: 12px; color: var(--ink-2, var(--ink-2)); line-height: 1.6; margin-top: 1px;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.nc-item.unread .nc-t2 { color: var(--ink, var(--ink)); }
.nc-item .nc-m { display: flex; gap: 10px; margin-top: 3px; font-size: 11px; color: var(--ink-2, var(--ink-2)); }
.nc-item .nc-m .src { color: var(--green-d, var(--green-900)); font-weight: 600; }
.nc-item .nc-m b { color: var(--amber-800); font-weight: 700; }
/* 每条通知右侧的 ×（2026-10-08 用户定）：常态淡、悬停才明显，避免抢正文注意力。
   24×24 是最小可点面积（手机上也能点中），条目因此要留出右侧空间。 */
.nc-item { position: relative; padding-right: 26px; }
.nc-item .nc-x, .np-item .np-x {
  position: absolute; top: 4px; right: 2px;
  width: 22px; height: 22px; line-height: 1;
  display: flex; align-items: center; justify-content: center;
  border: 0; border-radius: 50%; cursor: pointer;
  background: transparent; color: var(--ink-3);
  font-size: 15px; padding: 0; opacity: .55;
  transition: background var(--t-fast, .15s), color var(--t-fast, .15s), opacity var(--t-fast, .15s);
}
.nc-item:hover .nc-x, .np-item:hover .np-x { opacity: 1; }
.nc-item .nc-x:hover, .np-item .np-x:hover { background: var(--red-050, #fee2e2); color: var(--red-700, #b91c1c); opacity: 1; }
.nc-item .nc-x:disabled, .np-item .np-x:disabled { opacity: .3; cursor: default; }
.notif-panel .np-item { position: relative; padding-right: 26px; }
.nc-more { padding: 8px 6px 2px; font-size: 11px; color: var(--ink-3, var(--ink-3)); text-align: center; }

/* ---------- 主列顶部信息行（2026-09-29 用户定）：天气(方) + 远程喊话(方) + 通知(长) ----------
   三卡等高对齐：行内 stretch；两张方卡用 aspect-ratio:1 锁定正方形，通知卡占满剩余宽度。
   间距沿用首页标准 14px。 */
.top-row {
  display: grid;
  grid-template-columns: 220px 220px minmax(0, 1fr);
  grid-auto-rows: 220px;          /* 行高 220：两张方卡 220×220 正方形，课程表同高（列表内部滚动） */
  gap: 14px;
  align-items: stretch;
}
.top-row > .card {
  margin: 0; display: flex; flex-direction: column; min-width: 0;
  overflow: hidden;               /* 内容超出时裁剪，不允许撑破轨道造成卡片重叠 */
}
.top-row .course-card { flex: 1; }
.top-row .course-card .course-body { overflow-y: auto; min-height: 0; }
.notif-card #ncList { flex: 1; min-height: 0; overflow-y: auto; }
/* 天气卡内容按方卡紧凑排版 */
.top-row .weather-card { padding: 12px 14px; }
.top-row .weather-card .card-h { margin-bottom: 6px; }
/* ≤900px：两张方卡并排（恢复 aspect-ratio 保持正方形），课程表换行占满；≤600px 全部单列 */
@media (max-width: 900px) {
  .top-row { grid-template-columns: 1fr 1fr; grid-auto-rows: auto; }
  .top-row .weather-card, .top-row .shout-card { aspect-ratio: 1 / 1; }
  .top-row .course-card { grid-column: 1 / -1; }
  .top-row .course-card .course-body { overflow-y: visible; }
}
@media (max-width: 640px) {
  .top-row { grid-template-columns: 1fr; }
  .top-row .weather-card, .top-row .shout-card { aspect-ratio: auto; }
}

/* ---------- 首页 · 日期与天气卡片（2026-09-29 新增） ---------- */
.weather-card .card-h h2 { font-size: 14px; letter-spacing: .2px; white-space: nowrap; }

/* 远程喊话卡（正方形入口）：点击内嵌打开班务助手（老师端默认页签即喊话） */
.shout-card {
  cursor: pointer; justify-content: center; align-items: center;
  text-align: center; gap: 7px;
}
.shout-card .sh-ico {
  width: 50px; height: 50px; border-radius: 14px; flex: none;
  display: flex; align-items: center; justify-content: center;
  background: var(--green-050); color: var(--green-d, var(--green-900));
}
.shout-card h3 { margin: 0; font-size: 15px; font-weight: 800; color: var(--ink, var(--ink)); }
.shout-card p { margin: 0; font-size: 12px; color: var(--ink-2, var(--ink-2)); }
.shout-card .sh-tip { font-size: 11px; color: var(--ink-3, var(--ink-3)); }
/* 天气卡为正方形：三行文字排版（2026-09-29 用户定）：地名 / 今日区间+湿度 / 全天天气 */
.wx-body {
  flex: 1; display: flex; flex-direction: column;
  justify-content: center; align-items: flex-start;
  gap: 7px; padding: 0; min-width: 0;
}
.wx-line1 { font-size: 14px; font-weight: 800; color: var(--ink, var(--ink)); line-height: 1.3; }
.wx-line2 { font-size: 13px; color: var(--ink-2, var(--ink-2)); line-height: 1.5; overflow-wrap: anywhere; }
.wx-line3 { font-size: 12px; color: var(--ink-2, var(--ink-2)); line-height: 1.5; }

/* ---------- 首页 · 课程表卡片（最右独立列；2026-09-28 用户定：宽度约为主列一半） ---------- */
.course-card { margin: 0; display: flex; flex-direction: column; min-width: 0; }
.course-card .card-h { flex-wrap: wrap; }
.course-card .card-h .btn { padding: 4px 9px; font-size: 12px; }
.course-card .card-h .btn.on { background: var(--green-d, var(--green-900)); border-color: var(--green-d); color: var(--surface); }
.course-body { flex: 1; min-height: 0; }
/* 课程卡左右分栏（2026-09-29 用户定）：默认视图=今日|明日；班级课表=第1~3节|第4~6节 */
.crs-cols {
  display: grid; grid-template-columns: 1fr 1fr;
  gap: 10px; align-items: start;
}
.crs-col { min-width: 0; }
.crs-col-h {
  display: flex; align-items: center;
  font-size: 12px; font-weight: 700; color: var(--ink-2, var(--ink-2));
  padding: 0 2px 5px; margin-bottom: 1px;
  border-bottom: 1px solid var(--line, var(--line));
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.crs-empty { margin: 8px 0 0; font-size: 12px; }
/* 窄栏下课程行收紧：节次 pill 自适应宽度，课程名/班级省略，不出现横向溢出 */
.crs-cols .crs-item { gap: 6px; padding: 6px 2px; font-size: 12px; }
.crs-cols .crs-p { width: auto; padding: 3px 7px; font-size: 11px; }
.crs-cols .crs-s { font-size: 12px; }
.crs-cols .crs-c { font-size: 11px; }
/* ≤600px 单列堆叠 */
@media (max-width: 640px) { .crs-cols { grid-template-columns: 1fr; } }

.crs-list { display: flex; flex-direction: column; }
.crs-item {
  display: flex; align-items: center; gap: 12px;
  padding: 8px 6px; border-bottom: 1px solid var(--line-soft, var(--line-soft)); font-size: 13px;
}
.crs-item:last-child { border-bottom: 0; }
.crs-p {
  flex: none; width: 52px; text-align: center; font-weight: 700; font-size: 12px;
  color: var(--green-d, var(--green-900)); background: var(--green-050); border-radius: 6px; padding: 3px 0;
}
.crs-s { font-weight: 700; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 课程时间（2026-10-01）：位置在「第X节」与课程名之间；等宽数字让各行时间对齐 */
.crs-t {
  flex: none; min-width: 88px; color: var(--ink-3, var(--ink-3)); font-size: 11px;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.crs-c { color: var(--ink-2, var(--ink-2)); font-size: 12px; white-space: nowrap; }
/* 窄屏课程列表更紧凑 */
@media (max-width: 640px) {
  .crs-item { gap: 8px; padding: 7px 4px; font-size: 13px; }
  .crs-p { width: 44px; }
}

/* ---------- 首页 · 每周工作内容卡片（2026-10-01 用户定：替换原「文明班级积分」卡片；
     数据源：智能导入的周安排文档—— 周次 / 星期几 / 主要工作 / 部门 / 负责人） ----------
     样式沿用原卡片观感：同款线框表格、表头浅底；星期列窄且居中，主要工作左对齐并保留换行 */
.work-card { margin-top: 0; }   /* 间距统一交给 .col-main 的 gap */
.work-scroll { overflow-x: auto; }
.work-table { width: 100%; border-collapse: collapse; font-size: 13px; border: 1px solid var(--line, var(--line)); }
.work-table th, .work-table td {
  border: 1px solid var(--line, var(--line)); padding: 8px 10px;
  text-align: center; vertical-align: middle;
}
.work-table thead th { background: var(--surface, var(--surface-2)); font-weight: 700; white-space: nowrap; }
.work-table .wk-day { width: 96px; font-weight: 600; color: var(--ink-2, var(--ink-2)); white-space: nowrap; }
.work-table .wk-work { text-align: left; line-height: 1.7; color: var(--ink, var(--ink)); }
.work-table .wk-dept { width: 20%; color: var(--ink-2, var(--ink-2)); }
.work-table .wk-owner { width: 18%; color: var(--ink-2, var(--ink-2)); }
.work-none { color: var(--line-strong, var(--line)); }
/* 窄屏：字号收紧，横向滚动兜底 */
@media (max-width: 640px) {
  .work-table { font-size: 12px; }
  .work-table th, .work-table td { padding: 6px 6px; }
  .work-table .wk-day { width: 78px; }
}

/* ---------- 远程喊话弹层（首页内直接喊话，不跳转班务助手；2026-09-29） ---------- */
.shout-mask {
  position: fixed; inset: 0; z-index: 300;
  background: rgba(15, 23, 42, .45);
  display: flex; align-items: center; justify-content: center;
  padding: 24px;
}
.shout-mask.hidden { display: none; }
.shout-box {
  background: var(--surface, var(--surface));
  border-radius: var(--r-lg, 14px);
  box-shadow: var(--sh-2, 0 18px 50px rgba(0, 0, 0, .25));
  width: min(760px, 96vw); height: min(78vh, 760px);
  display: flex; flex-direction: column; overflow: hidden;
}
.shout-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 12px 16px; border-bottom: 1px solid var(--line, var(--line));
}
.shout-head h3 { margin: 0; font-size: 15px; font-weight: 800; }
.shout-actions { display: flex; gap: 10px; align-items: center; }
.shout-newwin { font-size: 12px; color: var(--green-d, var(--green-900)); }
.shout-newwin:hover { text-decoration: underline; }
.shout-tip {
  margin: 0; padding: 8px 16px; font-size: 12px;
  color: var(--ink-2, var(--ink-2)); background: var(--surface-2);
  border-bottom: 1px solid var(--line, var(--line));
}
.shout-frame { flex: 1; min-height: 0; width: 100%; border: 0; display: block; }

/* ---------- 值日统计弹窗（嵌入统计仪表盘分享页） ---------- */
.dstat-mask {
  position: fixed; inset: 0; z-index: 300;
  background: rgba(15, 23, 42, .45);
  display: flex; align-items: center; justify-content: center;
  padding: 12px;                    /* 原来是 24px：统计仪表盘列多，边距能省一点是一点 */
}
.dstat-mask.hidden { display: none; }
.dstat-box {
  background: var(--surface, var(--surface));
  border-radius: var(--r-lg, 14px);
  box-shadow: var(--sh-2, 0 18px 50px rgba(0, 0, 0, .25));
  /* 2026-10-01 用户反馈「统计弹窗没把信息展示完、右侧被切」→ 放大默认窗口 */
  width: min(1500px, 98vw); height: min(94vh, 1200px);
  display: flex; flex-direction: column; overflow: hidden;
}
/* 全屏（点弹窗右上角「全屏」）：铺满整个屏幕，多出来的列也有位置 */
.dstat-mask.full { padding: 0; }
.dstat-mask.full .dstat-box {
  width: 100vw; height: 100vh; max-width: none; border-radius: 0;
}
/* 窄屏（手机/平板竖屏）：直接铺满，别留边距浪费本来就小的屏 */
@media (max-width: 900px) {
  .dstat-mask { padding: 0; }
  .dstat-box { width: 100vw; height: 100vh; border-radius: 0; }
}
.dstat-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 12px 16px; border-bottom: 1px solid var(--line); flex: none;
}
.dstat-head h3 { margin: 0; font-size: 16px; }
.dstat-actions { display: flex; align-items: center; gap: 10px; }
.dstat-newwin { font-size: 13px; color: var(--green-800, var(--ink-2)); text-decoration: none; }
.dstat-newwin:hover { text-decoration: underline; }
/* 内嵌页的显示区。「缩放」的做法是**把 iframe 放大再整体缩回来**
   （wrapper 裁切 + transform: scale），这样统计仪表盘按更宽的尺寸布局，
   7 列这类宽表就能整表放下，而不是只能在里面左右拖（2026-10-01）。
   ⚠️ 不能用 iframe 的 zoom：实测内层可用宽度会跟着变小（越缩越挤）。 */
.dstat-view { flex: 1; position: relative; overflow: hidden; background: var(--surface); }
.dstat-frame {
  position: absolute; left: 0; top: 0; border: 0; background: var(--surface);
  width: calc(100% * var(--dzr, 1));      /* dzr = 1 / 缩放 */
  height: calc(100% * var(--dzr, 1));
  transform: scale(var(--dz, 1));
  transform-origin: 0 0;
}
.dstat-zoom {
  display: flex; align-items: center; gap: 4px;
  font-size: 12px; color: var(--ink-3, var(--ink-3));
}
.dstat-zoom select {
  font: inherit; padding: 3px 6px; border-radius: var(--r-sm, 6px);
  border: 1px solid var(--line); background: var(--surface); color: var(--ink);
}
.stat {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-1);
  padding: 18px;
  display: flex; flex-direction: column; gap: 10px;
  position: relative;
  overflow: hidden;
  min-width: 0;          /* 不加这行：网格里的卡片会被长文案顶宽，撑破四宫格 */
}
.stat .ico {
  width: 42px; height: 42px; border-radius: var(--r-sm);
  display: grid; place-items: center; flex: none;
}
.stat .ico svg { width: 21px; height: 21px; }
.stat .k { font-size: 13px; color: var(--ink-2); font-weight: 600; }
/* 大字值：**只放短词**（数字 / 词条），长文案一律走 .d 副标题 —— 参考图的
   "8,587 / Total tasks" 就是这个结构。用 clamp + nowrap 兜住，不要指望换行好看。 */
.stat .v {
  font-family: var(--font-num);
  font-size: clamp(18px, 2.1vw, 30px);
  font-weight: 800; line-height: 1.2;
  color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.stat .d { font-size: 12px; color: var(--ink-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.stat .d b { color: var(--green-800); font-weight: 700; }
/* 四色衬：色调来自校徽，面用浅底 + 深色图标，保证看得清 */
.stat.s1 .ico { background: var(--green-100); color: var(--green-800); }
.stat.s2 .ico { background: #e6f6ee; color: var(--green-800); }
.stat.s3 .ico { background: var(--amber-100); color: var(--amber-900); }
.stat.s4 .ico { background: var(--danger-bg); color: var(--danger); }

/* 应用网格 */
.apps {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(258px, 1fr));
  gap: 16px;
}
.app-card {
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: 18px;
  background: var(--surface-2);
  display: flex; flex-direction: column; gap: 12px;
  transition: transform .16s ease, box-shadow .16s ease, border-color .16s ease;
  position: relative;
}
.app-card:hover {
  transform: translateY(-2px);
  box-shadow: var(--sh-2);
  border-color: var(--green-100);
}
.app-card .ai {
  width: 46px; height: 46px; border-radius: 13px;
  display: grid; place-items: center;
  color: var(--surface); flex: none;
}
.app-card .ai svg { width: 23px; height: 23px; }
.app-card h3 { margin: 0; font-size: 15px; font-weight: 700; color: var(--ink); }
.app-card p { margin: 0; font-size: 13px; color: var(--ink-2); line-height: 1.6; }
.app-card .go {
  margin-top: auto; display: flex; align-items: center; gap: 6px;
  font-size: 13px; font-weight: 700; color: var(--green-800);
}
.app-card .go svg { width: 15px; height: 15px; }
/* 各应用图标底色：实心深色 + 白图标，保证对比度 */
.app-card.a1 .ai { background: var(--green-800); }
.app-card.a2 .ai { background: var(--green-800); }
.app-card.a3 .ai { background: var(--amber-800); }
.app-card.a4 .ai { background: var(--info); }
.app-card.a5 .ai { background: var(--acc-violet); }

/* 权限锁态 */
.app-card.locked { opacity: .62; }
.app-card.locked .go { color: var(--ink-3); }
.lock-tag {
  position: absolute; top: 14px; right: 14px;
  font-size: 11px; font-weight: 700;
  background: var(--warn-bg); color: var(--warn);
  padding: 3px 9px; border-radius: var(--r-pill);
}

/* 空/未接入态 */
.app-card.soon .ai { background: var(--line); color: var(--ink-3); }
.app-card.soon .go { color: var(--ink-3); }

/* 侧栏小卡：日程 */
.mini-list { display: flex; flex-direction: column; gap: 2px; }
.mini-item {
  display: flex; align-items: center; gap: 11px;
  padding: 10px 8px; border-radius: var(--r-sm);
}
.mini-item:hover { background: var(--green-050); }
.mini-item .mi {
  width: 34px; height: 34px; border-radius: 10px;
  display: grid; place-items: center; flex: none;
  background: var(--green-100); color: var(--green-800);
  font-family: var(--font-num); font-weight: 800; font-size: 13px;
}
.mini-item .mt { min-width: 0; display: flex; flex-direction: column; }
.mini-item .mt b { font-size: 13px; color: var(--ink); font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mini-item .mt small { font-size: 11px; color: var(--ink-3); }
.mini-item .tag {
  margin-left: auto; font-size: 11px; font-weight: 700;
  padding: 3px 8px; border-radius: var(--r-pill); flex: none;
}
.tag.g { background: var(--green-100); color: var(--green-900); }
.tag.a { background: var(--amber-100); color: var(--amber-900); }
.tag.i { background: var(--info-bg); color: var(--info); }

/* 未登录入口层（2026-09-30 用户定：门户自己的账号密码登录）
   版面上只有三样：横向校名 logo、账号/密码、登录按钮 —— **不放任何说明文字**。
   要点：logo 用横向校名图（2268×351），宽度撑满卡片内容区、居中不倒边；
   输入框和按钮同宽同行高（13px 上下内边距），整卡窄一点（400px）更聚气。 */
.gate {
  position: fixed; inset: 0; z-index: 100;
  /* 2026-11-07：去掉两层绿色光晕与 26 秒飘动 —— 登录页回到纯底色，
     品牌感交给校徽、留白与卡片本身。 */
  background: var(--bg);
  display: grid; place-items: center;
  padding: 24px;
}
.gate .box {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 20px;
  box-shadow: var(--sh-3);
  padding: 40px 36px 36px;
  /* 2026-10-01 用户定：logo 别太大 → 卡片收窄到 360（内容区 288），
     logo 与输入框同宽，左右就落在同一条竖线上（见下面的 .brand） */
  width: min(360px, 100%);
  text-align: center;
  /* 进场：轻轻上浮 + 淡入（0.42s，不拖沓） */
  animation: boxIn .42s cubic-bezier(.22, .8, .28, 1) both;
}
@keyframes boxIn {
  from { opacity: 0; transform: translateY(14px) scale(.985); }
  to { opacity: 1; transform: none; }
}
/* 登录成功：卡片淡淡退回，再进主界面（js 里 240ms 后 reload） */
.gate .box.bye { animation: boxOut .24s ease-in forwards; }
@keyframes boxOut {
  to { opacity: 0; transform: translateY(-8px) scale(.99); }
}
/* 登录失败：抖一下（js 加 .shake 类，去掉再加重放动画） */
.gate .box.shake { animation: shake .36s cubic-bezier(.36, .07, .19, .97); }
@keyframes shake {
  10%, 90% { transform: translateX(-3px); }
  20%, 80% { transform: translateX(5px); }
  30%, 50%, 70% { transform: translateX(-7px); }
  40%, 60% { transform: translateX(7px); }
}
/* ⚠️ 2026-10-11 首屏优化：logo 外面套了一层 <picture>（优先取 WebP ✓，老浏览器回退 PNG ✓）。
   `display:contents` 让这层"壳"**不产生任何盒子** —— logo 的排版与原来**逐像素一致** ✓
   （否则 <picture> 默认 inline，会在 logo 上下多出一个行盒，间距就变了 ✗） */
picture { display: contents; }
/* 横向校名 logo：**与输入框同宽**（width:100%，不设 max-width），
   这样 logo 的左右边缘 = 输入框的左右边缘，看上去是一条整齐的列。
   高度按原比例（1100×207 → 288 宽时约 54 高）。
   进场比卡片稍晚一点、自下而上淡入，视线自然落在校名上。 */
.gate .box .brand {
  display: block;
  width: 100%; height: auto;
  margin: 0 auto 22px;
  animation: brandIn .5s cubic-bezier(.22, .8, .28, 1) .07s both;
}
@keyframes brandIn {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: none; }
}
.gate .box .row { display: flex; flex-direction: column; gap: 11px; }

/* ---- 拒绝层（门户后台 /admin.html）专用的排版：方形校徽 + 三段式 ----
   为什么要单开一段：首页那条 gate 用的是**横向校名** logo-h.png，靠 `.brand { width:100% }`
   跟输入框左右对齐；而拒绝层用的是**方形校徽** logo.png，套同一套尺寸就会被撑满
   （250px 的校徽压着小字标题，卡片上下空档也不匀）—— 2026-10-06 用户报"布局不合理"。
   这里把校徽定成 96px（一眼认出本校，又不抢标题），标题/说明/按钮按视觉节奏排开。 */
.gate .box.gd { padding: 34px 34px 30px; }
.gate .box.gd .gd-logo {
  display: block; width: 96px; height: 96px;
  margin: 0 auto 16px;
  animation: brandIn .5s cubic-bezier(.22, .8, .28, 1) .07s both;
}
.gate .box.gd h2 {
  margin: 0 0 9px;
  font-size: 19px; font-weight: 700; letter-spacing: .3px; color: var(--ink);
}
.gate .box.gd .msg {
  margin: 0 auto 6px; max-width: 22em;
  font-size: 14px; line-height: 1.62; color: var(--ink-2);
}
.gate .box.gd .foot {
  margin: 0 auto 20px; max-width: 24em;
  font-size: 12px; line-height: 1.72; color: var(--ink-3);
}
.gate .box.gd .btn { padding: 12px 16px; }
@media (max-width: 420px) {
  .gate .box.gd { padding: 28px 22px 24px; }
  .gate .box.gd .gd-logo { width: 84px; height: 84px; margin-bottom: 14px; }
}
/* 入口层里的「前往桂教通」是个链接，按按钮渲染 */
.gate .box .btn { text-decoration: none; width: 100%; cursor: pointer; font-family: inherit; }
.field {
  display: flex; flex-direction: column; gap: 6px; text-align: left;
}
.field label { font-size: 12px; color: var(--ink-2); font-weight: 600; }
.field input {
  border: 1px solid var(--line); border-radius: var(--r-sm);
  padding: 11px 13px; font-size: 14px; outline: 0;
  background: var(--surface-2); color: var(--ink);
}
.field input:focus { border-color: var(--green-700); background: var(--surface); }
.btn {
  border: 1px solid transparent; border-radius: var(--r-sm);
  padding: 12px 16px; font-size: 14px; font-weight: 700;
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
}
.btn-primary { background: var(--green-800); color: var(--surface); }
.btn-primary:hover { background: var(--green-900); }
.btn-ghost { background: var(--surface); color: var(--ink-2); border-color: var(--line); }
.btn-ghost:hover { color: var(--green-800); border-color: var(--green-100); }
/* 报错/提示文字：平时是空的，就**不占位**（以前留 18px 空档，卡片显得上下不等） */
.gate .msg { margin: 0; font-size: 13px; }
.gate .msg:empty { display: none; }
.gate .msg.err { color: var(--danger); }
.gate .msg.ok { color: var(--green-800); }
.gate .foot { margin: 12px 0 0; font-size: 12px; color: var(--ink-3); line-height: 1.7; }
.gate .foot:empty { display: none; }

.hidden { display: none !important; }

/* 提示条：从桂教通进了但身份没带过来时 */
.notice {
  display: flex; align-items: center; gap: 12px;
  background: var(--warn-bg); border: 1px solid #f3dcb8;
  color: var(--warn);
  border-radius: var(--r-md);
  padding: 12px 16px; font-size: 13px;
}
.notice svg { width: 18px; height: 18px; flex: none; }

/* ============ 响应式 ============ */
@media (max-width: 1240px) {
  .cols { grid-template-columns: 1fr; }
  .stats { grid-template-columns: repeat(2, minmax(0,1fr)); }
}
@media (max-width: 980px) {
  .app { grid-template-columns: var(--side-w-mini) 1fr; }
  .nav-item span.t, .nav-sec, .collapse-lbl { display: none; }
  .nav-item { justify-content: center; padding: 12px 0; }
  /* 窄屏侧栏收窄：身份卡只留头像（与 mini 态一致） */
  .user-chip { justify-content: center; padding: 8px 0; }
  .user-chip .uc, .user-chip .bell-in { display: none; }
  :root { --pad: 18px; --gap: 14px; }
}
/* 手机（2026-09-30 用户定：参考玻璃拟态设计稿重做移动端 UI）：
   ① 柔绿渐变背景 + 光斑；② 内容卡片浮起成毛玻璃圆角卡；
   ③ 侧栏转成**底部 Dock**（毛玻璃悬浮条，圆形图标 + 小字标签，激活项实心绿）。
   配色不变（仍取 tokens 的绿色系），DOM 与交互逻辑零改动（纯 CSS 换位）。
   ⚠️ 全部限定在 body.portal（门户首页）下：app.css 也被 admin.html 复用，
      不加限定会把管理后台的左栏也改成 Dock（2026-09-30 实测踩过）。 */
@media (max-width: 640px) {
  /* ① 背景：多层径向光斑 + 纵向渐变（滚动时长页面也有柔和底色） */
  body.portal {
    background-color: #e9f3ea;
    background-image:
      radial-gradient(300px 240px at 88% 60px, rgba(255, 255, 255, .95), rgba(207, 232, 212, .55) 55%, transparent 75%),
      radial-gradient(280px 260px at -70px 240px, rgba(255, 255, 255, .85), rgba(179, 220, 189, .5) 55%, transparent 75%),
      radial-gradient(260px 240px at 105% 58%, rgba(214, 236, 214, .9), rgba(163, 210, 178, .45) 55%, transparent 78%),
      linear-gradient(168deg, #f0f8f0 0%, #e4f1e6 42%, #dcecdc 100%);
  }

  /* ② 骨架：侧栏撤出文档流变底部 Dock，主区占满 */
  body.portal .app { grid-template-columns: 1fr; grid-template-rows: 1fr; }
  body.portal .side {
    /* ⚠️ top 必须复位：基础样式是 top:0 + height:100vh（桌面左侧栏），
       只改 position/bottom 会让 Dock 从顶拉到脚，变成一层透明全屏遮罩，
       把整页的点击/触摸全吞掉（2026-09-30 手机端「点了没反应」的真凶） */
    position: fixed; top: auto; left: 10px; right: 10px;
    bottom: calc(10px + env(safe-area-inset-bottom, 0px));
    height: auto; max-height: 74px;
    z-index: 60;
    flex-direction: row; align-items: center; gap: 2px;
    padding: 8px 10px;
    background: rgba(255, 255, 255, .62);
    -webkit-backdrop-filter: blur(18px) saturate(1.4);
    backdrop-filter: blur(18px) saturate(1.4);
    border: 1px solid rgba(255, 255, 255, .78);
    border-radius: 26px;
    box-shadow: 0 14px 34px rgba(30, 80, 40, .16);
    overflow-x: auto; overflow-y: hidden;
    scrollbar-width: none;
  }
  body.portal .side::-webkit-scrollbar { display: none; }
  body.portal .side-nav { flex-direction: row; flex: 1; gap: 2px; overflow: visible; }
  /* Dock 项：圆形图标 + 小字标签；激活项实心绿圆角块（参考图） */
  body.portal .nav-item {
    flex: none; flex-direction: column; gap: 3px; justify-content: center;
    padding: 8px 10px; border-radius: 16px;
  }
  body.portal .nav-item svg { width: 21px; height: 21px; }
  body.portal .nav-item span.t {
    display: block; font-size: 11px; font-weight: 600; letter-spacing: 0;
  }
  body.portal .nav-item.on {
    background: var(--green-800); color: var(--surface);
    box-shadow: 0 6px 16px rgba(46, 110, 78, .35);
  }
  body.portal .nav-item .badge { position: absolute; top: 2px; right: 4px; margin: 0; }
  /* 身份卡只留头像圆， Dock 最左；点它退出登录（交互不变） */
  body.portal .user-chip { margin: 0 4px 0 0; padding: 0; background: none; border: 0; }
  body.portal .user-chip .av { width: 36px; height: 36px; border-radius: 50%; }
  /* 抽蛋/收起这类侧栏专属件在 Dock 里没意义，藏掉 */
  body.portal .side-foot { display: none; }
  body.portal .collapse { display: none; }
  /* 主区给 Dock 让位 */
  body.portal .main { padding-bottom: 104px; }

  /* ③ 内容卡片浮起成毛玻璃圆角卡（通讯录 / 应用内嵌窗口同待） */
  body.portal .card, body.portal .contacts-view {
    background: rgba(255, 255, 255, .6);
    -webkit-backdrop-filter: blur(16px) saturate(1.35);
    backdrop-filter: blur(16px) saturate(1.35);
    border: 1px solid rgba(255, 255, 255, .75);
    box-shadow: 0 10px 30px rgba(30, 80, 40, .10);
    border-radius: 22px;
  }
  body.portal .card-h { background: transparent; }
  body.portal .app-view iframe { height: calc(100vh - 186px); border-color: rgba(255, 255, 255, .8); }

  /* ④ 顶栏搜索：玻璃胶囊（顶部右上角的搜索入口，参考图同位） */
  body.portal .top { background: transparent; border-bottom: 0; }
  body.portal .search {
    max-width: none;
    background: rgba(255, 255, 255, .6);
    -webkit-backdrop-filter: blur(14px) saturate(1.3);
    backdrop-filter: blur(14px) saturate(1.3);
    border: 1px solid rgba(255, 255, 255, .78);
    border-radius: 999px;
    box-shadow: 0 8px 22px rgba(30, 80, 40, .08);
  }

  /* ⑤ 大标题加重（参考图「Make It Pop!」级别的标题存在感） */
  body.portal .hello h1 { font-size: 22px; color: var(--green-900); }
  body.portal .hello p { color: var(--ink-2); }

  body.portal .stats { grid-template-columns: 1fr; }
  body.portal .user-chip .uc { display: none; }

  /* ===== 手机端门户首页精修（2026-10-13）===== */
  /* 课程表卡头：标题独占一行，页签与下拉整排第二行，不再挤在右侧乱换行 */
  body.portal .course-card .card-h { flex-direction: column; align-items: stretch; gap: 8px; }
  body.portal .course-card .card-h .sp { width: 100%; flex-wrap: wrap; }
  body.portal #cTabMe, body.portal #cTabCls { flex: 1 1 0; margin: 0; padding: 9px 0; }
  body.portal #cDaySel { margin-left: auto; max-width: 92px; }
  /* 今日课程行：节次 pill 稍大、时间对齐、整行更透气 */
  body.portal .crs-item { padding: 6px 4px; gap: 6px; }
  body.portal .course-card .card-h { padding-bottom: 4px; }
  body.portal .course-card .crs-col-h { display: none; }
  /* 手机端值日安排卡片标题栏：**周次选择要留着** ✗
     2026-10-18 用户反馈「卡片功能与排版有问题」→ 原来这里把周次下拉隐藏了 ✗
       结果：卡片上看不出是哪一周 ✓ 也不能切周 ✓（只能点开弹窗看）✗
     现在：卡头允许换行 ✓ 下拉保留 ✓ 统计按钮保留 ✓ */
  body.portal .duty-card .card-h { flex-wrap: wrap; row-gap: 6px; }
  body.portal .duty-card .course-weekbar { display: flex !important; align-items: center; gap: 6px; margin-left: auto; }
  /* 点卡片能弹「完整值日」✓ 留一句短提示（原来那句太长 ✓ 换行后把标题挤成一团 ✗）*/
  body.portal .duty-card .card-h h2::after { content: '（点开看整周）'; font-size: 11px; font-weight: normal; color: var(--ink-3); margin-left: 4px; }
  body.portal .duty-card .card-h h2 { flex: 1 1 auto; }
  /* 手机端值日安排：不要内层卡片外壳 ✓ 但**每天的小标题必须留** ✗
     2026-10-18：原来 `.duty-day-h{display:none}` → 只剩"岗位→人名"两列 ✓
       两天叠在一起**分不清哪天是谁** ✓（截图里重复两遍但没有"周四/周五"✓）*/
  body.portal .duty-card .duty-day-card {
    background: transparent; border: none; box-shadow: none; padding: 0;
    border-top: 1px dashed var(--line);
    padding-top: 6px; margin-top: 6px;
  }
  body.portal .duty-card .duty-day-card:first-of-type { border-top: 0; padding-top: 0; margin-top: 0; }
  body.portal .duty-card .duty-day-h {
    display: block;
    font-size: 13px; font-weight: 700;
    color: var(--green-800);
    margin: 0 0 2px;
  }
  /* 手机端 toast 要抬到**底部导航 Dock 之上** ✗
     kit-ui 里 .ui-toasts 是 bottom:34px ✓ 而手机 Dock 高约 56px → 提示条被压在导航上 ✓
     （截图里"已跳过，你仍在使用初始密码…"就盖住了 首页/班务/材料 那几个图标 ✓）*/
  body.portal .ui-toasts { bottom: calc(64px + env(safe-area-inset-bottom, 0px)) !important; }
  /* 可折叠提示（2026-10-18）：手机端「每周工作内容」默认展开 ✓ 卡头右侧一个小三角 ✓
     没有它的话，折叠状态就是一张"只有标题的空卡" ✗ 用户会以为坏了 ✓ */
  body.portal .work-card .card-h { cursor: pointer; }
  body.portal .card-chev {
    flex: none; margin-left: 6px;
    font-size: 11px; line-height: 1; color: var(--ink-3);
  }
  body.portal .duty-card .duty-row { display: flex; justify-content: space-between; padding: 4px 0; gap: 10px; }
  body.portal .duty-card .duty-rk { color: var(--ink-3); flex: none; }
  body.portal .duty-card .duty-rv { text-align: right; padding-left: 8px; min-width: 0; }
  body.portal .crs-p { width: 46px; font-size: 12px; padding: 5px 0; border-radius: 7px; }
  body.portal .crs-t { min-width: 82px; font-size: 11px; }
  body.portal .crs-s { font-size: 14px; }
  /* 问候区紧凑：标题与寄语不占过多首屏 */
  body.portal .hello { padding: 6px 2px 2px; }
  body.portal .hello h1 { font-size: 21px; margin: 0 0 4px; }
  body.portal .hello p { font-size: 13px; margin: 0; line-height: 1.55; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  /* 卡片间距收紧一档，首屏能多露一点 */
  body.portal .col-main { gap: 12px; }
  /* Dock 导航：标签清晰、触控足；6 项在窄屏刚好排下不溢出 */
  body.portal .side { gap: 1px; padding: 7px 8px; }
  body.portal .nav-item { padding: 6px 7px; border-radius: 13px; flex: 1 1 0; }
  body.portal .nav-item svg { width: 20px; height: 20px; }
  body.portal .nav-item span.t { font-size: 10px; letter-spacing: 0; }
  body.portal .user-chip { margin-right: 2px; }
  body.portal .user-chip .av { width: 32px; height: 32px; }
  /* 手机端隐藏体验不好的入口：管理后台/值日助手/课程安排/智能打印 */
  body.portal .nav-item[href="/admin.html"],
  body.portal .nav-item[data-appkey="duty"],
  body.portal .nav-item[data-appkey="kecheng"],
  body.portal .nav-item[data-appkey="zhiyin"] { display: none; }
  /* 手机端隐藏顶部铃铛，点欢迎语弹通知 */
  body.portal #btnBell { display: none !important; }
  /* 手机端隐藏挥手 emoji */
  body.portal .hello .em { display: none; }
  /* 手机端铃铛往上一点，不挡寄语 */
  body.portal #btnHelloBell { margin-top: -4px; }
  /* 手机端课程表：**收起态**只显示前 2 节 ✓ 展开后看全 ✓（2026-10-18 加 :not(.expanded)）
     ⚠️ 原来这两条没有 :not(.expanded) ✗ → 展开后仍然只有 2 节、整排页签被 display:none 藏掉 ✓
        新交互（展开看整周 / 切班级）就全废了 ✓ */
  body.portal .course-card:not(.expanded) .crs-item:nth-child(n+3) { display: none; }
  body.portal .course-card .crs-col { flex: 1; }
  body.portal .course-card:not(.expanded) .card-h .sp { display: none !important; }
  body.portal .course-card:not(.expanded) .card-h h2::after { content: '（点我看完整课表）'; font-size: 11px; font-weight: normal; color: var(--ink-3); margin-left: 6px; }
  /* 手机端隐藏通知中心卡片（改为顶部铃铛弹窗） */
  body.portal .notif-card { display: none; }
  /* 手机端隐藏左上角用户头像（点欢迎语弹菜单） */
  body.portal #userChip { display: none; }
  /* 手机端通知面板居中弹窗 */
  .notif-panel {
    top: 50% !important; left: 50% !important;
    transform: translate(-50%, -50%);
  }
  /* 手机端底部 Dock 顺序调整 */
  body.portal .nav-item[href="/"] { order: 1; }
  body.portal .nav-item[href="/#contacts"] { order: 2; }
  body.portal .nav-item[data-appkey="banwu"] { order: 3; }
  body.portal .nav-item[data-appkey="cailiao"] { order: 4; }
  body.portal .nav-item[data-appkey="huodong"] { order: 5; }
  body.portal .nav-item[data-appkey="chengji"] { order: 6; }
  body.portal .nav-item[data-appkey="zujuan"] { order: 7; }
  body.portal .nav-item[data-appkey="downloads"] { order: 8; }
}

/* ===== 应用内嵌视图（点应用在当前窗口内直接打开；无顶部标题栏，返回走侧边栏，2026-09-26） ===== */
.app-view.hidden { display: none; }
.app-view iframe {
  width: 100%; height: calc(100vh - 146px);
  border: 1px solid var(--line); border-radius: 12px; background: var(--surface);
}

/* ===== 校通讯录（2026-09-26） =====
   全部颜色取自 tokens.css 已有令牌，不得在此新增色值（e2e 的 sync-kit 会拦令牌漂移）。
   结构：cv-head（标题+搜索，滚动时吸顶）→ cv-index（部门快捷 chips）→ cv-body（层级树） */
.contacts-view.hidden { display: none; }
.contacts-view {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-1);
  display: flex; flex-direction: column;
  max-height: calc(100vh - 146px);
  overflow: hidden;
}
.cv-head {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  padding: 16px 20px 12px;
  border-bottom: 1px solid var(--line-soft);
  background: var(--surface);
}
.cv-title { display: flex; align-items: center; gap: 10px; }
.cv-title h2 { margin: 0; font-size: 16px; color: var(--ink); }
.cv-title .tag { font-size: 11px; font-weight: 700; padding: 3px 9px; border-radius: var(--r-pill); }
.cv-search {
  margin-left: auto; display: flex; align-items: center; gap: 8px;
  min-width: 240px; max-width: 340px; flex: 1 1 240px;
  padding: 8px 12px; border: 1px solid var(--line);
  border-radius: var(--r-pill); background: var(--bg);
}
.cv-search:focus-within { border-color: var(--green-700); background: var(--surface); }
.cv-search svg { width: 15px; height: 15px; color: var(--ink-3); flex: none; }
.cv-search input {
  border: 0; background: none; outline: none; width: 100%;
  font-size: 13px; color: var(--ink); font-family: inherit;
}
.cv-search input::-webkit-search-cancel-button { cursor: pointer; }

.cv-index {
  display: flex; align-items: center; gap: 6px; overflow-x: auto; overscroll-behavior-x: contain;
  padding: 8px 16px; border-bottom: 1px solid var(--line-soft);
  scrollbar-width: none;
}
.cv-index::-webkit-scrollbar { display: none; }
.cv-index:empty { display: none; }
.cv-chip {
  flex: none; display: inline-flex; align-items: center; gap: 7px;
  padding: 5px 8px 5px 12px; border: 1px solid var(--line); border-radius: var(--r-pill);
  background: var(--surface); color: var(--ink-2);
  font-size: 12px; font-family: inherit; cursor: pointer; white-space: nowrap;
  transition: border-color .12s ease, background .12s ease, color .12s ease;
}
.cv-chip:hover { border-color: var(--green-700); color: var(--green-900); background: var(--green-050); }
.cv-chip.on { border-color: var(--green-700); background: var(--green-100); color: var(--green-900); font-weight: 700; }
.cv-chip i {
  font-style: normal; font-family: var(--font-num); font-size: 11px; line-height: 1;
  min-width: 18px; text-align: center; padding: 3px 5px;
  border-radius: var(--r-pill); background: var(--green-050); color: var(--green-800);
}
.cv-chip.on i { background: var(--surface); }
/* 处室 | 教研组 的分组分隔 */
.cv-chip-sep {
  flex: none; width: 1px; height: 18px;
  background: var(--line); margin: 0 4px;
}

.cv-body { overflow-y: auto; overscroll-behavior-y: contain; padding: 4px 10px 14px; }
.cv-meta { padding: 8px 6px 4px; font-size: 12px; color: var(--ink-3); }
.cv-meta b { font-family: var(--font-num); color: var(--ink-2); }
.cv-meta .dim { color: var(--ink-3); }
.cv-meta .warn-tag { color: var(--warn); }
.cv-empty { padding: 40px 16px; text-align: center; color: var(--ink-3); font-size: 13px; }

/* 部门节点：--d 是层级深度，缩进用 padding-left 而不是 margin，避免 hover 背景断开 */
.cv-node { border-radius: var(--r-sm); }
.cv-row {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 10px 8px calc(10px + var(--d, 0) * 18px);
  border-radius: var(--r-sm); cursor: default;
}
.cv-row[role="button"] { cursor: pointer; }
.cv-row[role="button"]:hover { background: var(--green-050); }
.cv-caret {
  width: 16px; height: 16px; flex: none; position: relative;
  border-radius: 4px; background: var(--green-100);
}
.cv-caret::after {
  content: ''; position: absolute; inset: 0; margin: auto;
  width: 5px; height: 5px; border-right: 1.6px solid var(--green-800);
  border-bottom: 1.6px solid var(--green-800); transform: rotate(-45deg);
  transition: transform .15s ease;
}
.cv-caret.on::after { transform: rotate(45deg); }
.cv-caret.none { background: none; }
.cv-caret.none::after { border: 0; width: 5px; height: 5px; border-radius: 50%; background: var(--line); }
.cv-name { font-size: 14px; font-weight: 700; color: var(--ink); }
.cv-num {
  margin-left: auto; font-size: 11px; font-family: var(--font-num);
  color: var(--ink-3); flex: none;
}

/* 成员行：网格卡片。宽屏 3 列 / 中屏 2 列 / 窄屏自动 1 列——
   通栏布局下姓名与电话之间会有数百 px 空白，网格把空白还给内容。 */
.cv-people {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 4px 10px;
  padding: 4px 12px 8px calc(26px + var(--d, 0) * 18px);
  border-left: 2px solid var(--line-soft);
  margin: 0 0 2px calc(18px + var(--d, 0) * 18px);
}
.cv-person {
  display: flex; align-items: center; gap: 10px;
  padding: 7px 10px; border-radius: var(--r-sm); min-height: 46px;
  background: var(--bg); border: 1px solid var(--line-soft);
}
.cv-person:hover { background: var(--green-050); }
.cv-av {
  width: 32px; height: 32px; border-radius: 10px; flex: none;
  display: grid; place-items: center;
  background: var(--green-100); color: var(--green-800);
  font-size: 13px; font-weight: 800;
}
.cv-info { min-width: 0; display: flex; flex-direction: column; }
.cv-info b { font-size: 13px; font-weight: 600; color: var(--ink);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cv-info small { font-size: 11px; color: var(--ink-3); }
.cv-info small.cv-notitle { color: var(--line); }

/* 手机号：有号 → tel 链接；缺号 → 占位「未登记」，保持同样的行高不塌 */
.cv-tel {
  margin-left: auto; flex: none;
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 12px; border-radius: var(--r-pill);
  font-size: 12px; font-family: var(--font-num); font-weight: 600;
  text-decoration: none; letter-spacing: .2px;
  background: var(--green-050); color: var(--green-800);
  border: 1px solid var(--green-100);
}
.cv-tel:hover { background: var(--green-100); border-color: var(--green-700); }
.cv-tel svg { width: 13px; height: 13px; }
.cv-tel.none { background: none; border-style: dashed; border-color: var(--line); color: var(--ink-3); font-weight: 500; }
.cv-tel.none i { font-style: normal; width: 13px; text-align: center; }

@media (max-width: 640px) {
  .contacts-view { max-height: calc(100vh - 188px); border-radius: var(--r-md); }
  .cv-head { padding: 12px 14px 10px; gap: 10px; }
  .cv-search { margin-left: 0; max-width: none; flex-basis: 100%; }
  .cv-index { padding: 8px 12px; }
  .cv-body { padding: 4px 8px 14px; }
  .cv-row { padding-top: 9px; padding-bottom: 9px; }
  .cv-row { padding-left: calc(6px + var(--d, 0) * 12px); }
  .cv-people { margin-left: calc(12px + var(--d, 0) * 12px); padding-left: calc(16px + var(--d, 0) * 12px); }
  .cv-name { font-size: 13px; }
  /* 手机上号码挤不进一行时整段换行，别把姓名挤成省略号 */
  .cv-person { flex-wrap: wrap; padding: 8px 10px; }
  .cv-tel { margin-left: 42px; }
}

/* ===== 手机端通用（2026-09-30 体检）：输入框 ≥16px，防 iOS Safari 聚焦强制放大页面 ===== */
@media (max-width: 900px) {
  input, select, textarea { font-size: 16px !important; }
}

/* 手机端：小按钮触点 ≥30px（2026-09-30 体检） */
@media (max-width: 900px) {
  .btn-sm { min-height: 30px; }
}

/* ===== 「桂教通内嵌视图」提示条已按用户要求撤掉（2026-09-30）
   跳出到自己域名的动作改成**静默**：加载时自动试一次 + 老师点页面时再试一次（见 js/app.js
   的 offerBreakout / tryBreakout）；实在跳不出去才用 toast 提示「⋯ → 在浏览器打开」。
   样式一并删除，避免留死代码。 */

/* ===== 入口层表单（2026-09-30 用户定：门户自己的账号密码登录，页面上不放任何说明文字） =====
   输入框与按钮同宽、行高一致（13px 内边距 + 15px 字号），
   逗号前是「安静态」（浅底、淡边），聚焦时变成白底 + 绿边 + 光晕，指示当前在填哪个。 */
.gate-form { display: flex; flex-direction: column; gap: 12px; margin: 0; }
.gate-form input {
  width: 100%; box-sizing: border-box;
  padding: 13px 15px;
  border: 1px solid var(--line, var(--line));
  border-radius: 11px;
  font-size: 15px; font-family: inherit;
  background: var(--surface-2); color: var(--ink, var(--ink));
  transition: border-color .15s, box-shadow .15s, background .15s;
}
.gate-form input::placeholder { color: var(--ink-3, var(--ink-3)); }
.gate-form input:focus {
  outline: none; border-color: var(--green-700, var(--green-700)); background: var(--surface);
  box-shadow: 0 0 0 3px rgba(47,125,50,.13);
}
.gate-form .btn {
  margin-top: 4px; padding: 13px 16px;
  font-size: 15px; border-radius: 11px;
  box-shadow: 0 6px 16px rgba(47,125,50,.20);
}
.gate-form .btn:hover { box-shadow: 0 8px 20px rgba(47,125,50,.26); }
.gate-form .btn:active { transform: translateY(1px); box-shadow: none; }
/* 提交中：按钮里吐出一个小转圈，文字换成「正在登录…」 */
.gate-form .btn .spinner {
  display: none; width: 15px; height: 15px; flex: none;
  border: 2px solid rgba(255,255,255,.45); border-top-color: var(--surface); border-radius: 50%;
  animation: spin .7s linear infinite;
}
.gate-form .btn.loading .spinner { display: inline-block; }
.gate-form .btn:disabled { opacity: .82; cursor: default; box-shadow: none; }
@keyframes spin { to { transform: rotate(360deg); } }

/* 「记住我」：自绘勾选框（原生那个灰框和本页气质不搭）。
   ⚠️ 内层类名必须是 .bx，不能叫 .box —— 那会被 `.gate .box`（登录卡片）命中，
   勾选框会被渲染成一张大卡片（这个坑实测踩过）。 */
.remember {
  display: inline-flex; align-items: center; gap: 8px;
  margin: 2px 0; cursor: pointer; user-select: none;   /* 左 0：与输入框/logo 严格同一条竖线 */
  font-size: 13px; color: var(--ink-2); align-self: flex-start;
}
.remember input { position: absolute; opacity: 0; width: 0; height: 0; }
.remember .bx {
  width: 17px; height: 17px; flex: none; border-radius: 5px;
  border: 1.5px solid var(--line, var(--line)); background: var(--surface);
  position: relative; transition: background .16s, border-color .16s, box-shadow .16s;
}
.remember .bx::after {
  content: ''; position: absolute; left: 5px; top: 1.5px;
  width: 4px; height: 8px; border: 2px solid var(--surface); border-top: 0; border-left: 0;
  transform: rotate(45deg) scale(.4); opacity: 0; transition: transform .16s, opacity .12s;
}
.remember input:checked + .bx { background: var(--green-700, var(--green-700)); border-color: var(--green-700, var(--green-700)); }
.remember input:checked + .bx::after { transform: rotate(45deg) scale(1); opacity: 1; }
.remember input:focus-visible + .bx { box-shadow: 0 0 0 3px rgba(47,125,50,.16); }
.remember:hover .bx { border-color: var(--green-700, var(--green-700)); }
.remember:hover .txt { color: var(--green-800); }

/* 无障碍：系统里开了「减少动态效果」就全部停掉动画（只保留淡入不位移） */
@media (prefers-reduced-motion: reduce) {
  .gate, .gate .box, .gate .box .brand, .gate .box.shake { animation: none !important; }
  .gate-form .btn .spinner { animation-duration: 1.4s; }
}

.gate .box .row { margin: 0 0 12px; }
.gate .box .row .btn { margin: 0; }

/* 窄屏（手机）：卡片内边距收紧；logo 仍与输入框同宽（不另设最大宽度），保证左右同样对齐 */
@media (max-width: 420px) {
  .gate { padding: 16px; }
  .gate .box { padding: 30px 22px 26px; border-radius: 16px; }
}

/* ===== 应用顶部标签（2026-09-30）：外链应用的多页面，把平台的左侧菜单变成门户顶端一排标签 ===== */
.app-tabs {
  display: flex; gap: 8px; align-items: center;
  overflow-x: auto; -webkit-overflow-scrolling: touch;
  padding: 0 2px 10px;
}
.app-tabs::-webkit-scrollbar { height: 0; }
.app-tabs .app-tab {
  flex: none; white-space: nowrap; cursor: pointer;
  padding: 8px 15px; border-radius: 999px;
  border: 1px solid var(--line, var(--line)); background: var(--surface);
  color: var(--ink-2); font-size: 13px; font-family: inherit;
}
.app-tabs .app-tab:hover { border-color: var(--green-700, var(--green-700)); color: var(--green-800, var(--green-800)); }
.app-tabs .app-tab.on {
  background: var(--green-800, var(--green-800)); border-color: var(--green-800, var(--green-800)); color: var(--surface);
}
/* 有标签时 iframe 让出标签条的高度（各减 54px），避免内容被挤到首屏之外 */
.app-view.has-tabs iframe { height: calc(100vh - 200px); }
body.portal .app-view.has-tabs iframe { height: calc(100vh - 240px); }

/* 裁掉平台页面自带的左侧菜单（2026-09-30）：iframe 往左推出 N 像素，由父容器裁掉溢出部分。
   N 写在 config.js 的 pages[].cropLeft（0 = 不裁）。这样平台那条"永远带着"的菜单就不占画面了。 */
.app-view.has-crop { overflow: hidden; }

/* ==== 手机端加固 2026-10-06 ==== */
/* 手机端统一加固（覆盖层）。要点：
   · 点按不再闪蓝框，但按钮要有"按下去"的反馈；
   · 独立控件 ≥40px、复选框 20px、表单控件 16px（iOS 聚焦不放大整页）；
   · 小字下限 12.5px；弹窗变底部抽屉；横屏矮屏压缩留白；贴底栏留安全区。
   实测口径见 tools/e2e-mobile.js（390px 下"<40px 的独立控件"应为 0）。 */
@media (max-width: 900px) {
  * { -webkit-tap-highlight-color: transparent !important; }
  button, a[href], [role=button], label, select, input, .tab, .nav-item, .mini-item {
    touch-action: manipulation !important;
  }
  button, .btn, .tab, .nav-item, [role=button], .mini-item, .app-card, .dl-tile {
    transition: transform .08s ease, filter .08s ease;
  }
  button:active, .btn:active, a.btn:active, [role=button]:active, .tab:active,
  .nav-item:active, .mini-item:active, .app-card:active, .dl-tile:active {
    transform: scale(.98); filter: brightness(.95);
  }
  button, .btn, .tab, .nav-item, [role=button], select { min-height: 40px !important; }
  button.more, .icon-btn, .btn-ico, .edit, .icon-only, .btn-sm.icon,
  button.step, .step, .collapse, .back-home, .ac-sub, .row-link {
    min-width: 40px !important; min-height: 40px !important;
  }
  /* 侧栏底部那两个小方块入口（返回/收起）也要够大 */
  .side-foot a, .side-foot button { min-height: 40px !important; }
  /* 域名之类的小链接，撑成 40px 高的行（列表里点着不费劲） */
  .ac-sub, .row-link { display: flex !important; align-items: center; }
  input:not([type=checkbox]):not([type=radio]), select, textarea {
    min-height: 40px !important; font-size: 16px !important;
  }
  /* 复选框/单选：框本身 22px（看得清），靠 padding 把"可点区域"撑到 40px
     —— 打分表里勾选框挨得近，手指点不准就会出现"给错班扣分"这种事故。 */
  input[type=checkbox], input[type=radio] {
    box-sizing: content-box !important;
    width: 22px !important; height: 22px !important; padding: 9px !important;
  }
  label.sw, .chk, label:has(> input[type=checkbox]) { min-height: 40px !important; align-items: center !important; }
  /* 工具栏里的小链接（"刷新""后台管理"这种）：撑成 40px 高、宽度不小于 40 */
  .app-top a, .topbar a, .head a, .pg-head a, .zone-tools a, .tools a, header a,
  .mini-item a, #toAdmin, #refresh { min-height: 40px !important; min-width: 40px !important; display: inline-flex !important; align-items: center !important; justify-content: center !important; }
  button.sm, .btn.sm, .btn-sm { min-width: 40px !important; }
  input[type=number], input[inputmode], .bonus { min-width: 40px !important; }
  .chip, #userChip, .user-chip, .tag-btn { min-width: 40px !important; min-height: 40px !important; }
  .scrolly, .scrollx, .tbl-wrap, .tab-scroll, .nav-scroll, .mini-list, .list { overscroll-behavior: contain; }
}

@media (max-width: 640px) {
  .hint, .zone-hint, .dim, .muted, small, .foot, .st-s, .tag, .k, .sub, .mi { font-size: 13px; }
  .mini-item a { display: inline-block; padding: 4px 0; }

  /* 弹窗 → 底部抽屉 */
  .modal-mask { align-items: flex-end !important; padding: 0 !important; }
  .modal-box, .modal {
    width: 100% !important; max-width: 100% !important;
    border-radius: 18px 18px 0 0 !important;
    max-height: 88vh !important; overflow: auto;
    animation: mSheetUp .24s cubic-bezier(.22, .8, .28, 1);
    padding-bottom: calc(16px + env(safe-area-inset-bottom));
  }
  .modal-box .modal-x, .modal .close, .modal-close, .modal-box .x { min-width: 44px !important; min-height: 44px !important; }
  @keyframes mSheetUp { from { transform: translateY(24px); opacity: .55; } to { transform: none; opacity: 1; } }

  .bottom-bar, .toolbar.bottom, .adm-acts, .duty-actions, .foot-bar {
    padding-bottom: calc(10px + env(safe-area-inset-bottom));
  }
}

@media (orientation: landscape) and (max-height: 480px) {
  .zone-h, .card-h, .pg-head, .card-hd, .card-h2 { padding-top: 8px !important; padding-bottom: 8px !important; }
  .zone-b, .card-bd, .card-body { padding-top: 10px !important; padding-bottom: 10px !important; }
  .hint, .zone-hint, .foot, .st-s, .zone-tools .zone-hint { display: none !important; }
  .modal-box, .modal { max-height: 94vh !important; }
}

/* 卡片折叠：点标题栏即可，不显示箭头按钮 */
.fold-arrow { display: none !important; }
.card.collapsed .card-h { margin-bottom: 0; }
.card.collapsed > :not(.card-h) { display: none !important; }
.card.collapsed .card-h > .sp { display: none !important; }

/* ===== 手机端值日表精修（2026-10-13）：7 列周表在 390px 下不再把名字压成竖排单字 =====
   · 表格 min-width: 720px，强制左右横滑（.duty-scroll 已有 overflow-x:auto）；
   · 单元格 white-space: nowrap，"钟良丽"永远一行；
   · 单元格 padding 收紧，字体 13px 保持；
   · 行名列宽固定，不被挤。 */
@media (max-width: 640px) {
  .duty-scroll {
    margin: 0 -16px;            /* 左右出血到卡片边缘，横滑区域更大 */
    padding: 0 16px;
  }
  .duty-table {
    min-width: 720px;
    font-size: 14px;
  }
  .duty-table th, .duty-table td {
    padding: 10px 8px;
    white-space: nowrap;         /* 关键：名字不换行成竖排 */
  }
  .duty-table .duty-k { width: 84px; font-size: 13px; }
  .duty-table td.duty-many { font-size: 13px; line-height: 1.5; }
  /* 横滑两端加渐变阴影，暗示可以左右滑 */
  .duty-scroll {
    -webkit-mask-image: linear-gradient(to right, #000 96%, transparent 100%);
            mask-image: linear-gradient(to right, #000 96%, transparent 100%);
  }

  /* 值日安排卡头：手机上标题独占一行，控件第二行，不再把"值日安排"挤成竖排 */
  .duty-card .card-h {
    flex-direction: column;
    align-items: stretch !important;
    gap: 10px;
  }
  .duty-card .card-h h2 {
    white-space: nowrap;
    font-size: 16px;
  }
  .duty-card .card-h .sp {
    flex-wrap: wrap;
    gap: 8px;
  }
  .duty-card .card-h .sp select {
    flex: 1 1 auto;
    min-width: 130px;
  }
  .duty-card .card-h #dutyLeader {
    flex: 1 1 100%;          /* 值周领导独占一行 */
    order: 5;
    text-align: left;
  }
  .duty-card .card-h #dwStats {
    margin-left: auto;
  }

  /* 课程表卡头：标题独占一行，下面[我的今日课程][班级课表][周X]三个同一行 */
  .course-card .card-h {
    flex-direction: column;
    align-items: stretch !important;
    gap: 10px;
  }
  .course-card .card-h h2 {
    white-space: nowrap;
    font-size: 16px;
  }
  .course-card .card-h > .sp {
    flex-wrap: nowrap;
    gap: 8px;
  }
  /* 两个页签按钮弹性平分 */
  .course-card .card-h #cTabMe,
  .course-card .card-h #cTabCls {
    flex: 1 1 0;
    margin: 0;
    min-height: 44px;
    font-size: 13px;
    padding: 0 8px;
  }
  /* 星期下拉固定宽度 */
  .course-card .card-h #cDaySel {
    flex: 0 0 76px !important;
    width: 76px !important;
    max-width: 76px !important;
    min-height: 44px;
    padding: 0 6px;
    font-size: 13px;
  }

  /* 每周工作内容卡头：标题独占一行，下拉+本周第二行 */
  .work-card .card-h {
    flex-direction: column;
    align-items: stretch !important;
    gap: 10px;
  }
  .work-card .card-h h2 {
    white-space: nowrap;
    font-size: 16px;
  }
  .work-card .card-h .sp {
    flex-wrap: wrap;
    gap: 8px;
  }
  .work-card .card-h .sp select {
    flex: 1 1 auto;
    min-width: 130px;
  }
  /* 工作内容表：手机端改成卡片式，每天一张卡，不用横滑 */
  .work-scroll {
    margin: 0;
    padding: 0;
    overflow: visible;
    max-width: 100%;
  }
  .work-wrap, .card-bd, .card-body { overflow: visible !important; }
  .work-table, .work-table tbody, .work-table tr, .work-table td {
    display: block;
    width: auto !important;
    min-width: 0 !important;
    max-width: none !important;
  }
  .work-table thead { display: none; }   /* 表头隐藏 */
  .work-table tr {
    border: 1px solid var(--line);
    border-radius: 10px;
    margin-bottom: 10px;
    padding: 10px 12px;
    background: var(--surface);
  }
  .work-table td {
    border: none;
    padding: 3px 0;
    text-align: left !important;
  }
  .work-table .wk-day {
    font-weight: 700;
    font-size: 14px;
    color: var(--green-800);
    padding-bottom: 6px;
    border-bottom: 1px solid var(--line);
    margin-bottom: 6px;
  }
  .work-table .wk-work { line-height: 1.6; font-size: 14px; }
  .work-table .wk-dept::before { content: "部门："; color: var(--ink-3); }
  .work-table .wk-owner::before { content: "负责人："; color: var(--ink-3); }
  .work-table .wk-dept, .work-table .wk-owner {
    font-size: 13px;
    color: var(--ink-2);
    padding-top: 6px;
    border-top: 1px dashed var(--line);
    margin-top: 6px;
  }
  .work-table .wk-dept + .wk-owner { border-top: none; margin-top: 0; padding-top: 2px; }

  /* 值日安排：手机端每天一张卡片 */
  .duty-cards { display: flex; flex-direction: column; gap: 10px; }
  .duty-day-card {
    border: 1px solid var(--line);
    border-radius: 10px;
    padding: 10px 12px;
    background: var(--surface);
  }
  .duty-day-h {
    font-weight: 700;
    font-size: 14px;
    color: var(--green-800);
    padding-bottom: 6px;
    margin-bottom: 6px;
    border-bottom: 1px solid var(--line);
  }
  .duty-row {
    display: flex;
    padding: 3px 0;
    font-size: 13px;
    line-height: 1.5;
  }
  .duty-rk {
    flex: 0 0 76px;
    color: var(--ink-3);
    font-size: 13px;
  }
  .duty-rv { flex: 1; color: var(--ink); }
  .duty-next-card {
    background: var(--surface-2);
    border-left: 3px dashed var(--line-strong, var(--line));
  }

  /* ===== 校通讯录手机端加固（2026-10-13）：紧凑列表风格 ===== */
  /* 手机端隐藏顶部全局搜索框（用户反馈占位置，手机上用不到） */
  .search { display: none !important; }
  /* 顶栏高度压缩，整体上移 */
  .top { height: 48px; padding: 0 12px; }
  .wrap { padding-top: 8px !important; }
  /* 通讯录：去掉卡片边框和内部滚动，和页面一起滚 */
  .contacts-view {
    border: 0 !important;
    border-radius: 0 !important;
    box-shadow: none !important;
    max-height: none !important;
    overflow: visible !important;
    background: transparent !important;
  }
  .cv-head {
    border-bottom: 1px solid var(--line-soft);
    padding: 8px 12px;
  }
  .cv-body {
    overflow: visible !important;
    padding-bottom: 20px;
  }
  .cv-head { padding: 10px 12px 8px; gap: 8px; }
  .cv-title h2 { font-size: 15px; }
  .cv-search {
    padding: 6px 12px;
    margin-top: 4px;
  }
  .cv-search input { font-size: 15px; padding: 3px 0; }
  .cv-search svg { width: 16px; height: 16px; }
  /* 部门快速跳转 pills */
  .cv-index { padding: 6px 10px; gap: 5px; }
  .cv-chip {
    min-height: 32px;
    padding: 4px 10px 4px 12px;
    font-size: 12px;
  }
  .cv-chip i { min-width: 16px; font-size: 10px; padding: 2px 4px; }
  /* 部门节点行：紧凑但 ≥40px */
  .cv-row {
    min-height: 40px;
    padding-top: 7px;
    padding-bottom: 7px;
  }
  .cv-name { font-size: 14px; }
  /* 成员卡片：紧凑单列，人名+号码同一行 */
  .cv-people {
    grid-template-columns: 1fr;
    gap: 4px;
    padding: 2px 6px 6px 8px;
    margin-left: 2px;
  }
  .cv-person {
    min-height: 48px;
    padding: 8px 10px;
    gap: 10px;
    flex-wrap: nowrap !important;
  }
  .cv-av {
    width: 32px; height: 32px;
    font-size: 13px;
    border-radius: 8px;
    flex: none;
  }
  .cv-info { flex: 1; min-width: 0; }
  .cv-info b { font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .cv-info small { font-size: 11px; }
  .cv-tel {
    min-height: 34px;
    padding: 5px 10px;
    font-size: 12px;
    margin-left: 0 !important;
    flex: none;
  }
}

/* ============================================================
   首页三卡「今天 → 整周」交互（2026-10-18 用户定）
   ------------------------------------------------------------
   规则：
     默认  = 只显示**当天**  ✓（老师第一眼就看今天的事）
     展开  = 显示**整周**    ✓ 并出现切换控件（周次 / 值日统计 / 班级课表）
   实现约定：
     · 切换控件统一打 `.only-open` 标记 ✓ 收起时 CSS 隐藏 ✓ 不用 JS 逐个开关 ✓
       （JS 开关容易漏一个 ✓ 一漏就是"控件还在但点不动"✗）
     · ⚠️ 别给 `.only-open` 加内联 display ✗ —— 内联会盖掉这条规则 ✓
       （#cClsSel / #dwNow / #workNow 有内联 display，由各自的 JS 管 ✓ 不冲突 ✓）
   ============================================================ */
.card:not(.expanded) .only-open { display: none !important; }

/* 卡头的「展开 / 收起」小按钮 */
.fold-btn { flex: none; }
.card.expanded > .card-h .fold-btn { background: var(--surface-2); }

/* 值日 · 今天（默认态）：四行"岗位 → 姓名" ✓ 与展开后的每天小块同一套排版 ✓ */
.duty-today { padding: 2px 0 2px; }
.duty-today-h {
  font-size: 13px; font-weight: 700; color: var(--green-800);
  margin: 0 0 4px;
}
.duty-today-empty { margin: 4px 0; }
.duty-row { display: flex; justify-content: space-between; gap: 12px; padding: 5px 0; }
.duty-rk { color: var(--ink-3); flex: none; }
.duty-rv { text-align: right; min-width: 0; }

/* 分段时间控件（本周值日 / 值日统计）*/
.seg {
  display: inline-flex; align-items: center; gap: 2px; flex: none;
  padding: 2px; border-radius: 9px;
  background: var(--surface-2);
  border: 1px solid var(--line);
}
.seg-i {
  border: 0; background: transparent; cursor: pointer;
  font-size: 13px; padding: 5px 10px; border-radius: 7px;
  color: var(--ink-2); white-space: nowrap;
}
.seg-i.on { background: var(--surface); color: var(--green-800); font-weight: 600; box-shadow: 0 1px 2px rgba(15, 23, 42, .06); }

/* 展开态 · 值日统计（就地显示，不再只能看弹窗 ✓）*/
.duty-stats-inline { padding: 2px 0 4px; }

/* 课程表 · 整周（展开态）：每天一小节 ✓ 今天那节高亮 ✓ */
.crs-week { display: flex; flex-direction: column; gap: 10px; }
.crs-wday { border-top: 1px dashed var(--line); padding-top: 8px; }
.crs-wday:first-child { border-top: 0; padding-top: 0; }
.crs-wday-h {
  display: flex; align-items: center; gap: 6px;
  font-size: 13px; font-weight: 700; color: var(--ink-2);
  margin-bottom: 4px;
}
.crs-wday.is-today .crs-wday-h { color: var(--green-800); }
/* 收起态只有一栏 ✓ 不要把两栏网格撑成半宽 ✓ */
.crs-cols-1 { grid-template-columns: 1fr !important; }
/* 收起态的小标题（"今天 · 周一"）—— 不能用 .crs-col-h（手机端它被隐藏 ✗）*/
.crs-today-h {
  font-size: 13px; font-weight: 700; color: var(--green-800);
  margin: 0 0 6px;
}

@media (max-width: 640px) {
  /* 手机端：值日/工作内容卡头本来就窄 ✓ 让「展开」按钮不把标题挤掉 ✓ */
  .duty-card .card-h, .work-card .card-h, .course-card .card-h { row-gap: 6px; }
  .fold-btn { margin-left: auto; }
  .seg { width: 100%; justify-content: center; }
}


/* ===== 2026-10-18 P3：表单控件统一描边（A 版） =====
   原来 input/select 多数没有边框或与卡片边同色，白底上"看不出是能填的框"。
   统一用 --line-ctl；聚焦时描边转绿并加一圈柔光。 */
input:not([type=checkbox]):not([type=radio]):not([type=file]):not([type=range]),
select, textarea {
  border: 1px solid var(--line-ctl);
  border-radius: var(--ctl-r, 10px);
  background: var(--surface);
}
input:not([type=checkbox]):not([type=radio]):not([type=range]):focus,
select:focus, textarea:focus {
  outline: none;
  border-color: var(--green-700);
  box-shadow: 0 0 0 3px rgba(47, 125, 50, .12);
}
@media (max-width: 640px) {
  input:not([type=checkbox]):not([type=radio]):not([type=range]),
  select { min-height: var(--ctl-h-m, 44px); }
}


/* ===== 2026-10-18 P3（A 版）：卡片皮肤统一 =====
   A 版定稿：白卡 + 1px 细边 + 14px 圆角 + 极轻阴影，去掉顶部/侧边彩色装饰条。
   B 版（色条+渐变+悬停上浮）在预览稿里留档，不启用。
   ⚠️ 想整体回退：删掉这一段即可。 */
.card {
  border-radius: var(--r-card, 14px);
  box-shadow: 0 1px 2px rgba(22, 33, 26, .04), 0 2px 8px rgba(22, 33, 26, .04);
}
.card::before, .card:before { display: none; }
/* 卡片标题前那根竖条也是装饰（A 版不要）：标题只有文字 */
.card-h h2::before, .card-h h2:before { display: none; }
.card:hover { transform: none; }


/* ===== 2026-10-18 P3（A 版）：页面底色去光晕 =====
   原来 body 铺了两层大半径绿/青光晕（2026-10-01 配色升级留下的），
   在 A 版里显得"脏"，白卡边界也被染色。改为中性底色，卡片靠 1px 边和轻阴影立起来。
   ⚠️ 用 html:not([data-theme=dark]) 限定，深色模式不受影响（深色规则在 weui-look，后加载）。 */
html:not([data-theme="dark"]) body {
  background: var(--bg);
}
/* 手机端的绿底光斑同理收掉（body.portal 在 620 断点里另铺了一层） */
@media (max-width: 640px) {
  html:not([data-theme="dark"]) body.portal {
    background-color: var(--bg);
    background-image: none;
  }
}


/* ===== 2026-10-18 P4：首页栅格重排（按设计稿） =====
   第一行 4 个小卡片（通讯录 / 通知 / 下载 / 喊话，与手机端同一组），
   下面才是大卡片（课程表、值日、每周工作、天气、通知中心）。
   原来 .m-toprow 只在 ≤620 显示，桌面端直接是"左三右三"两列，左右高度对不齐。 */
@media (min-width: 641px) {
  /* 喊话已经提到顶部快捷卡，这里不再重复一张 */
  #shoutCard { display: none; }
  .m-toprow {
    display: grid !important;
    grid-column: 1 / -1;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 14px;
  }
  html:not([data-theme="dark"]) .m-toprow .q-item {
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px;
    min-height: 96px;
    background: #fff; border: 1px solid var(--line-ctl); border-radius: var(--r-card, 14px);
    padding: 18px 12px; cursor: pointer;
    box-shadow: 0 1px 2px rgba(22,33,26,.04), 0 2px 8px rgba(22,33,26,.04);
  }
  .m-toprow .q-item .qi { width: 24px; height: 24px; color: var(--green-800); flex: none; }
  .m-toprow .q-item .qn { font-size: 14px; font-weight: 500; color: var(--ink); }
}

/* 首页两列只在桌面档启用；平板 641–1024 单列，避免主列被挤成竖排 */
@media (min-width: 1025px) {
  .cols { grid-template-columns: minmax(0, 1fr) clamp(260px, 19vw, 300px); align-items: stretch; gap: 16px; }
}


/* 2026-11-07：课程卡每行改两行栅格（节次跨两行；课程一行、时间+班级一行），
   窄右栏不再靠目测对齐四点。仅桌面折叠卡生效，展开弹窗保持原样。 */
@media (min-width: 641px) {
  .course-card .crs-item {
    display: grid;
    grid-template-columns: 44px minmax(0, 1fr) auto;
    grid-template-areas: "p s s" "p t c";
    column-gap: 6px; row-gap: 1px;
    align-items: center;
    padding: 7px 4px;
  }
  .course-card .crs-p {
    grid-area: p; width: 44px; padding: 4px 0;
    font-size: 11px; align-self: center;
  }
  .course-card .crs-s {
    grid-area: s; font-size: 12px; font-weight: 700;
    min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .course-card .crs-t {
    grid-area: t; font-size: 11px; color: var(--ink-3);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
  .course-card .crs-c {
    grid-area: c; font-size: 11px; color: var(--ink-3);
    justify-self: end; max-width: 68px;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
}

/* ===== 2026-10-18 P4 第二版：按预览稿的白色卡片 =====
   1) 卡片一律纯白，去掉 acc-green/acc-amber 带来的极浅绿/黄染色与渐变
   2) 通知中心与顶部"通知"快捷卡重复 → 移除
   3) 顺序：欢迎语 → 4 个快捷卡 → 课程表(2列)+天气 / 值日+每周工作 */
html:not([data-theme="dark"]) .card,
html:not([data-theme="dark"]) .card.acc-green,
html:not([data-theme="dark"]) .card.acc-amber {
  background: #fff !important;
  background-image: none !important;
  border: 1px solid var(--line) !important;
  border-radius: var(--r-card, 14px) !important;
  box-shadow: 0 1px 2px rgba(22, 33, 26, .04), 0 2px 8px rgba(22, 33, 26, .04) !important;
}
.notif-card { display: none !important; }


/* 2026-10-18：快捷卡现在在 col-main 里（欢迎语之后），补下间距；天气图标封顶，避免把首行撑爆 */
.m-toprow { margin-bottom: 16px; }
.weather-card svg { max-width: 120px; max-height: 120px; }
@media (min-width: 641px) {
  .m-toprow { grid-column: auto; }
}


/* ===== 2026-10-18：右栏天气 → 备忘录；左右两栏等高对齐 ===== */
.cols { align-items: stretch !important; }
.col-side { display: flex; flex-direction: column; gap: 16px; }
.col-side .card { flex: 1 1 auto; }
.weather-card { display: none !important; }
.memo-card { display: flex; flex-direction: column; }
.memo-card .memo-body { flex: 1; }
.memo-empty { margin: 0; padding: 24px 8px; text-align: center; color: var(--ink-3); font-size: 13px; line-height: 1.8; }


/* 2026-10-18：欢迎语与 4 快捷卡升为整行（在 .cols 直下），右栏从此行下方开始，不再凸出 */
.cols > .hello { grid-column: 1 / -1; margin-bottom: 4px; }
.col-main > .m-toprow { margin-bottom: 16px; }


/* 2026-10-18：课程表铺满左栏（原来它有自己的宽度上限，右侧留出一条空白） */
@media (min-width: 641px) {
  .col-main > .course-card, .col-main .course-card {
    width: 100% !important; max-width: none !important; min-width: 0;
  }
}


/* 2026-10-18：.top-row 原来是"课程表 + 喊话"两列；喊话卡隐藏后右列空着，
   课程表只占一半 → 改成单列铺满。 */
@media (min-width: 641px) {
  .top-row { display: block !important; }
  .top-row > * { width: 100% !important; max-width: none !important; }
}



/* ===== 2026-10-18：顶栏 logo 靠右；电脑端卡片默认全显 ===== */
header.top { justify-content: flex-end; }
header.top #topLogo { margin-left: auto; margin-right: 4px; }
@media (min-width: 641px) {
  .fold-btn { display: none !important; }   /* 不折叠，展开按钮收掉 */
}


/* 2026-10-18：课程表与备忘录互换位置（连同卡片格式一起换：备忘录占宽栏，课程表进窄栏） */
.col-side .top-row { display: block !important; }
.col-side .top-row > * { width: 100% !important; }


/* ===== 2026-10-18：表头去绿底 + 当天高亮 ===== */
html:not([data-theme="dark"]) .duty-card thead th,
html:not([data-theme="dark"]) .work-card thead th,
html:not([data-theme="dark"]) .course-card thead th { background: #fff !important; }
/* 空的选择框不显示（课程表里那些 only-open 的周次/班级下拉在没选项时会剩一个空框） */
select.course-select:empty { display: none !important; }

.is-today { background: var(--green-050) !important; }
th.is-today[data-today-tag], td.is-today[data-today-tag] {
  color: var(--green-800) !important; font-weight: 600;
}
th.is-today[data-today-tag]::after { content: ' · 今天'; font-weight: 500; font-size: 11px; }

/* 2026-10-18 用户定：课程表取消"当天高亮"，改为顶部两行「现在课程 / 下一节课」 */
.crs-now {
  background: transparent; border-radius: 0; padding: 0 0 10px; margin: 0 0 10px;
  border-bottom: 1px dashed var(--line);
  font-size: 13px; line-height: 1.9; font-weight: 600; color: var(--ink);
}
.crs-now b { color: var(--ink); font-weight: 700; }
.crs-now .crs-now-k { color: var(--ink-3); }
.crs-now .crs-now-t { color: var(--ink-3); font-size: 12px; font-weight: 400; margin-left: 6px; }

/* 2026-10-18：图标口径统一为 Lucide 规格（24 网格 / 2px 描边 / 圆角端点） */
svg[viewBox="0 0 24 24"] {
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}


/* 2026-10-18 深色模式补偿（上面那些"白底"规则都加了浅色限定，
   这里给深色下的快捷卡/表头/当天高亮补一套等价观感） */
html[data-theme="dark"] .m-toprow .q-item {
  background: #161c17; border-color: #2a352b; color: #e8efe9;
}
html[data-theme="dark"] .m-toprow .q-item .qn { color: #e8efe9; }
html[data-theme="dark"] .m-toprow .q-item .qi { color: #8fd44a; }
html[data-theme="dark"] .duty-card thead th,
html[data-theme="dark"] .work-card thead th,
html[data-theme="dark"] .course-card thead th { background: #1b231c !important; color: #cfdacf; }
html[data-theme="dark"] .is-today { background: rgba(111, 217, 0, .16) !important; }
html[data-theme="dark"] th.is-today[data-today-tag],
html[data-theme="dark"] td.is-today[data-today-tag] { color: #a7e06a !important; }




/* ===== 2026-10-18 课程表卡头（电脑端）：不换行、不显示"选周几"、切模式按钮不位移 ===== */
@media (min-width: 641px) {
  .course-card .card-h { flex-wrap: nowrap; align-items: center; }
  .course-card .card-h h2 { flex: none; }
  .course-card .card-h .sp { flex-wrap: nowrap; gap: 8px; min-width: 0; justify-content: space-between; min-height: 38px; }
  /* 下拉排到按钮左边，切换模式时"增删下拉"只影响左侧，右侧按钮位置恒定 */
  .course-card .card-h .sp > select { order: -1; }
  .course-card .card-h .sp > .btn { order: 0; flex: none; }
  /* 电脑端本来就展示整周，不需要"周五"这种单日选择 */
  .course-card #cDaySel { display: none !important; }
  .course-card #cClsSel { max-width: 168px; }
}

/* 2026-10-18 课程表卡头：下拉与按钮分属两个 .sp，把下拉排到前面、按钮用 margin-left:auto 钉在右边，
   切换「我的课程 / 班级课表」时按钮位置不再左右跳。 */
@media (min-width: 641px) {
  .course-card .card-h > .sp:last-of-type { order: -1; margin-right: 8px; }
  .course-card .card-h > .sp:first-of-type { margin-left: auto; }
}

/* 2026-10-18 课程表卡头定版：课程表在窄栏，宽度放不下"标题+下拉+两个按钮"，
   班级课表模式多出的下拉会把整行顶下去（标题下移 70px）。
   改成固定两行：标题独占一行；控件行"下拉在左、按钮在右"，两种模式结构完全一致。 */
@media (min-width: 641px) {
  .course-card .card-h { flex-wrap: wrap; row-gap: 6px; }
  .course-card .card-h h2 { flex: 1 1 100%; margin: 0; }
  .course-card .card-h > .sp:last-of-type { order: 1; margin: 0 auto 0 0; }   /* 下拉靠左 */
  .course-card .card-h > .sp:first-of-type { order: 2; margin: 0 0 0 auto; }   /* 按钮靠右 */
  .course-card .card-h > .sp { min-height: 32px; align-items: center; }
}

/* 2026-10-18：卡头固定在最上方（标题不再被上面的内容顶来顶去） */
@media (min-width: 641px) {
  .course-card { display: flex; flex-direction: column; }
  .course-card > .card-h { order: -1; }
}

/* 2026-10-18：课程表卡片去掉折叠/展开过渡动画（电脑端本来就是常展开） */
@media (min-width: 641px) {
  .course-card, .course-card * { animation: none !important; transition: none !important; }
}

/* 2026-10-18 定版：课程表卡头的 4 个元素（标题 / 班级下拉 / 我的课程 / 班级课表）同一行对齐 */
@media (min-width: 641px) {
  .course-card .card-h { flex-wrap: nowrap; align-items: center; row-gap: 0; }
  .course-card .card-h h2 { flex: 1 1 auto; min-width: 0; margin: 0; white-space: nowrap; }
  .course-card .card-h > .sp:last-of-type { order: 1; margin: 0 0 0 auto; }   /* 下拉靠右 */
  .course-card .card-h > .sp:first-of-type { order: 2; margin: 0 0 0 8px; }   /* 按钮紧跟 */
}

/* 2026-10-18 手机端：三张卡的卡头不要折行（标题与"展开"同排），避免大片空白 */
@media (max-width: 640px) {
  .course-card .card-h, .duty-card .card-h, .work-card .card-h { flex-wrap: nowrap; align-items: center; gap: 8px; }
  .course-card .card-h h2, .duty-card .card-h h2, .work-card .card-h h2 { flex: 1 1 auto; min-width: 0; }
}

/* 2026-10-18 ⚠️ 内嵌打开应用时首页仍显示（与 iframe 叠在一起）：
   mobile-redesign.css 用 `.cols{display:flex !important}` 压过了 `.hidden`，
   这里用同权重 !important + id 把隐藏兜住。 */
@media (max-width: 640px) {
  body.portal #homeCols.hidden { display: none !important; }
}

/* 2026-10-18 用户定：折叠按钮去掉，点卡头即可展开/收起 */
.fold-btn { display: none !important; }

/* 手机端卡片排版整理：卡头一行、间距统一、数字/按钮对齐 */
@media (max-width: 640px) {
  .card { padding: 14px 14px 12px; }
  .card-h { gap: 8px; margin-bottom: 10px; }
  .card-h h2 { font-size: 15px; }
  .duty-card .card-h, .work-card .card-h { flex-wrap: wrap; row-gap: 6px; }
  .ev-acts, .crs-cols { gap: 8px; }
}

/* 2026-10-18 手机端：内嵌应用时底部导航必须始终可见（防止被任何层遮住） */
@media (max-width: 640px) {
  body.portal .m-tabbar { display: flex !important; visibility: visible !important; z-index: 1200 !important; }
  body.portal .app-view iframe { max-height: calc(100vh - 112px); }
  /* 2026-11-06 用户要求：手机端页面不缩放 —— meta 里已锁 1:1；
     这里再把双击放大关掉（部分 WebView 只认 CSS 这一条）✓ 单指滑动/滚动不受影响 ✓ */
  html, body { touch-action: manipulation; }
}

/* 2026-10-18 手机端内嵌应用：铺满屏幕（原来 .wrap 有 18px 边距 + iframe 12px 圆角，
   应用像被塞进一张窄卡片，右侧还被切）。开应用时 body 会加 .in-app。 */
@media (max-width: 640px) {
  body.portal.in-app .wrap { padding-left: 0; padding-right: 0; padding-top: 0; }
  body.portal.in-app #appView { margin: 0; }
  body.portal.in-app #appView iframe {
    width: 100% !important; border-radius: 0 !important;
    /* 实测：iframe 顶 56、底部 Tab 顶 787（屏 844）→ 高度应占满到 Tab 之上 */
    height: calc(100vh - 112px) !important;
  }
}

/* 2026-10-27 用户反馈：应用里拉到最底后再往上拨，内容被底部色块挡住 ✗
   真因（390×844 实测，不是猜）：内嵌时 `.wrap` 仍留着首页的 80px 底部留白，
   外层门户页因此还能再滚 132px；在应用里滚到底继续上拨，滚动会传给外层，
   整个 iframe 被推上去 132px，底部一截就藏到 Tab 后面，再拨也回不来 ✗
   → ① 内嵌时外层页锁死不再滚（滚动只发生在 iframe 里 ✓）
     ② 去掉那段只给首页用的底部留白 ✓ */
@media (max-width: 640px) {
  body.portal.in-app { overflow: hidden; }
  /* 分两条写：老内核不认 :has() 时整条规则会被丢掉，body 那条还能兜住 ✓ */
  html:has(body.portal.in-app) { overflow: hidden; }
  body.portal.in-app .wrap { padding-bottom: 0 !important; }
}

/* 2026-10-18 手机端隐藏滚动条（保留滚动能力）：滚动条横在内容上很挡视线 */
@media (max-width: 640px) {
  html, body { scrollbar-width: none; -ms-overflow-style: none; }
  html::-webkit-scrollbar, body::-webkit-scrollbar { width: 0; height: 0; display: none; }
  body.portal *::-webkit-scrollbar { width: 0 !important; height: 0 !important; }
  body.portal * { scrollbar-width: none; }
}

/* 2026-10-18 手机端收口（严格限定 ≤640，不动电脑端）
   ① 卡片展开后不再显示"现在课程/下一节课"（已展开就看整周）
   ② 彻底禁止嵌套卡片（卡片里再套卡片 → 一律去掉外壳）
   ③ 折角：内层不设圆角，避免"又直角又圆角" */
@media (max-width: 640px) {
  .course-card:has(.crs-week) .crs-now { display: none !important; }

  .card .card, .card .card2, .card .zone,
  .card2 .card, .card2 .card2, .card2 .zone,
  .zone .zone, .zone .card2, .zone .card,
  .duty-card table, .work-card table {
    background: transparent !important;
    border: 0 !important;
    border-radius: 0 !important;
    box-shadow: none !important;
  }
}

/* 2026-10-18 手机端（≤640）：
   ① 课程表「周一」下拉删除——本来就整周展示，不需要选周几；
   ② 值日卡头：**用 grid 定两行**，不靠 flex-wrap 猜（wrap 会按基准宽度自动断，
      结果把 .seg 留在第一行、下拉掉到第二行 ✗ 用户反复说"还是两行"就是这个）：
        第一行：[值日安排]                    [值周领导 xxx]
        第二行：[本周值日][值日统计]           [第 7 周 ▾]
      —— 三个控件并排 ✓ 周次下拉在最右（最后）✓
   ⚠️ 620 那段给 .card-h 设了 flex-direction:column，grid 下 flex-direction 无效，
      但 align-items:stretch!important 仍然生效 → 这里必须显式 center ✓ */
@media (max-width: 640px) {
  /* ⚠️ 2026-10-19 修：原来手机端**一律**隐藏「星期」下拉 ✗
     但展开后可切「班级课表」✓ 那一页就是靠它选周几 ✓ 藏了就选不了 ✗
     → 只在**收起态**隐藏（收起只看今天，用不到）✓ 展开态给出来 ✓ */
  .course-card:not(.expanded) #cDaySel { display: none !important; }

  .duty-card .card-h {
    display: grid !important;
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      "dtitle  dleader"
      "dseg    dweek";
    align-items: center !important;
    column-gap: 8px;
    row-gap: 8px;
  }
  .duty-card .card-h > h2 { grid-area: dtitle; min-width: 0; }
  .duty-card .card-h > #dutyLeader {
    grid-area: dleader; justify-self: end;
    max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    font-size: 11px; padding: 2px 7px;
  }
  /* 没排值周领导时别留一个空壳（截图里那条细绿杠就是它）*/
  .duty-card .card-h > #dutyLeader:empty { display: none !important; }
  .duty-card .card-h > .seg {
    grid-area: dseg; justify-self: stretch;
    display: flex !important; flex-wrap: nowrap !important; margin: 0 !important;
  }
  .duty-card .card-h > .seg .seg-i { flex: 1 1 0; padding: 6px 6px !important; font-size: 12px !important; }
  .duty-card .card-h > .sp {
    grid-area: dweek; justify-self: end;
    display: flex; align-items: center; gap: 4px; margin: 0 !important; min-width: 0;
  }
  .duty-card .card-h > .sp > select {
    flex: 0 0 auto !important; width: auto; max-width: 152px; min-width: 0 !important;
    height: 40px; padding-left: 6px; padding-right: 2px; font-size: 12px;
  }
  /* 下拉文案本身已带「（本周）」，旁边那个绿色「本周」小标在 390 宽下纯占地方 → 手机不显示 */
  .duty-card .card-h > .sp > #dwNow { display: none !important; }
}

/* 2026-10-19 超窄屏（≤340，如 iPhone SE 一代 / 老安卓）：同一行会挤到字贴边
   → 下载收窄、分段字号降一档、标题后的提示先让位 ✓ 仍然是一行三个 ✓
   ⚠️ 只到 340：360/375/390 这些主流宽度用上面的默认值就够，别提前收窄
      （下拉字号是 16px —— 移动端为防 iOS 聚焦缩放设的，不能为省地方去动它 ✗）*/
@media (max-width: 340px) {
  .duty-card .card-h > h2::after { content: none !important; }
  .duty-card .card-h > .sp > select { max-width: 132px !important; }
  .duty-card .card-h > .seg .seg-i { padding: 6px 2px !important; font-size: 11px !important; }
}

/* ============================================================================
   2026-10-19 电脑端首页定版（用户要求，**只在 ≥641 生效，手机端一行不动**）
   ① 值日卡：值日统计改成**弹窗**（卡片内只保留整周值日表）；分段里"本周值日"是多余
      的（内容本来就是整周），电脑端收掉，只留一个「值日统计」按钮 ✓
   ② 首页卡片**不许折叠**：鼠标不再变手型（JS 那边也把 toggle 关了）✓
   ③ 通知面板：从"挂在铃铛下面的 360px 小卡"改成**居中大弹窗 + 遮罩** ✓
   ============================================================================ */
@media (min-width: 641px) {
  /* ② 卡片不可折叠的视觉提示：不留手型 */
  .course-card, .duty-card, .work-card { cursor: default !important; }

  /* ① 值日卡头的分段 → 单个「值日统计」按钮（点开是弹窗） */
  .duty-card #dwSeg {
    background: transparent !important;
    border: 0 !important;
    padding: 0 !important;
    gap: 0 !important;
  }
  .duty-card #dwSeg [data-dv="duty"] { display: none !important; }
  .duty-card #dwSeg .seg-i {
    border: 1px solid var(--line);
    background: var(--surface);
    border-radius: 8px;
    padding: 5px 11px;
    font-size: 13px;
  }
  .duty-card #dwSeg .seg-i:hover { border-color: var(--line-strong, var(--line)); background: var(--surface-2); }
  .duty-card #dwSeg .seg-i.on { box-shadow: none; }

  /* ③ 通知面板：居中、够大、内容可滚 */
  .notif-mask {
    position: fixed; inset: 0; z-index: 79;
    background: rgba(12, 26, 18, .42);
  }
  .notif-panel {
    top: 50% !important;
    left: 50% !important;
    transform: translate(-50%, -50%);
    width: min(860px, 78vw);
    min-height: 320px;                 /* 没通知时也别瘦成一条，要像个正经弹窗 ✓ */
    max-height: 82vh;
    padding: 18px 22px 16px;
    border-radius: 16px;
    box-shadow: 0 24px 70px rgba(15, 30, 10, .28);
    display: flex;
    flex-direction: column;
  }
  .notif-panel .np-hd {
    padding-bottom: 10px;
    border-bottom: 1px solid var(--line);
    margin-bottom: 4px;
  }
  .notif-panel .np-hd h3 { font-size: 17px; }
  .notif-panel .np-list { max-height: calc(82vh - 78px); overflow: auto; flex: 1 1 auto; }
  .notif-panel .np-empty { display: flex; align-items: center; justify-content: center; min-height: 220px; font-size: 14px; }
  .notif-panel .np-item { padding: 12px 26px 12px 6px; }
  .notif-panel .np-item .n-t { font-size: 14px; line-height: 1.65; }
  .notif-panel .np-item .n-m { font-size: 12px; margin-top: 5px; }
}

/* 值日统计弹窗的空态（2026-10-19）：没有导入数据时说人话，别留白板 */
.dstat-empty {
  margin: 18px 4px;
  font-size: 14px;
  line-height: 1.7;
  color: var(--ink-3);
  text-align: center;
}

/* ==========================================================================
  2026-10-19 手机端切子应用：平滑过渡（v2）
   --------------------------------------------------------------------------
   v1 是"白底 + 一句『正在打开…』"盖住整块 —— 像在等一个网页，很出戏 ✗
   v2 改成**不挡内容、不加文案**：
     · 顶部一条 2px 的不确定进度条（只在**慢**加载时出现，脚本延迟 140ms 才挂）
       → 局域网里通常 100ms 内就 load，老师**什么都看不到**，等于瞬间切换 ✓
     · iframe 就绪后 200ms 淡入 + 上移 4px 收住 —— 一次落地动作，不像"跳" ✓
     · 过渡期间露出的是页面本身的底色（不是刺眼的白板）✓
   ⚠️ 兜底：8 秒没等到 load 也会放出来（脚本里 v.__lbT），不会一直挂着进度条 ✗
   ========================================================================== */
.app-view { position: relative; }
/* ★ 2026-10-22 兜底：weui-look.css 的全局 `.loading` 是 20px 转圈（border-top 绿色），
   一旦落到整个应用容器上，就会变成"一长条一直在转的胶囊"（用户截图）。
   即使别处再给 #appView 加 .loading，这里也强制容器保持原样。 */
#appView.loading { width: auto; height: auto; border: 0; border-radius: 0; animation: none; }
/* ★ 2026-10-21 用户反馈「切子应用有个奇怪的条状旋转动画」
   来源就是下面这条 2px 渐变进度条（appViewBar 横扫）→ **整条移除** ✓
   （.app-view.loading 这个 class 仍会被脚本加上，但没有对应样式 = 不可见 ✓）*/
/* 已移除：切应用的进度条 */
/* appViewBar 关键帧已删除（进度条移除后不再需要）*/
/* ★ v3：一个应用一个 iframe（帧池）—— 非当前帧直接 `display:none`，
   **不销毁、不重载**；切回来只是显示层交换，0 次导航 ✓
   ★ 2026-10-22 用户要求：**首次进入/切换子应用都不要动画** ——
   原 180ms 淡入 + 4px 上浮已删除，iframe 直接出现（想恢复就加回 animation: appViewIn .18s ease）*/
/* 非当前帧：**占位但不可见**（不是 display:none）——
   后台预热时也要有真实尺寸，否则子应用按 0 宽高渲染，切过来会错位 ✗ */
.app-view iframe.av-f {
  position: absolute; left: 0; right: 0; top: 0;
  visibility: hidden; pointer-events: none; z-index: 1;
}
.app-view iframe.av-f.is-on {
  position: static; visibility: visible; pointer-events: auto; z-index: 2;
  animation: none;
}

/* ===== 【当天高亮 v2】2026-10-19 用户要求 =========================================
   原来"当天"是 `--green-050` 淡绿底（#f4fcea）✗ 用户反馈**不够明显** ✓
   现在：**绿底白字**（主绿 --green-700 / #2a6b04 + #fff ✓ 对比度 ≈ 5.9:1 ✓ AA 达标）
   覆盖三张卡里的"今天"：
     · 值日安排：当天那一列（th/td 都挂 .is-today ✓）
     · 每周工作内容：当天那一行（每个 td 都挂 .is-today ✓）
     · 课程表：当天那一段的表头（.crs-wday.is-today ✓）
   ⚠️ 只改浅色主题（html:not([data-theme="dark"]) ✓）—— 深色主题保持原来那套淡绿色 ✓
 ============================================================================ */
html:not([data-theme="dark"]) .is-today {
  background: var(--green-700) !important;
  color: #fff !important;
}
/* 单元格里的次级文字（班级/岗位名/小字）也一起变白，否则绿底上仍是深灰 ✗ */
html:not([data-theme="dark"]) .is-today,
html:not([data-theme="dark"]) .is-today * { color: #fff !important; }
/* 表头「周一 · 今天」的 ::after 标记也要白 ✓ */
html:not([data-theme="dark"]) th.is-today[data-today-tag]::after { color: #fff !important; opacity: .92; }
/* 日期格/项目名等原本是主题绿的字，绿底上改白 ✓ 但**链接/按钮不要动**（会看不清）✗ */
html:not([data-theme="dark"]) .is-today a,
html:not([data-theme="dark"]) .is-today button,
html:not([data-theme="dark"]) .is-today select { color: inherit !important; }
/* ---- 课程表：当天那一段的标题条 ---- */
html:not([data-theme="dark"]) .crs-wday.is-today .crs-wday-h {
  background: var(--green-700) !important;
  color: #fff !important;
  border-radius: 8px;
  padding: 4px 8px;
  margin: -2px -6px 6px;
}
html:not([data-theme="dark"]) .crs-wday.is-today .crs-wday-h *,
html:not([data-theme="dark"]) .crs-wday.is-today .crs-wday-h .tag {
  color: #fff !important;
  border-color: rgba(255, 255, 255, .45) !important;
  background: rgba(255, 255, 255, .14) !important;
}

/* ===== 【当天高亮 v2b】特异性修正（2026-10-19）====================================
   上面 v2 用的是 `html:not([data-theme="dark"]) .is-today`（0,2,1）✗
   但表头去绿底那条是 `html:not(...) .duty-card thead th`（0,2,2）✓ 更高 → 又把绿底压回白 ✗
   → 这里把三张卡的前缀带上（0,3,x）✓ 稳稳定覆盖 ✓
 ============================================================================ */
html:not([data-theme="dark"]) .duty-card th.is-today,
html:not([data-theme="dark"]) .duty-card td.is-today,
html:not([data-theme="dark"]) .work-card th.is-today,
html:not([data-theme="dark"]) .work-card td.is-today,
html:not([data-theme="dark"]) .course-card th.is-today,
html:not([data-theme="dark"]) .course-card td.is-today {
  background: var(--green-700) !important;
  color: #fff !important;
}
/* 单元格里的小字（岗位名 / 部门 / 小标签）也跟着白 ✓ */
html:not([data-theme="dark"]) .duty-card .is-today *,
html:not([data-theme="dark"]) .work-card .is-today *,
html:not([data-theme="dark"]) .course-card .is-today * { color: #fff !important; }

/* ===== 【当天高亮 v3】2026-10-19（按用户截图反馈修正）==============================
   ① 颜色：用户要「侧边栏首页按钮那个绿」✓ = --green-800（实测 rgb(42,107,4) ✓）
      原来用 --green-700（rgb(53,127,12)）偏亮偏黄 ✗
   ② 课程表：原来把整天段的所有文字都刷白 ✗ → 段内课程条目变成"白字白底 = 白块"✗
      现在**只有当天段的标题条**绿底白字 ✓ 底下课程行保持原色 ✓
 ============================================================================ */
html:not([data-theme="dark"]) .duty-card th.is-today,
html:not([data-theme="dark"]) .duty-card td.is-today,
html:not([data-theme="dark"]) .work-card th.is-today,
html:not([data-theme="dark"]) .work-card td.is-today {
  background: var(--green-800) !important;
  color: var(--surface) !important;
}
html:not([data-theme="dark"]) .duty-card .is-today *,
html:not([data-theme="dark"]) .work-card .is-today * { color: var(--surface) !important; }
html:not([data-theme="dark"]) .duty-card th.is-today[data-today-tag]::after { color: var(--surface) !important; opacity: .9; }

/* ---- 课程表：撤销"整天段刷白"，只保留标题条 ---- */
html:not([data-theme="dark"]) .course-card .crs-wday.is-today {
  background: transparent !important;
  color: inherit !important;
}
html:not([data-theme="dark"]) .course-card .crs-wday.is-today * {
  color: inherit !important;
  background-color: transparent !important;
}
/* 当天段的标题条：绿底 + 白字（放在上面两条之后 ✓ 后者优先）*/
html:not([data-theme="dark"]) .course-card .crs-wday.is-today .crs-wday-h {
  background: var(--green-800) !important;
  color: var(--surface) !important;
  border-radius: 8px; padding: 4px 8px; margin: -2px -6px 6px;
}
html:not([data-theme="dark"]) .course-card .crs-wday.is-today .crs-wday-h * {
  color: var(--surface) !important;
  background-color: transparent !important;
}
html:not([data-theme="dark"]) .course-card .crs-wday.is-today .crs-wday-h .tag {
  color: var(--surface) !important;
  background: rgba(255, 255, 255, .16) !important;
  border-color: rgba(255, 255, 255, .45) !important;
}

/* ===== 【当天高亮 v4】2026-10-19（用户截图：只高亮到标题条 ✗）========================
   用户口径：「课程表高亮没有显示完当天的内容」✓
   → 当天那一段要**整段都在高亮里**：
       · 段落容器：极浅绿底 + 左侧 3px 绿边 + 圆角（把课程行也包进来 ✓）
       · 标题条：仍绿底白字（最醒目 ✓）
       · 课程行：文字恢复深色（不再白字白底 ✗）
   ⚠️ 顺序很重要：容器底色放在"* 透明"规则**之后**才不会被冲掉 ✓
 ============================================================================ */
html:not([data-theme="dark"]) .course-card .crs-wday.is-today {
  background: #f2f9ee !important;
  border-left: 3px solid var(--green-800);
  border-radius: 10px;
  padding: 6px 8px 4px 7px;
  margin-left: -3px;
}
/* 标题条（当天）：绿底白字 → 左右贴齐容器 */
html:not([data-theme="dark"]) .course-card .crs-wday.is-today .crs-wday-h {
  background: var(--green-800) !important;
  color: var(--surface) !important;
  border-radius: 8px;
  padding: 5px 9px;
  margin: 0 0 6px;
}
html:not([data-theme="dark"]) .course-card .crs-wday.is-today .crs-wday-h * {
  color: var(--surface) !important;
  background-color: transparent !important;
}
html:not([data-theme="dark"]) .course-card .crs-wday.is-today .crs-wday-h .tag {
  color: var(--surface) !important;
  background: rgba(255, 255, 255, .16) !important;
  border-color: rgba(255, 255, 255, .45) !important;
}
/* 课程行：保持原色（浅绿底上读得清 ✓）*/
html:not([data-theme="dark"]) .course-card .crs-wday.is-today .crs-item {
  color: var(--ink) !important;
  background: transparent !important;
  border-bottom-color: rgba(42, 107, 4, .12) !important;
}
html:not([data-theme="dark"]) .course-card .crs-wday.is-today .crs-item * {
  color: inherit !important;
  background-color: transparent !important;
}
/* 「第X节」小胶囊：浅绿底上给一点绿，仍看得清 ✓ */
html:not([data-theme="dark"]) .course-card .crs-wday.is-today .crs-item .crs-p {
  background: #e2f1da !important;
  color: var(--green-900) !important;
}

/* ===== 【当天高亮 v5】2026-10-19（用户仍觉得"没改成功"→ 差异做大 + 版本号升级）========
   视觉目标（一眼就能看出"这一整块是今天"）：
     · 段容器：明显的浅绿底（#e9f6e2）+ 1px 绿边 + **左侧 4px 绿条** + 圆角 12 + 内边距
     · 标题条：绿底白字（侧栏同款 --green-800）
     · 课程行：白底小卡片（圆角 8 + 极轻阴影）→ 形成"绿框里一列白卡"的对比 ✓
     · 「第X节」胶囊：浅绿底深绿字
   ⚠️ 同时把 index.html 里 app.css 的 ?v= 升了（否则浏览器仍用缓存 ✗ 用户会觉得"没生效"）
 ============================================================================ */
html:not([data-theme="dark"]) .course-card .crs-wday.is-today {
  background: #e9f6e2 !important;
  border: 1px solid rgba(42, 107, 4, .35) !important;
  border-left: 4px solid var(--green-800) !important;
  border-radius: 12px !important;
  padding: 8px 9px 9px 8px !important;
  margin-left: -2px !important;
  box-shadow: 0 1px 2px rgba(42, 107, 4, .06);
}
html:not([data-theme="dark"]) .course-card .crs-wday.is-today .crs-item {
  background: #fff !important;
  border: 1px solid rgba(42, 107, 4, .12) !important;
  border-radius: 8px !important;
  padding: 7px 8px !important;
  margin: 4px 0 !important;
  color: var(--ink) !important;
}
html:not([data-theme="dark"]) .course-card .crs-wday.is-today .crs-item:last-child {
  margin-bottom: 0 !important;
  border-bottom: 1px solid rgba(42, 107, 4, .12) !important;
}
html:not([data-theme="dark"]) .course-card .crs-wday.is-today .crs-item * {
  color: inherit !important;
}
html:not([data-theme="dark"]) .course-card .crs-wday.is-today .crs-item .crs-p {
  background: #dcefd1 !important;
  color: var(--green-900) !important;
}

/* ===== 【当天高亮 v6】2026-10-19（用户明确：当天要**绿背景 + 白字**）==================
   之前 v4/v5 做成了"浅绿框 + 白卡 + 深字"✗ 用户要的是整块绿、字全白 ✓
   与值日/工作内容同一口径：--green-800 实心绿 + #fff 文字 ✓
   细节：
     · 段容器：整块绿底（覆盖标题 + 所有课程行 ✓）
     · 标题条：不再是独立绿条，融进整块（只留一条半透明白分隔线 ✓）
     · 课程行：透明底 + 白字；行间分隔用 18% 白
     · 「第X节」胶囊：18% 白底 + 白字（绿底上仍分得清 ✓）
     · 时间/班级：白字，次要信息降到 88% 透明度（不刺眼但看得清 ✓）
 ============================================================================ */
html:not([data-theme="dark"]) .course-card .crs-wday.is-today {
  background: var(--green-800) !important;
  color: #fff !important;
  border: 0 !important;
  border-left: 0 !important;
  border-radius: 12px !important;
  padding: 8px 10px 10px !important;
  margin-left: 0 !important;
  box-shadow: none !important;
}
/* 标题条：融进整块，只留一条半透明白分隔线 */
html:not([data-theme="dark"]) .course-card .crs-wday.is-today .crs-wday-h {
  background: transparent !important;
  color: #fff !important;
  border-radius: 0 !important;
  padding: 0 0 6px !important;
  margin: 0 0 6px !important;
  border-bottom: 1px solid rgba(255, 255, 255, .28) !important;
}
/* 段内一律白字（标题、课程名、时间、班级、标签） */
html:not([data-theme="dark"]) .course-card .crs-wday.is-today *,
html:not([data-theme="dark"]) .course-card .crs-wday.is-today .crs-wday-h * {
  color: #fff !important;
}
/* 课程行：透明底 + 白字，行间 18% 白分隔 */
html:not([data-theme="dark"]) .course-card .crs-wday.is-today .crs-item {
  background: transparent !important;
  border: 0 !important;
  border-bottom: 1px solid rgba(255, 255, 255, .18) !important;
  border-radius: 0 !important;
  padding: 7px 2px !important;
  margin: 0 !important;
  color: #fff !important;
}
html:not([data-theme="dark"]) .course-card .crs-wday.is-today .crs-item:last-child {
  border-bottom: 0 !important;
}
/* 次要信息（时间 / 班级 / 标签）：白色但略降透明度，层次还在 */
html:not([data-theme="dark"]) .course-card .crs-wday.is-today .crs-t,
html:not([data-theme="dark"]) .course-card .crs-wday.is-today .crs-c,
html:not([data-theme="dark"]) .course-card .crs-wday.is-today .tag {
  color: rgba(255, 255, 255, .9) !important;
}
/* 「第X节」胶囊：18% 白底 + 白字 */
html:not([data-theme="dark"]) .course-card .crs-wday.is-today .crs-item .crs-p {
  background: rgba(255, 255, 255, .18) !important;
  color: #fff !important;
  border-radius: 6px !important;
}

/* ===== 【手机端卡片弹窗】2026-10-19 ==============================================
   ① 三张卡"展开 = 弹窗"（底部抽屉 ✓ 与获奖详细同一套观感 ✓）
   ② 手机端**课程表 / 每周工作内容不再加绿色高亮**（用户口径 ✓ 值日保留 ✓）
   ⚠️ 全部包在 @media (max-width: 640px) 里 → 电脑端一行未改 ✓
 ============================================================================ */
@media (max-width: 640px) {
  body.msheet-lock { overflow: hidden; }
  .msheet {
    position: fixed; inset: 0; z-index: 70;
    display: flex; align-items: flex-end;
    background: rgba(18, 26, 22, .46);
    opacity: 0; visibility: hidden; pointer-events: none;
    transition: opacity .18s ease, visibility .18s ease;
  }
  .msheet.on { opacity: 1; visibility: visible; pointer-events: auto; }
  .msheet-dlg {
    width: 100%; max-height: 88vh; background: #fff;
    border-radius: 14px 14px 0 0; display: flex; flex-direction: column;
    transform: translateY(26px);
    transition: transform .2s cubic-bezier(.2, .8, .3, 1);
  }
  .msheet.on .msheet-dlg { transform: none; }
  .msheet-h { display: flex; align-items: center; gap: 10px;
    padding: 12px 12px 10px 14px; border-bottom: 1px solid var(--line); }
  .msheet-h b { flex: 1 1 auto; font-size: 16px; }
  .msheet-x { flex: none; width: 40px; height: 40px; border-radius: 10px; cursor: pointer;
    border: 1px solid var(--line); background: #fff; color: var(--ink2); font-size: 15px; }
  .msheet-b { padding: 10px 12px calc(18px + env(safe-area-inset-bottom, 0px));
    overflow: auto; -webkit-overflow-scrolling: touch; }
  .msheet-slot { display: none; }

  /* ② 课程表：手机端不要绿底（整段/标题条/胶囊全部回中性色）*/
  html:not([data-theme="dark"]) .course-card .crs-wday.is-today {
    background: transparent !important; border: 0 !important;
    border-radius: 0 !important; padding: 0 !important;
    margin-left: 0 !important; box-shadow: none !important;
  }
  html:not([data-theme="dark"]) .course-card .crs-wday.is-today .crs-wday-h {
    background: transparent !important; color: var(--ink) !important;
    border-bottom: 0 !important; padding: 0 0 4px !important; margin: 0 0 4px !important;
  }
  html:not([data-theme="dark"]) .course-card .crs-wday.is-today *,
  html:not([data-theme="dark"]) .course-card .crs-wday.is-today .crs-item * { color: inherit !important; }
  html:not([data-theme="dark"]) .course-card .crs-wday.is-today .crs-item {
    background: transparent !important; border-bottom: 1px solid var(--line-soft) !important;
    border-radius: 0 !important; padding: 6px 2px !important; margin: 0 !important;
  }
  html:not([data-theme="dark"]) .course-card .crs-wday.is-today .crs-item .crs-p {
    background: var(--green-050) !important; color: var(--green-900) !important;
  }

  /* ③ 每周工作内容：手机端同样不要绿底 */
  html:not([data-theme="dark"]) .work-card .is-today,
  html:not([data-theme="dark"]) .work-card th.is-today,
  html:not([data-theme="dark"]) .work-card td.is-today {
    background: transparent !important; color: inherit !important;
  }
  html:not([data-theme="dark"]) .work-card .is-today * { color: inherit !important; }
  html:not([data-theme="dark"]) .work-card th.is-today[data-today-tag]::after { color: var(--green-900) !important; }
}

/* ===== 【v7 去内层卡+弹窗去绿】2026-10-19 ========================================
   ① 工作内容：手机端 `.work-table tr` 自带 1px 描边+圆角+白底 → 在首页卡片里又套了一层卡 ✗
      → 首页卡片内与**弹窗内**都改成"无边框、无圆角、无底色，只用细分割线" ✓
   ② 课程表弹窗里还有绿高亮 ✗：
      原因是 v3 那条 `html:not([data-theme="dark"]) .crs-wday.is-today .crs-wday-h` **没带 .course-card 前缀**，
      内容被搬进弹窗后（不在 .course-card 内）它照样命中 → 标题条仍是绿底 ✗
      → 这里用 #mSheetBody 前缀（id 特异性更高）在手机端强制回中性色 ✓
   ⚠️ 全部在 @media (max-width: 640px) 里 → 电脑端一行未改 ✓
 ============================================================================ */
@media (max-width: 640px) {
  /* ① 工作内容：去掉内层卡片（首页卡片内 + 弹窗内）*/
  .work-card .work-table,
  #mSheetBody .work-table { border: 0 !important; background: transparent !important; }
  .work-card .work-table tr,
  #mSheetBody .work-table tr {
    border: 0 !important;
    border-bottom: 1px solid var(--line) !important;
    border-radius: 0 !important;
    background: transparent !important;
    padding: 9px 2px !important;
    margin: 0 !important;
  }
  .work-card .work-table tr:last-child,
  #mSheetBody .work-table tr:last-child { border-bottom: 0 !important; }
  .work-card .work-scroll,
  #mSheetBody .work-scroll { background: transparent !important; border: 0 !important; border-radius: 0 !important; }

  /* ② 弹窗内课程表：当天段的绿底/绿条全部回中性（标题条、段落、胶囊）*/
  #mSheetBody .crs-wday,
  #mSheetBody .crs-wday.is-today {
    background: transparent !important;
    border: 0 !important;
    border-left: 0 !important;
    border-radius: 0 !important;
    box-shadow: none !important;
    padding: 0 !important;
    margin-left: 0 !important;
    color: inherit !important;
  }
  #mSheetBody .crs-wday-h,
  #mSheetBody .crs-wday.is-today .crs-wday-h {
    background: transparent !important;
    color: var(--ink) !important;
    border-bottom: 0 !important;
    border-radius: 0 !important;
    padding: 0 0 4px !important;
    margin: 0 0 4px !important;
  }
  #mSheetBody .crs-wday.is-today *,
  #mSheetBody .crs-wday.is-today .crs-wday-h * { color: inherit !important; }
  #mSheetBody .crs-wday.is-today .crs-item {
    background: transparent !important;
    border: 0 !important;
    border-bottom: 1px solid var(--line-soft) !important;
    border-radius: 0 !important;
    padding: 6px 2px !important;
    margin: 0 !important;
  }
  #mSheetBody .crs-wday.is-today .crs-item:last-child { border-bottom: 0 !important; }
  #mSheetBody .crs-wday.is-today .crs-item .crs-p {
    background: var(--green-050) !important;
    color: var(--green-900) !important;
  }
}

/* ===== 【弹窗避让底部导航】2026-10-19（用户：弹窗被底部导航遮住文字）===============
   手机端 Dock 是 fixed：left/right 10px、bottom 10px、max-height 74px
   → 它实际占掉底部约 **84px**（含 10px 外边距）✗
   原来弹窗是"贴着屏幕底"的抽屉 ✓ 内容最后几行正好落在 Dock 下面被挡住 ✗
   现在：
     · 弹窗整体上移 86px（+ 安全区）✓ 变成"悬浮抽屉"，四周圆角不再贴边
     · 最大高度同步减掉这段（否则顶部会顶出屏幕）
     · 内容区底部内边距也留出来（最后一行不会被切 ✓）
   ⚠️ 只在 @media (max-width: 640px) 里 → 电脑端不受影响 ✓
 ============================================================================ */
@media (max-width: 640px) {
  .msheet { align-items: flex-end; }
  .msheet-dlg {
    margin: 0 10px calc(86px + env(safe-area-inset-bottom, 0px));
    max-height: calc(100vh - 130px);
    border-radius: 14px !important;
  }
  .msheet-b { padding-bottom: calc(16px + env(safe-area-inset-bottom, 0px)); }
  /* 获奖详细弹窗同理（它在子应用里，同样会被门户 Dock 压住一行）*/
  .aw-dlg {
    margin-bottom: calc(86px + env(safe-area-inset-bottom, 0px));
    max-height: calc(100vh - 140px) !important;
    border-radius: 14px !important;
  }
}

/* ===== 【当天高亮·统一侧栏绿】2026-10-20 ========================================
   用户要求：首页三张卡里"当天"的高亮色，**与侧边栏「首页」按钮同色** ✓
   侧栏 = `.nav-item.on { background: var(--green-800) }` = **#2a6b04**
   之前工作内容行/值日列用的是 --green-700（#357f0c 偏亮）✗ 与侧栏有色差 ✗
   这里统一钉死到 --green-800（含 tr/td/th 三种写法 ✓ 免得哪一处漏掉 ✓）
   ⚠️ 手机端「课程表 / 每周工作内容不上绿」的既有口径在下面**再重申一次** ✓
      （否则这条放在文件末尾会把手机端那条覆盖掉 ✗）
 ============================================================================ */
html:not([data-theme="dark"]) .duty-card th.is-today,
html:not([data-theme="dark"]) .duty-card td.is-today,
html:not([data-theme="dark"]) .work-card th.is-today,
html:not([data-theme="dark"]) .work-card td.is-today,
html:not([data-theme="dark"]) .work-card tr.is-today,
html:not([data-theme="dark"]) .course-card .crs-wday.is-today,
html:not([data-theme="dark"]) .course-card .crs-wday.is-today .crs-wday-h {
  background: var(--green-800) !important;
}
/* 值日/工作里的小字也跟着白 ✓（绿底上深字看不清 ✗）*/
html:not([data-theme="dark"]) .duty-card .is-today *,
html:not([data-theme="dark"]) .work-card .is-today * { color: #fff !important; }

/* ---- 手机端：仍然"课程表 / 工作内容"不上绿（保持你之前的定稿 ✓）---- */
@media (max-width: 640px) {
  html:not([data-theme="dark"]) .course-card .crs-wday.is-today,
  html:not([data-theme="dark"]) .course-card .crs-wday.is-today .crs-wday-h {
    background: transparent !important; color: inherit !important;
    border: 0 !important; border-radius: 0 !important; padding: 0 !important; margin-left: 0 !important;
  }
  html:not([data-theme="dark"]) .course-card .crs-wday.is-today * { color: inherit !important; }
  html:not([data-theme="dark"]) .work-card th.is-today,
  html:not([data-theme="dark"]) .work-card td.is-today,
  html:not([data-theme="dark"]) .work-card tr.is-today {
    background: transparent !important; color: inherit !important;
  }
  html:not([data-theme="dark"]) .work-card .is-today * { color: inherit !important; }
  /* 手机端**值日**当天仍保留绿底白字 ✓（用户没让去）*/
  html:not([data-theme="dark"]) .duty-card th.is-today,
  html:not([data-theme="dark"]) .duty-card td.is-today {
    background: var(--green-800) !important; color: #fff !important;
  }
}

/* ===== 【用户菜单·小卡片】2026-10-20 ============================================
   用户反馈：点身份卡弹出的菜单**框太大**（宽 840 / 高 320，像大弹窗 ✗）
   根因：这个菜单复用了 `.notif-panel` ✗ 而宽屏下那条规则把通知面板放大成
         `width: min(860px,78vw)` + `min-height: 320px` + 居中 ✗ 菜单被一起放大 ✗
   现在给 `#userMenu` 独立样式：小卡片、贴身份卡右下、两行菜单紧凑 ✓
   （id 选择器优先级高于 .notif-panel ✓ 且都带 !important 压过大弹窗规则 ✓）
 ============================================================================ */
#userMenu {
  top: auto;                        /* 真实位置由 JS 算（身份卡下方 ✓）*/
  transform: none !important;
  width: auto !important;
  min-width: 184px !important;
  max-width: 260px !important;
  min-height: 0 !important;
  max-height: none !important;
  padding: 0 !important;
  border-radius: 12px !important;
  box-shadow: 0 10px 26px rgba(15, 30, 10, .16) !important;
  display: block !important;
  overflow: hidden !important;
}
#userMenu .np-hd {
  margin: 0 !important; padding: 10px 14px 8px !important;
  border-bottom: 1px solid var(--line) !important;
}
#userMenu .np-hd h3 { font-size: 14px !important; color: var(--green-900) !important; }
#userMenu .np-list { padding: 6px !important; max-height: none !important; overflow: visible !important; }
#userMenu .np-item {
  padding: 9px 10px !important; border: 0 !important;
  border-radius: 8px !important; font-size: 14px !important;
}
#userMenu .np-item:hover { background: var(--green-050) !important; }

/* ===== 通知快捷卡：未读数做成铃铛右上角小角标（2026-10-21 用户要求）===== */
.m-toprow .q-item { position: relative; }
.q-item .qn-badge {
  position: absolute; top: 14px; left: 50%; margin-left: 6px;   /* 铃铛居中 → 右上角 */
  min-width: 19px; height: 19px; padding: 0 5px; box-sizing: border-box;
  display: inline-flex; align-items: center; justify-content: center;
  background: #d93025; color: #fff; font-size: 12px; font-weight: 700;
  line-height: 1; border-radius: 999px; border: 2px solid #fff;
  font-variant-numeric: tabular-nums; box-shadow: 0 1px 3px rgba(217,48,37,.35);
}
.q-item .qn-badge[hidden] { display: none; }

/* ===== 无权限时隐藏成绩助手入口（2026-10-21）=================================
   由 app.js 的 hideChengjiEntry() 给 body 加 class `no-chengji`。
   用 CSS 而不是单次 display:none —— 侧栏是异步渲染的，重绘会覆盖内联样式 ✗
   !important 是必要的：navRow 用 class 控制，内联覆盖优先 ✓ */
body.no-chengji #sideNav [data-appkey="chengji"],
body.no-chengji #mtChengji {
  display: none !important;
}

/* ============================================================================
   首页「本周值日」—— 圆角卡片（2026-10-22 用户要求，参考：深绿圆角卡）
   原来：绿格子表格（表头星期 + 竖排角色行）✗ 视觉重、像 Excel
   现在：每天一张深绿圆角卡，卡内每行一个角色（角色名小字、姓名大字）✓
   ============================================================================ */
.duty-cards { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: var(--sp-3, 12px); margin-top: var(--sp-2, 8px); }

.duty-day-card {
  background: linear-gradient(180deg, #2f7a0a 0%, var(--green-800) 100%);
  color: #fff;
  border-radius: var(--r-md, 16px);
  box-shadow: 0 6px 18px rgba(23, 82, 0, .18);
  padding: var(--sp-3, 12px) var(--sp-3, 12px) var(--sp-2, 8px);
  min-width: 0;
}

.duty-day-h {
  display: flex; align-items: center; gap: 6px;
  font-size: var(--fs-lg, 15px); font-weight: var(--fw-bold, 700);
  letter-spacing: .5px; padding-bottom: var(--sp-2, 8px);
  border-bottom: 1px solid rgba(255, 255, 255, .22);
  margin-bottom: var(--sp-1, 4px);
}
/* "今天"标记：浅色小胶囊 */
.duty-day-h::after { content: none; }
.duty-day-card.is-today .duty-day-h::after {
  content: '今天'; margin-left: auto;
  font-size: var(--fs-2xs, 11px); font-weight: var(--fw-mid, 500);
  background: rgba(255, 255, 255, .22); color: #fff;
  padding: 1px 7px; border-radius: var(--r-pill, 999px);
  letter-spacing: 0;
}

.duty-row { display: flex; flex-direction: column; gap: 1px;
  padding: var(--sp-2, 8px) 0;
  border-bottom: 1px solid rgba(255, 255, 255, .14); }
.duty-row:last-child { border-bottom: 0; padding-bottom: 2px; }

.duty-rk { font-size: var(--fs-2xs, 11px); color: rgba(255, 255, 255, .72);
  letter-spacing: .3px; }
.duty-rv { font-size: var(--fs-lg, 15px); font-weight: var(--fw-semi, 600);
  color: #fff; line-height: 1.4; overflow-wrap: anywhere; }
.duty-rv .duty-none { color: rgba(255, 255, 255, .45); font-weight: 400; }

/* 下周一卡：浅一号底色，区分"不是本周" */
.duty-next-card { background: linear-gradient(180deg, #3f8f2f 0%, #357f0c 100%);
  opacity: .96; }

/* 展开/收起按钮所在行保持原样 */
.duty-hint { color: var(--ink-3); font-size: var(--fs-xs, 12px); }

/* ---------- 桌面（901+）5 列；中屏 3 列；手机单列 ---------- */
@media (max-width: 1100px) {
  .duty-cards { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 640px) {
  .duty-cards { grid-template-columns: 1fr; gap: var(--sp-2, 8px); }

  /* ★ 2026-10-22 修正：手机端**不要深绿底**（用户明确「不需要加绿背景高亮」）
     上面那段「深绿圆角卡」是**桌面端**的视觉效果，手机端原来只改了列数，
     底色仍是深绿渐变 + 白字 → 整块绿、文字看不清 ✗
     → 手机端回到白底卡片：白底 + 深色字 + 横排（与 2026-10-18 的原设计一致 ✓）*/
  .duty-day-card {
    background: var(--surface) !important;
    color: var(--ink) !important;
    border: 1px solid var(--line) !important;
    border-radius: 10px !important;
    box-shadow: none !important;
    padding: 10px 12px !important;
  }
  /* ⚠️ 必须带 html:not([data-theme="dark"]) 前缀：
     文件里有一条超宽规则 `html:not([data-theme="dark"]) .is-today { background: green-700 }`，
     它的优先级高于 `.duty-day-card.is-today`，会把卡片强行染成深绿 ✗（真凶）
     → 这里用同前缀 + 更具体的选择器 + !important 才能压住 ✓ */
  html:not([data-theme="dark"]) .duty-day-card.is-today,
  html:not([data-theme="dark"]) .duty-card .duty-day-card.is-today {
    background: var(--surface) !important;
    color: var(--ink) !important;
    border: 1px solid var(--line) !important;
    border-left: 3px solid var(--green-700) !important;
    box-shadow: none !important;
  }
  html:not([data-theme="dark"]) .duty-day-card.is-today .duty-rk,
  html:not([data-theme="dark"]) .duty-day-card.is-today .duty-rv,
  html:not([data-theme="dark"]) .duty-day-card.is-today .duty-day-h {
    color: inherit !important;
  }
  html:not([data-theme="dark"]) .duty-day-card.is-today .duty-rk { color: var(--ink-3) !important; }
  html:not([data-theme="dark"]) .duty-day-card.is-today .duty-rv { color: var(--ink) !important; }
  html:not([data-theme="dark"]) .duty-day-card.is-today .duty-day-h { color: var(--green-800) !important; }
  .duty-day-h {
    color: var(--green-800) !important;
    border-bottom: 1px solid var(--line) !important;
    padding-bottom: 6px !important;
    margin-bottom: 4px !important;
    font-size: 14px !important;
  }
  .duty-day-card.is-today .duty-day-h::after {
    background: var(--green-050) !important;
    color: var(--green-900) !important;
  }
  .duty-row {
    flex-direction: row !important;
    justify-content: space-between;
    gap: 8px;
    border-bottom: 1px solid var(--line-soft) !important;
    padding: 5px 0 !important;
  }
  .duty-row:last-child { border-bottom: 0 !important; }
  .duty-rk {
    color: var(--ink-3) !important;
    font-size: 13px !important;
    flex: 0 0 76px;
  }
  .duty-rv {
    color: var(--ink) !important;
    font-size: 14px !important;
    font-weight: 600;
    text-align: right;
  }
  .duty-rv .duty-none { color: var(--ink-3) !important; font-weight: 400; }
  .duty-next-card {
    background: var(--surface-2) !important;
    border-left: 3px dashed var(--line-strong, var(--line)) !important;
    opacity: 1 !important;
  }
}

/* ============================================================================
   当天高亮 · 圆角化（2026-10-22 用户要求）
   ---------------------------------------------------------------------------
   之前：「当日」= 深绿实心整行/整列，四角是方的 → 像被荧光笔涂满 ✗
   现在：同一个绿，但**两端做成圆角**，看起来像一张圆角标签 ✓
   做法：单元格之间无缝拼接，只给「首格左上/左下」「末格右上/右下」加圆角 ✓
        （不改 border-collapse，不需要额外 DOM，任何表格都能用 ✓）
   覆盖：每周工作内容（当天那一行）、值日安排（当天那一列）、课程表（当天表头）
   ============================================================================ */

/* ---- 每周工作内容：当天那一行 → 圆角条（★ 仅电脑端） ----
   ⚠️ 2026-10-22 修正：手机端「不要绿底」是既有口径（见 L2677 的 @media(max-width:640px)），
      这里的选择器 table.work-table td.is-today 比那边更具体 + !important，把手机端规则压掉了 ✗
      → 整段包进 @media (min-width: 641px)，电脑端保留圆角条，手机端回到透明 ✓
      （值日安排不在此列 —— 手机端本来就要保留绿底 ✓ 见下面 duty-table）*/
@media (min-width: 641px) {
  /* 2026-10-27 修：同值日列 —— 格子自带的 1px 浅灰边框在 separate 模式下**两侧各画一条**，
     把当天那一行切成几块浅色竖缝 ✗ 现在整行同色（green-800），边框也染成同色 ✓ */
  html:not([data-theme="dark"]) table.work-table td.is-today {
    background: var(--green-800) !important;
    color: #fff !important;
    border-color: var(--green-800) !important;
  }
  html:not([data-theme="dark"]) table.work-table td.is-today:first-child {
    border-radius: 12px 0 0 12px !important;
  }
  html:not([data-theme="dark"]) table.work-table td.is-today:last-child {
    border-radius: 0 12px 12px 0 !important;
  }
  /* 行与行之间留一点缝，圆角才看得出来（只在工作内容表，别动其它表 ✓）*/
  html:not([data-theme="dark"]) table.work-table { border-collapse: separate; border-spacing: 0 4px; }
  html:not([data-theme="dark"]) table.work-table thead th { border-radius: 8px; }
}

/* ---- 值日安排：当天那一列 → 上下两端圆角（**手机/窄屏原样保留，别动**） ---- */
html:not([data-theme="dark"]) table.duty-table td.is-today {
  background: var(--green-700) !important;
  color: #fff !important;
}
html:not([data-theme="dark"]) table.duty-table tbody tr:first-child > td.is-today {
  border-radius: 12px 12px 0 0 !important;
}
html:not([data-theme="dark"]) table.duty-table tbody tr:last-child > td.is-today {
  border-radius: 0 0 12px 12px !important;
}
html:not([data-theme="dark"]) table.duty-table tbody tr:only-child > td.is-today {
  border-radius: 12px !important;
}
html:not([data-theme="dark"]) table.duty-table thead th.is-today {
  border-radius: 12px 12px 0 0 !important;
}

/* ---- 电脑端（≥641）：当天那一列 → **一整条**圆角标签（2026-10-27 修） ----
   之前：表头与第一格各自带 12px 顶圆角、表头深绿而格子浅绿、格线还是浅灰 1px
        → 整列被切成"深绿表头 + 一截截更亮的小块"，圆角看着是断的 ✗
   现在：整列同色（green-800，与侧栏一致），格线染成同色（折叠边框下仍保留 1px，
        不改行高），首格不再单独做顶圆角，只在表头顶部 / 末格底部做 12px 圆角 ✓ */
@media (min-width: 641px) {
  html:not([data-theme="dark"]) .duty-card table.duty-table th.is-today,
  html:not([data-theme="dark"]) .duty-card table.duty-table td.is-today {
    background: var(--green-800) !important;
    color: #fff !important;
    border-color: var(--green-800) !important;
  }
  html:not([data-theme="dark"]) .duty-card table.duty-table tbody tr:first-child > td.is-today {
    border-radius: 0 !important;
  }
  html:not([data-theme="dark"]) .duty-card table.duty-table tbody tr:only-child > td.is-today {
    border-radius: 0 0 12px 12px !important;
  }
}

/* ---- 课程表：当天表头已经是圆角（crs-wday-h ✓）保持 ---- */
html:not([data-theme="dark"]) .crs-wday.is-today .crs-wday-h {
  border-radius: 10px !important;
}



/* ============================================================================
   2026-10-31 深色对比度补丁（真机实测）
     · 值日卡表头「周一…」  #2a6b04 on #1b231c → 2.46:1 ✗
     · 课程表节次胶囊      深绿字 + 深底 → 1.72:1 ✗
   统一抬到 ≥4.5:1（表头走浅灰白，节次胶囊走亮绿底 + 亮绿字）
   ============================================================================ */
html[data-theme="dark"] .duty-card thead th,
html[data-theme="dark"] .work-card thead th,
html[data-theme="dark"] .course-card thead th {
  color: #cfdacf !important;
  background: #1b231c !important;
}
html[data-theme="dark"] .crs-p,
html[data-theme="dark"] body.portal .crs-p {
  background: rgba(143, 212, 74, .20) !important;
  color: #b6e88a !important;
}

/* 打开子应用时在顶栏显示当前应用名（只在 body.in-app 下出现，回首页自动消失）。
   返回首页走侧栏 / 手机底栏，顶栏不再放「← 首页」按钮。 */
.av-crumb { display: none; align-items: center; margin-right: auto; min-width: 0; }
body.in-app .av-crumb { display: flex; }
.av-crumb .av-crumb-name { font-size: 13px; color: var(--ink-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
