/* 后浪学习平台 · 样式
 * 约定：
 *   - 颜色一律走 CSS 变量，深色主题只覆盖变量，不写第二套规则
 *   - 字号三档由 html[data-font] 控制，全部尺寸用 rem/em 跟随
 *   - 不引入任何外部字体/图标库，图标用内联 SVG 或字符
 */

/* ================= 变量 ================= */
:root {
  --bg: #F6F7FB;
  --panel: #FFFFFF;
  --panel-2: #F1F3F9;
  --panel-3: #E9EDF6;
  --line: #E3E7F0;
  --line-2: #D5DBE8;
  --txt: #1B2030;
  /* ★ dim/dim2 的取值由 impeccable detect 实测 + WCAG AA 验算决定，不再凭眼睛调。
     改之前实测：--dim2 #9AA3B5 on #fff 只有 2.54:1（远低于 4.5），
     --dim #6B7488 on --bg 只有 4.38:1（差一点点）。两族都压深后仍保留一档层次。 */
  --dim: #4E5768;    /* 6.79:1 on --bg ｜ 6.56:1 on --panel-2 */
  --dim2: #5C6576;   /* 5.87:1 on #fff ｜ 5.29:1 on --panel-2 */
  --pri: #2563EB;
  --pri-2: #1D4ED8;
  /* ★ 按钮专用渐变：白字/白图标压在它上面，所以**不能**直接用 --pri-2 ——
     dark 主题的 --pri-2 是浅蓝 #7BA4FF，白字压上去比原来更看不清。
     两套主题各给一组，各自都实测过白字对比度（light 5.93:1 / dark 4.86:1）。 */
  --btn-from: #1D4ED8;
  --btn-to: #0369A1;
  --pri-soft: #E8EFFF;
  --ok: #16A34A;
  --ok-soft: #E6F6EC;
  --warn: #D97706;
  --warn-soft: #FDF3E3;
  --bad: #DC2626;
  --bad-soft: #FDECEC;
  --shadow: 0 1px 2px rgba(16, 24, 40, .04), 0 10px 26px -14px rgba(16, 24, 40, .22);
  --shadow-lg: 0 2px 4px rgba(16, 24, 40, .05), 0 24px 60px -22px rgba(16, 24, 40, .34);
  --r: 14px;
  --r-sm: 10px;
  --r-xs: 7px;
  /* 侧栏宽度可拖拽调整（批次2），存 localStorage；收起时走 .side-min */
  --side-w: 268px;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
}
[data-theme="dark"] {
  --bg: #0E1117;
  --panel: #161A22;
  --panel-2: #1C212B;
  --panel-3: #232935;
  --line: #272D3A;
  --line-2: #333A4A;
  --txt: #E8ECF4;
  --dim: #9BA5B8;   /* 7.62:1 on --bg */
  /* ★ 深色底上「越亮越清楚」，所以辅助色要往亮调，不是往暗调。
     改之前 --dim2 #6C7688 on --panel 只有 3.8:1（深色模式同样不过 AA）。 */
  --dim2: #8A94A8;  /* 6.19:1 on --bg ｜ 5.29:1 on --panel-2 */
  --pri: #5B8CFF;
  --pri-2: #7BA4FF;
  /* 深色底上按钮要"亮起来"才显眼，但亮到能让白字读清是有上限的：
     #7BA4FF 太浅（白字几乎看不见），这里退到 blue-600 档，白字 4.86:1。 */
  --btn-from: #2563EB;
  --btn-to: #1D4ED8;
  --pri-soft: #1B2740;
  --ok: #34D399;
  --ok-soft: #14301F;
  --warn: #FBBF24;
  --warn-soft: #33270E;
  --bad: #F87171;
  --bad-soft: #351A1A;
  --shadow: 0 1px 2px rgba(0, 0, 0, .45), 0 12px 30px -16px rgba(0, 0, 0, .75);
  --shadow-lg: 0 2px 6px rgba(0, 0, 0, .5), 0 28px 70px -26px rgba(0, 0, 0, .9);
}

/* 字号三档 */
html { font-size: 16px; }
html[data-font="sm"] { font-size: 15px; }
html[data-font="md"] { font-size: 16.5px; }
html[data-font="lg"] { font-size: 18px; }

/* ================= 基础 ================= */
* { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--txt);
  font-family: system-ui, -apple-system, "PingFang SC", "Microsoft YaHei", "Segoe UI", sans-serif;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
button, input, select, textarea { font: inherit; color: inherit; }
button { cursor: pointer; }
[hidden] { display: none !important; }
.dim { color: var(--dim); }
.sp { flex: 1; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-thumb { background: var(--line-2); border-radius: 8px; border: 2px solid transparent; background-clip: content-box; }
::-webkit-scrollbar-thumb:hover { background: var(--dim2); background-clip: content-box; }
::-webkit-scrollbar-track { background: transparent; }

/* ================= 通用控件 ================= */
.btn {
  border: 1px solid var(--line-2);
  background: var(--panel);
  color: var(--txt);
  border-radius: var(--r-sm);
  padding: .42em .95em;
  font-size: .9rem;
  font-weight: 500;
  transition: background .15s, border-color .15s, transform .08s, color .15s;
  white-space: nowrap;
}
.btn:hover { background: var(--panel-2); border-color: var(--dim2); }
.btn:active { transform: translateY(1px); }
.btn.primary { background: var(--pri); border-color: var(--pri); color: #fff; }
.btn.primary:hover { background: var(--pri-2); border-color: var(--pri-2); }
.btn.ghost { background: transparent; border-color: transparent; color: var(--dim); }
.btn.ghost:hover { background: var(--panel-2); color: var(--txt); }
.btn.sm { padding: .3em .7em; font-size: .82rem; }
.btn.danger { color: var(--bad); border-color: var(--line-2); }
.btn.danger:hover { background: var(--bad-soft); border-color: var(--bad); }
.btn[disabled] { opacity: .5; pointer-events: none; }

.inp, .sel, textarea {
  border: 1px solid var(--line-2);
  background: var(--panel);
  border-radius: var(--r-sm);
  padding: .42em .7em;
  font-size: .9rem;
  outline: none;
  transition: border-color .15s, box-shadow .15s;
}
.inp:focus, .sel:focus, textarea:focus { border-color: var(--pri); box-shadow: 0 0 0 3px var(--pri-soft); }
.sel { cursor: pointer; padding-right: 1.6em; appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, var(--dim) 50%), linear-gradient(135deg, var(--dim) 50%, transparent 50%);
  background-position: calc(100% - 14px) 55%, calc(100% - 9px) 55%;
  background-size: 5px 5px, 5px 5px; background-repeat: no-repeat;
}

.sw { display: inline-flex; align-items: center; gap: .45em; font-size: .88rem; color: var(--dim); cursor: pointer; user-select: none; }
.sw input { width: 1.05em; height: 1.05em; accent-color: var(--pri); cursor: pointer; }

.seg { display: inline-flex; background: var(--panel-2); border-radius: var(--r-sm); padding: 3px; gap: 2px; }
.seg button {
  border: 0; background: transparent; color: var(--dim);
  padding: .3em .85em; border-radius: 7px; font-size: .86rem; font-weight: 500;
  transition: background .15s, color .15s;
}
.seg button.on { background: var(--panel); color: var(--txt); box-shadow: var(--shadow); }

/* ================= 空间门 ================= */
/* ★ align-items 不能用 center：
   内容一旦高过视口，居中会把顶部推到 overflow 区域**且滚不回去**（flex 经典坑）。
   改成 flex-start + 子元素 margin:auto 0 —— 有空间时上下自动均分（视觉上仍是居中），
   没空间时两个 auto 归零、从顶部开始，滚得到。 */
.land {
  position: fixed; inset: 0; z-index: 50;
  display: flex; align-items: flex-start; justify-content: center;
  padding: 24px; overflow: auto;
  background: var(--bg);
}
.land-bg { position: absolute; inset: 0; overflow: hidden; }
.land-bg canvas { width: 100%; height: 100%; display: block; }
.land-bg::after {
  content: ""; position: absolute; inset: 0;
  background: radial-gradient(60% 55% at 50% 30%, transparent 0%, var(--bg) 78%);
}
/* 抬头与卡片同宽、同一条中轴 —— 宽度只写在这里，卡片用 100% 继承。
   两处各写一次 min(440px,100%) 迟早会漂移。 */
.land-col { position: relative; z-index: 2; width: min(440px, 100%); margin: auto 0; }
.land-head { text-align: center; margin-bottom: 18px; animation: rise .5s cubic-bezier(.22, .8, .3, 1) both; }
.land-stance {
  margin: 10px 0 0; font-size: .84rem; line-height: 1.6; color: var(--dim);
}
.land-card {
  position: relative;
  width: 100%;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 20px;
  padding: 26px 30px 20px;
  box-shadow: var(--shadow-lg);
  animation: rise .5s cubic-bezier(.22, .8, .3, 1) both;
}
@keyframes rise { from { opacity: 0; transform: translateY(14px) scale(.99); } to { opacity: 1; transform: none; } }
.land-logo {
  display: flex; align-items: center; gap: 12px;
}
/* ★ 品牌图形：浪形 + 浪尖一点光。宽度收着点，别抢了「后浪奔涌」四个字的视线。 */
.land-logo-img { width: 68px; height: auto; flex: none; }
.land-logo-word {
  font-size: 1.85rem; font-weight: 700; letter-spacing: .04em;
  background: linear-gradient(120deg, var(--pri), #0EA5E9 70%);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.land-sub { color: var(--dim); font-size: .85rem; margin: 4px 0 0; }
.land-stale {
  background: var(--warn-soft); color: var(--warn); border: 1px solid var(--warn);
  border-radius: var(--r-sm); padding: 8px 12px; font-size: .84rem; margin-bottom: 14px;
}
.land-tabs { display: flex; gap: 4px; background: var(--panel-2); border-radius: var(--r-sm); padding: 4px; margin-bottom: 20px; }
.land-tab {
  flex: 1; border: 0; background: transparent; color: var(--dim);
  padding: .5em; border-radius: 8px; font-size: .88rem; font-weight: 500;
  transition: background .15s, color .15s;
}
.land-tab.on { background: var(--panel); color: var(--txt); box-shadow: var(--shadow); }
.field { margin-bottom: 14px; }
.field label { display: block; font-size: .8rem; color: var(--dim); margin-bottom: 5px; font-weight: 500; }
.field input {
  width: 100%; border: 1px solid var(--line-2); background: var(--panel);
  border-radius: var(--r-sm); padding: .58em .8em; font-size: .95rem; outline: none;
  transition: border-color .15s, box-shadow .15s;
}
.field input:focus { border-color: var(--pri); box-shadow: 0 0 0 3px var(--pri-soft); }
.land-namehint { font-size: .78rem; min-height: 1.2em; margin: -8px 0 12px; color: var(--dim2); transition: color .15s; }
.land-namehint.ok { color: var(--ok); }
.land-namehint.bad { color: var(--warn); }
.land-btn {
  width: 100%; border: 0; border-radius: var(--r-sm);
  background: linear-gradient(120deg, var(--pri), #0EA5E9);
  color: #fff; font-size: 1rem; font-weight: 600; padding: .72em;
  transition: filter .15s, transform .08s;
}
.land-btn:hover { filter: brightness(1.06); }
.land-btn:active { transform: translateY(1px); }
.land-btn[disabled] { opacity: .6; pointer-events: none; }
.land-err { color: var(--bad); font-size: .84rem; min-height: 1.3em; margin-top: 12px; }
/* ★ 合规区的字号单独提一档（0.76 → 0.8125rem，12.16px → 13px）。
   impeccable 报 tiny-text 时点到过这里，但**没有全量改 0.68–0.78rem 那 120 多处** ——
   那些是标签/徽章/时间戳，改了会改变整个界面的密度与布局，属于另一个工程。
   这里必须改，因为它承载的是法律提示：「不满 14 周岁，请由监护人阅读并同意后再使用」
   是一条不能因为字号小就看不清的信息。 */
.land-foot { margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--line); color: var(--dim2); font-size: .8125rem; text-align: center; }
/* 用行内排版而不是 flex —— flex 换行会把「·」分隔符甩到行首。
   nowrap：链接标签绝不能折在词中间（320px 下会把「儿童个人信息保护规则」断成「…保护规/则」）。 */
.land-legal { margin-bottom: 7px; line-height: 1.9; }
.land-legal a { color: var(--dim); text-decoration: underline; text-underline-offset: 3px; white-space: nowrap; }
.land-legal a:hover { color: var(--pri); }
.land-sep { color: var(--line-2); margin: 0 5px; }
.land-note { color: var(--dim2); margin-bottom: 7px; line-height: 1.55; }
.land-meta { color: var(--dim2); line-height: 1.6; }
.land-link { border: 0; background: transparent; color: var(--dim); font-size: .8125rem; text-decoration: underline; text-underline-offset: 3px; padding: 0; }
.land-link:hover { color: var(--pri); }

/* ══════════════════════════════════════════════════════════════════
   空间门视觉升级（2026-10-04）：把「登录」重写成「把想不明白的地方解开」
   ──────────────────────────────────────────────────────────────────
   两条纪律，是这一整块能安全加在生产页面上的前提：

   1. **只追加覆盖，不改原规则。** 上面的 .land* 每一条都有断言守着
      （抬头在卡外、立场句存在、合规入口可见、字段顺序固定…）。
      改原规则等于和断言对赌；追加覆盖只在后面生效，断言读到的 DOM 一字未动。
   2. **一律加 `.land` 作用域。** `.field` / `.btn` / `.acct-*` 是
      主界面与登录页**共用**的类，动它们会连带改掉学生正在用的界面。

   DOM 只加了一个 <h1 class="land-h1">，其余元素 / id / 顺序 / 注释全部原样。
   ══════════════════════════════════════════════════════════════════ */

/* 两栏：左标题 / 右卡片。
   `display:contents` 是关键 —— 它让 .land-head 与 .land-card **跳过父容器**
   直接成为 .land 的 grid item，于是两栏布局一行 DOM 都不用改。
   原 .land-col 的 width/margin 随之失效（这正是我们想要的：它本来只为单列服务）。 */
.land {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(372px, 428px);
  align-items: center;
  gap: clamp(28px, 5vw, 88px);
  padding: clamp(28px, 6vh, 72px) clamp(24px, 7vw, 108px);
  justify-content: center;
}
.land-col { display: contents; }
.land-head { margin-bottom: 0; text-align: left; max-width: 34em; }
.land-logo { font-size: 1.5rem; letter-spacing: .08em; }
.land-sub { font-size: .8rem; letter-spacing: .04em; }
.land-stance { margin-top: 18px; max-width: 31em; font-size: .95rem; line-height: 1.85; }

/* 主标题：把平台的灵魂句放大成第一眼的视觉锤 */
.land-h1 {
  margin: .34em 0 0;
  font-size: clamp(38px, 4.7vw, 62px);
  line-height: 1.08; font-weight: 800; letter-spacing: -.025em;
  color: var(--txt);
}
.land-h1 em { font-style: normal; color: var(--pri); }

/* 卡片：半透明毛玻璃。结在背后隐约透出来，但文字层始终压在最上面 ——
   可读性靠「不透明的内容层」保证，不靠「祈祷线别太亮」。 */
.land-card {
  width: 100%;
  background: var(--panel);                                  /* 老浏览器兜底 */
  background: color-mix(in srgb, var(--panel) 74%, transparent);
  -webkit-backdrop-filter: blur(18px) saturate(1.25);
  backdrop-filter: blur(18px) saturate(1.25);
  border-radius: 22px; padding: 26px 28px 20px;
  box-shadow: 0 24px 68px rgba(15, 23, 42, .26);
  animation: rise .55s cubic-bezier(.22, .8, .3, 1) both;
}
.land-bg::after {
  background:
    radial-gradient(58% 52% at 72% 42%, transparent 0%, transparent 42%, var(--bg) 96%),
    linear-gradient(180deg, transparent 60%, var(--bg) 100%);
}

/* 页签：滑动胶囊（.on 是既有状态类，JS 只切这个，不能改） */
.land-tabs {
  position: relative; gap: 2px; border-radius: 999px;
  background: var(--panel-2); padding: 5px; margin-bottom: 20px;
}
/* ★★ 2026-10-05 用户反馈「页签字体看不清楚」。
   先量后定（真 Chromium 实测）：
     · 选中项 color = rgb(255,255,255) 压蓝渐变 ⇒ 5.93:1，**过 AA**；
     · 未选中项 color = rgb(78,87,104) 压 --panel-2 ⇒ 6.56:1，**过 AA**。
   ⇒ **不是颜色问题**。真因是 **font-size 只有 14.19px 且字重 400** ——
     汉字在低分辨率 / 缩放下笔画细就发糊，而页签又是全站第一眼要看的东西。
   对策：**加粗到 600**（比加深颜色更能提升汉字可读性）+ 未选中项改用 --dim2
   （#5C6576，比 --dim 深一档）拉开与选中项白字的**明度层次**，
   让人一眼看出哪个是当前项。 */
.land-tab {
  position: relative; z-index: 1; border-radius: 999px;
  padding: .52em .3em; font-size: .86rem; font-weight: 600;
  line-height: 1.25; transition: color .3s;
}
.land-tab.on { background: transparent; color: #fff; box-shadow: none; }
/* 胶囊位移：由 JS 写 --pill-x（app.js 的 moveLandPill）。
   ★★ 2026-10-05 修：此前**没有任何地方设置过 transform** ⇒ 胶囊恒在第 1 格。
   选中第 2/3 格时，胶囊压住的是已经变灰的第 1 格，实测对比度 1.14:1（灰字压蓝），
   看起来就是「一选中就看不见了」。
   为什么用 translateX(var(--pill-x)) 而不是 translateX(calc(100% * var(--i)))：
   三格之间有 gap、容器有 padding，100%/3 与实际格宽差几像素，累积到第三格会明显偏。
   JS 直接量出真实左边界差更准，也顺带把窗口 resize 一起管了。
   兜底：--pill-x 未设置时回落到第 1 格（旧行为），不会突然乱跳。 */
.land-tabs::before {
  content: ""; position: absolute; z-index: 0; top: 5px; left: 5px;
  width: calc((100% - 10px) / 3); height: calc(100% - 10px);
  border-radius: 999px; background: linear-gradient(120deg, var(--btn-from), var(--btn-to));
  box-shadow: 0 4px 14px rgba(37, 99, 235, .3);
  transform: translateX(var(--pill-x, 0px));
  transition: transform .45s cubic-bezier(.65, 0, .2, 1);
}
/* ★ 非选中项压深一档（--dim2 而非 --dim）：小字号下更清楚，也和选中项的白字拉开层次。
   ★ JS 只切 .on，胶囊位移仍由既有的 transform 规则（下方媒体查询/J S 侧）负责，未改动。 */
.land-tab:not(.on) { color: var(--dim2); }

/* 输入框：底线式。聚焦时底线铺满并发光，而不是套一个框 */
.land .field { position: relative; margin-bottom: 18px; }
.land .field label { font-size: .8rem; margin-bottom: 2px; }
.land .field input {
  border: 0; border-bottom: 1.5px solid var(--line-2);
  background: transparent; border-radius: 0;
  padding: .5em 0 .55em; font-size: 1.02rem;
  transition: border-color .3s;
}
.land .field input:focus {
  border-color: transparent; box-shadow: none;
  border-bottom-color: var(--pri);
}
.land .field::after {
  /* ★ 用 transform: scaleX 而不是 width —— width 是**布局属性**，
     动画它每帧都触发重排/reflow（impeccable 的 layout-transition 规则点到了这一行，
     而且这是我自己写的那处）。scaleX 走合成层，不触发布局。
     顺带把 box-shadow 也去掉了：同一条规则还命中 dark-glow，
     而输入框底线本来就不需要发光。 */
  content: ""; position: absolute; left: 0; bottom: 0; height: 1.5px; width: 100%;
  transform: scaleX(0); transform-origin: left center;
  background: linear-gradient(90deg, var(--pri), #0EA5E9);
  transition: transform .45s cubic-bezier(.2, .7, .2, 1); pointer-events: none;
}
.land .field:focus-within::after { transform: scaleX(1); }
.land .field input::placeholder { color: var(--dim2); }

/* 主按钮：保留全宽（不能改成「悬停才展开」的圆钮 —— 家长用户看不到文字会不敢点） */
.land-btn {
  border-radius: 12px; padding: .8em; font-size: .98rem; font-weight: 600;
  /* ★ 白字压在渐变上，末端色必须够深：#0EA5E9 只有 2.77:1（impeccable 实测 6 处命中）。
     改用 --btn-from/--btn-to（见 :root）⇒ 白字 5.93:1，视觉仍是「蓝到青」的家族色。
     只改**有白字/白图标压在上面**的渐变；logo 渐变字、底线等装饰性的保持原样。 */
  background: linear-gradient(120deg, var(--btn-from), var(--btn-to));
  box-shadow: 0 8px 22px rgba(37, 99, 235, .26);
  transition: filter .18s, transform .08s, box-shadow .2s;
}
.land-btn:hover { filter: brightness(1.05); box-shadow: 0 10px 28px rgba(37, 99, 235, .34); }

/* 合规文字是信息性内容，颜色不能淡到读不出来（尤其 14 岁监护人条款） */
.land-foot { color: var(--dim); }
.land-note, .land-meta { color: var(--dim); }

/* ★ 空间门的响应式规则**故意放在文件末尾**（见文末「空间门窄屏」块）。
   原因：app.css 里有两条断言是「位置型」的 ——
     _p25check  : .skill-c .sc-ico 必须排在**第一个** @media 之前
     _parity7check: .side-mask{display:none} 必须排在**第一个** @media(max-width) 之前
   它们的用意（媒体查询会盖掉同权重的基础规则）是对的，但比较基准是
   「文件里第一个 @media 的位置」。在文件中部插一个 @media 会把基准提前，
   让这两条**假红**（实际行为毫无变化 —— 我们的 media 里只有 .land* 规则）。
   把 media 挪到末尾 = 不动任何既有规则的相对顺序，两条断言都恢复原状。 */

/* ================= 主框架 ================= */
.app { display: flex; height: 100vh; overflow: hidden; }

.side {
  width: var(--side-w); flex: 0 0 var(--side-w);
  background: var(--panel);
  border-right: 1px solid var(--line);
  display: flex; flex-direction: column;
  overflow: hidden;
  transition: width .18s ease, flex-basis .18s ease;
}
/* 侧栏顶部只留品牌名 —— 空间卡 / 成长条 / 公告已收进右上角「我的」菜单
   （2026-10-04）。原来那块占 190px 竖向空间，而项目与对话列表才是学生天天在用的。 */
/* ★ 品牌图形与文字**并排**（2026-10-04 起）：原来是竖排（图形独占一行 + 名 + 副标），
   268px 宽的侧栏里白吃掉一行高度。并排后顶部省下 26px，刚好让给项目列表。
   文字仍分两行（名在上、副标在下），靠 .brand-tx 这个竖向 flex 容器收住。 */
.brand { display: flex; align-items: center; gap: 9px; padding: 14px 16px 12px; border-bottom: 1px solid var(--line); }
/* ★ 侧栏品牌图形：高度压到 26px —— 这里寸土寸金（268px 宽的侧栏，
   顶部每一像素都要让给项目/对话列表）。图形只做识别，不做主角。 */
.brand-logo { display: block; width: auto; height: 26px; flex: none; }
.brand-tx { display: flex; flex-direction: column; min-width: 0; }
.brand-tx > b { font-size: 1.05rem; font-weight: 700; letter-spacing: .02em; line-height: 1.2; }
.brand-tx > small { color: var(--dim2); font-size: .72rem; margin-top: 1px; }
/* .pet-line 的基准样式保留：成长条现在渲染在「我的」菜单里，用的是同一个类。 */
.pet-line { font-size: .76rem; color: var(--dim); display: flex; align-items: center; gap: 6px; cursor: default; }
.pet-line .pet-ico { font-size: .95rem; line-height: 1; }
.pet-line .pet-bar { flex: 1; height: 4px; background: var(--panel-3); border-radius: 3px; overflow: hidden; }
.pet-line .pet-bar i { display: block; height: 100%; background: linear-gradient(90deg, var(--ok), #86EFAC); border-radius: 3px; transition: width .6s cubic-bezier(.22,.8,.3,1); }

.nav { padding: 8px; display: flex; flex-direction: column; gap: 2px; }
.nav-i {
  border: 0; background: transparent; color: var(--dim);
  display: flex; align-items: center; gap: 10px;
  padding: .52em .7em; border-radius: var(--r-sm);
  font-size: .9rem; font-weight: 500; text-align: left;
  transition: background .15s, color .15s;
}
.nav-i:hover { background: var(--panel-2); color: var(--txt); }
.nav-i.on { background: var(--pri-soft); color: var(--pri); font-weight: 600; }
.ni-dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; opacity: .45; flex: 0 0 6px; }
.nav-i.on .ni-dot { opacity: 1; }
.badge {
  margin-left: auto; background: var(--bad); color: #fff;
  font-size: .68rem; font-weight: 700; border-radius: 9px; padding: 0 6px; min-width: 18px; text-align: center;
  line-height: 18px; height: 18px;
}
.side-h {
  display: flex; align-items: center; justify-content: space-between;
  padding: 12px 16px 6px; color: var(--dim2); font-size: .74rem; font-weight: 600; letter-spacing: .05em;
}
.proj-list { flex: 1; overflow-y: auto; padding: 0 8px 16px; }

.conv-i {
  display: flex; align-items: center; gap: 8px;
  padding: .5em .7em; border-radius: var(--r-sm); cursor: pointer;
  font-size: .86rem; color: var(--dim); transition: background .13s, color .13s;
}
.conv-i:hover { background: var(--panel-2); color: var(--txt); }
.conv-i.on { background: var(--panel-2); color: var(--txt); font-weight: 600; }
.conv-i .ci-t { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.conv-i .ci-x { opacity: 0; border: 0; background: transparent; color: var(--dim2); font-size: .95rem; line-height: 1; padding: 2px 4px; border-radius: 5px; }
.conv-i:hover .ci-x { opacity: 1; }
.conv-i .ci-x:hover { background: var(--bad-soft); color: var(--bad); }

.main { flex: 1; min-width: 0; display: flex; flex-direction: column; overflow: hidden; }

/* 抽屉遮罩：桌面端侧栏是常驻的，遮罩不存在。
 * ★ 这条必须写在下面所有媒体查询之前 —— 同权重下后写的赢，
 *   放到媒体查询后面会把窄屏的 display:block 直接盖掉。
 *   （测试 _parity7check.cjs J 组会守这条顺序） */
.side-mask { display: none; }
.view { flex: 1; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }
.topbar {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 18px; border-bottom: 1px solid var(--line);
  background: var(--panel); flex: 0 0 auto;
}
.tb-txt { min-width: 0; }
.tb-txt .t { font-weight: 600; font-size: .98rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 副标题一律单行省略。
 * 原来这里是 flex + flex-wrap:wrap —— 容器一旦被挤窄，文字就会**逐字换行**，
 * 变成一条竖排的"乱码"并撑高顶栏（390px 下实测 h=201px / 7 行）。
 * 单行省略在任何宽度下都是可控的。 */
.tb-txt .s { color: var(--dim2); font-size: .74rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* 对话页的副标题里还塞了一个项目下拉，这两个得排成一行 */
#convSub { display: flex; align-items: center; gap: 8px; }
#convSubText { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#convSub .proj-sel { flex: 0 0 auto; }
.proj-sel { font-size: .72rem; padding: 2px 6px; max-width: 140px; background: var(--panel-2); border-color: var(--line); }
.hamb { display: none; font-size: 1.1rem; padding: .2em .5em; }
/* 顶栏次级控件（模式 / 模型 / 智能体 / 分享 / 更多）整组包一层。
 * 桌面端 display:contents —— 子元素直接参与 .topbar 的 flex 布局，排版与拆分前逐像素一致；
 * 窄屏下整组切成一条独立行，见下面的 @media。 */
.tb-tools { display: contents; }
.tb-chk { font-size: .78rem; color: var(--dim); display: inline-flex; align-items: center; gap: 4px; cursor: pointer; white-space: nowrap; }
.tb-chk input { cursor: pointer; }
#kbSearch { max-width: 200px; }

/* ================= 对话流 =================
 * 版式（批次12）：**通栏**，不再居中 840px。
 *   之前 840px 居中在 1440+ 的屏幕上左右各空 300px 以上，
 *   学生的话和最右边的自己的话都缩在中间一小块里，很空、很散。
 *   现在 AI 永远贴左、用户永远贴右，中间那点空地正好把两边拉开。
 *   —— 只靠 32px 左右内边距撑出安全区，宽屏上也不会顶到窗口边。 */
.stream { flex: 1; overflow-y: auto; padding: 26px 32px 14px; scroll-behavior: smooth; }
.stream-in { max-width: none; margin: 0; }

/* ================= 空状态（批次8 · 第一印象层 P1）=================
 * 打开产品的前 5 秒。原来只有一个 60px 淡图标 + 两行小字，
 * 学生第一次进来不知道能问什么 —— 四张快捷卡就是来解决这件事的。 */
.empty { text-align: center; color: var(--dim); padding: 7vh 20px 4vh; }
.empty-art { display: block; width: 168px; height: 132px; margin: 0 auto 10px; }
.empty-hi { font-size: 1.55rem; font-weight: 700; color: var(--txt); margin: 0 0 6px; letter-spacing: .01em; }
.empty-ask { font-size: 1.02rem; font-weight: 500; color: var(--txt); margin: 0 0 10px; }
/* 立场句：桌面单行、窄屏自然折行。26em 时会在桌面折出「剩下的几步。」这种孤字行，
   所以放宽到 34em —— 中文没有词边界，宁可整句一行，也不要最后一行只剩几个字。 */
.empty-stance { font-size: .86rem; color: var(--dim2); line-height: 1.65; max-width: 34em; margin: 0 auto; }
.empty-cards {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px;
  max-width: 720px; margin: 26px auto 0;
}
.ec {
  display: block; text-align: left; width: 100%;
  border: 1px solid var(--line); background: var(--panel);
  border-radius: var(--r-sm); padding: 12px 13px 13px; color: var(--txt);
  transition: border-color .15s, background .15s, transform .12s, box-shadow .15s;
}
.ec:hover { border-color: var(--pri); background: var(--pri-soft); transform: translateY(-2px); box-shadow: var(--shadow); }
.ec:active { transform: translateY(0); }
.ec-i { display: block; width: 26px; height: 26px; border-radius: 8px; background: var(--pri-soft); color: var(--pri); }
.ec-i svg { display: block; width: 16px; height: 16px; margin: 5px; }
.ec b { display: block; font-size: .9rem; font-weight: 600; margin: 9px 0 3px; }
.ec .ec-d { display: block; font-size: .76rem; color: var(--dim2); line-height: 1.5; }
.ec:hover .ec-d { color: var(--dim); }
.empty-tips { margin: 20px auto 0; padding: .6em 1em; background: var(--pri-soft); border-radius: 8px; font-size: .8rem; color: var(--pri-2); max-width: 34em; line-height: 1.5; }
.learn-next-wrap { width: min(100%, 38em); margin: 18px auto 0; text-align: left; }
.learn-next { display: grid; gap: 12px; padding: 16px 18px; border: 1px solid var(--line); border-radius: 14px; background: var(--panel); box-shadow: 0 8px 24px rgba(25,45,75,.06); }
.learn-next-kicker { color: var(--pri-2); font-size: .76rem; font-weight: 700; letter-spacing: .04em; }
.learn-next h2 { margin: 4px 0 3px; color: var(--txt); font-size: 1.02rem; }
.learn-next p { margin: 0; color: var(--dim); font-size: .84rem; line-height: 1.55; }
.learn-next-actions { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
.learn-next-detail { color: var(--dim); font-size: .82rem; }
.learn-next small { color: var(--dim2); font-size: .72rem; }
.learn-intent { display: grid; gap: 7px; padding: 11px 12px; border: 1px solid var(--line); border-radius: 10px; background: var(--panel-2); }
.learn-intent label { color: var(--txt); font-size: .8rem; font-weight: 650; }
.learn-intent .inp { width: 100%; }
.learn-intent-actions { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.learn-intent-actions small { color: var(--dim2); font-size: .7rem; line-height: 1.45; }
.learn-intent-actions .btn { flex: none; }
.mem-source { border: 0; padding: 0; background: transparent; color: var(--pri-2); font: inherit; cursor: pointer; text-align: left; }
.mem-source:hover { text-decoration: underline; }
/* ★ .learn-next 的窄屏回退已移到文件末尾 —— app.css 有「第一个 @media 位置」型断言，
   在中部插 @media 会让 _p25check / _parity22fe / _growthcheck / _encheck / _leaderboardcheck
   等一整批静态套件假红（批次70 真踩过：原先插在第 543 行）。 */

/* 对齐规则（批次11 · 参考竞品的对话版式）：
 *   · AI 左、用户右，共用同一条内容列与基线；
 *   · AI **不套气泡**——长回答套气泡会把正文挤成窄栏，读起来比纯文档累；
 *   · 用户套浅色气泡，右缘贴齐内容列右缘；头像在最右，与首行文字对齐。 */
.msg { display: flex; gap: 12px; margin-bottom: 28px; align-items: flex-start; animation: msgIn .3s ease both; }
@keyframes msgIn { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
.msg .av {
  flex: 0 0 32px; width: 32px; height: 32px; border-radius: 10px;
  display: flex; align-items: center; justify-content: center;
  font-size: .78rem; font-weight: 700; user-select: none;
  box-shadow: 0 1px 2px rgba(16, 24, 40, .07);
}
.msg.ai .av { background: linear-gradient(135deg, var(--btn-from), var(--btn-to)); color: #fff; }
.msg.user .av { background: var(--panel-3); color: var(--dim); }
.msg .body { flex: 1; min-width: 0; }
/* 用户消息：整块靠右。用 row-reverse + flex-start 让头像落在最右侧，
   而不是靠 margin-left:auto 把气泡推过去——那会在头像和气泡之间留一大段空白。 */
.msg.user { flex-direction: row-reverse; justify-content: flex-start; }
.msg.user .body {
  flex: 0 1 auto;
  background: var(--pri-soft);
  border: 1px solid color-mix(in srgb, var(--pri) 15%, transparent);
  border-radius: 16px 16px 5px 16px;
  padding: 11px 15px; max-width: 78%;
  box-shadow: 0 1px 2px rgba(16, 24, 40, .04);
}
.msg.user .md p:first-child { margin-top: 0; }
.msg.user .md p:last-child { margin-bottom: 0; }
/* AI 落款行：谁在说话 + 什么时候说的（竞品同款） */
.msg .meta {
  font-size: .72rem; color: var(--dim2); margin: 0 0 7px;
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
}
.msg .meta .who { font-weight: 600; color: var(--dim); }
.msg.user .meta { display: none; }
/* 操作条：始终可见（用户反馈找不到删除按钮）。hover 时按钮本身变实。 */
.msg .acts { display: flex; gap: 5px; margin-top: 10px; opacity: 1; flex-wrap: wrap; }
.msg .acts .btn { opacity: .65; transition: opacity .18s; }
.msg:hover .acts .btn, .msg .acts:hover .btn { opacity: 1; }
.msg .acts .btn { font-size: .72rem; padding: .2em .58em; border-radius: 8px; }

/* ── 追问建议（批次47，2026-10-06）────────────────────────────────
   用户诉求「AI 交互互动性和趣味性不够」。
   位置：渲染在 .acts **之前**（追问是"继续对话"，比"对这条回复的操作"更靠前）。
   ★ 它是**建议**不是考试：所以做成"一点就把这句话发出去"的快捷方式，
     而不是要学生作答的题。学生想无视就无视，想自己打字就自己打字。
   ★ 视觉上必须比 .acts 显眼（.acts 是 .65 透明度的次要操作），
     否则学生根本注意不到 —— 那这个功能等于没做。
     用 --pri-soft 底 + --pri 字，跟互动卡的"想一想"同一族色，
     让人一眼认出"这是可以点的、和普通文字不一样"。 */
.asks { display: flex; gap: 6px; margin-top: 12px; flex-wrap: wrap; align-items: center; }
.asks-lead { font-size: .72rem; color: var(--dim); margin-right: 2px; flex: none; }
.ask-chip {
  font: inherit; font-size: .78rem; line-height: 1.4;
  padding: .32em .72em; border-radius: 999px; cursor: pointer;
  background: var(--pri-soft); color: var(--pri);
  border: 1px solid transparent;
  transition: background .16s, border-color .16s, transform .16s;
  text-align: left;
}
.ask-chip:hover { border-color: var(--pri); transform: translateY(-1px); }
.ask-chip:active { transform: translateY(0); }
/* 触屏没有 hover：给一点常驻的下划线暗示可点 */
@media (hover: none) {
  .ask-chip { background: var(--pri-soft); border-color: color-mix(in srgb, var(--pri) 28%, transparent); }
}

/* ================= 回复末尾的「飘带」：鼓励语 + 彩蛋（批次63） =================
   ★ 为什么单独一条、且比"接着问"更轻：
     鼓励是"对刚做完这件事的回应"（往回看），追问是"接下来能做什么"（往前看）——
     两件事混一行会既分不清、又都不想点。所以飘带用**更浅**的底、更小的字号，
     像一句附言，不抢正文和追问的注意力。
   ★ 为什么用 --ok（绿）而不是 --pri：学校场景里蓝"确定/可点"已经给了追问与按钮，
     鼓励换一族色（绿=肯定）才不会和"接着问"的 chip 混成一堆。
   ★ 彩蛋（--warn 琥珀）常驻一个可点样式，但**多数轮次根本不会出现**（模型只在
     真出现值得纪念的时刻才写）—— 稀缺感就是它全部的价值。 */
.tail-ribbon {
  display: flex; gap: 8px; margin-top: 12px; flex-wrap: wrap; align-items: baseline;
}
.tail-cheer {
  display: inline-flex; align-items: baseline; gap: .4em;
  font-size: .8rem; line-height: 1.6; color: var(--dim);
  padding: .18em .1em .18em 0;
}
.tail-cheer .tail-ico { color: var(--ok); font-size: .78em; flex: none; }
.tail-egg {
  font: inherit; font-size: .78rem; line-height: 1.6;
  display: inline-flex; align-items: baseline; gap: .4em;
  padding: .26em .7em; border-radius: 999px; cursor: pointer;
  background: var(--warn-soft); color: var(--warn);
  border: 1px solid color-mix(in srgb, var(--warn) 30%, transparent);
  transition: transform .16s, border-color .16s;
  text-align: left;
}
.tail-egg:hover { border-color: var(--warn); transform: translateY(-1px); }
.tail-egg:active { transform: translateY(0); }
.tail-egg .tail-ico { font-size: .95em; flex: none; }
/* 点一下"挖到了"的小动画：纯装饰。reduced-motion 下不动，尊重系统设置。 */
.tail-egg.pop { animation: eggPop .55s cubic-bezier(.34,1.56,.64,1); }
@keyframes eggPop {
  0% { transform: scale(1) rotate(0); }
  35% { transform: scale(1.06) rotate(-2deg); }
  65% { transform: scale(.99) rotate(1.5deg); }
  100% { transform: scale(1) rotate(0); }
}
@media (prefers-reduced-motion: reduce) {
  .tail-egg.pop { animation: none; }
  .tail-egg:hover { transform: none; }
}
/* 选中文字朗读浮动按钮 */
.sel-tts-btn { animation: popIn .18s ease-out; }
@keyframes popIn { from { transform: scale(.85); opacity: 0; } to { transform: scale(1); opacity: 1; } }

.caret { display: inline-block; width: 2px; height: 1.05em; background: var(--pri); vertical-align: text-bottom; animation: blink 1s step-end infinite; margin-left: 1px; }
@keyframes blink { 50% { opacity: 0; } }

.think { color: var(--dim2); font-size: .84rem; font-style: italic; margin-bottom: 6px; }

/* ================= Markdown 渲染 =================
 * 目标：让一段回答「条理分明、重点清晰」——
 *   · 加粗 = 荧光笔（不是单纯变粗），重点词一眼扫得到；
 *   · 有序列表 = 圆形序号徽章，步骤感强、顺序一眼看清；
 *   · 小标题 = 左侧色条，段落层次不用读也能分出来。 */
.md { font-size: .965rem; line-height: 1.82; word-wrap: break-word; overflow-wrap: anywhere; }
.md > *:first-child { margin-top: 0; }
.md > *:last-child { margin-bottom: 0; }
.md p { margin: .62em 0; }

/* 重点：加粗 + 主题色 + 底部荧光带 */
.md strong {
  font-weight: 700; color: var(--pri-2);
  background: linear-gradient(transparent 58%, color-mix(in srgb, var(--pri) 16%, transparent) 58%);
  padding: 0 .08em; border-radius: 3px;
}
/* 用户气泡底色本身就是浅蓝，荧光带叠上去会糊——那里只保留颜色 */
.msg.user .md strong { background: none; }

/* 标题：左侧色条，层次分明 */
.md h1, .md h2, .md h3, .md h4 { margin: 1.3em 0 .55em; line-height: 1.42; font-weight: 700; }
.md h1 { font-size: 1.28rem; }
.md h2 { font-size: 1.14rem; padding-left: .62em; border-left: 4px solid var(--pri); border-radius: 2px; }
.md h3 { font-size: 1.02rem; padding-left: .6em; border-left: 3px solid color-mix(in srgb, var(--pri) 42%, transparent); }
.md h4 { font-size: .95rem; color: var(--dim); }

/* 列表：有序 = 序号徽章，无序 = 主题色圆点 */
.md ul, .md ol { margin: .7em 0; }
.md ul { list-style: none; padding-left: 1.5em; }
.md ol { list-style: none; padding-left: 2.1em; counter-reset: md-ol; }
.md li { margin: .46em 0; }
.md ol > li { counter-increment: md-ol; position: relative; }
.md ol > li::before {
  content: counter(md-ol);
  position: absolute; left: -2.1em; top: .2em;
  width: 1.62em; height: 1.62em; line-height: 1.62em;
  border-radius: 50%; background: var(--pri-soft); color: var(--pri-2);
  font-size: .78em; font-weight: 700; text-align: center;
  font-variant-numeric: tabular-nums;
}
.md ul > li { position: relative; }
.md ul > li::before {
  content: ""; position: absolute; left: -1.1em; top: .78em;
  width: 6px; height: 6px; border-radius: 50%;
  background: color-mix(in srgb, var(--pri) 55%, transparent);
}
/* 嵌套一层换个颜色，避免满屏都是同一颗点 */
.md ul ul > li::before { background: var(--dim2); width: 5px; height: 5px; left: -1.05em; }
.md ul ul, .md ul ol, .md ol ul, .md ol ol { margin: .35em 0 .1em; }

.md blockquote {
  margin: .9em 0; padding: .68em 1.05em; border-left: 3px solid var(--pri);
  background: var(--panel-2); border-radius: 0 10px 10px 0; color: var(--dim);
}
.md blockquote p { margin: .28em 0; }
.md a { color: var(--pri); text-decoration: none; border-bottom: 1px solid color-mix(in srgb, var(--pri) 35%, transparent); }
.md a:hover { border-bottom-color: var(--pri); }
.md hr { border: 0; border-top: 1px solid var(--line); margin: 1.3em 0; }
.md code.inl {
  font-family: var(--mono); font-size: .87em;
  background: var(--panel-3); color: var(--pri-2);
  padding: .12em .42em; border-radius: 6px;
}
[data-theme="dark"] .md code.inl { color: var(--pri-2); }
.md table { border-collapse: collapse; width: 100%; margin: .95em 0; font-size: .88rem; display: block; overflow-x: auto; }
.md th, .md td { border: 1px solid var(--line); padding: .5em .75em; text-align: left; }
.md th { background: var(--panel-2); font-weight: 650; }
.md tbody tr:nth-child(even) { background: color-mix(in srgb, var(--panel-2) 55%, transparent); }
/* 正文里的图片（模型用 markdown 插图时） */
.md img {
  display: block; max-width: 100%; height: auto; margin: .8em 0;
  border-radius: 12px; border: 1px solid var(--line); box-shadow: var(--shadow);
}
.md .katex { font-family: var(--mono); }

/* 代码块 */
.code-wrap { position: relative; margin: .9em 0; border-radius: var(--r-sm); overflow: hidden; border: 1px solid var(--line); background: var(--panel-2); }
.code-h {
  display: flex; align-items: center; gap: 8px;
  padding: 5px 10px; background: var(--panel-3); border-bottom: 1px solid var(--line);
  font-size: .72rem; color: var(--dim); font-family: var(--mono);
}
.code-h .sp { flex: 1; }
.code-copy { border: 0; background: transparent; color: var(--dim); font-size: .72rem; padding: 2px 6px; border-radius: 5px; }
.code-copy:hover { background: var(--panel); color: var(--pri); }
.code-wrap pre { margin: 0; padding: 12px 14px; overflow-x: auto; }
.code-wrap code { font-family: var(--mono); font-size: .82rem; line-height: 1.7; white-space: pre; }
.tk-k { color: #C026D3; } .tk-s { color: #16A34A; } .tk-c { color: var(--dim2); font-style: italic; }
.tk-n { color: #EA580C; } .tk-f { color: #2563EB; } .tk-p { color: var(--dim); }
[data-theme="dark"] .tk-k { color: #F0ABFC; } [data-theme="dark"] .tk-s { color: #86EFAC; }
[data-theme="dark"] .tk-n { color: #FDBA74; } [data-theme="dark"] .tk-f { color: #93C5FD; }

/* 结构化图（AI 产出） */
/* 图示卡：宽度封顶 560px —— 不封顶时 SVG 会撑满 840px 的内容列，
   一张三角形就能占掉整个视口，把上下文全挤出屏幕。560 宽足够看清 A/B/C 和"高"。 */
.art { margin: 1em 0; border: 1px solid var(--line); border-radius: var(--r); background: var(--panel-2); padding: 12px; overflow: hidden; max-width: 560px; }
.art-h { font-size: .8rem; color: var(--dim); margin-bottom: 8px; display: flex; align-items: center; gap: 8px; }
.art-h b { color: var(--txt); font-weight: 600; }
.art svg { display: block; width: 100%; height: auto; }
.art .art-bad { font-size: .8rem; color: var(--warn); }

/* 数学（自研轻量渲染） */
.math { font-family: "Times New Roman", var(--mono), serif; font-style: italic; font-size: 1.02em; padding: 0 .1em; }
.math-block { display: block; text-align: center; margin: 1em 0; font-size: 1.12em; }
.frac { display: inline-flex; flex-direction: column; vertical-align: middle; text-align: center; margin: 0 .18em; font-style: normal; }
.frac > .num { border-bottom: 1px solid currentColor; padding: 0 .3em; font-size: .92em; }
.frac > .den { padding: 0 .3em; font-size: .92em; }
.sqrt { display: inline-block; font-style: normal; }
.sqrt > .rad { border-top: 1px solid currentColor; padding: 0 .18em; margin-left: -.06em; }

/* 环境（方程组 / 矩阵 / 对齐）—— \begin{cases} 这类整块排版。
   全部用主题变量，深浅色都跟着走。 */
.math-env { display: inline-flex; align-items: center; gap: .12em; font-style: normal; vertical-align: middle; }
.math-env table { border-collapse: collapse; font-style: italic; }
.math-env td { padding: .12em .45em; text-align: left; white-space: nowrap; }
/* cases：左侧大括号。用缩放的字形做，不引 SVG 字体 */
.cases-d { font-size: 2.7em; font-style: normal; font-weight: 300; line-height: 1; color: var(--txt); transform: scaleX(.62); }
.cases-d.left { margin-right: -.18em; }
.cases-d.right { margin-left: -.18em; }
.math-cases td.cond { font-style: normal; color: var(--dim); font-size: .92em; padding-left: .9em; }
/* 矩阵两侧的圆括号/方括号 */
.math-matrix-wrap > .mtx-d { font-size: 2.2em; font-style: normal; line-height: 1; color: var(--txt); transform: scaleX(.62); }
.math-matrix td { text-align: center; padding: .1em .5em; }
/* aligned：& 前的列右对齐、& 后的列左对齐，等号才能排成一条线 */
.math-aligned td { padding: .1em .2em; }
.math-aligned td.al-r { text-align: right; padding-right: .1em; }
.math-aligned td.al-l { text-align: left; padding-left: .1em; }
.math-block .math-env { display: block; }
.math-block .math-cases-wrap,
.math-block .math-matrix-wrap { display: inline-flex; }

/* ================= 输入区 ================= */
.composer {
  flex: 0 0 auto; display: flex; align-items: flex-end; gap: 8px;
  padding: 12px 18px 14px; border-top: 1px solid var(--line); background: var(--panel);
}
.composer textarea {
  flex: 1; resize: none; min-height: 2.6em; max-height: 9em;
  padding: .6em .85em; line-height: 1.6; border-radius: var(--r);
}
.mic {
  flex: 0 0 auto; width: 2.4em; height: 2.4em; border-radius: 50%;
  border: 1px solid var(--line-2); background: var(--panel); color: var(--dim);
  display: flex; align-items: center; justify-content: center; font-size: 1rem;
  transition: background .15s, color .15s, box-shadow .15s;
}
.mic:hover { color: var(--pri); border-color: var(--pri); }
.mic.on { background: var(--bad); border-color: var(--bad); color: #fff; animation: pulse 1.4s ease-in-out infinite; }
@keyframes pulse { 0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--bad) 45%, transparent); } 50% { box-shadow: 0 0 0 8px transparent; } }
.voice-hint { padding: 0 18px 8px; font-size: .74rem; color: var(--dim2); min-height: 1.1em; }
/* ================= 画板（画图回答）=================
   独立覆盖层，不复用 .modal —— 画板要的是一块尽量大的作画区，
   .modal 的 width:min(560px,…) 会把它压成一条。
   ★ 基准规则全部写在所有 @media 之前（本项目踩过"基准写在媒体查询之后
     被直接盖掉、零报错"的坑）。 */
.bd-mask { padding: 16px; }
.bd-panel {
  background: var(--panel); border: 1px solid var(--line); border-radius: 18px;
  width: min(1000px, 100%); max-height: 100%; display: flex; flex-direction: column;
  box-shadow: var(--shadow-lg); animation: rise .26s cubic-bezier(.22,.8,.3,1) both;
  overflow: hidden;
  /* ★★ 必须给 height，不能只给 max-height。
     只给 max-height 时 panel 会**按内容收缩**（实测 900px 视口下只有 320px 高），
     而 .bd-stage 的 flex:1 只能在 panel 已有的高度里分配 —— 结果作画区只剩 148px，
     一条横杠，根本画不了东西。给了 height:100% 才有"剩余空间"可分。 */
  height: 100%;
}
.bd-head {
  display: flex; align-items: center; gap: 10px; padding: 13px 16px;
  border-bottom: 1px solid var(--line); flex: 0 0 auto;
}
.bd-head b { font-size: 1rem; }
.bd-tip { font-size: .75rem; color: var(--dim2); }

.bd-bar {
  display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
  padding: 9px 16px; border-bottom: 1px solid var(--line);
  flex: 0 0 auto; background: var(--panel-2);
}
.bd-grp { display: flex; align-items: center; gap: 5px; }
.bd-sep { width: 1px; height: 20px; background: var(--line-2); }
.bd-t {
  width: 30px; height: 30px; border-radius: 8px; border: 1px solid var(--line-2);
  background: var(--panel); color: var(--dim); display: flex; align-items: center;
  justify-content: center; padding: 0;
}
.bd-t:hover { color: var(--pri); border-color: var(--pri); }
/* 选中态：工具按钮与颜色/粗细共用一套"选中"外观，学生一眼看出当前拿的是什么 */
.bd-t[aria-pressed="true"], .bd-dot[aria-pressed="true"], .bd-sz[aria-pressed="true"] {
  border-color: var(--pri); box-shadow: 0 0 0 2px var(--pri-soft);
}
.bd-t[aria-pressed="true"] { color: var(--pri); background: var(--pri-soft); }
.bd-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.bd-dot {
  width: 24px; height: 24px; border-radius: 50%; border: 1px solid var(--line-2);
  padding: 0; position: relative;
}
/* 颜色按钮内部再画一个小圆：border 用来表示选中，内圆才是真颜色 */
.bd-dot::after {
  content: ""; position: absolute; inset: 3px; border-radius: 50%; background: var(--c, #000);
}
.bd-sz {
  width: 30px; height: 30px; border-radius: 8px; border: 1px solid var(--line-2);
  background: var(--panel); display: flex; align-items: center; justify-content: center; padding: 0;
}
.bd-sz::after { content: ""; border-radius: 50%; background: var(--dim); width: var(--d, 6px); height: var(--d, 6px); }

/* 作画区：白底 —— 与导出的 PNG 一致，学生看到什么发出去就是什么。
   深色主题下也不改：手写答案就是写在白纸上的，这里保持"纸"的隐喻。
   min-height 给的是**下限**（极矮视口上别把作画区压成一条），
   正常情况由 panel 的 height:100% + flex:1 决定它有多高。 */
.bd-stage {
  flex: 1 1 auto; min-height: 200px; position: relative;
  background: #FFFFFF; overflow: hidden; touch-action: none;
}
.bd-canvas { display: block; cursor: crosshair; touch-action: none; }
.bd-foot {
  display: flex; align-items: center; gap: 10px; padding: 11px 16px;
  border-top: 1px solid var(--line); flex: 0 0 auto;
}
.bd-empty { font-size: .78rem; color: var(--dim2); }


/* ================= 知识卡 ================= */
.card-stats { display: flex; gap: 8px; padding: 14px 18px 4px; flex-wrap: wrap; flex: 0 0 auto; }
.st {
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--r-sm);
  padding: 8px 14px; min-width: 78px; cursor: pointer; transition: border-color .15s, transform .08s;
}
.st:hover { border-color: var(--dim2); }
.st:active { transform: translateY(1px); }
.st.on { border-color: var(--pri); background: var(--pri-soft); }
.st b { display: block; font-size: 1.22rem; font-weight: 700; line-height: 1.25; }
.st span { font-size: .72rem; color: var(--dim); }
.st.hot b { color: var(--bad); }
.st.done b { color: var(--ok); }

.card-bar { display: flex; gap: 8px; padding: 10px 18px; align-items: center; flex-wrap: wrap; flex: 0 0 auto; }
.card-bar .inp { flex: 1; min-width: 140px; }
.card-list { flex: 1; overflow-y: auto; padding: 4px 18px 20px; display: flex; flex-direction: column; gap: 9px; }

.kcard {
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--r);
  padding: 13px 15px; transition: border-color .15s, box-shadow .15s;
}
.kcard:hover { border-color: var(--line-2); box-shadow: var(--shadow); }
.kcard.due { border-left: 3px solid var(--warn); }
.kc-top { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; flex-wrap: wrap; }
.kc-k { font-weight: 650; font-size: .95rem; flex: 1; min-width: 0; }
.chip { font-size: .68rem; padding: .12em .55em; border-radius: 20px; font-weight: 600; white-space: nowrap; }
.chip.learning { background: var(--pri-soft); color: var(--pri); }
.chip.almost { background: var(--warn-soft); color: var(--warn); }
.chip.mastered { background: var(--ok-soft); color: var(--ok); }
.chip.retired { background: var(--panel-3); color: var(--dim); }
.chip.ok { background: var(--ok-soft); color: var(--ok); }
.chip.t-choice { background: var(--panel-3); color: var(--dim); }
.chip.t-spelling { background: var(--panel-3); color: var(--dim); }
.chip.t-understanding { background: var(--panel-3); color: var(--dim); }
.kc-q { color: var(--dim); font-size: .86rem; margin: 2px 0 8px; }
.kc-foot { display: flex; align-items: center; gap: 10px; font-size: .74rem; color: var(--dim2); flex-wrap: wrap; }
.kc-foot .sp { flex: 1; }
.pager { display: flex; justify-content: center; gap: 6px; padding: 6px 18px 18px; flex: 0 0 auto; }
.pager button { border: 1px solid var(--line-2); background: var(--panel); border-radius: 7px; padding: .25em .7em; font-size: .82rem; color: var(--dim); }
.pager button.on { background: var(--pri); border-color: var(--pri); color: #fff; }
.pager button[disabled] { opacity: .4; pointer-events: none; }

/* 练习弹窗 */
.practice { max-width: 620px; }
.pc-k { font-size: .78rem; color: var(--dim); margin-bottom: 4px; }
.pc-q { font-size: 1.02rem; font-weight: 550; margin-bottom: 16px; line-height: 1.7; }
.pc-opts { display: flex; flex-direction: column; gap: 8px; margin-bottom: 14px; }
.pc-opt {
  display: flex; align-items: flex-start; gap: 10px; text-align: left;
  border: 1px solid var(--line-2); background: var(--panel); border-radius: var(--r-sm);
  padding: .65em .9em; font-size: .93rem; transition: border-color .15s, background .15s;
}
.pc-opt:hover { border-color: var(--pri); background: var(--pri-soft); }
.pc-opt .oi {
  flex: 0 0 22px; width: 22px; height: 22px; border-radius: 50%;
  background: var(--panel-3); color: var(--dim); font-size: .76rem; font-weight: 700;
  display: flex; align-items: center; justify-content: center;
}
.pc-opt.right { border-color: var(--ok); background: var(--ok-soft); }
.pc-opt.right .oi { background: var(--ok); color: #fff; }
.pc-opt.wrong { border-color: var(--bad); background: var(--bad-soft); }
.pc-opt.wrong .oi { background: var(--bad); color: #fff; }
.pc-opt[disabled] { pointer-events: none; }
.pc-input { width: 100%; min-height: 5em; border: 1px solid var(--line-2); background: var(--panel); border-radius: var(--r-sm); padding: .7em .9em; resize: vertical; outline: none; }
.pc-input:focus { border-color: var(--pri); box-shadow: 0 0 0 3px var(--pri-soft); }
.pc-fb { border-radius: var(--r-sm); padding: 10px 13px; font-size: .88rem; margin: 12px 0; line-height: 1.7; }
.pc-fb.ok { background: var(--ok-soft); color: var(--ok); }
.pc-fb.bad { background: var(--bad-soft); color: var(--bad); }
.pc-fb.mid { background: var(--warn-soft); color: var(--warn); }
.pc-fb.neutral { background: var(--panel-2); color: var(--dim); }
.pc-hint { background: var(--panel-2); border-left: 3px solid var(--pri); border-radius: 0 var(--r-sm) var(--r-sm) 0; padding: 8px 12px; font-size: .86rem; color: var(--dim); margin: 10px 0; }
.pc-acts { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-top: 14px; }
.pc-acts .sp { flex: 1; }
.pc-verdict { display: flex; gap: 6px; margin: 8px 0 2px; flex-wrap: wrap; }
.pc-verdict .chip { font-size: .72rem; padding: .25em .7em; }
.pc-covered { font-size: .84rem; color: var(--ok); margin: 3px 0; }
.pc-missing { font-size: .84rem; color: var(--warn); margin: 3px 0; }

/* 自由练习开关 / 规则 / AI 收卡建议 */
.pc-free { display: inline-flex; align-items: center; gap: 6px; font-size: .8rem; color: var(--dim); margin: 4px 0 2px; cursor: pointer; user-select: none; }
.pc-free input { width: 15px; height: 15px; accent-color: var(--pri); }
.card-quota { font-size: .72rem; color: var(--dim); border: 1px solid var(--line); border-radius: 999px; padding: 3px 10px; white-space: nowrap; }
.card-quota.low { color: var(--warn); border-color: var(--warn); }
.card-ai { display: flex; align-items: center; gap: 10px; padding: 10px 18px; background: var(--pri-soft); border-radius: var(--r); margin: 4px 18px 0; flex-wrap: wrap; }
.card-ai-ic { color: var(--pri); font-weight: 700; }
.card-ai-txt { font-size: .82rem; color: var(--dim); flex: 1; min-width: 160px; }
.rules { margin: 4px 2px 2px; padding-left: 20px; line-height: 1.9; font-size: .92rem; }
.rules li { margin: 2px 0; }
.sg-note { font-size: .84rem; color: var(--dim); line-height: 1.7; margin-bottom: 12px; }
.sg-row { display: flex; align-items: center; gap: 10px; padding: 10px 12px; border: 1px solid var(--line); border-radius: var(--r-sm); margin-bottom: 8px; cursor: pointer; }
.sg-row:hover { border-color: var(--pri); }
.sg-chk { width: 16px; height: 16px; accent-color: var(--pri); flex: 0 0 auto; }
.sg-k { font-weight: 600; font-size: .92rem; flex: 0 0 auto; max-width: 46%; }
.sg-q { font-size: .8rem; color: var(--dim); flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }


/* 通用卡片行 */
.row-card {
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--r);
  padding: 13px 16px; display: flex; align-items: center; gap: 12px;
}
.row-card > div:first-child { flex: 1; min-width: 0; }
.row-card b { font-size: .93rem; }
.row-card .dim { font-size: .78rem; }

/* ---- 管理后台：异地运维用得到，要能一眼看清"这是谁、能不能登" ---- */
.adm-tabs { display: flex; gap: 8px; margin-bottom: 12px; }
.adm-tab {
  padding: .35em .85em; border-radius: 20px; border: 1px solid var(--line);
  background: var(--panel); color: var(--dim); font-size: .84rem; cursor: pointer;
}
.adm-tab.on { background: var(--pri-soft); color: var(--pri); border-color: var(--pri); font-weight: 600; }

/* 空间行的操作区（改口令）。窄屏换行，不要挤成一条。 */
.adm-sp-ops { display: flex; gap: 8px; align-items: center; flex: 0 0 auto; flex-wrap: wrap; }
/* "无口令"是个真问题，不能跟"有口令"一样是灰字 —— 那正是本次要修的东西。 */
.adm-nopw { color: var(--bad); font-weight: 600; }
.adm-user {
  display: block; margin-bottom: 10px; align-items: unset;
  border-left: 3px solid var(--line);
}
.adm-user:has(.chip.almost) { border-left-color: var(--warn); }
.adm-u-h { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 4px; }
.adm-u-m { font-size: .76rem; line-height: 1.7; word-break: break-all; }
.adm-u-act { display: flex; gap: 8px; margin-top: 9px; flex-wrap: wrap; }

/* ---- 管理后台：创建空间的授权码（批次59，2026-10-06）----
   它是"能不能建空间"的闸门，固定在空间列表上方。视觉与「入班码」同一套：
   码要大到一眼能抄走 —— 管理员的工作就是把它念给 / 发给要建空间的人。 */
.adm-grant { display: block; align-items: unset; margin-bottom: 12px; border-left: 3px solid var(--warn); }
.adm-grant-code {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin: 8px 0 2px; padding: .5em .7em;
  border: 1px dashed var(--line-2); border-radius: var(--r-sm); background: var(--panel-2);
}
.adm-grant-code b { font-family: var(--mono); font-size: 1.5rem; letter-spacing: .22em; color: var(--pri); }

/* ---- 管理后台：班级（批次58）----
   班级从"学生自己建"改成"管理员建 + 管理员主动拉人"。这块要装下：
   一个班一行、入班码（要能一键复制）、以及一个可展开的「拉人 / 成员」面板。 */
.adm-cls { display: block; margin-bottom: 10px; align-items: unset; border-left: 3px solid var(--pri); }
.adm-cls-code {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin-top: 7px; padding: .42em .65em;
  border: 1px dashed var(--line-2); border-radius: var(--r-sm); background: var(--panel-2);
}
.adm-cls-code span { font-size: .74rem; color: var(--dim2); }
.adm-cls-code b { font-family: var(--mono); font-size: 1.08rem; letter-spacing: .14em; color: var(--pri); }
.adm-cls-code i { font-style: normal; font-size: .72rem; color: var(--dim2); }
/* 展开区：拉人面板与成员名单都落在这里，跟行之间拉一条分隔线 */
.adm-cls-pane {
  margin-top: 10px; padding-top: 10px;
  border-top: 1px solid var(--line); 
}
.adm-pick-h { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 8px; }
.adm-pick-q {
  flex: 1 1 200px; min-width: 0; padding: .42em .7em;
  border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--panel); color: var(--txt); font-size: .84rem;
}
.adm-pick-list { max-height: 260px; overflow: auto; border: 1px solid var(--line); border-radius: var(--r-sm); }
.adm-pick-i {
  display: flex; align-items: center; gap: 8px; padding: .45em .65em;
  border-bottom: 1px solid var(--line); cursor: pointer; font-size: .84rem;
}
.adm-pick-i:last-child { border-bottom: 0; }
.adm-pick-i:hover { background: var(--panel-2); }
/* 已经在班里的人：不可勾（灰掉），一眼能和"可选"分开 */
.adm-pick-i.in { background: var(--panel-3); cursor: default; }
.adm-pick-i.in .adm-pick-n { color: var(--dim); }
.adm-pick-n { font-weight: 600; }
.adm-pick-m { font-size: .74rem; }
.adm-pick-note { font-size: .74rem; line-height: 1.7; margin-top: 8px; }
.adm-cls-memlist { border: 1px solid var(--line); border-radius: var(--r-sm); }
.adm-cls-mem {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: .45em .65em; border-bottom: 1px solid var(--line); font-size: .82rem;
}
.adm-cls-mem:last-child { border-bottom: 0; }
.adm-cls-mn { font-weight: 600; }

/* ================= 弹窗 / 提示 ================= */
.mask {
  position: fixed; inset: 0; z-index: 80; background: rgba(12, 16, 26, .5);
  display: flex; align-items: center; justify-content: center; padding: 20px;
  backdrop-filter: blur(2px); animation: fade .18s ease both;
}
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }
.modal {
  background: var(--panel); border: 1px solid var(--line); border-radius: 18px;
  width: min(560px, 100%); max-height: 86vh; display: flex; flex-direction: column;
  box-shadow: var(--shadow-lg); animation: rise .26s cubic-bezier(.22,.8,.3,1) both;
}
.modal-h { display: flex; align-items: center; gap: 10px; padding: 15px 18px; border-bottom: 1px solid var(--line); flex: 0 0 auto; }
.modal-h b { font-size: 1rem; }
.modal-b { padding: 18px; overflow-y: auto; }

/* 密码 / 口令浮层（2026-10-06）
   ★ z-index 必须高于 .mask(80) —— 管理后台弹窗里也会叠着弹它。
   输入框默认 type="password"（遮蔽）；只有"展示新口令给你抄"那次才临时切 text。 */
.pw-mask { z-index: 90; }
.pw-modal { width: min(420px, 100%); }
.pw-hint { margin: 0 0 10px; font-size: .84rem; line-height: 1.6; }
.pw-input { width: 100%; box-sizing: border-box; letter-spacing: .06em; }
.pw-err { min-height: 1.2em; margin-top: 6px; font-size: .8rem; color: var(--bad); }

/* 从错题本导入（wn = wrong-notebook）
   ★ 只用主题变量，别写死颜色 —— 亮/暗两套主题都要能看。 */
.wn-p { margin: 0 0 12px; line-height: 1.6; font-size: .9rem; }
.wn-hint { margin: 8px 0 0; font-size: .82rem; color: var(--dim); line-height: 1.55; }
.wn-err { color: var(--bad); font-size: .86rem; }
.wn-row { margin: 10px 0 4px; }
.wn-row .inp { width: 100%; }
.wn-prev { margin: 6px 0 14px; font-size: .88rem; line-height: 1.6; }
.wn-sample { margin: 8px 0 0; padding-left: 18px; color: var(--dim); }
.wn-sample li { margin: 3px 0; font-size: .84rem; }
.wn-acts { display: flex; align-items: center; gap: 10px; }
.wn-acts .sp { flex: 1 1 auto; }
/* 目标项目下拉 + 说明 */
.wn-top { margin: 0 0 10px; font-size: .88rem; }
.wn-top .inp { margin-left: 6px; min-width: 180px; }
.wn-top .wn-hint { margin-top: 4px; }
/* 错题勾选列表：撑高但封顶，超出的滚动 —— 一次列几十题也不至于把弹窗顶出屏幕 */
.wn-list {
  max-height: 46vh; overflow: auto;
  border: 1px solid var(--line); border-radius: 8px;
  padding: 4px; margin: 0 0 12px;
}
.wn-list .wn-all {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 8px; border-bottom: 1px solid var(--line);
  font-size: .84rem;
  position: sticky; top: 0;
  background: var(--panel); z-index: 1;
}
.wn-list .wn-all label { display: flex; align-items: center; gap: 6px; cursor: pointer; }
.wn-list .wn-all .dim { margin-left: auto; }
.wn-li {
  display: flex; align-items: flex-start; gap: 8px;
  padding: 7px 8px; border-radius: 6px; cursor: pointer; line-height: 1.5;
}
.wn-li:hover { background: var(--panel-2); }
.wn-li input { margin-top: 3px; flex: 0 0 auto; }
.wn-li-t { flex: 1 1 auto; font-size: .87rem; word-break: break-word; }
.wn-li-m { flex: 0 0 auto; font-size: .78rem; color: var(--dim); white-space: nowrap; }

/* 错题本分区（SSO 免密内嵌）。
   ★ 为什么要 flex:1 + min-height:0：分区容器本身是 flex 列，iframe 要"撑满剩余高度"。
     只写 height:100% 在 flex 子项里算不出基准高度，会塌成 0 —— min-height:0 才让
     flex 子项允许被压缩、从而正确分配。这是 iframe 嵌进 flex 布局最常见的坑。 */
.wn-frame-wrap { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
.wn-frame-tip { padding: 18px 4px; font-size: .9rem; line-height: 1.7; max-width: 520px; }
.wn-frame {
  flex: 1 1 auto; width: 100%; min-height: 0;
  border: 0; border-radius: 10px; background: var(--panel);
  /* 让内嵌页面在深色主题下不至于刺眼：与自身主题一致时无副作用 */
  color-scheme: normal;
}
.toast {
  position: fixed; left: 50%; bottom: 32px; transform: translateX(-50%);
  background: var(--txt); color: var(--bg); padding: .55em 1.1em; border-radius: 22px;
  font-size: .86rem; z-index: 120; box-shadow: var(--shadow-lg); max-width: 84vw; text-align: center;
  animation: toastIn .24s cubic-bezier(.22,.8,.3,1) both;
}
@keyframes toastIn { from { opacity: 0; transform: translate(-50%, 10px); } to { opacity: 1; transform: translate(-50%, 0); } }

/* 骨架屏 */
.skel { background: var(--panel-2); border-radius: var(--r-sm); height: 1em; animation: shimmer 1.4s ease-in-out infinite; }
@keyframes shimmer { 0%, 100% { opacity: .55; } 50% { opacity: 1; } }

/* 图表（原生 SVG） */
.chart { margin: 4px 0 2px; }
.chart-t { font-size: .82rem; color: var(--dim); margin-bottom: 2px; }
.chart-lg { display: flex; gap: 14px; flex-wrap: wrap; font-size: .74rem; color: var(--dim); margin-bottom: 4px; }
.chart-lg span { display: inline-flex; align-items: center; gap: 5px; }
.chart-lg i { width: 9px; height: 9px; border-radius: 3px; display: inline-block; }
.chart svg { display: block; width: 100%; height: auto; }

/* 看板 */
.dash-grid { display: flex; gap: 8px; flex-wrap: wrap; padding: 14px 18px 4px; }
.dash-kpi {
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--r-sm);
  padding: 9px 15px; min-width: 92px;
}
.dash-kpi b { display: block; font-size: 1.35rem; font-weight: 700; line-height: 1.2; }
.dash-kpi span { font-size: .72rem; color: var(--dim); }
.dash-kpi.up b { color: var(--ok); }
.dash-kpi.down b { color: var(--warn); }
.dash-panel {
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--r);
  padding: 14px 16px; margin-bottom: 10px;
}
.dash-panel > h3 { margin: 0 0 10px; font-size: .88rem; font-weight: 600; display: flex; align-items: center; gap: 8px; }
.dash-panel > h3 span { font-weight: 400; color: var(--dim2); font-size: .74rem; }
.report-lines { margin: 0; padding-left: 1.1em; font-size: .88rem; line-height: 1.85; color: var(--txt); }
.report-lines li { margin: .15em 0; }
.report-lines li::marker { color: var(--pri); }
.weak-i { display: flex; align-items: center; gap: 10px; padding: .5em 0; border-bottom: 1px solid var(--line); font-size: .86rem; }
.weak-i:last-child { border-bottom: 0; }
.weak-i .w-t { flex: 1; min-width: 0; }
.weak-i .w-n { font-family: var(--mono); font-size: .78rem; color: var(--bad); white-space: nowrap; }

/* ================= 学习日报（批次10）================= */
/* #dashBody 是 .card-list（flex 列 + overflow-y:auto），日报与看板是它里面两个普通块。
   ★ 都写 flex:none —— 默认 flex-shrink:1 时，两块内容一高就会被压缩，
     又变回"各自滚动、两块都看不全"。 */
#dailyBox, #dashPanels { flex: none; }

/* 周报（批次16 + 增强） */
#weeklyBox { margin-bottom: 10px; padding: 14px; background: var(--panel); border-radius: 12px; border: 1px solid var(--line); }

/* 头部：标题 + 日期选择器。
   ★ 这里不用 flex-wrap —— 中文/日期混排时 wrap 会让「–」分隔符单独掉到下一行。 */
.w-header { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 8px; flex-wrap: wrap; }
.w-header h3 { margin: 0; font-size: .92rem; font-weight: 600; color: var(--txt); white-space: nowrap; }
.w-cur { font-size: .68rem; font-weight: 500; color: var(--pri); background: var(--pri-soft); border-radius: 999px; padding: 1px 7px; margin-left: 4px; vertical-align: 1px; }
.w-dates { font-size: .74rem; color: var(--dim); }
.w-pick { display: flex; align-items: center; gap: 5px; }
.w-date {
  font-size: .74rem; padding: 4px 7px; border: 1px solid var(--line-2); border-radius: var(--r-xs);
  background: var(--panel); color: var(--txt); min-width: 0;
}
.w-date:focus { outline: none; border-color: var(--pri); }
.w-tilde { color: var(--dim2); font-size: .74rem; }

/* 快捷区间 */
.w-quicks { display: flex; align-items: center; gap: 6px; margin-bottom: 12px; flex-wrap: wrap; }
.w-q {
  font-size: .72rem; padding: 3px 10px; border: 1px solid var(--line-2); border-radius: 999px;
  background: var(--panel); color: var(--dim); white-space: nowrap;
}
.w-q:hover { border-color: var(--pri); color: var(--pri); }
.w-q.on { background: var(--pri-soft); border-color: var(--pri); color: var(--pri); font-weight: 600; }
.w-limit { font-size: .68rem; color: var(--dim2); margin-left: 2px; }

.w-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 8px; align-items: stretch; }
/* ★ 卡片必须等高：#weeklyBox 被截图/导出时是按元素高度裁的，
   高度参差会让"算不出来的那张"只露出半截说明字。 */
.w-kpi {
  display: flex; flex-direction: column; align-items: center; justify-content: flex-start;
  text-align: center; padding: 10px 6px; background: var(--bg); border-radius: 8px; border: 1px solid var(--line);
}
.w-kpi b { display: block; font-size: 1.25rem; font-weight: 700; color: var(--pri); margin-bottom: 2px; line-height: 1.2; }
/* ★ 算不出来的数字用灰色 + 比正文还小的说明字，视觉上就"低一档" ——
   补一个 0 会被误读成"这周一次都没对"，那是完全不同的一件事。 */
.w-kpi b.w-na { color: var(--dim2); font-weight: 600; }
.w-kpi span { font-size: .72rem; color: var(--dim); line-height: 1.3; }
.w-kpi .w-why { display: block; font-style: normal; font-size: .64rem; color: var(--dim2); line-height: 1.35; margin-top: 3px; }
.w-kpi .w-sub { display: block; font-style: normal; font-size: .66rem; color: var(--dim2); margin-top: 3px; }

/* ---- 数字溯源（批次22 前端接线）----
   基调：让"能点的数字"看起来**像数字本身可点**，而不是旁边多挂一个按钮。
   点击后明细展开在 .w-grid 下方（#weeklyEv），而不是浮层 —— 浮层在窄屏会盖住数字本身。
   ★ 布局要点：按钮是 inline，下划线只画在**数字那一行**，不画到「明细」上。
     若把 <u>明细</u> 也纳入下划线范围，它会跟数字挤在一行里（实测：数字和「明细」重叠）。 */
.w-kpi b .w-ev-btn {
  display: inline-block; padding: 0; border: none; background: none; cursor: pointer;
  font: inherit; color: inherit; line-height: inherit; font-weight: inherit;
  text-align: center;
}
.w-kpi b .w-ev-btn .w-ev-v {
  display: block; border-bottom: 1px dashed currentColor;  /* 虚线 = "这里可以再往下看一层" */
}
.w-kpi b .w-ev-btn:hover { color: var(--pri); }
.w-kpi b .w-ev-i {
  display: block; margin-top: 2px; font-size: .6rem; font-weight: 500;
  color: var(--dim2); text-decoration: none; letter-spacing: .04em; border-bottom: none;
}
.w-kpi b .w-ev-btn:hover .w-ev-i { color: var(--pri); }

.w-ev-sec { margin-top: 10px; }
/* 空容器在没内容时不占位：#weeklyEv 是全页常驻节点，
   不去掉它的 margin/padding 会在数字和「这几天」之间多出一块空白。 */
.w-ev-sec:empty { display: none; }
.w-ev-sec[data-open="0"] { display: none; }
.w-ev-hint { margin: 6px 0 0; font-size: .68rem; }
.w-ev-h {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding-bottom: 6px; border-bottom: 1px solid var(--line); margin-bottom: 8px;
}
.w-ev-h b { font-size: .8rem; color: var(--txt); }
.w-ev-range { font-size: .68rem; color: var(--dim2); font-family: var(--mono); }
.w-ev-x {
  margin-left: auto; flex: none; width: 22px; height: 22px; padding: 0;
  border: 1px solid var(--line); border-radius: 6px; background: var(--panel);
  color: var(--dim); font-size: .9rem; line-height: 1; cursor: pointer;
}
.w-ev-x:hover { border-color: var(--pri); color: var(--pri); }
.w-ev-list { max-height: 260px; overflow-y: auto; }
.w-ev-g { margin-bottom: 10px; }
.w-ev-g i { display: block; font-style: normal; font-size: .66rem; color: var(--dim2); margin-bottom: 4px; font-family: var(--mono); }
.w-ev-r { display: flex; align-items: baseline; gap: 8px; padding: 4px 0; border-bottom: 1px solid var(--line); }
.w-ev-r:last-child { border-bottom: none; }
.w-ev-t { flex: 1; min-width: 0; font-size: .78rem; color: var(--txt); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.w-ev-m { flex: none; font-size: .68rem; color: var(--dim2); font-family: var(--mono); white-space: nowrap; }
.w-ev-none { font-size: .78rem; color: var(--dim); line-height: 1.6; }
.w-ev-more { margin: 6px 0 0; font-size: .68rem; color: var(--dim2); }

/* ---- 与上一段对比（批次22-②）----
   ★★ 刻意**不用红绿**（这是本项目一条明确的产品主张）：
     记录多不等于学得好，用红/绿等于把"多"判成"好"。
     所以 up / down 用同一族的**中性强调色**，只在形态上区分（↑↓ 不画，改文字"多/少"）。
   ★ "没法比"必须是独立的一档样式：它既不是"一样"也不是"0"，不能混进 flat。 */
.w-cmp-row {
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  padding: 6px 0; border-bottom: 1px solid var(--line);
}
.w-cmp-row:last-of-type { border-bottom: none; }
.w-cmp-n { flex: none; width: 6.4em; font-size: .78rem; color: var(--txt); }
.w-cmp-v { flex: none; min-width: 3.4em; font-size: .86rem; font-weight: 700; color: var(--txt); font-family: var(--mono); }
.w-cmp-l { flex: 1; min-width: 0; font-size: .7rem; color: var(--dim2); font-family: var(--mono); }
.w-cmp-row em { flex: none; font-style: normal; font-size: .7rem; white-space: nowrap; }
/* up / down 同色 —— 只表达"变多了 / 变少了"，不表达"好 / 坏" */
.w-cmp-up, .w-cmp-down { color: var(--pri); font-family: var(--mono); }
.w-cmp-flat { color: var(--dim2); }
.w-cmp-na { color: var(--dim2); }   /* 「没法比」与「一样」视觉上须区分：na 更淡且带说明 */

/* ---- 7 阶段成长画像（批次22-③）----
   ★ 设计基调：这是一张"形状图"，不是成绩单。
     所以全页**不出现分数、百分比大字、排名**；雷达只表达"哪块凹下去"，
     维度用文字说清"这个数是从哪来的"。
   ★ 算不出来的维度：整块降低对比 + 明确写「还看不出来」，绝不显示 0%。 */
.g-wrap {
  margin-bottom: 12px; padding: 12px 14px;
  background: var(--panel-2); border: 1px solid var(--line); border-radius: 10px;
}
.g-err { font-size: .82rem; color: var(--dim); padding: 6px 0; }
.g-head h4 { margin: 0 0 6px; font-size: .86rem; font-weight: 600; color: var(--txt); }
.g-head h4 span { font-weight: 400; font-size: .7rem; color: var(--dim2); margin-left: 6px; }
.g-next { margin: 0 0 10px; font-size: .78rem; color: var(--dim); line-height: 1.65; }
.g-next b { color: var(--pri); }
.g-practice { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin: 0 0 12px; padding: 9px 10px; background: var(--panel); border: 1px solid var(--line); border-radius: 8px; }
.g-practice span { font-size: .72rem; color: var(--dim); line-height: 1.5; }
.g-practice .btn { flex: none; }

/* 雷达 + 维度列表：窄屏竖排，宽屏左右并排 */
.g-body { display: flex; gap: 14px; align-items: flex-start; flex-wrap: wrap; }
.g-radar { flex: none; width: 300px; max-width: 100%; height: auto; }
.g-dims { flex: 1; min-width: 200px; }
.g-dim { padding: 6px 0; border-bottom: 1px solid var(--line); }
.g-dim:last-child { border-bottom: none; }
.g-dim-h { display: flex; align-items: baseline; gap: 8px; }
.g-dim-h b { flex: 1; font-size: .78rem; color: var(--txt); }
.g-dim-v { flex: none; font-size: .84rem; font-weight: 700; color: var(--pri); font-family: var(--mono); }
.g-dim-v i { font-style: normal; font-size: .62rem; font-weight: 400; color: var(--dim2); margin-left: 1px; }
/* 「还看不出来」与「0%」必须是两种样子 —— 这就是规矩②在图上的体现 */
.g-na {
  flex: none; font-style: normal; font-size: .66rem; color: var(--dim2);
  padding: 1px 6px; border: 1px dashed var(--line-2); border-radius: 999px;
}
.g-dim.na b { color: var(--dim); }
.g-dim p { margin: 3px 0 0; font-size: .68rem; color: var(--dim2); line-height: 1.5; }

/* 阶梯：走到 = 实心，当前下一步 = 描边高亮，没到 = 灰空心。
   ★ 不用"等级名"、不给星星，只标"已走到"。 */
.g-lad-h { margin: 14px 0 8px; font-size: .82rem; font-weight: 600; color: var(--txt); }
.g-ladder { display: flex; flex-direction: column; gap: 2px; }
.g-step { display: flex; gap: 10px; padding: 7px 0; border-bottom: 1px solid var(--line); }
.g-step:last-child { border-bottom: none; }
.g-dot {
  flex: none; width: 22px; height: 22px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-style: normal; font-size: .68rem; font-family: var(--mono);
  background: var(--panel); border: 1px solid var(--line-2); color: var(--dim2);
}
.g-step.on .g-dot { background: var(--pri); border-color: var(--pri); color: #fff; font-weight: 700; }
.g-step.next .g-dot { border-color: var(--pri); color: var(--pri); border-width: 2px; }
.g-step-b { flex: 1; min-width: 0; }
.g-step-b b { display: block; font-size: .8rem; color: var(--txt); }
.g-step.on .g-step-b b { color: var(--pri); }
.g-ok {
  font-size: .62rem; font-weight: 500; color: var(--pri); margin-left: 6px;
  padding: 1px 5px; border: 1px solid var(--pri); border-radius: 999px;
}
.g-desc { display: block; font-size: .68rem; color: var(--dim); margin-top: 1px; }
.g-step-b p { margin: 2px 0 0; font-size: .68rem; color: var(--dim2); line-height: 1.5; }

/* 分区小节 */
.w-sec { margin-top: 16px; }
.w-sec h4 { margin: 0 0 8px; font-size: .82rem; font-weight: 600; color: var(--txt); }
.w-sec h4 span { font-weight: 400; font-size: .7rem; color: var(--dim2); margin-left: 6px; }
.w-none { font-size: .82rem; }
.w-note { margin: 8px 0 0; font-size: .72rem; color: var(--dim2); line-height: 1.6; }
.w-cover { margin: 0; font-size: .82rem; color: var(--dim); line-height: 1.7; }
.w-cover b { color: var(--txt); }

/* 逐天条：有记录=实心，没记录=灰色空心。
   ★ 「空」和「0」在视觉上必须不同 —— 这正是周报规矩②在界面上的体现。 */
.w-days { display: flex; gap: 6px; flex-wrap: wrap; }
.w-day {
  flex: 1 1 0; min-width: 58px; padding: 8px 4px; text-align: center;
  background: var(--panel-2); border: 1px dashed var(--line-2); border-radius: 8px;
}
.w-day.has { background: var(--pri-soft); border-style: solid; border-color: var(--pri); }
.w-day i { display: block; font-style: normal; font-size: .66rem; color: var(--dim); margin-bottom: 3px; white-space: nowrap; }
.w-day.has i { color: var(--pri); }
.w-day b { display: block; font-size: 1rem; font-weight: 700; color: var(--txt); line-height: 1.2; }
/* 「空」不是「算不出来的 —」：那天只是没发生任何事。同族样式、不同类名。 */
.w-day b.w-blank { color: var(--dim2); font-size: .78rem; font-weight: 500; }
.w-day em { display: block; font-style: normal; font-size: .66rem; color: var(--dim2); margin-top: 2px; }

/* 按科目：条形只表达"对/错的构成"，不给科目评分 */
.w-sub-row { display: flex; align-items: center; gap: 8px; margin-bottom: 7px; }
.w-sub-n { flex: none; width: 5.2em; font-size: .78rem; color: var(--txt); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.w-bar { flex: 1; height: 9px; border-radius: 999px; background: var(--bad-soft); overflow: hidden; }
.w-bar-ok { display: block; height: 100%; background: var(--ok); border-radius: 999px; }
.w-sub-v { flex: none; font-size: .7rem; color: var(--dim); white-space: nowrap; font-family: var(--mono); }

/* 卡住的卡 */
.w-stuck { display: flex; align-items: baseline; gap: 8px; padding: 6px 0; border-bottom: 1px solid var(--line); }
.w-stuck:last-child { border-bottom: none; }
.w-stuck-t { flex: 1; min-width: 0; font-size: .82rem; color: var(--txt); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.w-stuck-n { flex: none; font-size: .72rem; color: var(--bad); font-family: var(--mono); white-space: nowrap; }

.w-empty { font-size: .85rem; color: var(--dim); padding: 8px 0; }

/* ---- 历史周报（批次22）：只存"人写的那段" ----
   设计基调：这一块是全页**唯一写入**的东西，所以给它一个和纯事实区不同的边框，
   让人一眼看出"下面这两句是我写的、会被保存"。
   但**不给它更强的强调色** —— 写感想不该比看事实更显眼。 */
.w-note-sec {
  padding: 12px; border: 1px solid var(--line-2); border-radius: 10px;
  background: var(--panel-2);
}
.w-note-sec h4 { margin: 0 0 6px; }
.w-note-l { display: block; margin-top: 10px; }
.w-note-l b { display: block; font-size: .78rem; font-weight: 600; color: var(--txt); margin-bottom: 5px; }
.w-note-l input {
  width: 100%; box-sizing: border-box; padding: 8px 10px;
  background: var(--panel); border: 1px solid var(--line); border-radius: 8px;
  color: var(--txt); font-size: .85rem; font-family: inherit;
}
.w-note-l input:focus { outline: none; border-color: var(--pri); }
.w-note-act { display: flex; gap: 8px; margin-top: 12px; flex-wrap: wrap; }
.w-rd { margin-top: 10px; }
.w-rd b { display: block; font-size: .78rem; font-weight: 600; color: var(--txt); margin-bottom: 3px; }
.w-rd p { margin: 0; font-size: .85rem; color: var(--dim); line-height: 1.7; white-space: pre-wrap; word-break: break-word; }
.w-final-tag {
  font-size: .66rem; font-weight: 500; color: var(--pri);
  background: var(--pri-soft); padding: 2px 7px; border-radius: 999px; margin-left: 6px;
}

/* 历史列表：每一条是一个按钮（点开=跳到那一段区间） */
.w-hist {
  display: block; width: 100%; text-align: left; box-sizing: border-box;
  padding: 10px; margin-bottom: 8px; cursor: pointer;
  background: var(--panel-2); border: 1px solid var(--line); border-radius: 10px;
  font-family: inherit; color: inherit;
}
.w-hist:hover { border-color: var(--pri); }
.w-hist-h { display: flex; align-items: center; gap: 8px; }
.w-hist-h b { font-size: .8rem; color: var(--txt); font-family: var(--mono); }
.w-hist-st { font-size: .64rem; font-style: normal; color: var(--dim); border: 1px solid var(--line-2); border-radius: 999px; padding: 1px 6px; white-space: nowrap; }
.w-hist-st.final { color: var(--pri); border-color: var(--pri); }
.w-hist-n { margin-top: 4px; font-size: .7rem; color: var(--dim2); font-family: var(--mono); }
.w-hist-t { margin-top: 5px; font-size: .78rem; color: var(--dim); line-height: 1.6; overflow: hidden; text-overflow: ellipsis; }

/* 阶段进度推进条（看板）：回答"整批卡走到哪一步了" */
.pg-bar { display: flex; height: 22px; border-radius: 999px; overflow: hidden; background: var(--panel-2); border: 1px solid var(--line); }
.pg-bar i { display: block; height: 100%; }
.pg-bar i:first-child { border-top-left-radius: 999px; border-bottom-left-radius: 999px; }
.pg-bar i:last-child { border-top-right-radius: 999px; border-bottom-right-radius: 999px; }
.pg-lg { display: flex; flex-wrap: wrap; gap: 4px 14px; margin-top: 9px; }
.pg-lg span { font-size: .74rem; color: var(--dim); white-space: nowrap; }
.pg-lg span i { display: inline-block; width: 9px; height: 9px; border-radius: 2px; margin-right: 4px; vertical-align: -1px; }
.pg-lg span b { color: var(--txt); font-family: var(--mono); margin-left: 2px; }
.d-daily { padding-bottom: 16px; }
.d-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 6px; }
.d-head h3 { margin: 0; font-size: .92rem; font-weight: 600; }
.d-head h3 span { font-weight: 400; color: var(--dim2); font-size: .74rem; margin-left: 6px; }
.d-nav { margin-left: auto; display: flex; gap: 6px; }
.d-headline { margin: 2px 0 12px; font-size: .9rem; color: var(--txt); line-height: 1.6; }

/* 数字：每个都可点开看依据 */
.d-nums { display: grid; grid-template-columns: repeat(auto-fill, minmax(148px, 1fr)); gap: 8px; }
.d-num {
  display: flex; flex-direction: column; align-items: flex-start; gap: 1px;
  text-align: left; border: 1px solid var(--line); background: var(--panel-2);
  border-radius: var(--r-sm); padding: .6em .7em; cursor: pointer;
  transition: border-color .15s, background .15s, transform .08s;
}
.d-num:hover { border-color: var(--pri); background: var(--pri-soft); }
.d-num:active { transform: translateY(1px); }
.d-num b { font-size: 1.2rem; font-weight: 700; line-height: 1.25; color: var(--txt); }
.d-num span { font-size: .74rem; color: var(--dim); line-height: 1.4; }
.d-num i { font-style: normal; font-size: .68rem; color: var(--pri); margin-top: 3px; }
/* ★ 算不出来的数字：灰掉、值显示 —，并把"为什么算不出来"直接写在卡片上，
     而不是藏在 tooltip 里 —— 数据诚实要看得见才算数。 */
.d-num.na { cursor: default; background: var(--panel); }
.d-num.na:hover { border-color: var(--line); background: var(--panel); }
.d-num.na b { color: var(--dim2); }
.d-num.na i { color: var(--dim2); line-height: 1.45; }
.d-hint { margin: 8px 0 0; font-size: .74rem; color: var(--dim2); line-height: 1.6; }

/* 四问 */
.d-qs { margin-top: 18px; display: flex; flex-direction: column; gap: 14px; }
.d-q { border-left: 2px solid var(--line-2); padding-left: 12px; }
.d-q-h { display: flex; gap: 8px; align-items: baseline; }
.d-q-h i {
  font-style: normal; flex: none; width: 18px; height: 18px; border-radius: 50%;
  background: var(--pri-soft); color: var(--pri); font-size: .68rem; font-weight: 700;
  display: inline-flex; align-items: center; justify-content: center;
}
.d-q-h span { font-size: .88rem; font-weight: 600; color: var(--txt); line-height: 1.5; }
.d-f { margin: 7px 0 0; padding-left: 1.05em; font-size: .84rem; color: var(--dim); line-height: 1.8; }
.d-f li::marker { color: var(--pri); }
.d-f li.d-un { color: var(--dim2); font-style: italic; }
.d-f li.d-un::marker { color: var(--dim2); }
.d-ev {
  border: 0; background: transparent; color: var(--pri); font-size: .74rem;
  text-decoration: underline; text-underline-offset: 3px; padding: 0 2px; cursor: pointer;
}
.d-ev:hover { color: var(--pri-2); }
.d-ans {
  display: block; width: 100%; margin-top: 8px; resize: vertical;
  border: 1px solid var(--line-2); background: var(--panel); color: var(--txt);
  border-radius: var(--r-sm); padding: .5em .7em; font: inherit; font-size: .86rem;
  line-height: 1.7; outline: none; transition: border-color .15s, box-shadow .15s;
}
.d-ans:focus { border-color: var(--pri); box-shadow: 0 0 0 3px var(--pri-soft); }
.d-ans[readonly] { background: var(--panel-2); color: var(--dim); }

.d-savebar { display: flex; align-items: center; gap: 10px; margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--line); }
.d-savebar > span { flex: 1; min-width: 0; font-size: .76rem; color: var(--dim2); }

/* 折叠块：不评分的原因 / 这份日报不能证明什么 */
.d-fold { margin-top: 10px; border: 1px solid var(--line); border-radius: var(--r-sm); background: var(--panel-2); }
.d-fold > summary { cursor: pointer; padding: .55em .8em; font-size: .82rem; font-weight: 600; color: var(--txt); list-style: none; }
.d-fold > summary::-webkit-details-marker { display: none; }
.d-fold > summary::before { content: "▸ "; color: var(--dim2); }
.d-fold[open] > summary::before { content: "▾ "; }
.d-ns { padding: 0 .8em .5em; }
.d-ns-i { padding: .5em 0; border-top: 1px solid var(--line); }
.d-ns-i:first-child { border-top: 0; }
.d-ns-i b { display: block; font-size: .8rem; color: var(--txt); margin-bottom: 2px; }
.d-ns-i p { margin: 0; font-size: .78rem; color: var(--dim); line-height: 1.75; }
.d-lim { margin: 0; padding: .1em .8em .7em 2em; font-size: .78rem; color: var(--dim); line-height: 1.8; }
.d-lim li::marker { color: var(--dim2); }

.d-hwrap { margin-top: 16px; }
.d-ht { font-size: .76rem; color: var(--dim2); margin-bottom: 6px; }
.d-hist { display: flex; flex-wrap: wrap; gap: 6px; }
.d-hb {
  display: flex; flex-direction: column; align-items: flex-start;
  border: 1px solid var(--line); background: var(--panel); color: var(--txt);
  border-radius: var(--r-sm); padding: .35em .6em; font-size: .78rem; cursor: pointer;
}
.d-hb:hover { border-color: var(--pri); }
.d-hb i { font-style: normal; font-size: .66rem; color: var(--dim2); }

/* 溯源弹窗 */
.ev-list { display: flex; flex-direction: column; gap: 8px; }
.ev-i { border: 1px solid var(--line); border-radius: var(--r-sm); padding: .55em .7em; background: var(--panel-2); }
.ev-h { display: flex; align-items: baseline; gap: 8px; }
.ev-h b { flex: 1; min-width: 0; font-size: .86rem; }
.ev-h span { font-family: var(--mono); font-size: .74rem; color: var(--dim2); }
.ev-i p { margin: 4px 0 0; font-size: .8rem; color: var(--dim); line-height: 1.7; }
.ev-v { margin-top: 4px; font-size: .74rem; color: var(--dim2); }

/* ================= 家长视角（批次20）=================
   设计基调：**比看板更安静**。
   家长这一页的读者是"不坐在电脑前的大人"，他会在手机上、在晚上翻一下。
   所以：更大的字号、更少的颜色、更多的解释文字。
   颜色只用在**一句话总结**那一块（三种语气），其余一律走中性色 ——
   颜色本身在暗示"好/坏"，而这一页刻意不做评判。 */
.p-head {
  border: 1px solid var(--line); border-left: 3px solid var(--pri);
  border-radius: var(--r-sm); background: var(--panel-2);
  padding: .8em .95em; margin-bottom: 14px;
}
.p-head-t { font-size: 1rem; font-weight: 700; color: var(--txt); line-height: 1.6; margin-bottom: 6px; }
.p-head-p { margin: 0 0 8px; font-size: .82rem; color: var(--dim); line-height: 1.85; }
.p-head-a {
  font-size: .82rem; color: var(--txt); line-height: 1.7;
  border-top: 1px dashed var(--line); padding-top: 8px;
}
.p-head-a > span {
  display: inline-block; font-size: .7rem; font-weight: 700; color: var(--pri);
  margin-right: 6px; white-space: nowrap;
}
/* 三种语气：只在左侧色条 + 标题色上区分，不做整块底色（底色太重会像"警告"） */
.p-tone-quiet { border-left-color: var(--dim2); }
.p-tone-quiet .p-head-t { color: var(--dim); }
.p-tone-gap { border-left-color: #e0a33b; }
.p-tone-gap .p-head-a > span { color: #b8791f; }
.p-tone-active { border-left-color: var(--pri); }

/* 有记录的天：一天一个小方块，颜色深浅不表达好坏，只表达"有没有" */
.p-days { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.p-day {
  display: flex; flex-direction: column; align-items: center;
  border: 1px solid var(--line); background: var(--panel); border-radius: var(--r-sm);
  padding: .3em .55em; min-width: 44px;
}
.p-day i { font-style: normal; font-size: .64rem; color: var(--dim2); }
.p-day b { font-size: .86rem; color: var(--txt); }

/* 「算不出来」说明框：和 KPI 卡上的「—」配套，语气要平，不能像报错 */
.p-unknown-box {
  margin-top: 10px; padding: .6em .8em;
  border: 1px dashed var(--line); border-radius: var(--r-sm); background: var(--panel-2);
}
.p-unknown-box b { display: block; font-size: .8rem; color: var(--txt); margin-bottom: 3px; }
.p-unknown-box p { margin: 0; font-size: .78rem; color: var(--dim); line-height: 1.75; }

/* 可以帮上忙的地方：一条一条，不并排（并排会被读成"待办清单"） */
.p-help {
  border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--panel); padding: .6em .8em; margin-bottom: 8px;
}
.p-help-h { display: flex; align-items: baseline; gap: 8px; margin-bottom: 4px; }
.p-help-a { flex: 1; min-width: 0; font-size: .86rem; font-weight: 600; color: var(--txt); }
.p-help-n { font-size: .72rem; color: var(--dim2); white-space: nowrap; }
.p-help-t { margin: 0; font-size: .8rem; color: var(--dim); line-height: 1.75; }

/* 他自己写的那段 */
.p-note {
  border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--panel); padding: .6em .8em; margin-bottom: 8px;
}
.p-note-d { font-size: .7rem; color: var(--dim2); margin-bottom: 5px; }
.p-note-l { margin: 0 0 4px; font-size: .82rem; color: var(--txt); line-height: 1.8; }
.p-note-l:last-child { margin-bottom: 0; }
.p-note-l > span {
  display: inline-block; font-size: .68rem; font-weight: 700; color: var(--dim2);
  margin-right: 6px; white-space: nowrap;
}
.p-note-l > span::after { content: "："; }

/* 答不了的三件事 */
.p-cannot-i { padding: .5em 0; border-top: 1px solid var(--line); }
.p-cannot-i:first-child { border-top: 0; }
.p-cannot-i b { display: block; font-size: .84rem; color: var(--txt); margin-bottom: 3px; }
.p-cannot-i p { margin: 0; font-size: .78rem; color: var(--dim); line-height: 1.8; }

/* 承诺边界：一块安静的说明，不用警示色 —— 它不是"警告"，是"说明" */
.p-priv {
  margin-top: 14px; padding: .7em .85em;
  border: 1px solid var(--line); border-radius: var(--r-sm); background: var(--panel-2);
}
.p-priv b { display: block; font-size: .78rem; color: var(--txt); margin-bottom: 4px; line-height: 1.6; }
.p-priv p { margin: 0; font-size: .76rem; color: var(--dim2); line-height: 1.8; }

/* 现在有什么（存量，只摆不评）*/
.p-now { display: flex; flex-wrap: wrap; gap: 8px; }
.p-now-i {
  display: flex; align-items: baseline; gap: 4px;
  border: 1px solid var(--line); border-radius: var(--r-sm);
  background: var(--panel); padding: .3em .6em;
}
.p-now-i b { font-size: .9rem; color: var(--txt); }
.p-now-i i { font-style: normal; font-size: .7rem; color: var(--dim2); }

/* 能力中心 */
/* ★★ 网格密度（2026-10-05 用户要求"一屏看到更多卡片"）：
   minmax 从 240px 收到 190px ⇒ 1440 视口从 3 列变 5 列；卡片内边距同步收紧。
   放宽列的代价是「描述」一行放不下更多字 —— 所以描述改成最多 2 行省略号，
   把"看全说明"这件事交给点击后的**详情弹窗**（.sk-mask），而不是挤在卡片里。 */
.skill-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 9px; }
.skill-c {
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--r);
  padding: 10px 12px; transition: border-color .15s, background .15s, transform .08s;
  display: flex; flex-direction: column; gap: 4px;
}
.skill-c:hover { border-color: var(--dim2); }
.skill-c.on { border-color: var(--pri); background: var(--pri-soft); }
/* 卡片主体 = "打开详情"的可点区。它必须自己是个整块可点元素，
   不能靠外层 .skill-c 承担点击 —— 否则开关也会连带触发详情。 */
.skill-c .sc-main { display: flex; flex-direction: column; gap: 4px; cursor: pointer; border-radius: var(--r-sm); }
.skill-c .sc-main:focus-visible { outline: 2px solid var(--pri); outline-offset: 2px; }
.skill-c .sc-top { display: flex; align-items: center; gap: 7px; }
.skill-c .sc-n { font-weight: 600; font-size: .88rem; flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.skill-c .sc-d {
  font-size: .76rem; color: var(--dim); line-height: 1.5;
  /* 最多两行 + 省略号：190px 宽的列里，三行以上会把卡片拉得参差不齐。
     想看全就走详情弹窗（.sc-main 点开）。 */
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.skill-c .sc-b { font-size: .68rem; color: var(--dim2); display: flex; gap: 8px; align-items: center; }
.skill-c .sc-more { margin-left: auto; color: var(--pri); opacity: 0; transition: opacity .15s; }
.skill-c:hover .sc-more { opacity: 1; }
/* 技能图标（batch25）：57 条技能各一个手写原生 SVG。
   描边用 currentColor ⇒ 深浅主题、hover、选中态自动跟随，不写死颜色。
   尺寸固定在标题行里，不随字号放大缩小，保证 57 个形状观感一致。 */
.skill-c .sc-ico { flex: 0 0 20px; width: 20px; height: 20px; color: var(--dim); display: flex; align-items: center; justify-content: center; }
.skill-c:hover .sc-ico { color: var(--fg); }
.skill-c.on .sc-ico { color: var(--pri); }
.ski { width: 20px; height: 20px; display: block; }
/* 「用它开一段对话」：卡片内的次级动作。不做成实心主按钮 ——
   卡片整体点一下是"开详情"，这个按钮是"直接开始用"，两者权重必须分开。 */
.skill-c .sc-go {
  margin-top: 3px; align-self: flex-start;
  font: inherit; font-size: .72rem; color: var(--pri);
  background: none; border: 1px solid var(--line-2); border-radius: 999px;
  padding: 2px 9px; cursor: pointer; transition: border-color .15s, background .15s;
}
.skill-c .sc-go:hover { border-color: var(--pri); background: var(--pri-soft); }
.skill-c .sc-go:active { transform: translateY(1px); }
/* 技能开关（.sc-sw 是"启用/取消"的可点区域，独立于卡片主体）。
   ★ 2026-10-05：从"点整卡切换"改成"只点这个开关才切换" ——
     学生想看说明时不再误开技能。触摸目标靠 padding 撑到 28px 以上。 */
.skill-c .sc-sw {
  flex: 0 0 auto; display: inline-flex; align-items: center;
  padding: 4px 4px; margin: -4px -2px -4px 0; border-radius: 999px; cursor: pointer;
}
.skill-c .sc-sw:focus-visible { outline: 2px solid var(--pri); outline-offset: 2px; }
.sc-sw .tgl { width: 30px; height: 18px; border-radius: 11px; background: var(--panel-3); position: relative; flex: 0 0 30px; transition: background .18s; }
.sc-sw .tgl::after { content: ""; position: absolute; top: 2px; left: 2px; width: 14px; height: 14px; border-radius: 50%; background: var(--panel); box-shadow: var(--shadow); transition: transform .18s; }
.sc-sw.on .tgl { background: var(--pri); }
.sc-sw.on .tgl::after { transform: translateX(12px); }
/* 备考教练专区（2026-10-04 新增；2026-10-05 明确排在学科卡**之后**）：
   与学科卡网格同一套 .skill-c，单独成区、不进学科筛选。
   用一条虚线顶边 + 小标题把它和上方学科卡区分开。 */
.skill-zone { margin-top: 16px; padding-top: 12px; border-top: 1px dashed var(--line-2); }
.skill-zone .sz-head {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: .8rem; font-weight: 700; color: var(--pri);
  margin-bottom: 9px; padding-left: 9px; border-left: 3px solid var(--pri);
}
.coach-zone .skill-c { border-left: 3px solid var(--pri); }

/* ================= 技能详情弹窗（2026-10-05）=================
   不复用 .modal（560px 塞不下"分条方法清单"）⇒ 独立覆盖层，宽到 920px。
   ★ 基准规则一律写在所有 @media 之前。 */
.sk-mask { padding: 18px; }
.sk-panel {
  background: var(--panel); border: 1px solid var(--line); border-radius: 18px;
  width: min(920px, 100%); height: min(86vh, 760px); max-height: 100%;
  display: flex; flex-direction: column;
  box-shadow: var(--shadow-lg); animation: rise .26s cubic-bezier(.22,.8,.3,1) both;
  overflow: hidden;
}
.sk-head {
  display: flex; align-items: center; gap: 10px; padding: 14px 18px;
  border-bottom: 1px solid var(--line); flex: 0 0 auto;
}
.sk-head b { font-size: 1.02rem; }
.sk-body { flex: 1 1 auto; overflow-y: auto; padding: 18px 20px; }
.sk-foot {
  display: flex; align-items: center; gap: 10px; padding: 12px 18px;
  border-top: 1px solid var(--line); flex: 0 0 auto; background: var(--panel-2);
}
.sk-hero { display: flex; align-items: center; gap: 12px; margin-bottom: 12px; }
.sk-ico { flex: 0 0 44px; width: 44px; height: 44px; border-radius: 12px; background: var(--pri-soft); color: var(--pri); display: flex; align-items: center; justify-content: center; }
.sk-ico .ski { width: 26px; height: 26px; }
.sk-name { font-size: 1.1rem; font-weight: 700; }
.sk-tags { display: flex; gap: 6px; margin-top: 5px; flex-wrap: wrap; }
.sk-tag { font-size: .7rem; color: var(--dim); background: var(--panel-2); border: 1px solid var(--line); border-radius: 999px; padding: 2px 9px; }
.sk-tag.on { color: var(--pri); border-color: var(--pri); background: var(--pri-soft); }
.sk-desc { font-size: .9rem; color: var(--txt); line-height: 1.65; margin: 0 0 16px; }
.sk-sec-h {
  font-size: .82rem; font-weight: 700; color: var(--dim); margin: 0 0 10px;
  padding-left: 9px; border-left: 3px solid var(--pri);
}
/* 方法清单：宽屏两栏，窄屏一栏（见文件末尾的窄屏回退）。
   用 CSS 多列而不是 grid —— 条目高度不一，多列能自动平衡高度。 */
.sk-steps { margin: 0; padding-left: 0; list-style: none; columns: 2; column-gap: 26px; }
.sk-steps li {
  font-size: .84rem; line-height: 1.7; color: var(--txt);
  padding-left: 20px; position: relative; margin: 0 0 9px;
  break-inside: avoid;
}
.sk-steps li::before {
  content: ""; position: absolute; left: 6px; top: .62em;
  width: 5px; height: 5px; border-radius: 50%; background: var(--pri);
}
.sk-none { font-size: .86rem; color: var(--dim); }
.sk-note { font-size: .78rem; color: var(--dim2); line-height: 1.6; margin-top: 14px; padding-top: 12px; border-top: 1px dashed var(--line-2); }
/* 详情浮层打开时锁背景滚动（否则手机上能滚到底下的列表） */
body.sk-open { overflow: hidden; }


/* 知识库 */
.kb-drop {
  border: 1.5px dashed var(--line-2); border-radius: var(--r); background: var(--panel-2);
  padding: 22px 16px; text-align: center; color: var(--dim); font-size: .86rem;
  cursor: pointer; transition: border-color .15s, background .15s; margin: 0 18px 12px;
}
.kb-drop:hover, .kb-drop.over { border-color: var(--pri); background: var(--pri-soft); color: var(--pri); }
.kb-drop b { color: var(--txt); }
.kb-drop .dim { font-size: .76rem; margin-top: 5px; }
.doc-i {
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--r);
  padding: 12px 15px; display: flex; align-items: center; gap: 12px;
}
.doc-i > div:first-child { flex: 1; min-width: 0; }
.doc-i b { font-size: .92rem; display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.doc-i .dim { font-size: .75rem; margin-top: 2px; }
.doc-i.bad { border-color: var(--warn); background: var(--warn-soft); }
.doc-i .doc-err { font-size: .76rem; color: var(--warn); margin-top: 4px; line-height: 1.6; }
/* 扫描件识别进度：条要细、提示要小 —— 它是"在动"的证据，不是主角 */
.doc-i .doc-prog {
  margin-top: 6px; height: 4px; border-radius: 999px;
  background: var(--pri-soft); overflow: hidden; max-width: 420px;
}
.doc-i .doc-prog i {
  display: block; height: 100%; border-radius: 999px;
  background: linear-gradient(90deg, var(--pri), #38BDF8);
  transition: width .6s ease;
}
.doc-i .doc-prog-t { font-size: .74rem; color: var(--dim2); margin-top: 4px; }

/* ---------- 原页图查看器（批次34） ---------- */
/* 教材小字多，能放大是必须的————放不大看不清就等于没给。 */
.pgv { display: flex; flex-direction: column; gap: 10px; }
.pgv-bar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.pgv-bar .sp { flex: 1; }
.pgv-no { font-size: .84rem; color: var(--dim); }
.pgv-no b { color: var(--txt); font-size: .95rem; }
.pgv-z { font-size: .78rem; color: var(--dim2); min-width: 44px; text-align: center; }
/* 舞台有固定高度且可滚动：放大后图会超出，要能拖着看。 */
.pgv-stage {
  background: var(--bg-2, #f4f6fa); border: 1px solid var(--line); border-radius: var(--r);
  height: 58vh; overflow: auto; padding: 12px; text-align: center;
}
.pgv-img { display: block; margin: 0 auto; width: 100%; height: auto; border-radius: 4px;
  box-shadow: 0 2px 12px rgba(0,0,0,.10); background: #fff; }
.pgv-tip { font-size: .74rem; text-align: center; }
/* 「看全文」图文混排（批次37）：文字流里按位置插入教材插图。
   图自成一块、居中、宽度受限 —— 教材插图多为横向，铺满会太大、也会挤掉文字。 */
.kb-text-with-figs { }
.kb-page-no { margin: 1.4em 0 .4em; font-size: .78rem; font-weight: 600; color: var(--dim2);
  letter-spacing: .04em; border-bottom: 1px dashed var(--line); padding-bottom: 3px; }
.kb-text-with-figs .kb-page-no:first-child { margin-top: 0; }
.kb-fig { margin: 1em auto; text-align: center; max-width: 92%; }
.kb-fig a { display: inline-block; line-height: 0; }
.kb-fig img { max-width: 100%; height: auto; border: 1px solid var(--line); border-radius: var(--r-sm);
  background: #fff; box-shadow: 0 1px 6px rgba(0,0,0,.06); transition: box-shadow .15s ease; }
.kb-fig a:hover img { box-shadow: 0 3px 14px rgba(0,0,0,.14); }
.kb-fig figcaption { margin-top: .35em; font-size: .76rem; color: var(--fg); line-height: 1.5; }
/* 长描述（没有短标签时的回落）—— 更小更灰，因为它本来只是给检索用的说明 */
.kb-fig-desc { font-size: .72rem; color: var(--dim); font-weight: 400; }
.kb-fig-h { margin: 1.6em 0 .6em; font-size: .82rem; color: var(--dim2); }
.hit-i { background: var(--panel); border: 1px solid var(--line); border-radius: var(--r-sm); padding: 11px 14px; font-size: .84rem; }
.hit-i .h-f { font-size: .74rem; color: var(--pri); margin-bottom: 4px; font-weight: 600; }
.hit-i mark { background: color-mix(in srgb, var(--warn) 30%, transparent); color: inherit; border-radius: 3px; padding: 0 2px; }
.cat-bar { display: flex; gap: 7px; padding: 0 18px 10px; flex-wrap: wrap; align-items: center; }
.cat-chip {
  border: 1px solid var(--line-2); background: var(--panel); border-radius: 18px;
  padding: .22em .8em; font-size: .8rem; color: var(--dim); cursor: pointer;
}
.cat-chip.on { background: var(--pri); border-color: var(--pri); color: #fff; }
/* 能力页有两排筛选（分类 / 学科）。第二排顶到第一排下面，只留一行的间距 —— 
   两排都带 padding-bottom 的话中间会空出 20px，看着像两块不相干的东西。 */
#skillSubjects { padding-top: 0; }
#skillSubjects .cat-chip { font-size: .76rem; padding: .16em .7em; }

/* ================= 知识库主工作区 =================
   左边"本子树"+ 右边二级分区。原来 11 根平级导航里除对话外的 9 个功能
   都收在右边的分区条上，记忆 / 设置 走右上角头像菜单。 */
.kb-wrap { flex: 1; min-height: 0; display: flex; }
.kb-tree {
  flex: 0 0 220px; border-right: 1px solid var(--line);
  display: flex; flex-direction: column; padding: 12px 10px 16px; overflow-y: auto;
}
.kb-tree-h {
  display: flex; align-items: center; justify-content: space-between;
  padding: 0 6px 8px; color: var(--dim2); font-size: .74rem; font-weight: 600; letter-spacing: .05em;
}
.kb-tree-new {
  border: 1px solid var(--line-2); background: var(--panel); color: var(--dim);
  width: 22px; height: 22px; line-height: 1; border-radius: 6px; font-size: .95rem;
}
.kb-tree-new:hover { background: var(--pri-soft); border-color: var(--pri); color: var(--pri); }
.kb-tree-list { display: flex; flex-direction: column; gap: 2px; }
.kb-tree-i {
  display: flex; align-items: center; gap: 6px; padding: .46em .6em;
  border-radius: var(--r-sm); color: var(--dim); font-size: .86rem; cursor: pointer;
}
.kb-tree-i:hover { background: var(--panel-2); color: var(--txt); }
.kb-tree-i.on { background: var(--pri-soft); color: var(--pri); font-weight: 600; }
.kb-tree-i .kt-n { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.kb-tree-i .kt-c { font-size: .7rem; color: var(--dim2); font-variant-numeric: tabular-nums; white-space: nowrap; }
.kb-tree-i .kt-c.full { color: var(--warn); }
.kb-tree-i .kt-x {
  border: 1px solid var(--line-2); background: var(--panel); color: var(--warn);
  font-size: .68rem; border-radius: 5px; padding: 1px 5px; line-height: 1.5;
}
.kb-tree-i .kt-x:hover { background: var(--warn); border-color: var(--warn); color: #fff; }
.kb-tree-foot { margin-top: auto; padding: 10px 6px 0; font-size: .72rem; color: var(--dim2); line-height: 1.6; }

.kb-main { flex: 1; min-width: 0; display: flex; flex-direction: column; overflow: hidden; }
.kb-tabs { display: flex; gap: 4px; padding: 9px 18px 0; flex-wrap: wrap; flex: 0 0 auto; }
.kb-tab {
  border: 0; background: transparent; color: var(--dim);
  padding: .38em .8em; border-radius: 999px; font-size: .85rem; font-weight: 500;
  display: inline-flex; align-items: center; gap: 5px;
}
.kb-tab:hover { background: var(--panel-2); color: var(--txt); }
.kb-tab.on { background: var(--pri-soft); color: var(--pri); font-weight: 600; }
.kb-tab-b {
  background: var(--bad); color: #fff; font-size: .66rem; font-weight: 700;
  border-radius: 8px; min-width: 16px; text-align: center; line-height: 16px; height: 16px; padding: 0 4px;
}
.kb-sub { flex: 1; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }
/* 分区里的顶栏就是"控制条"，不再重复画一条下边框 —— 上面已经有分区条了 */
.kb-sub > .topbar { border-bottom: 0; padding-bottom: 4px; }

.kb-subbar {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 10px 18px 4px; flex: 0 0 auto;
}
.kb-state { display: flex; gap: 4px; background: var(--panel-2); padding: 3px; border-radius: 999px; }
.kb-state-i {
  border: 0; background: transparent; color: var(--dim); border-radius: 999px;
  padding: .3em .85em; font-size: .82rem; display: inline-flex; align-items: center; gap: 5px;
}
.kb-state-i b { font-size: .74rem; font-weight: 700; opacity: .7; font-variant-numeric: tabular-nums; }
.kb-state-i:hover { color: var(--txt); }
.kb-state-i.on { background: var(--panel); color: var(--pri); font-weight: 600; box-shadow: var(--shadow); }
.kb-state-i.on b { opacity: 1; }

/* ================= 知识库使用引导 ================= */
.kb-guide { display: flex; align-items: center; gap: 10px; padding: 10px 18px; background: var(--warn-soft); border-bottom: 1px solid var(--line); font-size: .82rem; color: var(--warn); }
.kb-guide[hidden] { display: none !important; }
.kg-body { flex: 1; min-width: 0; }
.kg-body b { color: var(--txt); margin-right: 6px; }
.kg-step { display: inline; }
.kg-step + .kg-step::before { content: ' | '; opacity: .5; margin: 0 6px; }
.kg-close { background: transparent; border: 0; color: var(--dim2); cursor: pointer; font-size: 1rem; padding: 2px 6px; }
.kg-close:hover { color: var(--txt); }

/* ================= 右上角头像入口 ================= */
.me-chip {
  display: inline-flex; align-items: center; gap: 6px; max-width: 168px;
  border: 1px solid var(--line); background: var(--panel); border-radius: 999px;
  padding: 2px 8px 2px 2px; color: var(--txt); font-size: .82rem;
}
.me-chip:hover { border-color: var(--pri); background: var(--pri-soft); }
.me-chip .me-av { width: 24px; height: 24px; border-radius: 50%; overflow: hidden; background: var(--panel-2); display: block; flex: 0 0 24px; }
.me-chip .me-av svg { display: block; width: 100%; height: 100%; border-radius: 50%; }
.me-chip .me-nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.me-chip .me-caret { color: var(--dim2); font-size: .7rem; }
.me-head { display: flex; align-items: center; gap: 12px; padding: 4px 2px 14px; border-bottom: 1px solid var(--line); margin-bottom: 10px; }
.me-head .me-av.big { width: 42px; height: 42px; flex: 0 0 42px; }
.me-head .me-av.big svg { width: 100%; height: 100%; }
.me-menu { display: flex; flex-direction: column; gap: 4px; }
.me-mi {
  display: flex; flex-direction: column; gap: 2px; text-align: left;
  border: 1px solid var(--line); background: var(--panel); border-radius: var(--r-sm);
  padding: .6em .8em; color: var(--txt);
}
.me-mi:hover { border-color: var(--pri); background: var(--pri-soft); }
.me-mi b { font-size: .9rem; }
.me-mi span { font-size: .76rem; color: var(--dim2); }
.me-mi.danger b { color: var(--bad); }
/* 「我的」菜单里的空间卡与成长条（原侧栏顶部那块，2026-10-04 挪过来）。
   .pet-line 的基准样式在上面，这里只补它在菜单里的间距 ——
   菜单里没有侧栏那条竖线的约束，左右留白跟 .me-mi 对齐即可。 */
.me-head .me-switch { margin-left: auto; flex: 0 0 auto; }
.me-pet {
  margin: 0 0 10px; padding: 8px 10px;
  background: var(--panel-2); border-radius: var(--r-sm);
}
.me-mi b { display: flex; align-items: center; gap: 6px; }
.me-mi .me-badge { margin-left: 0; }

/* ================= 英语 · 单元四关 + 艾宾浩斯（批次24）=================
   ★ 基准样式一律写在所有 @media 之前：同权重后写的赢，
     要是把 .en4-bar.na 这类基准规则写在 @media 后面，会被媒体查询里的规则直接盖掉且零报错。 */
.en4-wrap { padding: 4px 18px 8px; flex: 0 0 auto; }
.en4-err { padding: 12px; color: var(--bad); font-size: .84rem; }
.en4-empty { border: 1px dashed var(--line-2); border-radius: var(--r); padding: 14px 16px; font-size: .86rem; color: var(--dim); }
.en4-empty b { color: var(--txt); }
.en4-empty-s { margin-top: 6px; font-size: .78rem; color: var(--dim2); line-height: 1.7; }

.en4-unit { border: 1px solid var(--line); border-radius: var(--r); background: var(--panel); padding: 10px 12px; margin-bottom: 9px; }
.en4-unit.done { border-color: var(--ok); }
.en4-u-h { display: flex; align-items: center; gap: 10px; margin-bottom: 9px; flex-wrap: wrap; }
.en4-u-h b { font-size: .92rem; }
.en4-u-n { font-size: .74rem; color: var(--dim2); white-space: nowrap; }
.en4-u-ok { font-size: .72rem; color: var(--ok); border: 1px solid var(--ok); border-radius: 999px; padding: 1px 9px; white-space: nowrap; }

.en4-gates { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }
.en4-gates-all { margin-top: 10px; }
.en4-gate {
  display: flex; flex-direction: column; align-items: flex-start; gap: 5px;
  padding: 9px 10px; border: 1px solid var(--line-2); border-radius: var(--r-sm);
  background: var(--panel-2); color: var(--txt); cursor: pointer; text-align: left;
  transition: border-color .15s, transform .08s;
}
.en4-gate:hover { border-color: var(--pri); }
.en4-gate:active { transform: translateY(1px); }
.en4-ic { font-size: .9rem; font-weight: 700; color: var(--pri); line-height: 1; }
.en4-nm { font-size: .8rem; font-weight: 600; white-space: nowrap; }
.en4-bar { width: 100%; height: 5px; border-radius: 999px; background: var(--panel-3); overflow: hidden; }
.en4-bar i { display: block; height: 100%; background: var(--pri); border-radius: 999px; transition: width .25s; }
/* ★ null（没有分母）与 0（一个都没过）必须长得不一样 */
.en4-bar.na { background: transparent; border: 1px dashed var(--line-2); }
.en4-pct { font-family: var(--mono); font-size: .72rem; color: var(--dim); }
.en4-pct.na { color: var(--dim2); font-family: inherit; font-size: .72rem; }
.en4-cnt { font-family: var(--mono); font-size: .68rem; color: var(--dim2); }

/* 复习队列 */
.en4-review { padding: 0 18px 6px; flex: 0 0 auto; }
.en4-rv { border: 1px solid var(--line); border-radius: var(--r); background: var(--panel); padding: 10px 12px; }
.en4-rv-h { display: flex; align-items: baseline; gap: 10px; margin-bottom: 7px; flex-wrap: wrap; }
.en4-rv-h b { font-size: .9rem; }
.en4-rv-n { font-size: .72rem; color: var(--dim2); }
.en4-rv-s { display: flex; gap: 14px; flex-wrap: wrap; font-size: .78rem; color: var(--dim); }
.en4-rv-s b { font-family: var(--mono); font-size: .92rem; color: var(--txt); }
.en4-na { color: var(--dim2); font-style: normal; }
.en4-rv-tip { margin-top: 6px; font-size: .74rem; color: var(--dim2); line-height: 1.6; }
.en4-rv-a { display: flex; align-items: center; gap: 10px; margin-top: 10px; flex-wrap: wrap; }
.en4-rv-d { font-size: .74rem; color: var(--dim2); font-family: var(--mono); }

/* 答题 */
.en4-wrap-q { max-height: 60vh; overflow: auto; }
.en4-q { border: 1px solid var(--line); border-radius: var(--r-sm); padding: .75em .85em; margin-bottom: 8px; background: var(--panel); }
.en4-q-h { display: flex; align-items: center; gap: 9px; margin-bottom: 7px; flex-wrap: wrap; }
.en4-q-n { font-family: var(--mono); font-size: .74rem; color: var(--dim2); }
.en4-q-w { font-size: 1rem; font-weight: 600; }
.en4-q-p { font-size: .8rem; color: var(--dim); }
.en4-q-l { font-family: var(--mono); font-size: .74rem; color: var(--dim2); letter-spacing: .16em; }
.en4-q-m { font-size: .82rem; color: var(--dim); margin-bottom: 7px; }
.en4-start { font-size: .74rem; color: var(--dim2); margin: 2px 0 7px; }
.en4-start code { background: var(--panel-3); border-radius: 4px; padding: 1px 5px; margin-right: 5px; font-size: .72rem; }
.en4-ch { display: flex; flex-wrap: wrap; gap: 7px; }
.en4-ch-i { border: 1px solid var(--line-2); background: var(--panel-2); color: var(--txt); border-radius: var(--r-sm); padding: 5px 12px; font-size: .84rem; cursor: pointer; }
.en4-ch-i:hover { border-color: var(--pri); }
.en4-ch-i.on { border-color: var(--pri); background: var(--pri-soft); }
.en4-ch-i.ok { border-color: var(--ok); background: var(--ok-soft); }
.en4-ch-i.bad { border-color: var(--bad); background: var(--bad-soft); text-decoration: line-through; }
.en4-q.ok { border-color: var(--ok); background: var(--ok-soft); }
.en4-q.bad { border-color: var(--bad); background: var(--bad-soft); }
.en4-q.used { border-color: var(--pri); }
.en4-q.noused { border-color: var(--line-2); }
/* ★ 造句的 unknown（想不起来）用中性色，不用红 —— 它不是错 */
.en4-q.unk { border-color: var(--line-2); background: var(--panel-2); }
.en4-fb { font-size: .82rem; margin-top: 6px; line-height: 1.6; }
.en4-fb.ok { color: var(--ok); }
.en4-fb.bad { color: var(--bad); }
.en4-fb.mid { color: var(--dim); }
.en4-fb-x { color: var(--dim); margin-top: 3px; }
.en4-sum { display: flex; gap: 14px; align-items: baseline; padding: .6em 0 .5em; }
.en4-sum b { font-size: 1.5rem; font-family: var(--mono); }
.en4-rv-od { font-size: .7rem; color: var(--bad); border: 1px solid var(--bad); border-radius: 999px; padding: 1px 7px; white-space: nowrap; }
.en4-rv-c { font-size: .7rem; color: var(--dim2); font-family: var(--mono); white-space: nowrap; }
.en4-rv-q .inp { margin-bottom: 6px; }

/* ================= 班级 + 班级排行榜（批次28）=================
 * 全站唯一"把人排序"的地方。两条视觉纪律：
 *   ① 0 分 / null 的人**不给名次**，显示「—」—— 不画"第 37 名"这种羞辱。
 *   ② 只有「我」那一行描出来，别的行不强调 —— 榜单是给自己看进度的，不是给别人看的。
 */
.cls-wrap { display: flex; gap: 14px; align-items: flex-start; }
.cls-list { width: 190px; flex: none; }
.cls-list-h { font-size: .74rem; color: var(--dim2); padding: 0 4px 6px; }
.cls-i { display: block; width: 100%; text-align: left; border: 1px solid var(--line); background: var(--panel); border-radius: var(--r-sm); padding: .55em .7em; margin-bottom: 6px; cursor: pointer; font: inherit; }
.cls-i:hover { border-color: var(--line-2); }
.cls-i.on { border-color: var(--pri); background: var(--pri-soft); }
.cls-i-n { display: block; font-size: .88rem; color: var(--txt); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cls-i-m { display: block; font-size: .72rem; color: var(--dim2); margin-top: 2px; }
.cls-main { flex: 1; min-width: 0; }

.cls-head { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.cls-head-l b { font-size: 1.02rem; }
.cls-head-l span { display: block; font-size: .76rem; color: var(--dim2); margin-top: 2px; }
.cls-code { display: flex; align-items: center; gap: 8px; border: 1px dashed var(--line-2); border-radius: var(--r-sm); padding: .5em .7em; margin-bottom: 12px; background: var(--panel-2); }
.cls-code span { font-size: .74rem; color: var(--dim2); }
.cls-code b { font-family: var(--mono); font-size: 1.15rem; letter-spacing: .14em; color: var(--pri); }
.cls-code i { font-style: normal; font-size: .72rem; color: var(--dim2); }
/* 被管理员直接拉进班的告知条（批次58）：不是报错、不是警告，是一条"这事怎么发生的"说明。 */
.cls-notice {
  border-left: 3px solid var(--pri); border-radius: var(--r-sm);
  background: var(--pri-soft); color: var(--pri);
  padding: .5em .75em; margin-bottom: 12px; font-size: .8rem; line-height: 1.7;
}

.cls-tabs { display: flex; gap: 6px; margin-bottom: 8px; }
.cls-tab { border: 1px solid var(--line); background: var(--panel); border-radius: 999px; padding: .32em .85em; font: inherit; font-size: .8rem; color: var(--dim); cursor: pointer; }
.cls-tab:hover { border-color: var(--line-2); color: var(--txt); }
.cls-tab.on { border-color: var(--pri); background: var(--pri-soft); color: var(--pri); }
.cls-hint { font-size: .74rem; color: var(--dim2); margin-bottom: 10px; }

.cls-board { border: 1px solid var(--line); border-radius: var(--r-sm); overflow: hidden; background: var(--panel); }
.cls-row { display: flex; align-items: center; gap: 10px; padding: .55em .8em; border-bottom: 1px solid var(--line); }
.cls-row:last-child { border-bottom: 0; }
.cls-row.me { background: var(--pri-soft); }
.cls-row.off { opacity: .62; }
.cls-rk { width: 2.1em; flex: none; text-align: right; font-family: var(--mono); font-size: .95rem; color: var(--dim); }
.cls-row.me .cls-rk { color: var(--pri); }
.cls-nm { flex: 1; min-width: 0; font-size: .88rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cls-nm i { font-style: normal; font-size: .68rem; color: var(--pri); border: 1px solid var(--pri); border-radius: 999px; padding: 0 5px; margin-left: 6px; }
.cls-vl { font-family: var(--mono); font-size: .95rem; color: var(--txt); }
.cls-na { font-family: inherit; font-size: .76rem; color: var(--dim2); }
.cls-none, .cls-note, .cls-err { padding: .8em; font-size: .78rem; color: var(--dim2); }
.cls-note { padding: .5em .8em 0; }
.cls-err { color: var(--bad); }

.cls-members { margin-top: 12px; display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.cls-m-h { font-size: .74rem; color: var(--dim2); margin-right: 2px; }
.cls-m { font-size: .78rem; color: var(--dim); border: 1px solid var(--line); border-radius: 999px; padding: .15em .6em; }
.cls-m.me { border-color: var(--pri); color: var(--pri); }

.cls-empty { border: 1px dashed var(--line-2); border-radius: var(--r); padding: 2.2em 1.4em; text-align: center; background: var(--panel); }
.cls-empty b { display: block; font-size: 1rem; margin-bottom: 6px; }
.cls-empty span { display: block; font-size: .8rem; color: var(--dim2); max-width: 34em; margin: 0 auto; line-height: 1.7; }
.cls-empty-btns { display: flex; gap: 10px; justify-content: center; margin-top: 14px; }
/* ★ 批次66：没登录账号时的提示里，"你已经有账号了，账号名是 XXX" 那句要**跳出来** ——
 * 它才是学生真正需要的那条信息（后半句"密码就是你的访问口令"他能立刻试）。
 * 与上面那条 span 的区别靠**颜色 + 间距**，不靠加粗（加粗在暗色下几乎看不出来）。 */
.cls-na-hint { margin-top: 10px !important; color: var(--pri) !important; }
.cls-na-hint b { display: inline; font-weight: 600; }

/* ================= 上下文折叠条（批次53） =================
 * 服务端只把最近 20 条消息发给模型，更早的会被压成摘要（见 server/core.js）。
 * ★ 摘要以前是**纯后端行为**、界面上看不见 —— 学生只觉得"聊久了它就忘了"。
 * 这条把"更早的 N 条已压成摘要"摆出来，可展开看原文。
 * 视觉上要**弱于消息本身**：它是一段被折叠的记忆，不是一条新消息。 */
.ctx-fold { margin: 6px 0 14px; }
.cf-head {
  display: flex; align-items: center; gap: 8px;
  padding: .55em .85em; border-radius: var(--r-sm);
  background: var(--panel-2); border: 1px dashed var(--line-2);
}
.cf-ico { color: var(--dim2); font-size: .9rem; line-height: 1; flex: none; }
.cf-t { flex: 1; min-width: 0; font-size: .78rem; color: var(--dim); }
.cf-btn {
  font: inherit; font-size: .74rem; flex: none; cursor: pointer;
  padding: .2em .6em; border-radius: 999px;
  background: transparent; border: 1px solid var(--line-2); color: var(--dim);
}
.cf-btn:hover { border-color: var(--pri); color: var(--pri); }
.cf-body {
  margin-top: 8px; padding: .8em .95em; border-radius: var(--r-sm);
  background: var(--panel); border: 1px solid var(--line);
  font-size: .8rem; line-height: 1.75; color: var(--dim2);
  /* 摘要是模型写的多段自由文本，用 pre-wrap 保住它自己的换行 */
  white-space: pre-wrap; word-break: break-word;
}
/* 摘要生成时间（批次55a）—— 比正文还弱，只回答"这段记忆有多新" */
.cf-at { flex: none; font-size: .72rem; color: var(--dim2); }

/* 记忆面板：把「AI 自动总结」的条目和手写的区分开（批次53） */
.mem-note {
  margin: 0 18px 8px; padding: .5em .8em; border-radius: var(--r-xs);
  background: var(--pri-soft); color: var(--pri-2);
  font-size: .75rem; line-height: 1.6;
}
/* 记忆行内编辑（批次55b）：输入框直接顶替原来的文字，保存/取消在右侧 */
.mrow-txt { display: block; }
.mem-edit-input {
  flex: 1; min-width: 0; font: inherit; font-size: .9rem;
  padding: .42em .62em; border-radius: var(--r-xs);
  background: var(--panel-2); border: 1px solid var(--pri); color: var(--txt);
}
.mem-edit-input:focus { outline: none; border-color: var(--pri-2); background: var(--panel); }
.chip.mem-auto { background: var(--pri-soft); color: var(--pri); }

/* 重置码面板（批次60）：没有重置码时给的"找谁要"提示，要比普通 dim 更容易被看见 ——
   这条信息的**唯一用途**就是告诉一个进不来的人下一步该找谁。 */
.rs-rc-hint {
  margin: .1em 0 .9em; padding: .6em .75em; border-radius: var(--r-xs);
  background: var(--panel-2); border-left: 3px solid var(--pri);
  line-height: 1.55;
}

/* 创建空间成功卡片（批次61）。
   三行"账号/口令/空间ID"的键值表：值用等宽字 + 可点击复制，
   因为这几串是要被**抄下来**的（学生下次靠账号名回来）。 */
.sp-done { display: flex; flex-direction: column; gap: 8px; margin: .4em 0 1.1em; }
.sp-done-row {
  display: flex; align-items: center; gap: 10px;
  background: var(--panel-2); border-radius: var(--r-xs); padding: 9px 12px;
}
.sp-done-row .k { color: var(--dim); font-size: .84rem; flex: 0 0 5.2em; }
.sp-done-row .v {
  font-family: var(--mono, monospace); font-size: .95rem; font-weight: 500;
  color: var(--txt); overflow-wrap: anywhere; flex: 1;
}
.sp-done-row .c { flex: 0 0 auto; }
.sp-done-row.hi { background: var(--pri-soft); }
.sp-done-row.hi .v { color: var(--pri); }

/* 「进入已有空间」的重名候选列表（批次61）。
   空间名允许重名（suggestName 会自动加数字），所以按名字进可能命中多个 ——
   这里把候选摆出来让人**点选**，而不是替他猜一个（猜错就进了别人的空间）。 */
.sp-picks { display: flex; flex-direction: column; gap: 6px; margin: 0 0 14px; }
.sp-pick {
  display: flex; align-items: center; gap: 10px; text-align: left;
  background: var(--panel-2); border: 1px solid var(--line); color: var(--txt);
  border-radius: var(--r-xs); padding: 9px 12px; font-size: .9rem; cursor: pointer;
}
.sp-pick:hover { border-color: var(--pri); background: var(--pri-soft); }
.sp-pick .id { font-family: var(--mono, monospace); color: var(--dim); font-size: .84rem; }

/* ================= 顶栏响应式 =================
 * 窗口不够宽时让顶栏**换行**，而不是把标题压成竖排。
 * 单开 900px 而不是并进侧栏的 820px —— 侧栏停靠时（>820）主区反而更窄，
 * 顶栏需要比侧栏更早换行。820 以下侧栏变浮层，主区反而变宽了。 */
@media (max-width: 900px) {
  .topbar { flex-wrap: wrap; row-gap: 8px; }
  /* 换行布局里 .sp 会把标题挤扁，去掉它，让标题自己长满第一行 */
  .topbar > .sp { display: none; }
  /* flex-basis 用 0（不是 auto）：假设主尺寸为 0 才一定放得下第一行，
   * 否则标题会带着自己的整段文字宽度去抢位置，换行位置就不可控了。 */
  .topbar > .tb-txt { flex: 1 1 0; min-width: 0; }
  .topbar > .tb-tools {
    display: flex; flex: 1 0 100%; order: 1;
    align-items: center; gap: 8px;
    overflow-x: auto; overscroll-behavior-x: contain;
    scrollbar-width: none;
  }
  .topbar > .tb-tools::-webkit-scrollbar { display: none; }
  .topbar > .tb-tools > * { flex: 0 0 auto; }
}

@media (max-width: 820px) {
  /* 手机上本子树占了半屏就没法看文档了 —— 收成一条横向滚动的条 */
  .kb-wrap { flex-direction: column; }
  .kb-tree {
    flex: 0 0 auto; border-right: 0; border-bottom: 1px solid var(--line);
    flex-direction: row; align-items: center; gap: 8px; padding: 8px 12px; overflow-x: auto;
  }
  .kb-tree-h, .kb-tree-foot { display: none; }
  .kb-tree-list { flex-direction: row; flex: 1; gap: 6px; }
  .kb-tree-i { flex: 0 0 auto; border: 1px solid var(--line); padding: .3em .7em; }
  .kb-tabs { padding: 8px 12px 0; }
  .kb-subbar { padding: 8px 12px 4px; }
  /* 班级：手机上"班级列表"横过来收成一条，别把榜单挤没 */
  .cls-wrap { flex-direction: column; }
  .cls-list { width: 100%; display: flex; gap: 6px; overflow-x: auto; }
  .cls-list-h { display: none; }
  .cls-i { flex: 0 0 auto; width: auto; margin-bottom: 0; }
  .me-chip .me-nm { display: none; }
}

/* ================= 移动端 ================= */
@media (max-width: 820px) {
  .side {
    position: fixed; top: 0; bottom: 0; left: 0; z-index: 60;
    transform: translateX(-100%); transition: transform .26s cubic-bezier(.22,.8,.3,1);
    box-shadow: var(--shadow-lg); width: 280px; flex-basis: 280px;
  }
  .side.open { transform: none; }
  /* 侧栏打开后 280px 的浮层正好盖住汉堡 —— 不给遮罩就"点得到开、点不到关"。
   * 遮罩压在侧栏下面（55 < 60），点它即关。关闭时 pointer-events:none，
   * 不会挡住任何点击。 */
  .side-mask {
    display: block; position: fixed; inset: 0; z-index: 55;
    background: rgba(16, 24, 40, .38);
    opacity: 0; pointer-events: none;
    transition: opacity .22s ease;
  }
  .side.open ~ .side-mask { opacity: 1; pointer-events: auto; }
  /* 浮层抽屉没有"拖宽"这回事 */
  .side-resizer { display: none; }
  .hamb { display: block; }
  .stream { padding: 16px 12px 4px; }
  .composer { padding: 10px 12px 12px; }
  .card-stats, .card-bar, .card-list { padding-left: 12px; padding-right: 12px; }
  /* 四关在窄屏改两列：四列会把「认得出」这类中文标签挤成竖排 */
  .en4-wrap, .en4-review { padding-left: 12px; padding-right: 12px; }
  .en4-gates { grid-template-columns: repeat(2, 1fr); }
  /* ★ 单词行必须换行：一行里塞了 词(96) + 音标(84) + 意思 + 计数 + 两个按钮 + 删，
     390px 宽下放不下 —— 实测 speak-btn 与删除按钮被推出右边界。
     换行后第一行给词/音标/意思，第二行放计数与按钮。 */
  .en-i { flex-wrap: wrap; row-gap: 6px; gap: 8px; }
  .en-i .en-w { min-width: 0; }
  .en-i .en-p { min-width: 0; }
  .en-i .en-m { flex: 1 1 100%; order: 3; }
  .en-i .en-n { order: 4; }
  .msg.user .body { max-width: 92%; }
  .land-card { padding: 24px 20px 18px; }
  /* 落地页抬头：手机上压缩，别让"我们是谁"把表单顶出首屏 */
  .land { padding: 18px 14px; }
  .land-head { margin-bottom: 14px; }
  .land-logo { gap: 9px; }
  .land-logo-img { width: 48px; }
  .land-logo-word { font-size: 1.5rem; }
  .land-stance { margin-top: 8px; font-size: .8rem; }
  .topbar { padding: 10px 12px; gap: 8px; }
  .topbar .sel { max-width: 92px; }
  /* 手机上「收起成图标」没意义（侧栏本来就是浮层），让位给汉堡 */
  #sideToggle { display: none; }
  /* 搜索框独占一行，别和标题抢。
   * order:1 让它排在头像之后 —— 否则它会把头像挤到第三行。 */
  .topbar > #kbSearch { flex: 1 0 100%; max-width: none; order: 1; }
  /* 项目下拉在窄屏再收一点，给副标题留出省略的空间 */
  #projectSel { max-width: 104px; }
  /* 空状态：手机上四张卡排 2×2，视觉主体收小，别把首屏占满 */
  .empty { padding: 4vh 16px 3vh; }
  .empty-art { width: 132px; height: 104px; margin-bottom: 6px; }
  .empty-hi { font-size: 1.28rem; }
  .empty-ask { font-size: .96rem; margin-bottom: 8px; }
  .empty-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; margin-top: 18px; }
  .ec { padding: 10px 11px 11px; }
  .ec b { margin: 7px 0 2px; }
  .empty-tips { margin-top: 14px; }
  /* 日报：窄屏下数字排 2 列，日期导航换到下一行 */
  .d-nums { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .d-head h3 { width: 100%; }
  .d-nav { margin-left: 0; width: 100%; }
  .d-nav .btn { flex: 1; }
}
@media (max-width: 480px) {
  .msg .av { display: none; }
  .msg.user .body { max-width: 100%; }
  /* 落地页三个 tab 在 320px 下会折成两行（"创建学习 / 空间"），
     胶囊高 62px 且三格高度不齐，很难看。
     中文没有词边界、也不会缩字，只能整体压字号 + 压内边距把它塞回一行。
     算过：3 格共 16 个汉字，容器 252px ⇒ 字号必须 ≤ 12.6px（.79rem）。取 .76rem 留余量。 */
  .land { padding: 14px 12px; }
  .land-card { padding: 20px 16px 16px; }
  .land-tabs { padding: 3px; gap: 3px; margin-bottom: 16px; }
  /* 标签强制单行（不折成两行胶囊）。.76rem 在 252px 容器下差 1–2px 会折行，
     降到 .72rem 并压横向内边距，保证"创建学习空间"六个字一行塞下；高度随之回到 ≤44px。 */
  .land-tab { font-size: .72rem; padding: .5em .14em; white-space: nowrap; }
  /* 三份文件横排在 320px 放不下。藏掉「·」，改用外边距分隔 ——
     这样换行点只会落在两个链接之间，不会留下一个孤零零的分隔符。 */
  .land-legal { font-size: .72rem; line-height: 2.1; }
  .land-legal a { margin: 0 6px; }
  .land-sep { display: none; }
}

/* 打印 / 分享页 */
.share-body { max-width: 720px; margin: 0 auto; padding: 28px 18px 60px; }
.share-head { border-bottom: 1px solid var(--line); padding-bottom: 14px; margin-bottom: 22px; }
.share-head h1 { font-size: 1.3rem; margin: 0 0 4px; }

/* ================= 英语（P10）================= */
.en-i { display: flex; align-items: center; gap: 12px; padding: .62em .8em; border: 1px solid var(--line); border-radius: var(--r-sm); background: var(--panel); margin-bottom: 7px; }
.en-i .en-w { font-weight: 600; font-size: .95rem; min-width: 96px; }
.en-i .en-p { font-family: var(--mono); font-size: .78rem; color: var(--dim2); min-width: 84px; }
.en-i .en-m { flex: 1; min-width: 0; font-size: .86rem; color: var(--dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.en-i .en-n { font-family: var(--mono); font-size: .72rem; color: var(--dim2); white-space: nowrap; }
.en-i .en-n.hot { color: var(--bad); }
.en-i .en-x { opacity: 0; transition: opacity .15s; }
.en-i:hover .en-x { opacity: 1; }

/* 听写 */
.dict-wrap { max-height: 62vh; overflow: auto; }
.dict-i { padding: .8em .9em; border: 1px solid var(--line); border-radius: var(--r-sm); margin-bottom: 8px; background: var(--panel); }
.dict-i .dict-h { display: flex; align-items: center; gap: 10px; margin-bottom: 7px; }
.dict-i .dict-n { font-family: var(--mono); font-size: .76rem; color: var(--dim2); }
.dict-i .dict-p { font-size: 1rem; font-weight: 600; flex: 1; }
.dict-i .dict-l { font-family: var(--mono); font-size: .76rem; color: var(--dim2); letter-spacing: .18em; }
.dict-i.ok { border-color: var(--ok); background: var(--ok-soft); }
.dict-i.bad { border-color: var(--bad); background: var(--bad-soft); }
.dict-i .dict-fb { font-size: .82rem; margin-top: 6px; }
.dict-i .dict-fb.ok { color: var(--ok); }
.dict-i .dict-fb.bad { color: var(--bad); }
.dict-score { display: flex; gap: 14px; align-items: baseline; padding: .6em 0 1em; }
.dict-score b { font-size: 1.6rem; font-family: var(--mono); }
.speak-btn { border: 1px solid var(--line-2); background: var(--panel-2); color: var(--txt); border-radius: 999px; width: 30px; height: 30px; cursor: pointer; font-size: .9rem; line-height: 1; flex: 0 0 30px; }
.speak-btn:hover { border-color: var(--pri); color: var(--pri); }
/* ================= 测评（P6）================= */
.ex-i { display: flex; align-items: center; gap: 12px; padding: .7em .85em; border: 1px solid var(--line); border-radius: var(--r-sm); background: var(--panel); margin-bottom: 8px; }
.ex-i > div:first-child { flex: 1; min-width: 0; }
.ex-i b { font-size: .93rem; display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ex-i .dim { font-size: .75rem; margin-top: 3px; }
.ex-pill { font-size: .72rem; padding: 2px 9px; border-radius: 999px; background: var(--panel-3); color: var(--dim); white-space: nowrap; }
.ex-pill.open { background: var(--pri-soft); color: var(--pri); }
.ex-pill.done { background: var(--ok-soft); color: var(--ok); }
.ex-q { border: 1px solid var(--line); border-radius: var(--r-sm); padding: .8em .9em; margin-bottom: 9px; background: var(--panel); }
.ex-q .eq-h { display: flex; align-items: center; gap: 8px; font-size: .74rem; color: var(--dim2); margin-bottom: 6px; }
.ex-q .eq-t { font-size: .95rem; font-weight: 600; line-height: 1.6; margin-bottom: 9px; }
.ex-q.ok { border-color: var(--ok); }
.ex-q.bad { border-color: var(--bad); }
.ex-q.unknown { border-color: var(--line-2); }
.ex-q .eq-fb { font-size: .82rem; margin-top: 7px; padding-top: 7px; border-top: 1px dashed var(--line); }
.ex-q .eq-fb .k { color: var(--dim2); }
.ex-res { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 14px; }
.ex-res .r { background: var(--panel-2); border-radius: var(--r-sm); padding: .5em .8em; font-size: .78rem; color: var(--dim); }
.ex-res .r b { display: block; font-size: 1.15rem; font-family: var(--mono); color: var(--txt); }
.ex-flow { font-size: .76rem; color: var(--dim2); line-height: 1.7; }

/* ================= 公共资料池（P3）================= */
.pool-i { border: 1px solid var(--line); border-radius: var(--r-sm); background: var(--panel); padding: .75em .85em; margin-bottom: 8px; }
.pool-i.mine { border-color: var(--pri); }
.pool-i.hidden-i { opacity: .6; }
.pool-top { display: flex; align-items: center; gap: 8px; margin-bottom: 5px; }
.pool-top b { font-size: .93rem; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pool-tag { font-size: .7rem; padding: 2px 8px; border-radius: 999px; background: var(--panel-3); color: var(--dim); white-space: nowrap; }
.pool-tag.k-cards { background: var(--pri-soft); color: var(--pri); }
.pool-tag.k-words { background: var(--ok-soft); color: var(--ok); }
.pool-tag.k-exam { background: var(--warn-soft); color: var(--warn); }
.pool-tag.gone { background: var(--bad-soft); color: var(--bad); }
.pool-sum { font-size: .82rem; color: var(--dim); line-height: 1.6; }
.pool-prev { font-size: .78rem; color: var(--dim2); font-family: var(--mono); margin-top: 5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pool-bot { display: flex; align-items: center; gap: 8px; margin-top: 8px; }
.pool-bot .by { font-size: .74rem; color: var(--dim2); flex: 1; }
.pool-ban { padding: .7em .9em; border-radius: var(--r-sm); background: var(--warn-soft); color: var(--warn); font-size: .82rem; margin-bottom: 10px; line-height: 1.6; }
.pool-note { font-size: .78rem; color: var(--dim2); padding: 0 0 10px; line-height: 1.6; }

/* 表单标签（弹窗内用，与空间门的 .field label 视觉一致但独立） */
.lb { display: block; font-size: .78rem; color: var(--dim); margin-bottom: 5px; font-weight: 500; }
.lb-wrap { margin-bottom: 10px; }
.card-stats .st { cursor: default; }

/* ================= 动效（P8）=================
 * 三条原则，写在这里免得后来被加歪：
 *   1. 只做"状态变化"的动效，不做无意义的持续炫耀。没有撒花、没有抽奖、没有音效 ——
 *      那是短视频的刺激打法，不是学习工具的活。
 *   2. 时长一律 ≤ 320ms（图表生长稍长，但只放一次）。动效是让"变化被看见"，
 *      不是让人盯着看。任何需要等它演完的动效都是坏动效。
 *   3. 全部在 prefers-reduced-motion: reduce 下关闭，见文件末尾的兜底块。
 */

/* 视图切换：轻微上浮，让"换页了"这件事被感知到。
   知识库里的二级分区（.kb-sub）原本是顶层 .view，改版后降了一级，
   这里补上同一条动画，切分区才有和切顶层视图一致的手感。 */
.view:not([hidden]), .kb-sub:not([hidden]) { animation: viewIn .22s cubic-bezier(.22, .8, .3, 1) both; }
@keyframes viewIn { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: none; } }

/* 导航当前项：小圆点弹一下（一次，不循环） */
.nav-i.on .ni-dot { animation: dotPop .26s cubic-bezier(.22, .8, .3, 1) both; }
@keyframes dotPop { 0% { transform: scale(.4); } 60% { transform: scale(1.5); } 100% { transform: scale(1); } }

/* 知识卡列表入场：极轻的错峰，避免整屏同时"啪"地出现 */
.kcard { animation: kcIn .24s cubic-bezier(.22, .8, .3, 1) both; }
@keyframes kcIn { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
.kcard:nth-child(1) { animation-delay: 0ms; }
.kcard:nth-child(2) { animation-delay: 22ms; }
.kcard:nth-child(3) { animation-delay: 44ms; }
.kcard:nth-child(4) { animation-delay: 66ms; }
.kcard:nth-child(5) { animation-delay: 88ms; }
.kcard:nth-child(n+6) { animation-delay: 100ms; }

/* 状态刚变化的那张卡：状态 chip 呼吸一次，指明"是这里变了" */
.chip.just { animation: chipFlash .62s cubic-bezier(.22, .8, .3, 1) 1; }
@keyframes chipFlash {
  0% { transform: scale(1); box-shadow: 0 0 0 0 color-mix(in srgb, var(--pri) 50%, transparent); }
  35% { transform: scale(1.14); box-shadow: 0 0 0 5px transparent; }
  100% { transform: scale(1); box-shadow: 0 0 0 0 transparent; }
}

/* 宠物成长值涨了：整条轻轻一亮，不放大不弹跳（成长是慢事） */
.pet-line.up { animation: petUp .7s ease-out 1; }
@keyframes petUp {
  0% { filter: none; }
  30% { filter: drop-shadow(0 0 6px color-mix(in srgb, var(--ok) 60%, transparent)); }
  100% { filter: none; }
}

/* 图表：柱子从底部长起来、折线从左往右画出来。
 * 折线用 pathLength="1" 把路径长度归一，这样任意长度的折线都能用同一套
 * dasharray 动画，不需要在 JS 里调 getTotalLength()（那要等布局完成，会闪）。 */
.chart .ch-bar { transform-box: fill-box; transform-origin: bottom; animation: barGrow .42s cubic-bezier(.22, .8, .3, 1) both; }
@keyframes barGrow { from { transform: scaleY(.02); } to { transform: scaleY(1); } }
.chart .ch-line { stroke-dasharray: 1; stroke-dashoffset: 1; animation: chDraw .78s cubic-bezier(.22, .8, .3, 1) both; }
@keyframes chDraw { to { stroke-dashoffset: 0; } }
.chart .ch-area { animation: fade .7s ease both; }
.chart .ch-dot { animation: dotIn .3s cubic-bezier(.22, .8, .3, 1) both; }
@keyframes dotIn { from { opacity: 0; } to { opacity: 1; } }

/* 图示生成中：一条流动的虚线轮廓，说明"还在画"，而不是死等 */
.art-pending { border: 1px dashed var(--line-2); border-radius: var(--r-sm); padding: 14px; }
.art-pending .art-h { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.art-pending .art-h b { font-size: .84rem; color: var(--dim); }
.art-pending .dotline { flex: 1; height: 1px; background: repeating-linear-gradient(90deg, var(--line-2) 0 6px, transparent 6px 12px); background-size: 200% 100%; animation: flow 1.2s linear infinite; }
@keyframes flow { from { background-position: 0 0; } to { background-position: -24px 0; } }

/* 流式光标：给正在吐字的位置一点柔光，让"这里还在写"比一根硬线更容易被注意到。
 * 用 step-end 闪烁（不是渐隐）—— 渐隐在余光里像"卡住了"，闪烁才像"在打字"。 */
.caret { box-shadow: 0 0 7px color-mix(in srgb, var(--pri) 55%, transparent); border-radius: 1px; }

/* ================= 账号体系（批次1）=================
 * 登录页的账号面板、协议勾选、公告入口、个人资料。
 * 全部走已有 CSS 变量，深色主题零额外规则。 */

/* --- 登录页：账号面板 --- */
.acct-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin: 2px 0 12px; }
.acct-row.center { justify-content: center; margin-top: 12px; }
.acct-code { display: flex; gap: 8px; }
.acct-code input { flex: 1; min-width: 0; }
.acct-code .btn { flex: 0 0 auto; white-space: nowrap; }
.acct-2col { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.acct-agree {
  display: flex; align-items: flex-start; gap: 8px;
  font-size: .82rem; line-height: 1.55; color: var(--dim);
  margin: 4px 0 12px; cursor: pointer;
}
.acct-agree input { margin-top: .22em; flex: 0 0 auto; }
.acct-agree a { color: var(--pri); text-decoration: none; border-bottom: 1px solid color-mix(in srgb, var(--pri) 35%, transparent); }
.acct-agree a:hover { border-bottom-color: var(--pri); }
.land-link {
  background: none; border: 0; padding: 0; cursor: pointer;
  color: var(--pri); font: inherit; font-size: .85rem;
}
.land-link:hover { text-decoration: underline; }
.land-link[disabled] { color: var(--dim2); cursor: default; text-decoration: none; }

/* 公告入口原来在侧栏（.ann-line），现在收进「我的」菜单，样式不再需要 */
.ann-item { padding: 12px 0; border-bottom: 1px solid var(--line); }
.ann-item:last-child { border-bottom: 0; }
.ann-item .ah { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; }
.ann-item .ah b { font-weight: 500; }
.ann-item .ab { color: var(--dim); font-size: .88rem; line-height: 1.65; white-space: pre-wrap; }
.ann-item .at { color: var(--dim2); font-size: .78rem; margin-top: 6px; }
.ann-tag {
  font-size: .7rem; padding: 1px 6px; border-radius: 999px;
  background: var(--bad-soft); color: var(--bad); border: 1px solid color-mix(in srgb, var(--bad) 30%, transparent);
}
.ann-unread { width: 6px; height: 6px; border-radius: 50%; background: var(--pri); flex: 0 0 auto; }

/* --- 协议正文 --- */
.doc-body h4 { font-size: .95rem; font-weight: 500; margin: 18px 0 6px; }
.doc-body h4:first-child { margin-top: 4px; }
.doc-body p { color: var(--dim); font-size: .9rem; line-height: 1.75; margin: 0 0 4px; }
.doc-meta { color: var(--dim2); font-size: .78rem; margin-bottom: 10px; }

/* --- 个人资料 --- */
.prof-head { display: flex; align-items: center; gap: 14px; }
.prof-av {
  width: 52px; height: 52px; border-radius: 50%; flex: 0 0 auto;
  display: flex; align-items: center; justify-content: center;
  background: var(--panel-3); border: 1px solid var(--line);
  overflow: hidden;
}
.prof-av svg { width: 100%; height: 100%; display: block; }
.av-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(56px, 1fr)); gap: 10px; margin-top: 12px; }
.av-cell {
  border: 1px solid var(--line); border-radius: var(--r-sm); background: var(--panel-2);
  padding: 6px; cursor: pointer; display: flex; flex-direction: column; align-items: center; gap: 4px;
}
.av-cell:hover { border-color: var(--line-2); }
.av-cell.on { border-color: var(--pri); background: var(--pri-soft); }
.av-cell svg { width: 34px; height: 34px; display: block; }
.av-cell span { font-size: .72rem; color: var(--dim); }
.av-cell.on span { color: var(--pri); }
.chip-wrap { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.chip-btn {
  border: 1px solid var(--line); background: var(--panel-2); color: var(--dim);
  border-radius: 999px; padding: 5px 13px; font: inherit; font-size: .84rem; cursor: pointer;
}
.chip-btn:hover { border-color: var(--line-2); }
.chip-btn.on { background: var(--pri-soft); border-color: var(--pri); color: var(--pri); }
.goal-list { margin-top: 10px; }
.goal-item {
  display: flex; align-items: center; gap: 10px; padding: 8px 0;
  border-bottom: 1px solid var(--line); font-size: .9rem;
}
.goal-item:last-child { border-bottom: 0; }
.goal-item .gt { flex: 1; min-width: 0; }
.goal-empty { color: var(--dim2); font-size: .88rem; padding: 6px 0; }

/* ================= 批次2：Chat 富功能 ================= */

/* ---- 侧栏收起（只留图标）---- */
.app.side-min .side { width: 66px; flex-basis: 66px; }
.app.side-min .brand-tx > small,
.app.side-min .nav-txt,
.app.side-min .side-h span,
.app.side-min .conv-filter,
.app.side-min .conv-i .ci-t,
.app.side-min .conv-i .ci-p,
.app.side-min .conv-i .ci-x,
.app.side-min .side-h #newConv { display: none; }
/* 收起态：66px 宽塞不下并排 —— 退回竖排居中，图形在名之上。 */
.app.side-min .brand { flex-direction: column; gap: 4px; padding: 16px 8px 10px; text-align: center; }
.app.side-min .brand-tx { align-items: center; }
/* ★ 66px 的栏里 1.05rem「后浪奔涌」会折成两行 —— 收到 .82rem 仍不够，必须禁折行。
   这是收起态**唯一**会破版的地方（不锁死就会出现「后浪奔 / 涌」这种断字）。 */
.app.side-min .brand-tx > b { font-size: .78rem; letter-spacing: 0; white-space: nowrap; }
.app.side-min .brand-logo { height: 30px; }
.app.side-min .nav { padding: 8px 6px; }
.app.side-min .nav-i { justify-content: center; padding: .6em 0; font-size: 1rem; }
.app.side-min .conv-i { justify-content: center; }
.app.side-min .conv-i .ci-fav { display: none; }
.app.side-min .side-h { justify-content: center; padding: 10px 4px 4px; }
.app.side-min .side-h::after { content: "···"; color: var(--dim2); letter-spacing: .1em; }
.app.side-min .proj-list { padding: 0 4px 16px; }

/* ---- 目录宽度拖拽 ---- */
.side-resizer {
  flex: 0 0 5px; width: 5px; cursor: col-resize;
  background: transparent; position: relative; z-index: 3;
  margin-left: -3px;
}
.side-resizer::after {
  content: ""; position: absolute; top: 0; bottom: 0; left: 2px; width: 1px;
  background: transparent; transition: background .15s;
}
.side-resizer:hover::after, body.resizing .side-resizer::after { background: var(--pri); }
body.resizing { cursor: col-resize; user-select: none; }
body.resizing * { cursor: col-resize !important; }

/* ---- 侧栏筛选 ---- */
.conv-filter { display: flex; gap: 6px; padding: 6px 10px 8px; align-items: center; }
.conv-filter .inp { flex: 1; min-width: 0; font-size: .82rem; padding: .4em .6em; }
.conv-filter .btn.on { background: var(--pri-soft); border-color: var(--pri); color: var(--pri); }
.conv-i .ci-fav { color: var(--warn); font-size: .72rem; flex: 0 0 auto; }
.conv-i .ci-p {
  font-size: .62rem; color: var(--pri); background: var(--pri-soft);
  border-radius: 4px; padding: 1px 4px; flex: 0 0 auto;
}

/* ---- 批次7：左栏项目树（项目是主轴，对话挂在项目下）---- */
.side-empty { padding: 14px 10px; }
.side-empty p { margin: 0 0 6px; font-size: .86rem; color: var(--txt); }
.side-empty .dim { color: var(--dim); font-size: .78rem; line-height: 1.7; }
.side-empty .se-acts { display: flex; gap: 6px; margin-top: 12px; flex-wrap: wrap; }

.side-p { margin: 1px 0; }
.sp-h {
  display: flex; align-items: center; gap: 5px;
  padding: 5px 7px; border-radius: var(--r-sm); cursor: pointer;
  transition: background .13s;
}
.sp-h:hover { background: var(--panel-2); }
.sp-caret { width: 11px; flex: 0 0 auto; font-size: .68rem; color: var(--dim2); }
.sp-n {
  flex: 1; min-width: 0; font-size: .84rem; font-weight: 600; color: var(--txt);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sp-c { flex: 0 0 auto; font-size: .7rem; color: var(--dim2); }
.sp-add, .sp-x {
  flex: 0 0 auto; opacity: 0; border: 0; background: transparent;
  color: var(--dim2); cursor: pointer; font-size: .82rem; line-height: 1;
  padding: 2px 4px; border-radius: 5px; transition: opacity .13s, background .13s;
}
.sp-h:hover .sp-add, .sp-h:hover .sp-x { opacity: 1; }
.sp-add:hover { background: var(--pri-soft); color: var(--pri); }
.sp-x:hover { background: var(--panel-3); color: var(--txt); }
.side-p .conv-i { padding-left: 24px; }

/* 建议"后面再做"的分区（批次57：门控从"墙"改成"建议"）——淡一点，但仍然点得进去 */
.kb-tab.locked { opacity: .55; cursor: pointer; }
.kb-tab.locked:hover { background: var(--hover, rgba(127,127,127,.08)); }
.kb-tab-l { display: inline-flex; align-items: center; margin-left: 3px; color: var(--dim2); }
/* 门控"改成建议"之后弹的选择框（批次57） */
.unlock-lead { margin: 0 0 8px; line-height: 1.6; }
.unlock-acts { display: flex; gap: 10px; justify-content: flex-end; margin-top: 14px; }
.me-mi.locked { opacity: .5; cursor: not-allowed; }
.btn.ghost.on { background: var(--pri-soft); border-color: var(--pri); color: var(--pri); }
/* 「已收成卡」（批次65）：复用 on 的"已激活"观感，但它是**终态**（disabled），
   所以要用 not-allowed 之外的中性提示 —— 学生要能看出"这事办好了"而不是"坏了"。
   opacity 不能压太低，否则像被禁用出错。 */
.btn.ghost.done { background: var(--pri-soft); border-color: var(--pri); color: var(--pri); opacity: 1; cursor: default; }
.btn.ghost.done:hover { background: var(--pri-soft); }

/* ---- 快速滚动 ---- */
.jump-btns {
  position: absolute; right: 26px; bottom: 168px; z-index: 6;
  display: flex; flex-direction: column; gap: 6px;
}
.view#view-chat { position: relative; }
.jump {
  width: 34px; height: 34px; border-radius: 50%;
  border: 1px solid var(--line-2); background: var(--panel); color: var(--dim);
  box-shadow: var(--shadow); font-size: .95rem; line-height: 1;
  display: flex; align-items: center; justify-content: center;
  transition: color .15s, border-color .15s, opacity .15s;
}
.jump:hover:not(:disabled) { color: var(--pri); border-color: var(--pri); }
.jump:disabled { opacity: .35; cursor: default; }

/* ---- 输入区扩展 ---- */
.composer-wrap {
  flex: 0 0 auto; border-top: 1px solid var(--line); background: var(--panel);
}
.composer-tools {
  display: flex; align-items: center; gap: 6px;
  padding: 8px 18px 0; flex-wrap: wrap;
}
.composer-tools .grow { flex: 1; }
.ct {
  border: 1px solid var(--line); background: var(--panel); color: var(--dim);
  border-radius: 999px; padding: .26em .8em; font-size: .76rem; font-weight: 500;
  transition: background .15s, color .15s, border-color .15s;
}
.ct:hover { color: var(--txt); border-color: var(--line-2); background: var(--panel-2); }
.ct.on { background: var(--pri-soft); border-color: var(--pri); color: var(--pri); font-weight: 600; }
.ct-hint { font-size: .72rem; color: var(--dim2); }
.composer { border-top: 0; padding-top: 8px; }

/* 输入区里的模型下拉：跟 .ct 按钮一样高、一样圆，不像表单控件 */
.composer-tools .ct-sel { font-size: .76rem; padding: .26em 1.6em .26em .8em; max-width: 11.5em; }
.composer-tools .ct-chk { display: inline-flex; align-items: center; gap: .35em; font-size: .76rem; }

/* ---- 当前技能标签（输入区左下角，2026-10-05）----
   ★ 为什么放在 composer-wrap 里、排在 composer-tools **之前**：
     它在 DOM 上先出现就自然落在左下角（composer-wrap 是普通块级流），
     而且与「＋」菜单同一个父容器 ⇒ 换技能时能就近对齐、不用跨区定位。
   ★ 只在选了技能时显示（`hidden`）——「全部技能」是默认态，默认态挂标签是噪音。 */
.ag-chip {
  /* ★ 靠左靠的是 `width: fit-content`，**不是** align-self ——
     .composer-wrap 是普通块级容器（不是 flex），align-self 在这里是空操作。
     写成 flex + fit-content 就够了；加 align-self 只会误导下一个人。 */
  display: flex; align-items: stretch; margin: 8px 18px 0;
  width: fit-content;
  border: 1px solid var(--pri); background: var(--pri-soft);
  border-radius: 999px; overflow: hidden;
}
.ag-chip[hidden] { display: none; }   /* 别让 flex 覆盖掉 hidden */
.ag-chip-b {
  display: inline-flex; align-items: center; gap: .4em;
  border: 0; background: transparent; color: var(--pri);
  padding: .28em .55em .28em .8em; font-size: .76rem; font-weight: 600;
  max-width: 16em;
}
.ag-chip-b:hover { background: rgba(37, 99, 235, .09); }
.ag-chip-k { font-weight: 500; color: var(--dim); font-size: .72rem; }
.ag-chip-k::after { content: '·'; margin-left: .4em; }
.ag-chip-n { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ag-chip-x {
  border: 0; background: transparent; color: var(--pri);
  padding: 0 .7em 0 .1em; font-size: .95rem; line-height: 1;
  border-left: 1px solid rgba(37, 99, 235, .22);
}
.ag-chip-x:hover { background: rgba(37, 99, 235, .14); color: var(--bad); }

/* ---- 「要不要我直接讲最关键的那一步？」（批次57）----
   位置与 .ag-chip 相同（输入区上方、靠左），但语义不同：那是"当前状态"（选了哪个技能），
   这是"给你的一个选项"。所以用**虚线**边框 + 中性文字色，视觉上后退一步 ——
   它是建议，不是提示或警告，不该抢眼（抢眼了就变成系统在推他，与自主感相冲）。
   ★ 靠左靠的是 `width: fit-content`（.composer-wrap 是普通块级容器，align-self 在此是空操作）。 */
.ask-key {
  display: flex; align-items: stretch; margin: 8px 18px 0;
  width: fit-content; max-width: calc(100% - 36px);
  border: 1px dashed var(--line); background: var(--panel);
  border-radius: 999px; overflow: hidden;
}
.ask-key[hidden] { display: none; }   /* 别让 flex 覆盖掉 hidden */
.ask-key-b {
  display: inline-flex; align-items: center;
  border: 0; background: transparent; color: var(--txt);
  padding: .28em .5em .28em .8em; font-size: .76rem; font-weight: 500;
  cursor: pointer; font-family: inherit;
}
.ask-key-b:hover { background: var(--pri-soft); color: var(--pri); }
.ask-key-x {
  border: 0; background: transparent; color: var(--dim);
  padding: 0 .7em 0 .1em; font-size: .95rem; line-height: 1;
  border-left: 1px dashed var(--line); cursor: pointer; font-family: inherit;
}
.ask-key-x:hover { color: var(--bad); }

/* ---- 「＋」动作菜单 ---- */
.plus-wrap { position: relative; display: inline-flex; }
.ct-plus { font-size: .95rem; line-height: 1; padding: .1em .55em .2em; font-weight: 500; }
.ct-plus.on { background: var(--pri-soft); border-color: var(--pri); color: var(--pri); }
.plus-menu {
  position: absolute; bottom: calc(100% + 8px); left: 0; z-index: 60;
  min-width: 15.5rem; padding: 6px;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--r); box-shadow: 0 12px 32px rgba(15, 23, 42, .16);
  animation: pmIn .12s ease-out;
}
.plus-menu.r { left: auto; right: 0; }
@keyframes pmIn { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
.pm-i {
  display: flex; align-items: center; gap: 10px; width: 100%;
  border: 0; background: transparent; color: var(--txt); cursor: pointer;
  border-radius: var(--r-sm); padding: 9px 10px; font-size: .86rem; text-align: left;
}
.pm-i:hover { background: var(--panel-2); }
.pm-i.on { color: var(--pri); font-weight: 600; }
.pm-i.on .pm-ic { color: var(--pri); }
.pm-ic {
  width: 1.5em; flex: none; display: inline-flex; align-items: center; justify-content: center;
  color: var(--dim);
}
.pm-ic svg { display: block; }
.pm-tx { flex: 1; }
.pm-ar { color: var(--dim2); font-size: 1rem; }
.pm-sep { height: 1px; background: var(--line); margin: 5px 8px; }

/* ---- 「技能」的二级面板（悬停展开，2026-10-05）----
   从 ＋ 菜单右侧伸出，贴着「技能」那一项。
   ★ 定位是相对 `.pm-wrap`（不是 .plus-menu）—— 只有这样才能对齐到那一项本身；
     挂到 .plus-menu 上的话，面板顶部会对到菜单顶，看着像脱离了那一行的从属关系。
   ★ `max-height` + 滚动：技能有 11 个，加上分组标题会很高，
     不封顶的话面板会顶出屏幕上方（它是从底往上长的）。 */
.pm-wrap { position: relative; }
.pm-fly {
  position: absolute; left: calc(100% + 4px); bottom: -6px; z-index: 61;
  /* 批次48：技能从 11 条涨到 33 条（4 个分类），原来 17rem 一次只露 9 条，
     滚到底很费劲。抬到 21rem；再往上会顶到视口上沿。 */
  min-width: 12.5rem; max-height: 21rem; overflow-y: auto;
  padding: 5px;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--r); box-shadow: 0 12px 32px rgba(15, 23, 42, .18);
  animation: pmIn .12s ease-out;
}
.pm-fly[hidden] { display: none; }
.pm-fly.l { left: auto; right: calc(100% + 4px); }   /* 右边放不下时翻到左侧 */
.pm-f-cat {
  font-size: .68rem; font-weight: 700; letter-spacing: .06em; color: var(--dim);
  padding: 6px 8px 3px;
}
.pm-f-i {
  display: block; width: 100%; text-align: left;
  border: 0; background: transparent; color: var(--txt);
  border-radius: var(--r-sm); padding: .38em .55em; font-size: .8rem;
}
.pm-f-i:hover { background: var(--panel-2); }
.pm-f-i.on { background: var(--pri-soft); color: var(--pri); font-weight: 600; }
.pm-f-i b { font-weight: inherit; }
.pm-f-i span { display: block; font-size: .7rem; color: var(--dim); font-weight: 400; }
.pm-f-sep { height: 1px; background: var(--line); margin: 5px 6px; }
.pm-f-all {
  display: block; width: 100%; text-align: left;
  border: 0; background: transparent; color: var(--pri);
  border-radius: var(--r-sm); padding: .38em .55em; font-size: .76rem; font-weight: 600;
}
.pm-f-all:hover { background: var(--pri-soft); }

/* ---- 待发送附件 ---- */
.attach-preview { display: flex; flex-wrap: wrap; gap: 8px; padding: 10px 18px 0; }
.att-chip {
  display: inline-flex; align-items: center; gap: 7px;
  background: var(--panel-2); border: 1px solid var(--line);
  border-radius: var(--r-sm); padding: 4px 6px 4px 4px; font-size: .78rem;
}
.att-chip img { width: 34px; height: 34px; object-fit: cover; border-radius: 6px; }
.att-chip b { max-width: 12em; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 500; }
.att-chip i { color: var(--dim2); font-style: normal; font-size: .7rem; }
.att-chip button {
  border: 0; background: transparent; color: var(--dim2); font-size: .95rem;
  line-height: 1; padding: 0 3px; border-radius: 4px;
}
.att-chip button:hover { background: var(--bad-soft); color: var(--bad); }

/* ---- 对话级临时资料 ---- */
.temp-docs { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; padding: 10px 18px 0; }
.td-label { font-size: .72rem; color: var(--dim2); font-weight: 600; letter-spacing: .04em; }
.td-chip {
  display: inline-flex; align-items: center; gap: 5px;
  background: var(--pri-soft); color: var(--pri); border: 1px solid transparent;
  border-radius: 999px; padding: .2em .4em .2em .7em; font-size: .75rem; max-width: 20em;
}
.td-chip.bad { background: var(--bad-soft); color: var(--bad); }
.td-chip button { border: 0; background: transparent; color: inherit; opacity: .6; font-size: .9rem; line-height: 1; padding: 0 3px; }
.td-chip button:hover { opacity: 1; }
.td-note { font-size: .7rem; color: var(--dim2); }

/* ---- 消息内扩展 ---- */
/* 配图区：正文之前放用户上传的图/文件，正文之后放 AI 配的图与互动课堂。
   图片宽度跟着版式走：批次12 对话列从 840px 改成通栏后，
   420px 在宽屏上又变回"缩略图"，所以提到 520px 一档。 */
.msg-atts { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 10px; }
.msg-atts:last-child { margin-bottom: 0; }
.msg-att-img {
  display: block; border-radius: 12px; overflow: hidden; border: 1px solid var(--line);
  line-height: 0; box-shadow: var(--shadow); transition: transform .16s, box-shadow .16s;
}
.msg-att-img:hover { transform: translateY(-2px); box-shadow: var(--shadow-lg); }
.msg-att-img img { display: block; max-width: 520px; max-height: 520px; object-fit: cover; }
.msg-att-file {
  display: inline-block; background: var(--panel-2); border: 1px solid var(--line);
  border-radius: var(--r-xs); padding: .2em .6em; font-size: .76rem; color: var(--dim);
}
/* AI 自动配的插画：独占一行，跟随主题边框。
   · svg 模式（几何/物理示意图）= 模型画的结构化矢量图，620px 够看清；
   · raster 模式（AI 生图，规划/总结/计划的场景插画）= 760px，值得看清细节。 */
.msg-att-illu {
  flex: 1 1 100%; width: 100%; max-width: 620px; margin: 4px 0 2px;
  border: 1px solid var(--line); border-radius: 12px; overflow: hidden; line-height: 0;
  box-shadow: var(--shadow); background: var(--panel-2);
}
.msg-att-illu.art-ai { max-width: 760px; }
/* 「AI 海报」（批次80）：整张 AI 生图，**竖版 2:3**（1696x2528）——
   铺满 760px 宽会变成 1140px 高，在聊天里太霸道，所以**限高、宽度自适应**：
   想看清细节就点「下载」拿原图。★ 只加普通规则、**不新增 @media**
   （_mapcheck 断言"文件里最后一个 @media 是地图那条窄屏回退"）。 */
.msg-att-illu.art-aiposter img { width: auto; max-width: 100%; max-height: 640px; margin: 0 auto; }
.msg-att-illu.art-aiposter .poster-dl {
  margin-left: .5em; color: var(--pri); text-decoration: none;
  border-bottom: 1px solid currentColor; white-space: nowrap;
}
.msg-att-illu svg, .msg-att-illu img { display: block; width: 100%; height: auto; }
.msg-att-illu .illu-cap {
  display: block; line-height: 1.5; font-size: .72rem; color: var(--dim2);
  padding: .45em .8em; background: var(--panel); border-top: 1px solid var(--line);
}
/* 计划卡（学习计划 / 阶段安排）：是"信息图"，不是插画。
   · 列数不定（2~8 列），用 auto-fit 让浏览器自己决定一行放几列 ——
     窄屏自动折成两行，全程不需要 JS 参与；
   · 每条要点由浏览器自动折行，中英混排也不会算错（这正是当初不画 SVG 的原因）；
   · 宽一点（1040px）：竞品那种 7 列计划表在 420px 里是排不下的。 */
.msg-att-illu.art-plan-wrap { max-width: 1040px; line-height: 1.5; background: var(--panel); }
.msg-att-illu.art-plan-wrap .art {
  margin: 0; padding: 0; max-width: none; width: 100%;
  border: 0; border-radius: 0; background: transparent; box-shadow: none;
}
.art-plan .art-h { padding: 12px 14px 9px; margin-bottom: 0; border-bottom: 1px solid var(--line); }
.art-plan .art-h > span {
  font-size: .68rem; color: var(--pri); background: var(--pri-soft);
  border-radius: 999px; padding: .12em .55em;
}
/* 「存成图片」：计划卡右上角，导出 1080 宽竖版海报（见 public/ai/js/poster.js） */
.art-plan .art-h .plan-save {
  margin-left: auto; flex: none; font-family: inherit; line-height: 1.4;
  padding: .24em .72em; border-radius: 999px; cursor: pointer;
  border: 1px solid var(--line); background: var(--panel); color: var(--dim);
  font-size: .68rem; transition: color .15s, border-color .15s;
}
.art-plan .art-h .plan-save:hover { color: var(--pri); border-color: var(--pri); }
.art-plan .art-h .plan-save:disabled { opacity: .6; cursor: default; }
.plan-sub { font-size: .78rem; color: var(--dim); padding: 9px 14px 0; }

.art-map-wrap { max-width: 1040px; }
/* ===== 学习地图卡（批次73 改版）：与 Canvas 海报共用同一套视觉语言 =====
   暖色标题带 + 红色主线缎带 + 序号药丸 + 彩色描边组卡 + 底部主线条。
   ★ 为什么必须和海报逐项对齐：聊天里看到的、和点「下载海报」拿到的那张，
     必须是**同一个东西**。否则"在聊天里觉得好看、下载下来是另一张图"是最伤的体验。
   ★ 卡片自带浅色底（#FFFBF0），所以里面的文字一律写**固定深色**、不跟主题变量走
     —— 否则深色主题下会变成浅字配浅底，整张卡看不见。 */
.art-lmap { overflow: hidden; border-radius: 20px; background: #FFFBF0; border: 1px solid #EFE3CE; }
.art-lmap .lmap-h { display:flex; align-items:center; gap:10px; padding:14px 16px; }
.art-lmap .lmap-h b { font-size:16px; color:#7A3E0A; }
.art-lmap .lmap-h span { color:#A8834A; font-size:12px; }
.art-lmap .map-save { margin-left:auto; border:1px solid #E7D6B4; border-radius:9px; background:#fff; color:#7A3E0A; padding:7px 11px; cursor:pointer; }
.art-lmap .map-save:hover { border-color:#BA7517; }
.art-lmap .map-save:disabled { opacity:.6; cursor:default; }

.lmap-ribbon { margin:0 16px; padding:9px 14px; border-radius:999px; background:#E24B4A; color:#fff; font-size:13px; font-weight:700; text-align:center; overflow-wrap:anywhere; }
/* ★ 批次78：序号药丸退场 → **卡片内编号徽章**（与海报左上角那枚同款：
   圆角方块、主题色实底、白色粗体数字）。悬空药丸是一排数字孤零零飘在缎带下面，
   没有连线、也看不出是什么；收进卡片左上角之后它才真的读得出"这是第几步"。
   ★ 圆角取 9px（不是 50%）：海报那枚是 roundRect(46,46,r15)，比例 0.326，
     缩到 26px 就是 9px —— 两处必须同一形状，否则聊天里圆、下载下来方。 */
.lmap-g-no { flex:0 0 auto; width:26px; height:26px; border-radius:9px; color:#fff; font-style:normal; font-size:14px; font-weight:700; display:flex; align-items:center; justify-content:center; }

.lmap-grid { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:14px; padding:14px 16px 16px; }
/* ★ 批次78：卡底/描边改成**从主题色派生**（与海报的 mixWhite(color,0.90/0.60) 同一套比例）。
   先写死色再写 color-mix：老浏览器不认 color-mix 时整条声明会被丢弃，必须留前一行兜底
   —— 直接只写 color-mix 的话，不支持的环境里卡底会掉成透明。 */
.lmap-g { min-width:0; background:#fff; background:color-mix(in srgb, var(--lm-color) 10%, #fff); border:2px solid #F0E7D6; border-color:color-mix(in srgb, var(--lm-color) 42%, #fff); border-radius:16px; padding:12px; display:flex; flex-direction:column; }
.lmap-g-head { display:flex; align-items:center; gap:10px; margin-bottom:10px; flex:0 0 auto; }
.lmap-g-txt { min-width:0; flex:1 1 auto; display:flex; flex-direction:column; gap:3px; }
.lmap-g-txt span { color:var(--lm-color); font-size:12px; font-weight:700; }
.lmap-g-txt b { color:#172554; font-size:15px; overflow-wrap:anywhere; }
.lmap-g-art { flex:0 0 auto; width:88px; height:66px; border-radius:12px; overflow:hidden; background:#F3F6FC; background:color-mix(in srgb, var(--lm-color) 20%, #fff); display:flex; align-items:center; justify-content:center; }
.lmap-g-art img { width:100%; height:100%; object-fit:cover; display:block; }
.lmap-g-art .lmap-ic { width:34px; height:34px; color:var(--lm-color); }

/* ★ 同排两张组卡被 grid 拉到等高 ⇒ 内容少的那张底部会空一大块。
   ★ 批次78：改成**条目整摞垂直居中**（justify-content:center），与海报的 `extra / 2` 同一招。
     旧版是让每个条目自己 `flex:1 1 auto` 摊开 —— 那是"高盒子装少内容、字全挤在顶部"，
     条目一少就变成三块大留白，比空在底部更难看。 */
.lmap-items { display:flex; flex-direction:column; gap:9px; flex:1 1 auto; justify-content:center; }
/* ★ 条目 = 白底 + 左侧主题色竖条（海报 lmDrawItem 的画法：卡底是浅色、条目才是白块，
   层次靠"浅色托白"而不是"白托浅灰"。旧版 #F7F9FD 是一块冷灰蓝，跟暖色卡底不是一套色系）。 */
.lmap-item { min-width:0; background:#fff; border-left:3px solid var(--lm-color); border-radius:12px; padding:10px 12px; flex:0 0 auto; }
.lmap-item h4 { display:flex; align-items:center; gap:7px; margin:0 0 6px; color:#1E293B; font-size:14px; overflow-wrap:anywhere; }
.lmap-item h4 .lmap-ic { width:16px; height:16px; flex:0 0 auto; color:var(--lm-color); }
/* ★ 这里**不能**跟主题变量走：条目底是固定白，深色主题的 --dim(#9BA5B8) 压上去只有 ~2.5:1。
   卡片自带浅色底 ⇒ 里面的文字一律写固定深色（这条本来就在文件里写着，旧代码漏了）。 */
.lmap-item p { margin:4px 0 0; color:#475569; font-size:12px; line-height:1.55; overflow-wrap:anywhere; }
.lmap-item p.lmap-texts { color:#64748B; }
.lmap-item p i { display:inline-block; min-width:32px; margin-right:6px; color:var(--lm-color); font-style:normal; font-weight:700; }
.lmap-item p.lmap-pit i { color:#C2410C; }

.lmap-sum { display:grid; gap:7px; margin:0 16px 16px; padding:13px 15px; border-radius:14px; background:#EAF1FE; }
.lmap-sum b { color:#1D4ED8; font-size:13px; }
.lmap-sum span { display:flex; align-items:baseline; gap:8px; color:#334155; font-size:13px; }
.lmap-sum span i { flex:0 0 auto; width:7px; height:7px; border-radius:50%; }

/* ── 单元一览卡（批次76）：专属"知识板"版式 ──────────────────────────
   ★ 与学习地图卡共用同一套暖色视觉语言（标题带 / 缎带 / 序号药丸 / 主题色描边 / 底部总结），
     但"格子里装什么"完全不同：地图格 = 若干篇目小卡，单元格 = 通栏插画 + 要点清单 + 任务/留心。
   ★ 基准规则全部写在所有 @media 之前（窄屏回退在文件末尾）。 */
.art-ub { overflow: hidden; border-radius: 20px; background: #FFFBF0; border: 1px solid #EFE3CE; }
.art-ub .ub-h { display:flex; align-items:center; gap:10px; padding:14px 16px; }
.art-ub .ub-h b { font-size:16px; color:#7A3E0A; }
.art-ub .ub-h span { color:#A8834A; font-size:12px; }
.art-ub .ub-save { margin-left:auto; border:1px solid #E7D6B4; border-radius:9px; background:#fff; color:#7A3E0A; padding:7px 11px; cursor:pointer; }
.art-ub .ub-save:hover { border-color:#BA7517; }
.art-ub .ub-save:disabled { opacity:.6; cursor:default; }

.ub-ribbon { margin:0 16px; padding:9px 14px; border-radius:999px; background:#E24B4A; color:#fff; font-size:13px; font-weight:700; text-align:center; overflow-wrap:anywhere; }
.ub-pills { display:flex; justify-content:center; flex-wrap:wrap; gap:14px; padding:14px 16px 2px; }
.ub-pills i { width:26px; height:26px; border-radius:50%; color:#fff; font-style:normal; font-size:13px; font-weight:700; display:flex; align-items:center; justify-content:center; }

.ub-grid { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:14px; padding:14px 16px 16px; }
.ub-u { min-width:0; background:#fff; border:1px solid #F0E7D6; border-top:5px solid var(--ub-color); border-radius:16px; padding:12px; display:flex; flex-direction:column; }
.ub-u-art { width:100%; height:96px; border-radius:12px; overflow:hidden; background:#F3F6FC; display:flex; align-items:center; justify-content:center; margin-bottom:10px; }
.ub-u-art img { width:100%; height:100%; object-fit:cover; display:block; }
.ub-u-art .lmap-ic { width:48px; height:48px; color:var(--ub-color); }
.ub-u-head { display:flex; align-items:baseline; gap:8px; flex-wrap:wrap; margin-bottom:6px; }
.ub-u-head i { color:var(--ub-color); font-style:normal; font-size:12px; font-weight:700; }
.ub-u-head b { color:#172554; font-size:15px; overflow-wrap:anywhere; }
.ub-words { margin:0 0 8px; color:var(--muted); font-size:12px; line-height:1.55; overflow-wrap:anywhere; }
.ub-pts { list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:6px; }
.ub-pts li { position:relative; padding-left:14px; color:#334155; font-size:13px; line-height:1.5; overflow-wrap:anywhere; }
.ub-pts li::before { content:''; position:absolute; left:0; top:7px; width:6px; height:6px; border-radius:50%; background:var(--ub-color); }
.ub-task, .ub-pit { margin:8px 0 0; color:var(--dim); font-size:12px; line-height:1.55; overflow-wrap:anywhere; }
.ub-task i, .ub-pit i { display:inline-block; min-width:32px; margin-right:6px; color:var(--ub-color); font-style:normal; font-weight:700; }
.ub-pit i { color:#C2410C; }

.ub-sum { display:grid; gap:7px; margin:0 16px 16px; padding:13px 15px; border-radius:14px; background:#EAF1FE; }
.ub-sum b { color:#1D4ED8; font-size:13px; }
.ub-sum span { display:flex; align-items:baseline; gap:8px; color:#334155; font-size:13px; }
.ub-sum span i { flex:0 0 auto; width:7px; height:7px; border-radius:50%; }

.plan-grid {
  list-style: none; margin: 0; padding: 12px 14px 6px; display: grid; gap: 10px;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}
.plan-col {
  position: relative; background: var(--panel-2); border: 1px solid var(--line);
  border-radius: 10px; padding: 11px 10px 10px; overflow: hidden;
}
.plan-col::before { content: ""; position: absolute; left: 0; right: 0; top: 0; height: 3px; background: var(--c); }
.plan-day { font-size: .72rem; font-weight: 700; color: var(--c); letter-spacing: .02em; }
.plan-theme { font-size: .86rem; font-weight: 650; color: var(--txt); margin: 3px 0 6px; }
.plan-pts { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 3px; }
.plan-pts li { position: relative; padding-left: 11px; font-size: .78rem; color: var(--dim); }
.plan-pts li::before {
  content: ""; position: absolute; left: 1px; top: .52em;
  width: 4px; height: 4px; border-radius: 50%; background: var(--c); opacity: .8;
}
.plan-focus {
  margin-top: 8px; padding: 5px 8px; border-radius: 6px; font-size: .74rem;
  color: var(--txt); background: var(--panel); border-left: 3px solid var(--c);
}
.plan-tips {
  margin: 6px 14px 13px; padding: 9px 11px; border-radius: 9px;
  background: var(--panel-2); border: 1px dashed var(--line-2);
}
.plan-tips > b { display: block; font-size: .74rem; color: var(--dim); font-weight: 600; margin-bottom: 4px; }
.plan-tips ol { margin: 0; padding-left: 1.2em; }
.plan-tips li { font-size: .78rem; color: var(--dim); margin: 2px 0; }

/* ---- 互动卡（步骤条 / 选择题 / 翻卡）----
 * 三样共用 .art-int 外框。设计约束（见 render.js 的 interactive() 注释）：
 *   · **无 JS 也读得懂**：步骤全展开、选项与解析都在、翻卡正面朝上 →
 *     所以这里所有"隐藏"都必须是 `.on`/`.open` 这类**增量**类，
 *     不能在 CSS 里默认 display:none 掉正文（打印/复制时正文会一起消失）。
 *   · 与主题变量绑定（浅色/深色都对），不写死颜色。
 *   · 窄屏（≤480）按钮不换行、字号不缩到看不清。
 */
.art-int { padding: 0; overflow: hidden; }
.art-int .art-h { padding: 11px 14px 9px; margin-bottom: 0; border-bottom: 1px solid var(--line); }
.art-int .art-h > span {
  font-size: .68rem; color: var(--pri); background: var(--pri-soft);
  border-radius: 999px; padding: .12em .55em; flex: none;
}
.int { padding: 12px 14px 14px; }
.int-btn {
  font-family: inherit; font-size: .76rem; line-height: 1.4; cursor: pointer;
  padding: .34em .9em; border-radius: 999px; border: 1px solid var(--line);
  background: var(--panel); color: var(--dim); transition: color .15s, border-color .15s, background .15s;
}
.int-btn:hover { color: var(--pri); border-color: var(--pri); }
.int-btn:focus-visible { outline: 2px solid var(--pri); outline-offset: 2px; }
.int-foot { display: flex; gap: 7px; align-items: center; margin-top: 11px; flex-wrap: wrap; }
.int-foot .int-prev, .int-foot .int-next { color: var(--pri); border-color: var(--pri-soft); background: var(--pri-soft); }
.int-foot .int-prev:disabled, .int-foot .int-next:disabled { opacity: .4; cursor: default; color: var(--dim); border-color: var(--line); background: var(--panel); }
.int-foot .int-all { margin-left: auto; }

/* —— 步骤条：默认全部展开（无 JS 可读），点了才变成"一步一步" —— */
.int-steps-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 3px; }
.int-step { border-radius: var(--r-sm); border: 1px solid transparent; transition: background .15s, border-color .15s; }
.int-step.open { background: var(--panel-2); border-color: var(--line); }
.int-step-h {
  display: flex; align-items: center; gap: 9px; width: 100%; text-align: left;
  font-family: inherit; font-size: .84rem; font-weight: 600; color: var(--txt);
  background: none; border: 0; cursor: pointer; padding: 8px 8px; border-radius: var(--r-sm);
}
.int-step-n {
  flex: none; width: 22px; height: 22px; border-radius: 50%; font-size: .72rem; font-weight: 700;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--panel-3); color: var(--dim);
}
.int-step.done .int-step-n { background: var(--ok); color: #fff; }
.int-step.cur .int-step-n { background: var(--pri); color: #fff; }
.int-step-t { flex: 1; min-width: 0; }
.int-step-x {
  flex: none; width: 8px; height: 8px; margin-right: 3px;
  border-right: 1.6px solid var(--dim2); border-bottom: 1.6px solid var(--dim2);
  transform: rotate(45deg); transition: transform .18s;
}
.int-step.open .int-step-x { transform: rotate(-135deg); }
.int-step-b { font-size: .82rem; color: var(--dim); line-height: 1.65; padding: 0 8px 9px 39px; }
/* JS 接管后才折叠 —— 用一个类标记，保证"没跑 JS 时正文可见" */
.int-steps.armed .int-step:not(.open) .int-step-b { display: none; }
.int-steps.armed .int-step:not(.open) .int-step-h { color: var(--dim); font-weight: 500; }

/* —— 选择题：点一下给对错 + 解析，不记分 —— */
.int-q { font-size: .88rem; font-weight: 600; color: var(--txt); line-height: 1.6; margin-bottom: 10px; }
.int-opts { display: flex; flex-direction: column; gap: 6px; }
.int-opt {
  display: flex; align-items: flex-start; gap: 9px; width: 100%; text-align: left;
  font-family: inherit; font-size: .84rem; color: var(--txt); line-height: 1.55;
  background: var(--panel-2); border: 1px solid var(--line); border-radius: var(--r-sm);
  padding: 9px 11px; cursor: pointer; transition: border-color .15s, background .15s;
}
.int-opt:hover:not(:disabled) { border-color: var(--pri); }
.int-opt:disabled { cursor: default; }
.int-opt-k {
  flex: none; width: 20px; height: 20px; border-radius: 6px; font-size: .72rem; font-weight: 700;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--panel); border: 1px solid var(--line-2); color: var(--dim);
}
.int-opt-t { flex: 1; min-width: 0; }
.int-opt.right { border-color: var(--ok); background: var(--ok-soft); }
.int-opt.right .int-opt-k { background: var(--ok); border-color: var(--ok); color: #fff; }
.int-opt.wrong { border-color: var(--bad); background: var(--bad-soft); }
.int-opt.wrong .int-opt-k { background: var(--bad); border-color: var(--bad); color: #fff; }
/* 没标标准答案时（data-noanswer）：点中的那个只做"你看的是这条"的标记，
   一律中性色 —— 不判分就不该出现对/错的绿红两色。 */
.int-opt.picked { border-color: var(--pri); background: var(--pri-soft); }
/* 揭晓后：正确答案始终有标记（选错的人也要看得到对的是哪个） */
.int-quiz.revealed .int-opt[data-ok="1"] { border-color: var(--ok); }
.int-quiz.revealed .int-opt[data-ok="1"] .int-opt-k { background: var(--ok); border-color: var(--ok); color: #fff; }
.int-why { font-size: .78rem; color: var(--dim); line-height: 1.6; padding: 5px 2px 0 29px; }
.int-quiz:not(.revealed) .int-why { display: none; }
.int-res {
  margin-top: 10px; padding: 8px 11px; border-radius: var(--r-sm);
  font-size: .82rem; font-weight: 600; color: var(--txt); background: var(--panel-2); border-left: 3px solid var(--pri);
}
.int-res.ok { background: var(--ok-soft); border-left-color: var(--ok); color: var(--ok); }
.int-res.no { background: var(--warn-soft); border-left-color: var(--warn); color: var(--warn); }

/* —— 翻卡：正面朝上（无 JS 时就是一张卡片列表），点了翻 —— */
.int-flip-grid { display: grid; gap: 8px; grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); }
.int-card {
  position: relative; min-height: 78px; text-align: center; font-family: inherit; cursor: pointer;
  background: var(--panel-2); border: 1px solid var(--line); border-radius: var(--r-sm);
  padding: 12px 10px; display: flex; align-items: center; justify-content: center;
  transition: border-color .15s, background .15s;
}
.int-card:hover { border-color: var(--pri); }
.int-card[aria-pressed="true"] { background: var(--pri-soft); border-color: var(--pri); }
.int-card-in { display: flex; flex-direction: column; gap: 5px; align-items: center; }
.int-card-f { font-size: .86rem; font-weight: 650; color: var(--txt); line-height: 1.5; }
.int-card-b { font-size: .78rem; color: var(--dim); line-height: 1.55; }
/* 未翻开时背面藏起来 —— 但只在 armed（JS 已接管）之后，否则复制/打印会丢背面 */
.int-flip.armed .int-card[aria-pressed="false"] .int-card-b { display: none; }
.int-flip.armed .int-card[aria-pressed="false"] .int-card-f::after {
  content: "点开看答案"; display: block; font-size: .68rem; font-weight: 400;
  color: var(--dim2); margin-top: 4px;
}
@media (max-width: 480px) {
  .int-foot { gap: 6px; }
  .int-foot .int-all { margin-left: 0; width: 100%; }
  .int-flip-grid { grid-template-columns: repeat(auto-fill, minmax(104px, 1fr)); }
}

/* 打印/导出：把互动卡的"未展开"状态也摊平 —— 纸上没有可点的东西 */
@media print {
  .int-foot { display: none; }
  .int-steps.armed .int-step .int-step-b { display: block !important; }
  .int-flip.armed .int-card .int-card-b { display: block !important; }
  .int-quiz .int-why { display: block !important; }
}
.msg-net { font-size: .74rem; border-radius: var(--r-xs); padding: .4em .7em; margin-top: 8px; }
.msg-net.on { background: var(--ok-soft); color: var(--ok); }
.msg-net.off { background: var(--warn-soft); color: var(--warn); }
.msg-net .net-list { display: flex; flex-direction: column; gap: 2px; margin-top: 5px; }
.msg-net .net-list a { color: inherit; text-decoration: underline; font-size: .72rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* 骨架条下面的一行等待说明（如"正在打开你发的链接…"）：干等几秒最容易被当成卡死 */
.wait-hint { font-size: .76rem; color: var(--dim2); margin-top: 6px; }
.msg-src { display: flex; flex-wrap: wrap; gap: 5px; align-items: center; font-size: .72rem; color: var(--dim2); margin-top: 8px; }
.src-chip { background: var(--panel-2); border: 1px solid var(--line); border-radius: var(--r-xs); padding: .1em .5em; color: var(--dim); }
.msg-tr {
  margin-top: 8px; border-left: 3px solid var(--pri); background: var(--pri-soft);
  border-radius: 0 var(--r-xs) var(--r-xs) 0; padding: .5em .8em; font-size: .88rem;
}
.msg-tr > b { display: block; font-size: .7rem; color: var(--pri); margin-bottom: 3px; font-weight: 600; letter-spacing: .03em; }
.msg-tr .md > p:first-child { margin-top: 0; }
.msg-tr .md > p:last-child { margin-bottom: 0; }
.msg-flag { font-size: .74rem; color: var(--dim2); margin-top: 8px; }
.msg-flag.bad { color: var(--bad); }
.msg .acts { flex-wrap: wrap; }

/* ---- AI 配图 ---- */
.img-holder { margin: 0 0 22px; }
.imgload { font-size: .8rem; color: var(--dim); display: flex; align-items: center; gap: 8px; }
.imgload::before {
  content: ""; width: 12px; height: 12px; border-radius: 50%;
  border: 2px solid var(--line-2); border-top-color: var(--pri);
  animation: spin .8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
.imgfail {
  font-size: .8rem; color: var(--bad); background: var(--bad-soft);
  border-radius: var(--r-sm); padding: .6em .8em;
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
}
.art-img { display: block; width: 100%; max-width: 560px; border-radius: var(--r-sm); }

/* ---- 弹窗里的列表（对话菜单 / 智能体 / 移动项目）---- */
.cm-list, .ag-list { display: flex; flex-direction: column; gap: 6px; margin-top: 10px; max-height: 52vh; overflow-y: auto; }
.cm-i, .ag-i {
  text-align: left; border: 1px solid var(--line); background: var(--panel);
  border-radius: var(--r-sm); padding: .6em .8em; display: flex; flex-direction: column; gap: 2px;
  transition: border-color .15s, background .15s;
}
.cm-i:hover, .ag-i:hover { border-color: var(--pri); background: var(--pri-soft); }
.cm-i.on, .ag-i.on { border-color: var(--pri); background: var(--pri-soft); }
.cm-i b, .ag-i b { font-size: .88rem; font-weight: 600; }
.cm-i span, .ag-i span { font-size: .74rem; color: var(--dim); }
.cm-i.danger b { color: var(--bad); }
.cm-i.danger:hover { border-color: var(--bad); background: var(--bad-soft); }
.ag-i b i { font-style: normal; font-weight: 400; font-size: .7rem; }
/* 已选中/已挂上：右上角一个小勾。
   只有浅蓝底 + 蓝描边时，用户分不清"这是被选中的"还是"这类条目就长这样"——
   挂资料的弹窗是多选，点过没点过必须一眼可辨（纯 CSS 画，不用图标字体）。 */
.ag-i.on { position: relative; }
.ag-i.on::after {
  content: ''; position: absolute; top: .62em; right: .75em;
  width: 5px; height: 10px; border: solid var(--pri);
  border-width: 0 2px 2px 0; transform: rotate(45deg);
}
/* ---- 技能弹窗的分类小标题（2026-10-05）----
   改前是一整条平铺列表，学科卡与方法卡交错（语文/费曼讲解/数学/错题复盘…），
   用户看不出哪些是学科。加组名把「学科」「备考教练」分开。
   ★ 用 sticky 让它滚动时钉住 —— 列表 max-height:52vh 会滚，
     组名跟着滚走的话，用户滑到中间就不知道自己在哪一组了。 */
.ag-cat {
  display: flex; align-items: center; gap: .45em;
  margin: 6px 0 -2px; font-size: .72rem; font-weight: 700;
  letter-spacing: .06em; color: var(--dim);
  position: sticky; top: 0; z-index: 1;
  background: var(--panel); padding: 5px 2px 4px;
}
.ag-cat::after { content: ''; flex: 1; height: 1px; background: var(--line); }
.ag-cat-n { font-weight: 500; color: var(--dim2); letter-spacing: 0; }
.ag-cat-n::after { content: ' 项'; font-size: .68rem; }
/* 第一个组名贴着搜索框，去掉多余上边距 */
#agList .ag-cat:first-of-type { margin-top: 0; }

/* ---- 技能选择弹窗：加宽 + 分组网格（批次48，2026-10-06）----
   改前是 `width: min(560px)` 的**单列长列表**，33 条技能要滚很久才看得到
   「备考教练」；用户原话：「窗口要横向大一点，尽可能展示更多技能，
   技能要分门别类」。现在弹窗 960px、每个分类各自一个自适应网格。 */
.modal.wide { width: min(960px, 100%); }
/* 弹窗变宽后，纵向反而更该给足 —— 否则宽出来的空间全浪费在下拉上 */
.modal.wide .ag-list { max-height: 66vh; }

/* 「全部技能」横跨整行：它不属于任何分类，是"不指定"，不该混进网格 */
.ag-all { width: 100%; }

/* 一个分类 = 标题 + 它自己的网格。
   ★ 为什么不是"一个大网格 + 标题当格子"：那样标题要 `grid-column: 1/-1` 硬撑，
     列数一变就得跟着改；而且 sticky 标题会被同一行的卡片挤住。
     分组各自成网格后，列数由容器宽度自适应，与标题互不干扰。 */
.ag-sec + .ag-sec { margin-top: 13px; }
.ag-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(212px, 1fr));
  gap: 6px;
}
/* 网格里卡片更紧凑：宽度由列宽决定，标题和描述都可能折行 */
.ag-grid .ag-i { padding: .5em .68em; }
.ag-grid .ag-i b { font-size: .84rem; }
/* 描述压到两行 —— 高度不齐的卡片会让网格出现"锯齿"，截断比参差好 */
.ag-grid .ag-i span {
  font-size: .71rem; line-height: 1.45;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
/* 窄屏退回单列：212px 在手机上两列会挤成豆腐块 */
@media (max-width: 560px) {
  .ag-grid { grid-template-columns: 1fr; }
  .modal.wide .ag-list { max-height: 60vh; }
}

/* 档位锁住的助手（2026-10-03）：灰掉 + 写明"需要哪一档"。
   必须和"没全局启用"区分开 —— 后者选中就能用，前者选了也不会生效。
   原来两者都画成「未开启」，学生会选中一个锁住的助手，看到"已选「XX」"，
   然后发现 AI 毫无变化（服务端 promptsFor 会把它静默滤掉）。 */
.ag-lock { font-style: normal; font-weight: 400; font-size: .7rem; color: var(--warn); }
/* 弹窗顶部那段"怎么用"的说明：默认 .dim 太小，这里是关键信息，给足行距 */
.ag-tip { margin: 8px 0 0; line-height: 1.6; }

/* ---- 拖入文件 ---- */
.stream.drop { outline: 2px dashed var(--pri); outline-offset: -10px; background: var(--pri-soft); }

/* 触屏没有 hover：操作条必须常显，否则手机上根本点不到"朗读/翻译/删除" */
@media (hover: none) {
  .msg .acts { opacity: 1; }
  .conv-i .ci-x { opacity: 1; }
}


/* 无障碍兜底：系统里关了动效就全关。
 * 放在最后，靠层叠盖住上面所有 animation —— 比逐个加 media 包裹可靠。 */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }
  .caret, .mic.on { animation: none !important; }
  .chart .ch-line { stroke-dashoffset: 0 !important; }
  .chart .ch-bar { transform: none !important; }
}

/* ================= 互动课堂（批次4）================= */
.msg-att-ivt {
  display: flex; align-items: center; gap: 10px; width: 100%; max-width: 420px;
  border: 1px solid var(--line-2); border-radius: var(--r-sm); background: var(--panel-2);
  padding: 10px 12px; margin-bottom: 8px;
}
.ivt-ic { font-size: 1.3rem; color: var(--pri); line-height: 1; }
.ivt-txt { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 0; }
.ivt-txt b { font-weight: 600; }
.ivt-txt .dim { font-size: .78rem; }

/* 右侧面板（默认收起在右侧，打开后滑出） */
.iv-panel {
  position: fixed; top: 0; right: 0; bottom: 0; width: 440px; max-width: 92vw; z-index: 60;
  background: var(--panel); border-left: 1px solid var(--line-2);
  box-shadow: var(--shadow-lg); display: flex; flex-direction: column;
  animation: ivIn .22s cubic-bezier(.22, .8, .3, 1) both;
}
.iv-panel[hidden] { display: none; }
.iv-panel.full { width: auto; left: 0; max-width: none; }
@keyframes ivIn { from { transform: translateX(24px); opacity: 0; } to { transform: none; opacity: 1; } }
.iv-resizer {
  position: absolute; left: -3px; top: 0; bottom: 0; width: 6px; cursor: col-resize; z-index: 2;
}
.iv-resizer::after {
  content: ""; position: absolute; top: 0; bottom: 0; left: 2px; width: 1px; background: transparent; transition: background .15s;
}
.iv-resizer:hover::after, body.resizing .iv-resizer::after { background: var(--pri); }
.iv-h {
  display: flex; align-items: center; gap: 8px; padding: 10px 12px;
  border-bottom: 1px solid var(--line); font-weight: 600;
}
.iv-h .sp { flex: 1; }
.iv-body { flex: 1; min-height: 0; overflow: auto; padding: 14px; }
.iv-fig { display: flex; flex-direction: column; gap: 12px; }
.iv-guide { display: flex; flex-direction: column; gap: 8px; padding: 12px; background: var(--panel-2); border: 1px solid var(--line); border-radius: var(--r-sm); }
.iv-guide-title { color: var(--pri); font-size: .95rem; }
.iv-guide-goal, .iv-guide-prompt, .iv-guide-status, .iv-guide-reflection p { margin: 0; font-size: .86rem; line-height: 1.55; }
.iv-guide-prompt { color: var(--txt); font-weight: 600; }
.iv-guide-input { width: 100%; resize: vertical; padding: 8px 10px; color: var(--txt); background: var(--panel); border: 1px solid var(--line); border-radius: var(--r-sm); font: inherit; }
.iv-guide-input:focus-visible, .iv-guide-action:focus-visible { outline: 2px solid var(--pri); outline-offset: 2px; }
.iv-guide-status { color: var(--dim); }
.iv-guide-reflection { display: flex; flex-direction: column; gap: 5px; padding-top: 8px; border-top: 1px solid var(--line); }
.iv-guide-reflection[hidden] { display: none; }
.iv-svg { width: 100%; height: auto; background: var(--panel-2); border: 1px solid var(--line); border-radius: var(--r-sm); touch-action: none; }
.iv-ctrl { display: flex; flex-direction: column; gap: 10px; }
.iv-read { font-size: .9rem; line-height: 1.6; background: var(--panel-2); border: 1px solid var(--line); border-radius: var(--r-sm); padding: 10px 12px; }
.iv-read b { color: var(--pri); }
.iv-read .dim { color: var(--dim); font-size: .82rem; }
.iv-slider { display: flex; flex-direction: column; gap: 4px; font-size: .85rem; }
.iv-slider label { color: var(--dim); }
.iv-slider .iv-pv { color: var(--txt); }
.iv-slider input[type=range] { width: 100%; accent-color: var(--pri); }
.iv-err { padding: 14px; color: var(--bad); background: var(--bad-soft); border-radius: var(--r-sm); }
.iv-share-fig { width: 100%; max-width: 640px; margin: 0 auto; }
.iv-share-fig .iv-svg { background: var(--panel-2); border: 1px solid var(--line); border-radius: var(--r); }
.share-head h1 { font-size: 1.3rem; margin: 0 0 4px; }
.share-head .dim { margin-bottom: 14px; }


/* ================= 思考过程（reasoning_content） =================
 * 模型在正文前先流出的推理。默认折叠，正在生成时展开跟随。
 * 用 .live 让圆点脉动 —— 这是"它还在想"的唯一可见证据，
 * 也是学生不再以为"没响应"的关键。
 */
.rsn { margin: 2px 0 6px; }
.rsn-hd {
  display: inline-flex; align-items: center; gap: 6px;
  background: none; border: 0; padding: 2px 6px 2px 2px;
  cursor: pointer; font: inherit; font-size: .78rem; color: var(--dim);
  border-radius: var(--r-xs);
}
.rsn-hd:hover { color: var(--txt); background: var(--panel-2); }
.rsn-dot { width: .45rem; height: .45rem; border-radius: 50%; background: var(--pri); opacity: .45; flex: none; }
.rsn.live .rsn-dot { opacity: 1; animation: thinkPulse 1.1s ease-in-out infinite; }
.rsn-ar { font-size: .62rem; line-height: 1; transition: transform .18s ease; }
.rsn.open .rsn-ar { transform: rotate(180deg); }
.rsn-bd {
  display: none;
  margin: 4px 0 4px 2px; padding: 9px 11px;
  border-left: 2px solid var(--line-2);
  border-radius: 0 var(--r-xs) var(--r-xs) 0;
  background: var(--panel-2);
  font-size: .78rem; line-height: 1.7; color: var(--dim);
  white-space: pre-wrap; word-break: break-word;
  max-height: 20rem; overflow-y: auto;
}
.rsn.open .rsn-bd { display: block; }
@keyframes thinkPulse { 0%, 100% { opacity: .3; transform: scale(.85); } 50% { opacity: 1; transform: scale(1); } }

/* 裸写公式兜底出来的上下标（render.js 的 2b 分支）。
   浏览器默认的 sup/sub 会把行高顶开，这里压住，并跟正文协调。 */
.msg sup, .msg sub { font-size: .74em; line-height: 0; position: relative; vertical-align: baseline; }
.msg sup { top: -.5em; }
.msg sub { bottom: -.25em; }

/* ══════════════════════════════════════════════════════════════════
   空间门 · 窄屏回退（2026-10-04）
   放在文件**末尾**是刻意的 —— 理由见上面 .land-note 附近那段注释：
   app.css 里有两条「第一个 @media 位置」型断言，在中部插 @media 会让它们假红。
   display:contents 的两栏在 320px 上会把卡片挤没，所以这里必须回退单栏。
   ══════════════════════════════════════════════════════════════════ */
@media (max-width: 1000px) {
  .land {
    grid-template-columns: minmax(0, 1fr);
    align-content: center; gap: clamp(18px, 3.4vh, 30px);
    padding: 84px 20px 84px;
  }
  .land-head { text-align: center; margin: 0 auto; }
  .land-logo { font-size: 1.3rem; }
  .land-h1 { font-size: clamp(30px, 8.6vw, 44px); }
  .land-stance { margin: 14px auto 0; font-size: .88rem; }
  .land-card { padding: 22px 20px 18px; }
  .land-bg::after { background: linear-gradient(180deg, transparent 30%, var(--bg) 92%); }
}
/* 极窄屏：标题别再往下压，卡片才是主角 */
@media (max-width: 380px) {
  .land-h1 { font-size: 27px; }
  .land-stance { font-size: .84rem; }
  .land-tabs { margin-bottom: 16px; }
}
@media (prefers-reduced-motion: reduce) {
  .land-head, .land-card { animation: none; }
  .land-tabs::before { transition: none; }
}

/* ── 画板 · 窄屏回退 ──────────────────────────────────────────────
   ★ 同样放文件末尾：理由与上面那段相同 —— app.css 里有「第一个 @media
     位置」型断言，在中部插 @media 会让它们假红。
   手机上作画区本来就小，工具条再横排会挤成两行把画布顶没，
   所以窄屏把提示文字收掉、按钮收窄，优先保证画布高度。 */
@media (max-width: 640px) {
  .bd-mask { padding: 0; }
  .bd-panel { width: 100%; max-height: 100%; border-radius: 0; border: 0; }
  .bd-tip { display: none; }
  .bd-bar { gap: 5px; padding: 8px 10px; }
  .bd-head { padding: 11px 12px; }
  .bd-foot { padding: 9px 12px; }
  /* 工具按钮收到 26px：仍满足触摸目标下限（约 26–28px 视具体 DPR），
     但能让「工具 + 5 色 + 3 粗细 + 撤销清空」在一行内排得下 */
  .bd-t, .bd-sz { width: 27px; height: 27px; }
  .bd-dot { width: 22px; height: 22px; }
  .bd-empty { font-size: .72rem; }
}

/* ── 技能详情 · 窄屏回退 ────────────────────────────────────────────
   ★ 同样放文件末尾：app.css 里有「第一个 @media 位置」型断言，
      在中部插 @media 会让它们假红。
   手机上 920px 用不上、两栏方法清单会挤成每栏半行，所以：
   面板铺满、清单改单栏、图标缩小。 */
@media (max-width: 640px) {
  .sk-mask { padding: 0; }
  .sk-panel { width: 100%; height: 100%; max-height: 100%; border-radius: 0; border: 0; }
  .sk-head { padding: 12px 14px; }
  .sk-body { padding: 14px; }
  .sk-foot { padding: 10px 14px; }
  .sk-steps { columns: 1; }
  .sk-ico { flex: 0 0 36px; width: 36px; height: 36px; }
  .sk-ico .ski { width: 22px; height: 22px; }
  .sk-name { font-size: 1rem; }
  .sk-foot .btn { flex: 1 1 auto; }
}
/* 中等宽度（手机上横屏、小平板）：两栏会太挤，提前收成一栏 */
/* ── 首页「今天的下一步」· 窄屏回退 ──────────────────────────────
   ★ 放文件末尾：app.css 里有「第一个 @media 位置」型断言，在中部插 @media 会让
     _p25check / _parity22fe / _growthcheck / _encheck / _leaderboardcheck 等假红。
   手机上按钮横排会挤成两行，所以整组竖排、按钮占满一行。 */
@media (max-width: 560px) { .learn-next { padding: 14px; } .learn-next-actions { align-items: stretch; flex-direction: column; } .learn-next-actions .btn { width: 100%; } }
/* ★ .g-practice 的窄屏回退与 .learn-next 同处文件末尾 —— 中部插 @media 会顶掉「第一个响应式 @media」断言（_p25check/_encheck/_leaderboardcheck）。 */
@media (max-width: 560px) { .g-practice { align-items: flex-start; flex-direction: column; } }

@media (max-width: 860px) {
  .sk-steps { columns: 1; }
}

/* ── 沿地图学习 + 掌握检查（批次75）────────────────────────────────
   地图不再只是海报：每个知识点带一个「学这一步」入口 + 一个掌握状态徽章；
   点进去之后，讲解下面会挂一张「掌握检查」卡（1~2 道小题）。
   ★ 配色沿用地图卡的暖色并**写死**（不跟主题变量）—— 深色主题下浅字配浅底会看不见，
     批次73 已经在这上面踩过一次。
   ★ 徽章为空时不占位（`.lmap-st:empty`）：还没学的知识点上不该出现一个空药丸。 */
.lmap-foot { display:flex; align-items:center; gap:8px; margin-top:9px; }
.lmap-st { font-size:11px; font-weight:700; border-radius:999px; padding:2px 8px; }
.lmap-st:empty { display:none; }
.lmap-st.st-learning { color:#7A3E0A; background:#FAEEDA; }
.lmap-st.st-mastered { color:#166534; background:#DCFCE7; }
.lmap-learn { margin-left:auto; border:1px solid #E7D6B4; border-radius:9px; background:#fff; color:#7A3E0A; padding:5px 10px; font-size:12px; cursor:pointer; }
.lmap-learn:hover { border-color:#BA7517; background:#FFF7E6; }
.lmap-learn:disabled { opacity:.6; cursor:default; }

.art-mastery { border-radius:18px; background:#FFFBF0; border:1px solid #EFE3CE; padding:14px 16px; }
.mst-h { display:flex; align-items:center; gap:9px; flex-wrap:wrap; }
.mst-tag { font-size:11px; font-weight:700; color:#fff; background:#BA7517; border-radius:999px; padding:2px 9px; }
.mst-h b { color:#7A3E0A; font-size:15px; overflow-wrap:anywhere; }
.mst-where { margin-top:5px; color:#A8834A; font-size:12px; }
.mst-intro { margin:8px 0 0; color:#4B5563; font-size:13px; line-height:1.6; }
.mst-qs { list-style:none; margin:12px 0 0; padding:0; display:grid; gap:12px; }
.mst-q { background:#fff; border:1px solid #F0E7D6; border-radius:13px; padding:11px 13px; }
.mst-q-t { margin:0; color:#1E293B; font-size:14px; line-height:1.6; overflow-wrap:anywhere; }
.mst-opts { display:grid; gap:7px; margin-top:9px; }
.mst-opt { display:flex; align-items:flex-start; gap:8px; padding:8px 10px; border:1px solid #EDE4D3; border-radius:10px; background:#FFFDF8; cursor:pointer; font-size:13px; color:#334155; line-height:1.5; }
.mst-opt:hover { border-color:#D9A94A; }
.mst-opt input { margin-top:2px; flex:0 0 auto; }
.mst-ta { width:100%; margin-top:9px; box-sizing:border-box; border:1px solid #EDE4D3; border-radius:10px; padding:8px 10px; font:inherit; font-size:13px; line-height:1.55; background:#FFFDF8; color:#334155; resize:vertical; }
.mst-res { margin-top:9px; display:flex; align-items:baseline; gap:8px; flex-wrap:wrap; }
.mst-res:empty { display:none; }
.mst-mark { font-size:11px; font-weight:700; border-radius:999px; padding:2px 9px; flex:0 0 auto; }
.mst-mark.ok { color:#166534; background:#DCFCE7; }
.mst-mark.no { color:#9A3412; background:#FFEDD5; }
.mst-why { color:var(--dim); font-size:12px; line-height:1.55; }
.mst-hint, .mst-ex { margin:8px 0 0; font-size:12.5px; line-height:1.6; color:#4B5563; background:#FDF6E8; border-left:3px solid #EF9F27; border-radius:0 9px 9px 0; padding:8px 11px; }
.mst-ex { background:#F2F7EE; border-left-color:#97C459; }
.mst-hint[hidden], .mst-ex[hidden] { display:none; }
.mst-hint i, .mst-ex i { display:inline-block; margin-right:7px; font-style:normal; font-weight:700; color:#B45309; }
.mst-ex i { color:#3B6D11; }
.mst-foot { display:flex; align-items:center; gap:11px; flex-wrap:wrap; margin-top:13px; }
.mst-submit { border:0; border-radius:10px; background:#BA7517; color:#fff; padding:8px 18px; font-size:13px; font-weight:700; cursor:pointer; }
.mst-submit:hover { background:#9A6212; }
.mst-submit:disabled { opacity:.65; cursor:default; }
.mst-note { color:#A8834A; font-size:12px; line-height:1.5; flex:1 1 200px; }


/* ================= 学习成就与现场激励 ================= */
.ach-wrap { flex: 1; min-height: 0; overflow: auto; padding: 12px 18px 24px; }
.ach-summary { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; margin-bottom: 14px; }
.ach-summary > div { display: flex; flex-direction: column; gap: 5px; padding: 14px 16px; background: var(--panel); border: 1px solid var(--line); border-radius: var(--r); }
.ach-summary span, .ach-card small { color: var(--dim2); font-size: .78rem; }
.ach-summary b { color: var(--txt); font-size: 1.05rem; }
.ach-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 270px), 1fr)); gap: 10px; }
.ach-card { display: flex; align-items: flex-start; gap: 12px; min-width: 0; padding: 14px; border: 1px solid var(--line); background: var(--panel); border-radius: var(--r); }
.ach-card.earned { border-color: color-mix(in srgb, var(--ok) 34%, var(--line)); background: color-mix(in srgb, var(--ok-soft) 40%, var(--panel)); }
.ach-card.pending { opacity: .82; }
.ach-icon { display: grid; place-items: center; flex: 0 0 40px; width: 40px; height: 40px; border-radius: 12px; background: var(--panel-2); font-size: 1.35rem; }
.ach-info { min-width: 0; flex: 1; }
.ach-title { display: flex; align-items: center; gap: 7px; color: var(--txt); font-weight: 700; }
.ach-title > span { padding: 2px 6px; border-radius: 999px; background: var(--ok-soft); color: var(--ok); font-size: .68rem; font-weight: 600; }
.ach-info p { margin: 5px 0 8px; color: var(--dim); font-size: .82rem; line-height: 1.5; }
.ach-info small { display: block; margin-top: 5px; }
.ach-progress, .mf-progress { height: 5px; overflow: hidden; border-radius: 999px; background: var(--panel-3); }
.ach-progress i, .mf-progress i { display: block; height: 100%; border-radius: inherit; background: linear-gradient(90deg, var(--pri), var(--ok)); transition: width .35s ease; }
.ach-pilot { margin-top: 16px; padding: 14px 16px; background: var(--panel-2); border: 1px solid var(--line); border-radius: var(--r); }
.ach-pilot h3 { margin: 0 0 9px; color: var(--txt); font-size: .92rem; }
.ach-pulse, .ach-pulse-done { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ach-pulse > div { flex: 1 1 240px; display: flex; flex-direction: column; gap: 4px; }
.ach-pulse b { color: var(--txt); font-size: .86rem; }
.ach-pulse small, .ach-pulse-done span { color: var(--dim2); font-size: .77rem; }
.ach-pulse-done { color: var(--txt); font-size: .84rem; }
.motivation-feedback { margin: 9px 0; padding: 10px 12px; border: 1px solid var(--line); background: var(--panel-2); border-radius: var(--r-sm); color: var(--txt); }
.motivation-feedback.evolved { border-color: color-mix(in srgb, var(--ok) 55%, var(--line)); background: var(--ok-soft); }
.mf-main { display: flex; align-items: center; gap: 9px; }
.mf-pet { display: inline-flex; align-items: center; justify-content: center; width: 27px; height: 27px; flex: 0 0 27px; }
.mf-pet svg { width: 25px; height: 25px; }
.mf-main b { display: block; font-size: .85rem; }
.mf-main small { display: block; margin-top: 2px; color: var(--dim2); font-size: .73rem; }
.mf-evolved { margin-left: auto; padding: 3px 8px; border-radius: 999px; background: var(--ok); color: #fff; font-size: .72rem; animation: mfPop .55s ease-out both; }
.mf-progress { margin: 8px 0 0 36px; }
.mf-badges { display: flex; flex-wrap: wrap; gap: 5px; margin: 8px 0 0 36px; }
.mf-badge { padding: 3px 7px; border-radius: 999px; background: var(--pri-soft); color: var(--pri-2); font-size: .73rem; }
.mf-tip { margin: 7px 0 0 36px; color: var(--dim); font-size: .77rem; line-height: 1.45; }
.motivation-feedback.compact { padding: 7px 9px; }
.motivation-feedback.compact .mf-tip { margin-left: 0; }
.motivation-feedback.compact .mf-progress { display: none; }
@keyframes mfPop { 0% { transform: scale(.86); opacity: .4; } 70% { transform: scale(1.06); } 100% { transform: scale(1); opacity: 1; } }

/* 班内只展示明确允许公开的奖励摘要；不传成长值、错题和答题详情。 */
.cls-optin, .cls-optin-info { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin: 10px 0; padding: 12px 14px; background: var(--panel-2); border: 1px solid var(--line); border-radius: var(--r-sm); }
.cls-optin > div { display: flex; flex-direction: column; gap: 3px; }
.cls-optin b { color: var(--txt); font-size: .84rem; }
.cls-optin span, .cls-optin-info { color: var(--dim2); font-size: .77rem; }
.cls-reward-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 245px), 1fr)); gap: 8px; margin-top: 8px; }
.cls-reward-member { min-width: 0; padding: 10px 12px; background: var(--panel); border: 1px solid var(--line); border-radius: var(--r-sm); }
.cls-reward-member.me { border-color: var(--pri); }
.cls-reward-name { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; }
.cls-reward-name b { overflow: hidden; color: var(--txt); text-overflow: ellipsis; white-space: nowrap; font-size: .83rem; }
.cls-reward-name span { color: var(--dim2); white-space: nowrap; font-size: .72rem; }
.cls-awards { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 7px; }
.cls-award { padding: 3px 7px; border-radius: 999px; background: var(--pri-soft); color: var(--pri-2); font-size: .7rem; }
.cls-award-empty { color: var(--dim2); font-size: .72rem; }

.cls-members { display: block; }
.cls-members > .cls-m-h { display: block; margin: 0; }

@media (prefers-reduced-motion: reduce) {
  .mf-evolved, .ach-progress i, .mf-progress i { animation: none; transition: none; }
}

/* ★ 学习地图卡（批次73）、单元一览卡（批次76）与掌握检查卡（批次75）的窄屏回退
   都放在文件末尾 —— 中部插 @media 会顶掉「第一个响应式 @media」断言。
   ⚠️ 三套回退**并进同一个 @media 块**（而不是各写一个）：_mapcheck.cjs 断言
   「地图那条窄屏回退是文件里**最后一个** @media」，另起一块会把它顶掉。 */
@media (max-width: 680px) {
  .lmap-grid { grid-template-columns: 1fr; padding: 12px; gap: 10px; }
  .art-lmap .lmap-h { flex-wrap: wrap; }
  .art-lmap .lmap-h b { flex: 1 1 60%; }
  .art-lmap .map-save { margin-left: 0; }
  .lmap-g-no { width: 22px; height: 22px; border-radius: 8px; font-size: 12px; }
  .ub-grid { grid-template-columns: 1fr; padding: 12px; gap: 10px; }
  .art-ub .ub-h { flex-wrap: wrap; }
  .art-ub .ub-h b { flex: 1 1 60%; }
  .art-ub .ub-save { margin-left: 0; }
  .ub-pills { gap: 10px; }
  /* 批次75：窄屏下「状态 + 学这一步」换行、交卷按钮占满一行 */
  .lmap-foot { flex-wrap: wrap; }
  .lmap-learn { margin-left: 0; }
  .mst-foot { flex-direction: column; align-items: stretch; }
  .mst-submit { width: 100%; }
  /* 批次78：成就页 / 班级奖励的窄屏回退 —— 并进本块（另起媒体查询会顶掉 _mapcheck 断言） */
  .ach-wrap { padding: 10px 12px 18px; }
  .ach-summary { grid-template-columns: 1fr; gap: 7px; }
  .ach-summary > div { padding: 10px 12px; }
  .cls-optin { align-items: flex-start; flex-direction: column; }
  .cls-optin .btn { width: 100%; }
  .cls-reward-name { align-items: flex-start; flex-direction: column; gap: 2px; }
}
