/* 索引云 · 全局样式 */

:root {
  /* ---- 颜色 ---- */
  --bg: #f4f6f9;
  --surface: #ffffff;
  --surface-2: #f1f3f7;
  --surface-3: #e9edf3;
  --line: #e4e7ed;
  --line-strong: #ccd2dc;
  --text: #161a20;
  --text-2: #58616f;
  --text-3: #8b95a3;
  --brand: #185fa5;
  --brand-strong: #12507f;
  --brand-soft: #e5f0fb;
  --brand-ring: rgba(24, 95, 165, .22);
  --z-side: 90;
  --ok: #1d9e75;
  --ok-soft: #e1f5ee;
  --warn: #ba7517;
  --warn-soft: #faeeda;
  --bad: #d64545;
  --bad-soft: #fcebeb;

  /* ---- 形状 ---- */
  --radius: 14px;
  --radius-sm: 9px;
  --radius-xs: 7px;

  /* ---- 阴影层次 ---- */
  --sh-1: 0 1px 2px rgba(16, 24, 40, .05);
  --sh-2: 0 2px 6px rgba(16, 24, 40, .06), 0 1px 2px rgba(16, 24, 40, .04);
  --sh-3: 0 8px 22px rgba(16, 24, 40, .09), 0 2px 6px rgba(16, 24, 40, .05);
  --sh-4: 0 18px 46px rgba(16, 24, 40, .16), 0 4px 12px rgba(16, 24, 40, .07);

  /* ---- 动效 ---- */
  --ease: cubic-bezier(.32, .72, .28, 1);          /* 通用的柔和收尾 */
  --ease-out: cubic-bezier(.16, 1, .3, 1);         /* 弹出感，用于入场 */
  --spring: cubic-bezier(.34, 1.56, .64, 1);       /* 轻微回弹，用于选中 */
  --dur-1: .13s;   /* 即时反馈：hover / 按压 */
  --dur-2: .24s;   /* 常规过渡：颜色 / 边框 / 阴影 */
  --dur-3: .38s;   /* 强调入场：视图切换 / 选中弹出 */

  /* ---- 顶栏 ----
     顶栏平时正常显示；不想看的时候点最右边的「收起」把它滑上去，给内容让地方。
     收上去之后右上角留一个小把手，鼠标碰一下就滑回来。
     ⚠️ 顶栏是浮动的，内容靠 `body` 的 padding-top 让出它的高度，
        而那个边距用的就是这个变量 —— 改高度只改这一个值。 */
  --topbar-h: 56px;

  /* ---- 层级（z-index）总表 ----
     ⚠️ 浮窗开着时，底下的页面会被抬高（见 app-launcher.css 的 body.wb-win-open），
        所以「抽屉 / 设置浮窗」这些盖在页面上的东西，必须用这里的变量，
        不能各写各的数字 —— 否则会被抬高后的页面反超，出现「字跑到框上面」。
     从下往上：
        页面内容              < 100
        侧边小按钮（前往练习）     --z-side      (90)  —— 浮在内容上，让开顶栏与提示条
        桌面小鸟（右边那只）      --z-bird      (1190) —— 同上，但要压在"浮窗打开时被抬高的内容"之上
        顶栏（普通）            101   —— 浮动层，可以收起来
        页面内容（浮窗开着时抬高）--z-page-nav  (1180)
        浮窗层（窗口本体）       --z-win       (1300)
        顶栏（浮窗打开时抬高）    --z-topbar    (1320)
        抽屉遮罩               --z-mask      (1330)
        抽屉                   --z-drawer    (1340)
        浮窗层（窗口最大化时）    --z-win-max   (1400)  —— 压过顶栏，见下
        设置浮窗               --z-settings  (1450)  —— 模态，要盖住浮窗
        整屏覆盖层               --z-cover     (1900)  —— 盖住一切，只留一个「退出」
        成就提示条             --z-toast     (2000)  —— 永远最上面

     ⚠️ 抽屉为什么在浮窗之上：抽屉是「选小程序」的菜单，开着的时候必须能看见、
        能点，所以它要盖过浮窗；否则浮窗一开，抽屉就被吃掉、点不着了。
        顶栏(1320) 又在浮窗上面一点，保证浮窗压住顶栏那截时顶栏按钮仍可点。
     ⚠️ 「抽屉 > 浮窗」这条是**无条件**的：最大化时浮窗抬到 1400 了，
        抽屉和它的遮罩也得跟着抬（见 app-launcher.css 的 body.wb-win-max），
        否则这条约定就出现了例外。

     ⚠️ 为什么还要一个 --z-win-max：浮窗「最大化」后会铺满整屏，
        它的标题栏正好落在顶栏那个位置。顶栏(1320) 一旦压在上面，
        窗口自己的「还原 / 退出」按钮就被盖住 —— 用户放大之后就还原不回来了。
        所以最大化时给 body 加 wb-win-max，把浮窗层抬到顶栏之上（1400）。
        （非最大化时仍然保持顶栏在上，这样后面那层页面照常能点。）

     ⚠️ 设置浮窗(1450) 必须比**最大化后的浮窗**(1400) 还高：
        它是模态 —— 用户明确点了齿轮才打开的，打开后就得看得见、点得到。
        早先它是 1200（比浮窗 1300 还低），于是「浮窗一开，设置面板就被压在下面」；
        闹钟最大化时想调夜间模式的颜色，面板整个看不见。（踩过）

     ⚠️ 整屏覆盖层(1900) 是「一进去整个网页都盖上」的那种东西（现在没有小程序用它，
        原来唯一用它的是「幻想岛」，2026-09-28 已归档）：
        所以它比设置浮窗还高、只留自己那个「退出」按钮。
        但**故意比成就提示条(2000) 低** —— 被盖住的页面里拿到成就，提示照样能飘出来。

     ⚠️ 右边那只小鸟（--z-bird 1190）为什么卡在 1180 和 1300 中间：
        它是"桌面小伙伴"，得浮在页面内容之上（不然会被 wb-win-open 抬高后的内容盖住），
        又要**让开浮窗** —— 浮窗盖住它就算了，别去抢浮窗的地方。
        闹钟开夜间模式时同理：幕布盖下来，小鸟跟着藏起来，不该浮在黑幕上面。
        其它「约定压过它」的层（抽屉 / 设置 / 提示条）本来就都比它高。 */
  --z-page: 100;
  --z-topbar-base: 101;
  --z-page-nav: 1180;
  --z-bird: 1190;
  --z-ver: 1190;                          /* 页面版本号（左下角）：跟小鸟同层，一样要让开浮窗 */
  /* 选中文字的浮标（复制 / 问绿豆，2026-10-18 加）：
     要比页面内容高，但**必须低于抽屉遮罩和设置面板** —— 弹窗打开时它还浮在上面会很怪。 */
  --z-pick: 1210;
  --z-win: 1300;
  --z-topbar: 1320;
  --z-mask: 1330;
  --z-drawer: 1340;
  --z-win-max: 1400;
  --z-settings: 1450;
  --z-cover: 1900;
  --z-toast: 2000;
}

/* 用户在系统里关掉了动效 → 全局降级（保留功能性过渡，去掉位移与缩放） */
@media (prefers-reduced-motion: reduce) {
  :root { --dur-1: .01ms; --dur-2: .01ms; --dur-3: .01ms; }
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Microsoft YaHei", "PingFang SC", sans-serif;
  font-size: 15px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

a { color: var(--brand); text-decoration: none; }
a:hover { text-decoration: underline; }

.wrap { max-width: 1080px; margin: 0 auto; padding: 0 20px; }
.wrap-narrow { max-width: 760px; margin: 0 auto; padding: 0 20px; }

/* 顶栏
   ——「平时正常显示，不想看的时候可以收上去」

    · 进页面就是正常的一条，不会自己消失；
    · 最右边有个「收起」按钮（`assets/topbar.js` 插进去的），
      点它 → 整条滑到屏幕外，把那 56px 还给内容；
    · 收上去之后右上角留一个小把手，鼠标碰它一下 → 顶栏滑回来。

   三个地方别改坏：
   ① 必须是 `position: fixed`（脱出文档流），内容的上边距靠下面
      `body { padding-top: var(--topbar-h) }` 补回来。
      ⚠️ 用 sticky 的话，收起来那 56px 会变成一块**空白** —— 内容顶不上来，
      用户会觉得"上面破了个洞"。
   ② 收起时 **transform 和 padding-top 必须一起动、同时做完**：
      transform 让顶栏滑走，padding-top 让内容跟着往上顶。
      只动 transform → 顶上留一条空白；只动 padding → 顶栏压着内容。
   ③ 收起态是挂在 `body.topbar-hidden` 上的**状态**，不是 `:hover`。
      因为用户要的是「点一下收起来、再碰一下放下来」，
      纯 CSS 的悬停表达不了"点一下记住"，所以由 topbar.js 管这个 class。 */
body {
  padding-top: var(--topbar-h);
  transition: padding-top var(--dur-3) var(--ease-out);
}
body.topbar-hidden { padding-top: 0; }

.topbar {
  background: rgba(255, 255, 255, .92);
  backdrop-filter: saturate(180%) blur(12px);
  -webkit-backdrop-filter: saturate(180%) blur(12px);
  border-bottom: 1px solid var(--line);
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: var(--z-topbar-base);
  transform: translateY(0);
  box-shadow: var(--sh-2);
  transition:
    transform var(--dur-3) var(--ease-out),
    box-shadow var(--dur-2) var(--ease);
  will-change: transform;
}
body.topbar-hidden .topbar { transform: translateY(-100%); box-shadow: none; }

/* 顶栏最右边那个「收起 / 展开」开关。
   ⚠️ 它必须是 `position: fixed`、**不能**放进 .topbar 里面：
      顶栏收起来时整条会 translateY(-100%) 滑走，按钮要是跟着走，
      屏幕上就没人能把它叫回来了。固定住它 → 顶栏滑上去、它留在原地，
      就地缩成右上角的一个小把手，再碰它 / 点它就把顶栏放下来。
   ⚠️ 做成**同一个控件**而不是"一个收起按钮 + 一个展开把手"：
      两个元素的话，鼠标碰一下把手 → 顶栏滑下来 → 光标正好落在收起按钮上，
      用户顺手一点就又被收回去了，看起来像"点了没反应"。
      同一个控件就没这个问题（碰回来之后它也换了位置和形状）。
   ⚠️ 对齐：外面套一层跟 .topbar-inner 一模一样的「居中 + 1080 宽」盒子，
      否则宽屏下会跑到内容区外面、跟顶栏右端对不齐。
      对应地 .navlinks 要留出 40px 右内边距，免得设置齿轮被它压住。 */
.topbar-tabwrap {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  max-width: 1080px;
  margin: 0 auto;
  padding: 0 20px;
  display: flex;
  justify-content: flex-end;
  align-items: flex-start;
  pointer-events: none;       /* 只有开关本身收鼠标，外面那一圈不要挡 */
  z-index: calc(var(--z-topbar-base) + 1);   /* 要压在顶栏自己的背景之上才点得到 */
}
/* 浮窗打开时顶栏会抬到 --z-topbar，开关得跟着抬，否则被浮窗盖住点不着 */
body.wb-win-open .topbar-tabwrap { z-index: calc(var(--z-topbar) + 1); }

.topbar-toggle {
  pointer-events: auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-top: 12px;           /* = (56 - 32) / 2，正常显示时跟顶栏里其它按钮齐平 */
  width: 34px;
  height: 32px;
  padding: 0;
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--text-2);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: margin-top var(--dur-3) var(--ease-out),
              width var(--dur-3) var(--ease-out),
              height var(--dur-3) var(--ease-out),
              border-radius var(--dur-3) var(--ease-out),
              background var(--dur-2) var(--ease),
              color var(--dur-2) var(--ease),
              border-color var(--dur-2) var(--ease),
              transform var(--dur-1) var(--ease);
}
.topbar-toggle:hover { background: var(--surface-2); color: var(--text); border-color: var(--line-strong); }
.topbar-toggle:active { transform: scale(.93); }
.topbar-toggle svg { display: block; }

/* 收起来之后：同一个开关缩成贴在顶边上的小把手
   （去掉上边框、只留下圆角，像"从上面探出来的头"）。 */
body.topbar-hidden .topbar-toggle {
  margin-top: 0;
  width: 38px;
  height: 15px;
  border-top: 0;
  border-radius: 0 0 var(--radius-xs) var(--radius-xs);
  color: var(--text-3);
  box-shadow: var(--sh-1);
}
body.topbar-hidden .topbar-toggle:hover { height: 20px; color: var(--text); }
.topbar-inner {
  max-width: 1080px;
  margin: 0 auto;
  padding: 0 20px;
  height: var(--topbar-h);
  display: flex;
  align-items: center;
  gap: 16px;
}
.brand {
  font-size: 16px;
  font-weight: 600;
  color: var(--text);
  display: flex;
  align-items: center;
  gap: 8px;
  text-decoration: none;
  /* ⚠️ 不加这个的话，窗口窄一点「索引云」会被折成两行
     （变成"学习管理 / 系统"），看着像字挤在一块。窄屏宁肯让右边的导航换行。 */
  white-space: nowrap;
  flex: 0 0 auto;
}
.brand:hover { text-decoration: none; }
.brand-dot {
  width: 22px; height: 22px; border-radius: 7px;
  background: linear-gradient(145deg, #2470bd, var(--brand) 55%, var(--brand-strong));
  color: #fff;
  font-size: 12px;
  display: flex; align-items: center; justify-content: center;
  font-weight: 600;
  box-shadow: 0 2px 6px rgba(24, 95, 165, .3);
  transition: transform var(--dur-2) var(--spring), box-shadow var(--dur-2) var(--ease);
}
.brand:hover .brand-dot { transform: rotate(-6deg) scale(1.06); box-shadow: 0 3px 10px rgba(24, 95, 165, .4); }

/* ⚠️ 右边那 40px 内边距是给顶栏最右端那个「收起 / 展开」开关留的位置
   （它是 position: fixed 的，不占流，所以要在这里把地方让出来），
   否则设置齿轮会被它压住点不着。 */
.navlinks { display: flex; gap: 4px; margin-left: auto; padding-right: 40px; flex-wrap: wrap; align-items: center; }
.navlinks a {
  position: relative;
  padding: 7px 13px;
  border-radius: var(--radius-sm);
  color: var(--text-2);
  font-size: 14px;
  transition: background var(--dur-2) var(--ease), color var(--dur-2) var(--ease),
              transform var(--dur-1) var(--ease);
}
.navlinks a:hover { background: var(--surface-2); text-decoration: none; color: var(--text); }
.navlinks a:active { transform: scale(.96); }
.navlinks a.active { background: var(--brand-soft); color: var(--brand); font-weight: 500; }
/* 选中项下方一条小横线，切换时淡入 */
.navlinks a.active::after {
  content: "";
  position: absolute; left: 50%; bottom: 2px;
  width: 14px; height: 2px; margin-left: -7px;
  border-radius: 2px; background: var(--brand);
  animation: navLine var(--dur-3) var(--ease-out);
}
@keyframes navLine {
  from { transform: scaleX(0); opacity: 0; }
  to   { transform: scaleX(1); opacity: 1; }
}

/* 页面标题区 */
.page-head { padding: 32px 0 20px; }
.page-head h1 { font-size: 26px; margin: 0 0 6px; font-weight: 600; letter-spacing: -.2px; }
.page-head p { margin: 0; color: var(--text-2); font-size: 14px; }

/* 卡片 */
.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 20px 22px;
  margin-bottom: 16px;
  box-shadow: var(--sh-1);
  transition: box-shadow var(--dur-2) var(--ease);
}
.card:hover { box-shadow: var(--sh-2); }
.card h2 { font-size: 16px; margin: 0 0 4px; font-weight: 600; letter-spacing: -.1px; }
.card h3 { font-size: 15px; margin: 0 0 8px; font-weight: 600; }
.card .sub { color: var(--text-3); font-size: 13px; margin: 0 0 14px; }

/* 门户模块卡片 */
.mod-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 14px;
}
/* 系统首页（2026-10-06 改成**单栏**之后）：学科模块 5 张、小程序 2 张，改成 3 列排。
   默认的 auto-fill 在这个宽度下出 4 列 —— 5 张卡会剩一张孤零零挂在第二行、
   小程序 2 张也只占一半宽，右边空一片。
   ⚠️ 只加在首页那两个网格上（类名写在 index.html 里），**别顺手改 .mod-grid 本体** ——
      小程序中心那一页正好是 4 列。 */
.home-mods { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.mod {
  display: block;
  position: relative;
  overflow: hidden;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 18px 20px;
  color: var(--text);
  box-shadow: var(--sh-1);
  transition: border-color var(--dur-2) var(--ease),
              box-shadow var(--dur-2) var(--ease),
              transform var(--dur-2) var(--ease-out);
}
/* hover 时右上角晕开一层极淡的品牌色 */
.mod::before {
  content: "";
  position: absolute; right: -40px; top: -40px;
  width: 140px; height: 140px; border-radius: 50%;
  background: var(--brand-soft);
  opacity: 0;
  transform: scale(.6);
  transition: opacity var(--dur-3) var(--ease), transform var(--dur-3) var(--ease-out);
  pointer-events: none;
  z-index: 0;
}
.mod > * { position: relative; z-index: 1; }
.mod:hover {
  text-decoration: none;
  border-color: var(--brand);
  box-shadow: var(--sh-3);
  transform: translateY(-3px);
}
.mod:hover::before { opacity: .8; transform: scale(1); }
.mod:active { transform: translateY(-1px) scale(.995); }
.mod .ico {
  font-size: 22px; margin-bottom: 8px;
  display: inline-block;
  transition: transform var(--dur-3) var(--spring);
}
.mod:hover .ico { transform: scale(1.14) rotate(-4deg); }
.mod .name { font-size: 16px; font-weight: 600; margin-bottom: 4px; }
.mod .desc { font-size: 13px; color: var(--text-2); line-height: 1.5; }
.mod.disabled { opacity: .55; cursor: not-allowed; box-shadow: none; }
.mod.disabled:hover { border-color: var(--line); transform: none; box-shadow: var(--sh-1); }
.mod.disabled:hover::before { opacity: 0; }
.mod.disabled:hover .ico { transform: none; }
.mod .tag {
  display: inline-block; margin-top: 10px;
  font-size: 12px; padding: 2px 8px; border-radius: 999px;
  background: var(--ok-soft); color: var(--ok);
}
.mod .tag.soon { background: var(--surface-2); color: var(--text-3); }

/* 按钮 */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  position: relative;
  border: 1px solid var(--line-strong);
  background: var(--surface);
  color: var(--text);
  padding: 9px 18px;
  border-radius: var(--radius-sm);
  font-size: 14px;
  font-family: inherit;
  cursor: pointer;
  box-shadow: var(--sh-1);
  transition: background var(--dur-2) var(--ease),
              border-color var(--dur-2) var(--ease),
              color var(--dur-2) var(--ease),
              box-shadow var(--dur-2) var(--ease),
              transform var(--dur-1) var(--ease);
}
.btn:hover { background: var(--surface-2); border-color: var(--line-strong); box-shadow: var(--sh-2); transform: translateY(-1px); }
.btn:active { transform: translateY(0) scale(.975); box-shadow: var(--sh-1); }
.btn:disabled { opacity: .45; cursor: not-allowed; box-shadow: none; transform: none; }
.btn:disabled:hover { background: var(--surface); box-shadow: none; transform: none; }
/* 主按钮禁用时别被上面那条 hover 规则刷成白底（抄写没抄完的「下一题」就是这种） */
.btn-primary:disabled, .btn-primary:disabled:hover { background: var(--brand); color: #fff; opacity: .4; }
.btn-primary { background: var(--brand); border-color: var(--brand); color: #fff; box-shadow: 0 2px 8px rgba(24, 95, 165, .26); }
.btn-primary:hover { background: var(--brand-strong); border-color: var(--brand-strong); box-shadow: 0 4px 14px rgba(24, 95, 165, .34); }
.btn-lg { padding: 13px 26px; font-size: 16px; border-radius: 11px; }
.btn-sm { padding: 5px 12px; font-size: 13px; border-radius: var(--radius-xs); }
.btn-ghost { border-color: transparent; background: transparent; color: var(--text-2); box-shadow: none; }
.btn-ghost:hover { background: var(--surface-2); color: var(--text); box-shadow: none; }
.btn-row { display: flex; gap: 10px; flex-wrap: wrap; }

/* ---- 练习页底部那一排操作（2026-10-06 美化）----
   原来四个按钮一排同样轻重、全挤在左边，看着散。
   现在：主按钮（提交/下一题）照旧；「提示 / 不会」是一组淡一点的"求助"按钮
   （hover 才亮成品牌色）；「退出」推到最右当次要动作。
   ⚠️ 「提示 / 不会」朗读那一关还开着时是**藏起来的**（app.js 的 syncAidButtons）。 */
.prac-actions { align-items: center; }
.prac-actions .prac-aid { color: var(--text-2); }
.prac-actions .prac-aid:hover {
  color: var(--brand); border-color: var(--brand);
  background: var(--brand-soft); box-shadow: var(--sh-2);
}
.prac-actions .prac-quit { margin-left: auto; }

/* 练习页底部那行快捷键说明：再淡一点，别跟上面那排按钮抢注意力 */
.prac-keys { margin-top: 12px; color: var(--text-3); }
.prac-keys kbd { font-size: 11px; line-height: 15px; margin-left: 2px; }

/* 表单 */
label.field { display: block; margin-bottom: 16px; }
label.field > .lab { display: block; font-size: 13px; color: var(--text-2); margin-bottom: 6px; font-weight: 500; }
input[type="text"], input[type="number"], input[type="date"], select, textarea {
  width: 100%;
  padding: 9px 12px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  font-size: 14px;
  font-family: inherit;
  background: var(--surface);
  color: var(--text);
  transition: border-color var(--dur-2) var(--ease),
              box-shadow var(--dur-2) var(--ease),
              background var(--dur-2) var(--ease);
}
input[type="text"]:hover, textarea:hover, select:hover { border-color: var(--text-3); }
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--brand);
  box-shadow: 0 0 0 3px var(--brand-ring);
}
textarea { line-height: 1.7; resize: vertical; }

/* 选项组 */
.chips { display: flex; gap: 8px; flex-wrap: wrap; }
.chip {
  position: relative;
  border: 1px solid var(--line-strong);
  background: var(--surface);
  padding: 6px 14px;
  border-radius: 999px;
  font-size: 13px;
  cursor: pointer;
  user-select: none;
  box-shadow: var(--sh-1);
  transition: background var(--dur-2) var(--ease),
              border-color var(--dur-2) var(--ease),
              color var(--dur-2) var(--ease),
              box-shadow var(--dur-2) var(--ease),
              transform var(--dur-1) var(--ease);
}
.chip:hover {
  border-color: var(--brand);
  color: var(--brand);
  box-shadow: var(--sh-2);
  transform: translateY(-1px);
}
.chip:active { transform: scale(.95); }
.chip.on {
  background: var(--brand);
  border-color: var(--brand);
  color: #fff;
  font-weight: 500;
  box-shadow: 0 2px 8px rgba(24, 95, 165, .28), 0 0 0 3px var(--brand-ring);
}
.chip.on:hover { color: #fff; }
/* 选中瞬间：轻微弹一下 + 从中心扩散一圈水波（幅度刻意做小，一排小胶囊上不抢眼） */
.chip.pop { animation: chipPop var(--dur-3) var(--spring); }
@keyframes chipPop {
  0%   { transform: scale(1); }
  40%  { transform: scale(1.04); }
  100% { transform: scale(1); }
}
.chip.pop::after {
  content: "";
  position: absolute; inset: -1px;
  border-radius: inherit;
  border: 1.5px solid var(--brand);
  animation: chipRipple .44s var(--ease-out) forwards;
  pointer-events: none;
}
@keyframes chipRipple {
  from { opacity: .6; transform: scale(1); }
  to   { opacity: 0; transform: scale(1.22); }
}
/* 未选中的 chip 互相轻微错开，hover 时更明显（顺手的小质感） */
.chips:hover .chip:not(.on):hover { transform: translateY(-1px); }

/* chip 里那个小数字（「A 部分 22」里的 22）—— 一眼看出这一部分多少条 */
.chip-n {
  display: inline-block;
  margin-left: 2px;
  padding: 0 6px;
  border-radius: 999px;
  background: var(--surface-3);
  color: var(--text-3);
  font-size: 11px;
  line-height: 16px;
}
.chip.on .chip-n { background: rgba(255, 255, 255, .28); color: #fff; }

/* 「前往复习」/「前往语法」右下角悬浮按钮：那两屏都很长，很容易找不到「开始」。
   ⚠️ 只在**该页**出现（body 上的类由 app.js 的 syncSideJump() 同步）：
      · #revJumpBtn  → body.rev-page（复习页整页）
      · #graJumpBtn  → body.grammar-page（语法页整页）
      所以显隐是**按 id 分开写的**，别图省事写成 `.side-jump` 一条（两颗按钮会同时冒出来）。
   ⚠️ 2026-10-01：「前往练习」那颗**撤了** —— 练习页现在是一张"点一下就开练"的列表，
      就在页面最上面、一屏能看完，不再需要"滚到底找开始"的悬浮入口。
      （body.prac-setup 这个类留着：bird.css 靠它把右下角的小鸟往上让。）
   ⚠️ 层级取 90：压得住页面内容（< 100）、低于顶栏（101），也低于提示条（100）——
      它浮在内容上，不该盖住提示条。
   用户 2026-09-26 要求「调到右下角偏大」，所以是**实心品牌色**的一颗悬浮按钮
   （原来是贴在右侧居中的浅色小标签）；2026-09-27 用户要求复习页、语法页也各来一颗同款的。 */
.side-jump {
  display: none;
  position: fixed;
  right: 22px;
  bottom: 22px;
  align-items: center;
  gap: 9px;
  padding: 15px 24px;
  border: 1px solid var(--brand);
  border-radius: 999px;
  background: var(--brand);
  color: #fff;
  font-size: 15px;
  font-weight: 600;
  cursor: pointer;
  box-shadow: 0 6px 22px rgba(24, 95, 165, .34);
  z-index: var(--z-side);
  transition: transform var(--dur-1) var(--ease),
              background var(--dur-2) var(--ease),
              box-shadow var(--dur-2) var(--ease);
}
body.rev-page #revJumpBtn { display: inline-flex; }
body.grammar-page #graJumpBtn { display: inline-flex; }
.side-jump:hover {
  background: var(--brand-strong); border-color: var(--brand-strong);
  box-shadow: 0 9px 28px rgba(24, 95, 165, .44);
  transform: translateY(-2px);
}
.side-jump:active { transform: translateY(0) scale(.97); }
.side-jump svg { display: block; }

/* ---- 「每日提醒」：左下角常驻按钮 + 两个小窗（2026-09-28） ---- */
/* 左下角那颗「每日练习」。为什么不做成实心大按钮：右下角那三颗「前往…」是
   **临时**出现的（只在某一屏），而这颗是**常驻**的 —— 常驻还实心就会一直抢注意力。
   所以用浅色胶囊 + 品牌色文字（和顶栏、卡片一套质感）。
   ⚠️ 层级 --z-side(90)：压住页面内容、低于顶栏；和右下角那三颗同一层，
      但一个在左一个在右，位置不冲突。 */
.daily-btn {
  position: fixed;
  left: 22px;
  bottom: 22px;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 11px 16px 11px 14px;
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  background: rgba(255, 255, 255, .94);
  backdrop-filter: saturate(180%) blur(8px);
  -webkit-backdrop-filter: saturate(180%) blur(8px);
  color: var(--brand);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  box-shadow: var(--sh-2);
  z-index: var(--z-side);
  transition: background var(--dur-2) var(--ease),
              border-color var(--dur-2) var(--ease),
              transform var(--dur-1) var(--ease);
}
.daily-btn:hover { background: var(--brand-soft); border-color: var(--brand); transform: translateY(-1px); }
.daily-btn:active { transform: translateY(0) scale(.98); }
.daily-btn svg { display: block; color: var(--brand); }
/* 今天这件事已经做完了（听写填了 / 委托全清了）→ 右上角点一颗小绿点 */
.daily-btn.done { border-color: var(--ok); color: var(--ok); }
.daily-btn.done svg { color: var(--ok); }
.daily-btn.done::after {
  content: '';
  position: absolute; top: -3px; right: -3px;
  width: 10px; height: 10px; border-radius: 50%;
  background: var(--ok);
  border: 2px solid var(--surface);
}

/* 听写小窗里的表单：首页那张卡是 5 列网格（150px + 3 列 + 按钮），
   弹窗只有 400px 宽，硬套会挤成一团 —— 这里改成两列。 */
.dict-form-mini {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px 12px;
  margin-bottom: 12px;
}
.dict-form-mini > .dict-f:first-child { grid-column: 1 / -1; }   /* 「一共听写」占一整行 */

/* 每日练习小窗下半部分：「今天该练什么」自动建议 */
.sug-wrap { margin-top: 18px; border-top: 1px solid var(--line); padding-top: 14px; }
.sug-title { font-size: 13px; font-weight: 600; color: var(--text-2); margin-bottom: 10px; }
.sug {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 12px; margin-bottom: 8px;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface);
}
.sug:last-child { margin-bottom: 0; }
.sug-body { flex: 1; min-width: 0; }
.sug-body b { display: block; font-size: 14px; }
.sug-body span { font-size: 12.5px; color: var(--text-2); }
.sug .btn { flex-shrink: 0; }
.sug-empty { font-size: 13px; color: var(--text-3); padding: 4px 2px; }


/* ---- 语法口诀页 ---- */
/* 语法题里那块「口诀参考」：把规则摆在题干上面，学生照着它推空里该填什么 */
.gra-ref {
  border-left: 3px solid var(--brand);
  background: var(--brand-soft);
  border-radius: var(--radius-sm);
  padding: 12px 16px;
  margin-bottom: 20px;
}
.gra-ref-t { font-size: 13px; font-weight: 600; color: var(--brand-strong); }
.gra-ref-m { font-size: 15px; font-weight: 600; margin-top: 7px; line-height: 1.7; }
.gra-ref-e { font-size: 12px; color: var(--text-2); margin-top: 7px; line-height: 1.7; }

/* 口诀一览：按篇章 → 小节分组，每条可展开 */
.gra-group { margin-bottom: 22px; }
.gra-group-t { font-size: 15px; font-weight: 600; margin-bottom: 4px; }
.gra-sec {
  font-size: 13px; color: var(--text-2); font-weight: 500;
  margin: 14px 0 2px; padding-left: 2px;
}
.gra-item { border-bottom: 1px solid var(--line); }
.gra-item:last-child { border-bottom: 0; }
.gra-item-head {
  display: flex; align-items: baseline; gap: 8px;
  padding: 9px 2px; cursor: pointer; border-radius: var(--radius-xs);
  transition: background var(--dur-2) var(--ease);
}
.gra-item-head:hover { background: var(--surface-2); }
.gra-caret { color: var(--text-3); font-size: 11px; }
.gra-item-title { font-size: 14px; font-weight: 500; }
.gra-item-meta { font-size: 12px; color: var(--text-3); margin-left: auto; }
.gra-item-body { padding: 2px 2px 14px 20px; }
.gra-mn { font-size: 14px; line-height: 1.8; }
.gra-ex { font-size: 12px; color: var(--text-2); line-height: 1.95; margin-top: 6px; }
.gra-ex b { color: var(--brand-strong); }

/* 滚到「开始练习 / 开始复习」之后，给落点闪一下高亮 —— 不然滚完还得自己找 */
.btn.landing { animation: landing 1.8s var(--ease-out); }
@keyframes landing {
  0%   { box-shadow: 0 0 0 0 var(--brand-ring); }
  25%  { box-shadow: 0 0 0 9px var(--brand-ring); }
  100% { box-shadow: 0 0 0 0 rgba(24, 95, 165, 0); }
}

/* 练习区 */
.quiz-stage { max-width: 720px; margin: 0 auto; }
.progress-track { height: 6px; background: var(--surface-3); border-radius: 999px; overflow: hidden; margin-bottom: 8px; }
.progress-fill {
  height: 100%; border-radius: 999px;
  background: linear-gradient(90deg, #2470bd, var(--brand));
  transition: width var(--dur-3) var(--ease-out);
}
.progress-meta { display: flex; justify-content: space-between; font-size: 13px; color: var(--text-2); margin-bottom: 24px; }

/* ---- 「过渡测试」那一环的过场提示条（2026-09-30） ----
   一轮练习分成「前半批 → 过渡测试① → 后半批 → 过渡测试②」，
   换段时弹这条，两秒多后自己淡出。⚠️ 用 --dur-3 走入场、--ease-out 收尾，
   跟视图切换一个节拍；别写死毫秒。 */
.phase-banner {
  background: var(--brand-soft);
  border: 1px solid var(--brand-ring);
  border-radius: var(--radius-sm);
  padding: 14px 18px;
  margin-bottom: 16px;
  text-align: center;
  opacity: 0;
  transform: translateY(-6px);
  transition: opacity var(--dur-3) var(--ease-out), transform var(--dur-3) var(--ease-out);
}
.phase-banner.show { opacity: 1; transform: translateY(0); }
.phase-banner .pb-title { font-size: 17px; font-weight: 700; color: var(--brand-strong); letter-spacing: .5px; }
.phase-banner .pb-sub { font-size: 13px; color: var(--text-2); margin-top: 6px; line-height: 1.6; }
/* 过渡测试的角标：和「复习」「Unit 1」并排，所以跟着 .pill 的观感走 */
.pill.phase-pill { background: var(--warn-soft); color: var(--warn); }

/* 「这一轮怎么走」那张说明卡：不是选项，是流程预告 */
.prac-flow { margin: 0; padding-left: 20px; font-size: 14px; line-height: 2; color: var(--text-2); }
.prac-flow b { color: var(--text); }
.prac-flow strong { color: var(--brand-strong); }

/* ------------------------------------------------------------------ *
 * 练习入口列表（2026-10-01 起）：点一下就开练。
 *
 * 之前是「选单元（可多选）→ 设题量 → 点开始」三步 —— 太绕。现在压成一次点击：
 *   单练 —— 每个单元一行，行里并排 A / B / C 三个可点的段；
 *   综合 —— 每个单元一整块（外加一条「全部单元」）。
 * 2026-10-05 又收了一轮版式（用户："有的地方它是空白的，太乱了"）：
 *   单练从"一单元一个盒子"压成**一整张表**（行高统一，不再参差）；
 *   综合从"一行一整条"改成**两列卡片**（右边原来空一大片）。
 * 用 <button> 而不是 div：它本来就"点了会发生事"，键盘也能 Tab 到。
 * ------------------------------------------------------------------ */
.prac-list { display: flex; flex-direction: column; gap: 8px; }

/* ---- 单练：一整张表（2026-10-05 改版） ----
   原来是"一个单元一个盒子"（单元名长短不一 → 盒子高低不齐、留白多，很乱）。
   现在 8 个单元共用一个表壳，每个单元一行：行首单元名 + 右边并排 A/B/C 三段。
   ⚠️ 行高统一：`.pr-name` 固定不换行 + 省略号，名字再长也不把行撑高。 */
.prac-table {
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  overflow: hidden;
}
.prac-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.45fr);
  align-items: center;
  gap: 12px;
  padding: 7px 12px;
  border-bottom: 1px solid var(--line);
}
.prac-row:last-child { border-bottom: 0; }
.prac-row:hover { background: var(--surface-2); }
.pr-name {
  font-size: 13.5px; font-weight: 600; color: var(--text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  min-width: 0;
}
.pr-name .pr-u {
  display: inline-block; min-width: 34px;
  font-size: 11.5px; font-weight: 700; color: var(--brand);
  font-variant-numeric: tabular-nums;
}
.pr-segs { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; min-width: 0; }
/* 单练里那三段是窄条，不是整行按钮 —— 把默认的外边距/内边距压小 */
.prac-go.seg { padding: 6px 10px; justify-content: space-between; box-shadow: none; }
.prac-go.seg .pg-t { font-size: 12.5px; }

/* ---- 练习页·设置屏单独放开宽度（2026-10-10 加，2026-10-11 再加长）----
   答题态 / 成绩页跟设置屏原来共用 `.quiz-stage`（max-width:720px）—— 那个宽度是给答题卡用的，
   设置屏那张两栏表挤在 720px 里太窄。
   做法：把**设置屏**（#pracSetup）从 `.quiz-stage` 里**挪出来**（见 english/index.html），
   `.quiz-stage` 只包住答题态 / 成绩页。
   ⚠️ 别再给 `.quiz-stage` 里的东西设 >720px 的 max-width —— 块级子元素顶不出父级，不会有任何效果。
   ⚠️ 2026-10-11：用户要求"框再宽些"。光给 #pracSetup 设 max-width 没用 ——
      **它的父级 `.wrap` 是 1080px**（还有 40px 内边距，实际只有 1040），卡死。
      所以：练习页给 body 挂 `view-practice-page`（app.js 的 go() 里切），
      **只对练习页**把 .wrap 放宽到 1240、#pracSetup 到 1200；别的页仍是 1080。 */
body.view-practice-page .wrap { max-width: 1240px; }
#pracSetup { max-width: 1200px; margin: 0 auto; }

/* ---- 题型两栏（2026-10-10 加，同日由单栏改成两栏）----
   左栏「练什么」（.prac-nav-row）+ 右栏「该题型的单练表」（#pracSoloList / #pracTypePane）。
   左栏窄一点（题型少），右栏给宽些。窄屏折成一列（见媒体查询）。 */
.prac-two {
  display: grid;
  grid-template-columns: minmax(180px, 210px) minmax(0, 1fr);
  gap: 16px;
  /* ⚠️ 2026-10-18 用户要求「两栏上下底齐平、高度相等」—— 用 stretch 拉齐，不能用 start */
  align-items: stretch;
}
.prac-col-left, .prac-col-right { display: flex; flex-direction: column; min-width: 0; }
.prac-col-left .prac-quick { margin-top: auto; }
/* 右栏的内容层：跟左栏等高，内容多了就在**这一层**里滚（不把整页撑长） */
.prac-pane-scroll {
  flex: 1 1 auto; min-height: 0;
  /* ⚠️ 8 个单元 + 「全部」一共 9 行，接近 420px —— 阈值给太小会“刚好够也冒滚动条”。留余地到 68vh，
     真正多出来的时候（小窗口 / 以后单元变多）才滚。 */
  max-height: min(68vh, 640px);
  overflow-y: auto; overscroll-behavior: contain;
  padding-right: 4px;
}
.prac-col-right h2 { margin-bottom: 2px; }
.prac-nav, .prac-forms { display: flex; flex-direction: column; gap: 8px; }

/* 左栏一行：题型名 + 副标题 + 右边题数 */
.prac-nav-row {
  display: flex; align-items: center; gap: 8px;
  width: 100%; text-align: left; cursor: pointer;
  font: inherit; color: var(--text);
  border: 1px solid var(--line-strong);
  background: var(--surface);
  border-radius: var(--radius-sm);
  padding: 11px 13px;
  box-shadow: var(--sh-1);
  transition: background var(--dur-2) var(--ease),
              border-color var(--dur-2) var(--ease),
              color var(--dur-2) var(--ease),
              box-shadow var(--dur-2) var(--ease),
              transform var(--dur-1) var(--ease);
}
.prac-nav-row .pn-main {
  display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1;
}
.prac-nav-row .pn-t { font-weight: 600; font-size: 14px; }
.prac-nav-row .pn-desc {
  font-size: 12px; color: var(--text-3); font-weight: 400;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.prac-nav-row .pn-n { font-size: 12px; color: var(--text-3); white-space: nowrap; }
.prac-nav-row:hover { border-color: var(--brand); transform: translateY(-1px); }
.prac-nav-row:active { transform: scale(.99); box-shadow: var(--sh-1); }
.prac-nav-row:focus-visible { outline: 2px solid var(--brand-ring); outline-offset: 2px; }
.prac-nav-row:hover .pn-desc { color: var(--brand); opacity: .85; }
/* 选中态（class 名 .on 不能改：测试依赖）—— 单词默认选中 */
.prac-nav-row.on {
  border-color: var(--brand);
  background: var(--brand-soft);
  box-shadow: var(--sh-1);
}
.prac-nav-row.on .pn-t { color: var(--brand-strong); }
.prac-nav-row.on .pn-desc, .prac-nav-row.on .pn-n { color: var(--brand); opacity: .85; }
.prac-nav-row[disabled] { opacity: .5; cursor: not-allowed; box-shadow: none; }
.prac-nav-row[disabled]:hover { transform: none; border-color: var(--line-strong); }

/* ---- 左栏底部「快捷入口」（2026-10-11 加）----
   一小块而已，接去语法页（在那边能细调范围 / 小节）。
   刻意做得**比题型行小一号**：它是"去别处"，不该抢题型那三条的注意力。
   ⚠️ 用虚线边框 + 无阴影，跟上面的实心题型卡拉开层次（"嵌入背景"那味儿）。 */
.prac-quick { margin-top: 10px; padding-top: 10px; border-top: 1px dashed var(--line); }
.prac-quick-btn {
  display: flex; align-items: center; gap: 6px;
  width: 100%; text-align: left; cursor: pointer;
  font: inherit; color: var(--text-2);
  background: transparent; border: 0; border-radius: var(--radius-sm);
  padding: 6px 8px;
  transition: background var(--dur-2) var(--ease), color var(--dur-2) var(--ease);
}
.prac-quick-btn .pq-ico { font-size: 13px; line-height: 1; }
.prac-quick-btn .pq-t { font-size: 12.5px; font-weight: 500; }
.prac-quick-btn:hover { background: var(--surface-2); color: var(--brand-strong); }
.prac-quick-btn:active { transform: scale(.99); }
.prac-quick-btn:focus-visible { outline: 2px solid var(--brand-ring); outline-offset: 2px; }

/* 右栏一条（语法篇章 / 句子单元）：名称 + 说明 + 右边题数；可点的整条 */
.prac-form {
  display: flex; align-items: center; gap: 8px;
  width: 100%; text-align: left; cursor: pointer;
  font: inherit; color: var(--text);
  border: 1px solid var(--line-strong);
  background: var(--surface);
  border-radius: var(--radius-sm);
  padding: 10px 14px;
  box-shadow: var(--sh-1);
  transition: background var(--dur-2) var(--ease),
              border-color var(--dur-2) var(--ease),
              color var(--dur-2) var(--ease),
              box-shadow var(--dur-2) var(--ease),
              transform var(--dur-1) var(--ease);
}
.prac-form .pf-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.prac-form .pf-t { font-weight: 600; font-size: 14px; }
.prac-form .pf-desc {
  font-size: 12px; color: var(--text-3); font-weight: 400;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.prac-form .pf-n { font-size: 12.5px; color: var(--text-3); white-space: nowrap; }
.prac-form:hover { border-color: var(--brand); transform: translateY(-1px); }
.prac-form:hover .pf-desc, .prac-form:hover .pf-n { color: var(--brand); opacity: .85; }
.prac-form:active { transform: scale(.99); box-shadow: var(--sh-1); }
.prac-form:focus-visible { outline: 2px solid var(--brand-ring); outline-offset: 2px; }
/* 「全部…一起练」那条整宽高亮（放在列表最上面） */
.prac-form.wide {
  border-color: var(--brand);
  background: var(--brand-soft);
}
.prac-form.wide .pf-t { color: var(--brand-strong); font-size: 14.5px; }
.prac-form.wide .pf-n { color: var(--brand); opacity: .85; }
.prac-form[disabled] { opacity: .5; cursor: not-allowed; box-shadow: none; }
.prac-form[disabled]:hover { transform: none; border-color: var(--line-strong); }

/* 可点的入口（两个列表共用） */
.prac-go {
  display: flex; align-items: center; gap: 8px;
  width: 100%; text-align: left; cursor: pointer;
  font: inherit; color: var(--text);
  border: 1px solid var(--line-strong);
  background: var(--surface);
  border-radius: var(--radius-xs);
  padding: 9px 12px;
  box-shadow: var(--sh-1);
  transition: background var(--dur-2) var(--ease),
              border-color var(--dur-2) var(--ease),
              color var(--dur-2) var(--ease),
              box-shadow var(--dur-2) var(--ease),
              transform var(--dur-1) var(--ease);
}
.prac-go:hover {
  border-color: var(--brand);
  color: var(--brand);
  box-shadow: var(--sh-2);
  transform: translateY(-1px);        /* 位移刻意压到 1px，多了会晃 */
}
.prac-go:active { transform: scale(.99); box-shadow: var(--sh-1); }
.prac-go:focus-visible { outline: 2px solid var(--brand-ring); outline-offset: 2px; }
.prac-go .pg-t { font-weight: 600; font-size: 13.5px; }
.prac-go .pg-n {
  margin-left: auto; font-size: 12px; color: var(--text-3);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.prac-go:hover .pg-n { color: var(--brand); opacity: .8; }

/* ⚠️ 2026-10-11：「综合」那一列（.prac-go.wide / .accent / .carded / .prac-pairs）已随
   「综合 · 一步练完」一起删掉 —— 现在只剩单练那张表里的 .prac-go.seg。 */

.prompt-card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 30px 26px 26px;
  text-align: center;
  margin-bottom: 16px;
  box-shadow: var(--sh-2);
}
.prompt-hint {
  font-size: 20px;
  font-weight: 600;
  line-height: 1.6;
  word-break: break-word;
}
/* 词性小标签（adj. / pron. …）—— 由 app.js 的 promptZhHTML() 圈出来。
   目的是让"词性"和"中文意思"分开，别挤成一坨同字号的字。
   ⚠️ 那个尾随空格必须留在标签里，见 promptZhHTML 的注释（测试靠 textContent 全等）。 */
.prompt-hint .pos {
  font-size: .6em;
  font-weight: 500;
  color: var(--text-3);
  letter-spacing: 0;
}
.prompt-small {
  font-size: 13.5px;
  color: var(--text-3);
  margin-top: 7px;
  letter-spacing: .3px;
}
.answer-input {
  font-size: 24px !important;
  padding: 14px 18px !important;
  text-align: center;
  letter-spacing: .5px;
  border-radius: var(--radius) !important;
}
.answer-input.ok { border-color: var(--ok) !important; background: var(--ok-soft); box-shadow: 0 0 0 3px rgba(29, 158, 117, .18) !important; }
.answer-input.bad { border-color: var(--bad) !important; background: var(--bad-soft); box-shadow: 0 0 0 3px rgba(214, 69, 69, .16) !important; }
.answer-input.done { border-color: var(--line-strong) !important; background: var(--surface-2); color: var(--text-3); box-shadow: none !important; }
/* 判分结果整体轻微弹入 */
.answer-input.ok, .answer-input.bad { animation: verdictIn var(--dur-3) var(--ease-out); }
@keyframes verdictIn {
  from { transform: scale(.985); }
  to   { transform: scale(1); }
}

.verdict { margin-top: 16px; font-size: 15px; }
.verdict .big { font-size: 18px; font-weight: 600; }
.verdict.ok { color: var(--ok); }
.verdict.bad { color: var(--bad); }
.verdict .right { color: var(--text); font-size: 17px; font-weight: 600; margin-top: 6px; }
.verdict.ok, .verdict.bad { animation: fadeUp var(--dur-3) var(--ease-out); }
@keyframes fadeUp {
  from { opacity: 0; transform: translateY(5px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ---- 选择题 / 阅读题（2026-10-11 加）------------------------------
   #pracChoicePane = 短文（可选）+ 题干 + 四个选项按钮。
   ⚠️ 阅读短文可能十几行：给它一个**自己的滚动区**（限高 + overflow:auto），
      不然整个答题卡会被撑得很长，选项被推到屏幕外面去。 */
.passage-box {
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 14px 16px;
  margin-bottom: 16px;
  max-height: 260px;
  overflow-y: auto;
}
.passage-box .psg-t { font-size: 13px; font-weight: 600; color: var(--brand-strong); margin-bottom: 8px; }
.passage-box .psg-body { font-size: 15px; line-height: 1.75; color: var(--text); text-align: left; }
.choice-q {
  font-size: 18px;
  font-weight: 600;
  line-height: 1.6;
  text-align: left;
  margin-bottom: 14px;
}
.choice-opts { display: grid; gap: 10px; }
.choice-opt {
  display: flex; align-items: center; gap: 12px;
  width: 100%;
  text-align: left;
  padding: 13px 16px;
  font-size: 16px;
  font-family: inherit;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  cursor: pointer;
  transition: background var(--dur-1) var(--ease),
              border-color var(--dur-1) var(--ease),
              transform var(--dur-1) var(--ease);
}
.choice-opt .co-k {
  flex: none;
  width: 24px; height: 24px;
  border-radius: 50%;
  background: var(--surface-2);
  color: var(--text-2);
  font-size: 12.5px; font-weight: 700;
  display: inline-flex; align-items: center; justify-content: center;
}
.choice-opt .co-t { flex: 1 1 auto; }
.choice-opt:hover:not(:disabled) { background: var(--surface-2); transform: translateY(-1px); }
.choice-opt:active:not(:disabled) { transform: scale(.995); }
.choice-opt:focus-visible { outline: 2px solid var(--brand-ring); outline-offset: 2px; }
.choice-opt:disabled { cursor: default; }
.choice-opt.ok  { border-color: var(--ok)  !important; background: var(--ok-soft);  color: var(--ok); }
.choice-opt.ok .co-k  { background: var(--ok); color: #fff; }
.choice-opt.bad { border-color: var(--bad) !important; background: var(--bad-soft); color: var(--bad); }
.choice-opt.bad .co-k { background: var(--bad); color: #fff; }
.choice-opt.dim { opacity: .5; }
.choice-opt.pop { animation: verdictIn var(--dur-3) var(--ease-out); }

/* ---- 答错 → 解析 + 重做（2026-10-11 加）---------------------------
   只给「练单词」那条路：答错先把解析展开（**不给答案**），学生原地再写一遍。
   重做对了揭晓答案走人；重做还错才进错题本。
   ⚠️ 动画走 token，prefers-reduced-motion 下由全局那段降级（见文件末尾）。 */
.redo-bar {
  margin-top: 14px;
  padding: 12px 16px;
  border-radius: var(--radius);
  background: var(--brand-soft);
  border: 1px solid var(--brand-ring);
  text-align: left;
  animation: redoIn var(--dur-3) var(--ease-out);
}
@keyframes redoIn {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0); }
}
.redo-bar .redo-t {
  font-size: 13px; font-weight: 600; color: var(--brand-strong);
  display: flex; align-items: center; gap: 6px; margin-bottom: 6px;
}
.redo-bar .redo-body { font-size: 14px; line-height: 1.8; color: var(--text); }
.redo-bar .redo-body b { color: var(--brand-strong); }
.redo-bar .redo-body .rd-wrong { color: var(--bad); text-decoration: line-through; }
.redo-bar .redo-body .rd-right { color: var(--ok); font-weight: 600; }
/* 「重做」两字的小提示 —— 稍微晚一点淡入，别有"刷"一下就出来的感觉 */
.redo-tag {
  display: inline-flex; align-items: center;
  margin-top: 10px;
  padding: 3px 10px;
  border-radius: 999px;
  background: var(--brand);
  color: #fff;
  font-size: 12px; font-weight: 700; letter-spacing: 2px;
  animation: redoTagIn var(--dur-3) var(--ease-out) 120ms both;
}
@keyframes redoTagIn {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* 大喇叭按钮 */
.speaker {
  width: 108px; height: 108px;
  border-radius: 50%;
  border: 1px solid var(--brand);
  background: var(--brand-soft);
  color: var(--brand);
  font-size: 38px;
  cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  box-shadow: 0 4px 14px rgba(24, 95, 165, .16);
  transition: transform var(--dur-1) var(--ease),
              background var(--dur-2) var(--ease),
              box-shadow var(--dur-2) var(--ease);
}
.speaker:hover { background: #d5e7f7; box-shadow: 0 6px 20px rgba(24, 95, 165, .25); transform: translateY(-1px); }
.speaker:active { transform: scale(.95); box-shadow: 0 2px 8px rgba(24, 95, 165, .18); }
.speaker.playing { animation: pulse 1s infinite; }
@keyframes pulse { 0%,100% { transform: scale(1); } 50% { transform: scale(1.05); } }

/* 「正在合成语音」—— 微软云端语音要联网合成，实测出声要 0.1～4 秒。
   这期间界面如果一点动静都没有，用户会以为坏了、于是又点一下，
   而再点一次会打断正在合成的请求 —— 结果越点越没声。
   所以合成期间必须**看得见**：外圈转一道进度环。
   ⚠️ 环画在 ::after 上并向外扩 5px，不动按钮本身的大小（免得整块布局跳）。 */
.speaker.loading { animation: none; border-color: var(--brand-ring); }
.speaker.loading::after {
  content: "";
  position: absolute;
  inset: -5px;
  border-radius: 50%;
  border: 3px solid transparent;
  border-top-color: var(--brand);
  animation: spin .9s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
/* ::after 要定位，按钮本身得是定位上下文 */
.speaker { position: relative; }

/* 发音区下面那行小字：在合成 / 失败提示 */
.voice-state {
  margin-top: 14px;
  font-size: 13px;
  line-height: 1.7;
  color: var(--text-3);
}
.voice-state.warn { color: var(--warn); }
.voice-state a { color: var(--brand); text-decoration: underline; }

/* ------------------------------------------------------------------ *
 * 「先读一遍」环节（2026-10-05 加，2026-10-06 重做 + 2026-10-06 晚再美化）
 *
 * 原来是题目卡里一个大方块（用户："好丑"），现在压成**一块浅底的细条**：
 *   一行「🎤 + 一句话 + 分数」，下面一条会随时间流逝的细进度条（15 秒）。
 *  · 绿色 = 读到合格；红色 = 时间走完没读到；蓝色脉动 = 正在听。
 *  ⚠️ 2026-10-06 美化：给它一块**浅底圆角**（`--surface-2`），
 *     读的时候是"当前步骤"、读完转绿底（`--ok-soft`）—— 一眼看出这一步在干嘛。
 * 过渡测试不给听力（喇叭收起来），但这一关照样过。
 * 识别不可用时**直接放行**（不显示这条），绝不把人卡在写不了字。
 * ------------------------------------------------------------------ */
.read-bar {
  margin: 14px 0 6px;
  padding: 10px 13px 12px;
  text-align: left;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  transition: background var(--dur-2) var(--ease), border-color var(--dur-2) var(--ease);
}
.read-bar.ok { background: var(--ok-soft); border-color: transparent; }
.read-bar.bad { background: var(--bad-soft); border-color: transparent; }
.rb-line {
  display: flex; align-items: center; gap: 8px;
  font-size: 13px; line-height: 1.5; color: var(--text-2);
  min-height: 20px;
}
.rb-mic {
  flex: none; font-size: 14px; line-height: 1;
  transition: transform var(--dur-2) var(--ease);
}
/* ⚠️ 2026-10-17：只给「还没拿到音频」的 🎤 加脉动。
   拿到音频（.on）/ 检测到说话（.snd）/ 报错（.err）时停掉 —— 否则 animation 会一直
   盖住下面那套状态灯的 opacity / transform，三种状态看起来一模一样。 */
.read-bar.listening .rb-mic:not(.on):not(.snd):not(.err) { animation: rbPulse 1.4s var(--ease) infinite; }
.rb-mic.on, .rb-mic.snd, .rb-mic.err { animation: none; }
@keyframes rbPulse {
  0%, 100% { transform: scale(1); opacity: .75; }
  50%      { transform: scale(1.22); opacity: 1; }
}
.rb-text { flex: 1 1 auto; min-width: 0; }
.rb-score {
  flex: none; font-size: 12.5px; font-weight: 600;
  font-variant-numeric: tabular-nums; color: var(--text-3);
}
.read-bar.ok .rb-score { color: var(--ok); }
.read-bar.bad .rb-score { color: var(--bad); }
/* 「停止」（2026-10-12 加）：停掉继续收音，拿**停止前读到的内容**结算。
   ⚠️ 朗读进行中键盘快捷键是禁用的 → 它是这一关**唯一的手动出口**，所以做得显眼一点，
      但仍然收在条子里面、比正文略小（克制：用户不喜欢大动静）。 */
.rb-stop {
  flex: none; margin-left: 4px; padding: 3px 11px;
  font-family: inherit; font-size: 12.5px; font-weight: 600; line-height: 1.4;
  color: var(--bad); background: var(--bad-soft);
  border: 1px solid transparent; border-radius: 999px; cursor: pointer;
  transition: background var(--dur-2) var(--ease), transform var(--dur-1) var(--ease);
}
.rb-stop:hover { background: var(--bad); color: #fff; }
.rb-stop:active { transform: scale(.96); }
.rb-stop:focus-visible { outline: 2px solid var(--brand-ring); outline-offset: 2px; }
/* 已经读合格 / 已放行 → 收起来（那会儿没得停了） */
.read-bar.ok .rb-stop { display: none; }
/* 时间条：默认淡蓝（正在走），合格转绿、失败转红 —— 中国习惯（红=错/绿=对） */
.rb-track {
  margin-top: 7px; height: 5px; border-radius: 999px; overflow: hidden;
  background: var(--surface-3); border: 1px solid var(--line);
}
.rb-fill {
  height: 100%; width: 100%; border-radius: 999px;
  background: var(--brand);
  transition: width 120ms linear, background var(--dur-2) var(--ease);
}
.read-bar.ok .rb-fill { background: var(--ok); }
.read-bar.bad .rb-fill { background: var(--bad); }
@media (prefers-reduced-motion: reduce) {
  .read-bar.listening .rb-mic { animation: none; }
  .rb-fill { transition: none; }
}

/* 表格 */
table.tbl { width: 100%; border-collapse: collapse; font-size: 14px; }
table.tbl th, table.tbl td { text-align: left; padding: 9px 10px; border-bottom: 1px solid var(--line); }
table.tbl th { font-size: 13px; color: var(--text-2); font-weight: 500; background: var(--surface-2); }
table.tbl tr:last-child td { border-bottom: none; }
table.tbl tbody tr { transition: background var(--dur-1) var(--ease); }
table.tbl tbody tr:hover { background: var(--brand-soft); }
table.tbl td.mono { font-family: ui-monospace, Consolas, monospace; }

/* 统计 */
.stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 12px; }
.stat {
  background: var(--surface-2);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  padding: 14px 16px;
  transition: background var(--dur-2) var(--ease),
              border-color var(--dur-2) var(--ease),
              box-shadow var(--dur-2) var(--ease),
              transform var(--dur-2) var(--ease-out);
}
.stat:hover {
  background: var(--surface);
  border-color: var(--line);
  box-shadow: var(--sh-2);
  transform: translateY(-2px);
}
.stat .k { font-size: 12px; color: var(--text-2); }
.stat .v { font-size: 24px; font-weight: 600; line-height: 1.3; font-variant-numeric: tabular-nums; }

.bars { display: flex; align-items: flex-end; gap: 10px; height: 140px; padding-top: 8px; }
.bar-col { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 6px; height: 100%; justify-content: flex-end; }
.bar {
  width: 100%; max-width: 46px; min-height: 2px;
  background: linear-gradient(180deg, #3a86cf, var(--brand));
  border-radius: 5px 5px 0 0;
  transition: height var(--dur-3) var(--ease-out), filter var(--dur-2) var(--ease);
  animation: barGrow var(--dur-3) var(--ease-out) backwards;
}
.bar-col:hover .bar { filter: brightness(1.12); }
@keyframes barGrow { from { height: 0 !important; opacity: .3; } }
.bar-lab { font-size: 11px; color: var(--text-3); }

/* ---------- 听写统计（每天记一次 + 左右两张曲线） ---------- */
.dict-form {
  display: grid;
  grid-template-columns: 150px repeat(3, minmax(88px, 1fr)) auto;
  gap: 12px;
  align-items: end;
  margin-bottom: 12px;
}
.dict-f { display: block; min-width: 0; }
.dict-f > span { display: block; font-size: 12px; color: var(--text-2); margin-bottom: 5px; font-weight: 500; }
.dict-f input { width: 100%; }
/* 按钮那一格没有 label，跟输入框底边对齐 */
.dict-act { display: flex; align-items: flex-end; }
.dict-act .btn { white-space: nowrap; }

.dict-live { font-size: 13px; color: var(--text-2); margin: 0 0 14px; }
.dict-live b { color: var(--brand); font-size: 16px; }
.dict-live.warn, .dict-live.warn b { color: var(--warn); }
/* 「这天已经记过了」的提醒；没内容时不占地方 */
.dict-hint { font-size: 12.5px; color: var(--warn); margin: -6px 0 14px; }
.dict-hint:empty { display: none; margin: 0; }

/* 两张曲线：一左一右；窄屏自动叠成上下 */
.chart2 { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.chart-box {
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 11px 12px 4px;
  background: var(--surface);
}
.chart-title {
  display: flex; justify-content: space-between; align-items: baseline; gap: 8px;
  font-size: 13px; font-weight: 600; color: var(--text); margin-bottom: 2px;
}

/* 手写 SVG 折线图（项目约定：不引图表库，图都自己画） */
.lc { display: block; width: 100%; height: auto; overflow: visible; }
.lc-grid { stroke: var(--line); stroke-width: 1; }
.lc-avg { stroke: var(--text-3); stroke-width: 1; stroke-dasharray: 3 3; opacity: .8; }
.lc-line { fill: none; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.lc-area { stroke: none; opacity: .1; }
.lc-dot { stroke: var(--surface); stroke-width: 1.5; }
.lc-ylab, .lc-xlab { font-size: 10px; fill: var(--text-3); }

/* ---------- 每日委托 ---------- */
/* 「第 1 步 / 第 2 步」那种一步一行的块 */
.daily-step {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
}
.ds-no {
  flex: none;
  width: 26px; height: 26px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--brand); color: #fff; font-size: 13px; font-weight: 600;
}
.ds-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.ds-body b { font-size: 14px; }
.ds-body span { font-size: 12.5px; color: var(--text-2); line-height: 1.55; }
.daily-tip { font-size: 12.5px; color: var(--text-3); margin-top: 10px; line-height: 1.6; }
.daily-tip b { color: var(--warn); }

/* 测试结果那一行 */
.daily-result {
  display: flex; align-items: center; gap: 14px;
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  margin-bottom: 12px;
}
.daily-result.good { border-color: var(--ok); background: var(--ok-soft); }
.dr-num { font-size: 26px; font-weight: 600; color: var(--brand); line-height: 1; }
.dr-num span { font-size: 14px; }
.daily-result.good .dr-num { color: var(--ok); }
.dr-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.dr-body b { font-size: 14px; }
.dr-body span { font-size: 12px; color: var(--text-3); }

/* 免除提示 */
.daily-exempt {
  display: flex; align-items: center; gap: 14px;
  padding: 16px;
  border: 1px solid var(--ok);
  border-radius: var(--radius-sm);
  background: var(--ok-soft);
}
.de-icon { font-size: 26px; }
.de-body { display: flex; flex-direction: column; gap: 3px; }
.de-body b { font-size: 15px; color: var(--ok); }
.de-body span { font-size: 12.5px; color: var(--text-2); line-height: 1.55; }

/* 免除条件的说明 */
.daily-crit {
  font-size: 12.5px; color: var(--text-2);
  padding: 9px 12px; margin-bottom: 12px;
  border-left: 3px solid var(--warn);
  background: var(--warn-soft);
  border-radius: 0 var(--radius-xs) var(--radius-xs) 0;
}
.daily-crit b { color: var(--warn); }

/* 4 个委托 */
.mission-list { display: flex; flex-direction: column; gap: 8px; }
.mission {
  display: flex; align-items: center; gap: 12px;
  padding: 11px 13px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  transition: border-color var(--dur-2) var(--ease), background var(--dur-2) var(--ease);
}
.mission:hover { border-color: var(--line-strong); }
.mission.done { background: var(--surface-2); border-color: var(--line); }
.m-check {
  flex: none; width: 24px; height: 24px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 12.5px; font-weight: 600;
  background: var(--surface-3); color: var(--text-2);
}
.mission.done .m-check { background: var(--ok); color: #fff; }
.m-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.m-body b { font-size: 13.5px; }
.mission.done .m-body b { color: var(--text-3); text-decoration: line-through; }
.m-body span { font-size: 12px; color: var(--text-2); line-height: 1.5; }
.mission .btn { flex: none; }

@media (max-width: 640px) {
  .daily-step, .daily-result { flex-wrap: wrap; }
  .daily-step .btn, .daily-result .btn { width: 100%; }
  .mission { flex-wrap: wrap; }
  .mission .btn { width: 100%; }
}

/* 标签 */
.pill {
  display: inline-block; font-size: 12px; padding: 2px 9px; border-radius: 999px;
  background: var(--surface-2); color: var(--text-2);
  transition: filter var(--dur-1) var(--ease);
}
.pill:hover { filter: brightness(.97); }
.pill.ok { background: var(--ok-soft); color: var(--ok); }
.pill.warn { background: var(--warn-soft); color: var(--warn); }
.pill.bad { background: var(--bad-soft); color: var(--bad); }
.pill.brand { background: var(--brand-soft); color: var(--brand); }

/* 工具 */
.muted { color: var(--text-2); }
.tiny { font-size: 12px; color: var(--text-3); }
.mt0 { margin-top: 0; } .mt8 { margin-top: 8px; } .mt16 { margin-top: 16px; } .mt24 { margin-top: 24px; }
.mb0 { margin-bottom: 0; } .mb8 { margin-bottom: 8px; } .mb16 { margin-bottom: 16px; }
.hidden { display: none !important; }
.center { text-align: center; }
.row { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; }
.row-between { display: flex; gap: 12px; align-items: center; justify-content: space-between; flex-wrap: wrap; }
.grow { flex: 1; min-width: 0; }
hr.sep { border: none; border-top: 1px solid var(--line); margin: 20px 0; }

.empty { text-align: center; padding: 40px 20px; color: var(--text-3); font-size: 14px; }

/* 课文挖空 */
.blank-text { font-size: 17px; line-height: 2.4; }
.blank-text .bl {
  display: inline-block;
  width: 118px;
  border: none;
  border-bottom: 1.5px solid var(--line-strong);
  background: transparent;
  font-size: 17px;
  font-family: inherit;
  text-align: center;
  padding: 0 4px;
  color: var(--brand);
  border-radius: 4px 4px 0 0;
  transition: border-color var(--dur-2) var(--ease),
              background var(--dur-2) var(--ease),
              color var(--dur-2) var(--ease);
}
.blank-text .bl:focus { outline: none; border-bottom-color: var(--brand); background: var(--brand-soft); }
.blank-text .bl.ok { border-bottom-color: var(--ok); color: var(--ok); background: var(--ok-soft); animation: blankIn var(--dur-3) var(--ease-out); }
.blank-text .bl.bad { border-bottom-color: var(--bad); color: var(--bad); background: var(--bad-soft); animation: blankIn var(--dur-3) var(--ease-out); }
@keyframes blankIn {
  from { transform: scale(.94); }
  to   { transform: scale(1); }
}
/* 多空题里按回车跳到下一个空：淡淡扩散一圈，让人知道光标跑哪去了。
   ⚠️ 用 box-shadow 而不是 margin/padding —— 那个会把整行文字挤动。 */
.blank-text .bl.jump { animation: blankJump .5s var(--ease-out); }
@keyframes blankJump {
  0%   { box-shadow: 0 0 0 0 var(--brand-ring); }
  55%  { box-shadow: 0 0 0 6px var(--brand-ring); }
  100% { box-shadow: 0 0 0 0 rgba(255, 255, 255, 0); }
}
/* ⚠️ 原来这里有一条 .blank-text .fixed-ans —— 那是"答错时在空后面插一个 (正确答案)"。
   2026-10-12 起挖空题答错走「解析 + 原地重做」（**不给答案**），那个 span 没人再插了，
   所以把这条样式删掉。 */

/* 快捷键键帽：按钮上那个「回车」，以及练习页底部那行提示。
   做成"键帽"的样子（底下厚一点），一眼就知道是让你按键、不是按钮文字。 */
kbd {
  display: inline-block;
  min-width: 22px;
  padding: 0 5px;
  margin-left: 3px;
  border: 1px solid var(--line-strong);
  border-bottom-width: 2px;
  border-radius: var(--radius-xs);
  background: var(--surface-2);
  color: var(--text-2);
  font-family: inherit;
  font-size: 12px;
  line-height: 17px;
  text-align: center;
  white-space: nowrap;
  vertical-align: 1px;
}
/* 品牌底的主按钮上要反过来配色，否则深底上是一块灰 */
.btn-primary kbd {
  border-color: rgba(255, 255, 255, .5);
  background: rgba(255, 255, 255, .18);
  color: #fff;
}

/* 答错了要抄几遍（练习页）：答案摆在上面，下面一行抄一遍，抄对了打勾。
   ⚠️ 抄没抄完直接决定「下一题」能不能点 —— 这是这个功能的全部意义，别改成"只提示"。 */
.copy-task {
  margin-top: 20px;
  padding: 16px 18px;
  border: 1px solid var(--line-strong);
  border-left: 3px solid var(--bad);
  border-radius: var(--radius);
  background: var(--surface-2);
}
.copy-task .ct-head { font-size: 15px; font-weight: 600; }
.copy-task .ct-head b { color: var(--bad); font-size: 17px; }
.copy-task .ct-target {
  display: inline-block;
  margin-left: 6px;
  padding: 2px 10px;
  border-radius: var(--radius-xs);
  background: var(--surface);
  border: 1px solid var(--line-strong);
  color: var(--brand-strong);
  font-weight: 600;
  letter-spacing: .3px;
}
.copy-task .ct-tip { margin-top: 6px; font-size: 12px; color: var(--text-2); }
.copy-task .ct-tip.ok { color: var(--ok); }
.copy-task .ct-rows { margin-top: 12px; display: flex; flex-direction: column; gap: 8px; }
.copy-task .ct-row { display: flex; align-items: center; gap: 10px; }
.copy-task .ct-no {
  flex: 0 0 20px; height: 20px; line-height: 20px;
  border-radius: 50%; background: var(--surface-3); color: var(--text-2);
  font-size: 12px; text-align: center;
}
.copy-task .ct-in {
  flex: 1 1 auto; min-width: 0;
  padding: 9px 12px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
  font-family: inherit;
  font-size: 16px;
  color: var(--text);
  transition: border-color var(--dur-2) var(--ease), background var(--dur-2) var(--ease),
              color var(--dur-2) var(--ease);
}
.copy-task .ct-in:focus { outline: none; border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-ring); }
.copy-task .ct-in.ok { border-color: var(--ok); color: var(--ok); background: var(--ok-soft); }
.copy-task .ct-in.bad { border-color: var(--bad); background: var(--bad-soft); }
/* 抄错了抖一下（幅度做小，别晃眼） */
.copy-task .ct-in.shake { animation: ctShake .3s var(--ease); }
@keyframes ctShake {
  0%, 100% { transform: translateX(0); }
  25%      { transform: translateX(-3px); }
  75%      { transform: translateX(3px); }
}
.copy-task .ct-mark { flex: 0 0 18px; color: var(--ok); font-weight: 700; text-align: center; }

/* 提示条 */
.toast {
  position: fixed; left: 50%; bottom: 32px; transform: translateX(-50%) translateY(8px);
  background: #1b1f27; color: #fff; font-size: 14px;
  padding: 10px 20px; border-radius: 999px; z-index: 100;
  box-shadow: var(--sh-3);
  opacity: 0; pointer-events: none;
  transition: opacity var(--dur-2) var(--ease), transform var(--dur-3) var(--ease-out);
}
.toast.show { opacity: .95; transform: translateX(-50%) translateY(0); }

/* 确认弹窗（不用原生 confirm，避免在嵌入式预览里被拦截）
   ⚠️ 内容比屏幕高时必须能滚（「每日练习」小窗有 4 个委托 + 几条建议，很容易超屏）：
      外层 align-items 用 flex-start + 弹窗自己 margin:auto ——
      短内容照样居中，长内容从顶部开始、由遮罩滚动。
      老版本是 align-items:center 且谁都不滚，超出的部分**直接看不到**（用户 2026-09-28 报的）。 */
.modal-mask {
  position: fixed; inset: 0; z-index: 200;
  background: rgba(20, 25, 33, .42);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
  display: flex; align-items: flex-start; justify-content: center;
  padding: 24px;
  overflow-y: auto;
  animation: maskIn var(--dur-2) var(--ease) backwards;
}
@keyframes maskIn { from { opacity: 0; } to { opacity: 1; } }
.modal {
  background: var(--surface);
  border-radius: var(--radius);
  padding: 22px 24px 18px;
  width: 100%; max-width: 400px;
  margin: auto;                 /* 短则居中、长则贴顶（配合遮罩的 overflow-y: auto） */
  box-shadow: var(--sh-4);
  animation: modalIn var(--dur-3) var(--ease-out) backwards;
}
@keyframes modalIn {
  from { opacity: 0; transform: translateY(10px) scale(.97); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}
.modal-title { font-size: 16px; font-weight: 600; margin-bottom: 8px; }
.modal-msg { font-size: 14px; line-height: 1.7; color: var(--text-2); margin-bottom: 20px; white-space: pre-line; }
.modal-actions { display: flex; gap: 10px; justify-content: flex-end; }

/* 底部 */
.foot { padding: 32px 0 48px; text-align: center; font-size: 12px; color: var(--text-3); }

/* ============================ 页面版本号（右下角） ============================
 * 由 `assets/version.js` 插进来，版本号来自页面自己的 `<body data-ver="v1.0">`。
 * 规则：**两个数都按「这次改动的条数」加**（用户 2026-10-15 定稿，不是每次 +1、不归零）——
 *      小数点前 = 整页 / 功能 / 模式调整 + 新模块 / 新功能 / 新模式的**条数**；
 *      小数点后 = 修 bug / 换图这类小调整的**条数**。详见 `MEMORY.md` §3 铁律 15。
 * ⚠️ 就在**右下角**。小鸟（`bird.js`）原来也蹲这儿，所以它的 `bottom` 已经**抬高**
 *    （见 bird.css）——改这边位置时记得两边一起看，别又叠上。
 * ⚠️ 纯标记，`pointer-events: none`，别挡住下面的东西。
 * ⚠️ 整页是场景的页面（如已归档的农场）不用这条 —— 它自带一套渲染，版本号跟着场景里的东西摆。 */
.ver-tag {
  position: fixed;
  right: 14px; bottom: 10px;
  z-index: var(--z-ver);
  font-size: 11.5px; font-weight: 600;
  letter-spacing: .3px;
  color: var(--text-3);
  font-variant-numeric: tabular-nums;
  opacity: .8;
  user-select: none;
  pointer-events: none;
}
@media (max-width: 640px) { .ver-tag { right: 10px; bottom: 8px; font-size: 11px; } }

/* ====================== 小程序页左上角的「← 退出」 ======================
 * 2026-10-18 用户：「退出键统一放在左上角，小程序中心和教材的返回键也改到左上角」。
 * 用法：在页面 `.wrap` 的**最前面**放一行
 *   <a class="app-exit" href="...">← 退出</a>
 * ⚠️ 是**普通流元素**（不是 fixed）：闹钟 / 小程序中心上面有顶栏，塞进 fixed 会撞到品牌。
 *    放在内容区左上角，三页看起来一致。 */
.app-exit {
  display: inline-flex; align-items: center; gap: 6px;
  margin: 0 0 12px;
  padding: 7px 14px;
  border: 1px solid var(--line, #e4e7ed);
  border-radius: 8px;
  background: var(--surface, #fff);
  color: var(--text-2, #58616f);
  font-size: 13px; line-height: 1;
  text-decoration: none;
  transition: background var(--dur-1, .13s) var(--ease, ease),
              color var(--dur-1, .13s) var(--ease, ease);
}
.app-exit:hover { background: var(--surface-3, #eef1f5); color: var(--text, #161a20); }
.app-exit:active { transform: scale(.98); }

/* ============================ 总门户的「← 返回 」 ============================
 * 2026-10-10：总门户（`index.html`，选学科那个页面）不再要顶上那条栏 ——
 * 由 `assets/home-back.js` 把 `.topbar` 整条收走（走 body.topbar-hidden），
 * 换成左上角这一个按钮，点它回到"切过来时的那个学科模块"。
 * ⚠️ 它是 `position: fixed`、贴在左上角（不是塞进 .wrap 里）——
 *    这样内容从屏幕最顶上开始，不会先空出一条。 */
.home-back {
  position: fixed;
  left: 20px; top: 14px;
  z-index: var(--z-topbar-base);
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 14px 7px 11px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--text-2);
  font-size: 14px;
  line-height: 1;
  text-decoration: none;
  box-shadow: var(--sh-1);
  transition: background var(--dur-2) var(--ease), color var(--dur-2) var(--ease),
              border-color var(--dur-2) var(--ease), transform var(--dur-1) var(--ease);
}
.home-back:hover {
  background: var(--surface-2);
  color: var(--text);
  border-color: var(--line-strong);
  text-decoration: none;
}
.home-back:active { transform: scale(.96); }
.home-back svg { display: block; }
.home-back .sub { font-weight: 600; color: var(--brand); }
/* 不知道从哪儿来的时候（从没进过学科模块）→ 按钮淡掉、点不动，
   不留一个"点了没反应"的死按钮。 */
.home-back.is-void {
  opacity: .45;
  pointer-events: none;
}
/* 浮窗开着时顶栏会抬到 --z-topbar，这个按钮得跟着抬，否则被浮窗压住点不着 */
body.wb-win-open .home-back { z-index: calc(var(--z-topbar) + 1); }

/* 总门户：顶栏没了，内容从顶上开始。
   ⚠️ 上面那条「← 返回」是 fixed 的，贴在最左上角（top:14 高约 32）——
      内容区要给它让出这一段，否则「索引云」那个大标题会钻到按钮底下。 */
body.home-page { padding-top: 0; }
body.home-page .wrap { padding-top: 56px; }
@media (max-width: 640px) {
  .home-back { left: 12px; top: 10px; padding: 6px 12px 6px 9px; font-size: 13px; }
  body.home-page .wrap { padding-top: 50px; }
}

/* 宽弹窗（文本导出用） */
.modal-wide { max-width: 640px; }
.modal-wide textarea { font-family: ui-monospace, Consolas, "Microsoft YaHei", monospace; }

/* 视图切换：每次 go() 切进来时轻微上浮淡入 */
.view-enter { animation: viewIn var(--dur-3) var(--ease-out); }
@keyframes viewIn {
  from { opacity: 0; transform: translateY(7px); }
  to   { opacity: 1; transform: translateY(0); }
}
/* 列表行统一淡入 */
.fade-row { animation: fadeRow var(--dur-3) var(--ease-out) backwards; }
@keyframes fadeRow {
  from { opacity: 0; transform: translateY(5px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ============================================================================
   学科首页紧凑化（2026-09-30）
   —— 目的：一屏能看见 3~4 个功能，不再「滑一下只剩一个」。
   —— 做法：只新增规则，不覆盖已有的 `.card` / `.tbl` / `.bars` 基础定义；
            靠 `.dense`（主体紧凑）与 `.unit-table`（单元一览）这套新类来切换。
            不给 `.card` 全局改内边距，避免影响练习页的题卡。
   —— 全部用 :root 的 token，不硬编码。 */

/* 卡片间距收一点：16px → 12px（只作用于加了 .dense 的容器） */
.dense > .card { margin-bottom: 12px; }
.dense > .card:last-child { margin-bottom: 0; }

/* 紧凑卡：内边距 20/22 → 14/16，标题下边距收一半 */
.card.card-tight { padding: 14px 16px; }
.card.card-tight h2 { font-size: 15px; margin-bottom: 2px; }
.card.card-tight .sub { margin-bottom: 10px; }
.card.card-tight .row-between.mb8,
.card.card-tight > .row-between { margin-bottom: 6px; }

/* 统计格紧凑：14/16 → 10/12，数字略小 */
.stat-grid.stat-tight { gap: 8px; }
.stat-grid.stat-tight .stat { padding: 10px 12px; }
.stat-grid.stat-tight .stat .k { font-size: 11px; }
.stat-grid.stat-tight .stat .v { font-size: 19px; }
/* 卡片被 `grow-last` 拉高时，让数字格跟着长高填满 ——
   否则卡片底部会空出一块，看着像没做完（英语首页「学习档案」踩过）。 */
.card.stat-card { display: flex; flex-direction: column; }
.card.stat-card .stat-grid { flex: 1 1 auto; }
.stat-grid.fill { grid-auto-rows: minmax(0, 1fr); }

/* ⚠️⚠️ 只把格子撑高还不够 —— **数字还是死写 19px**：卡片被撑到 462px 时，
   130px 高的格子里躺着一个 19px 的数字，旁边全是空。
   用户 2026-10-06 直接截了图："咱又不是没空，旁边那么大的空看不见吗？把字体调大"。

   ⚠️⚠️ **2026-10-07 又改回「固定字号」** —— 中间那版是让数字**跟着卡片高度长**
      （容器查询单位 `cqh`）：`clamp(17px, 6.4cqh, 28px)`。
      问题：**卡片有多高是两个页面自己决定的**，所以同一张「学习档案」在
      英语首页（卡片 363px → 数字 19.6px）和数学首页（462px → 26px）**字号不一样**。
      用户要求"学习档案的字体大小和数学的统一"。
      改法：**两个值都写死**（= 数学那一版量出来的实际值）：
        · 标签 `.k`：12px（数学原先 cqh 算出来是 11.76px）
        · 数字 `.v`：**26px**（数学原先 25.96px）
        · 数字后面的小尾巴 `.tiny`：`.55em`（跟着 26px → 14.3px，和数学一致）
      `container-type: size` 也一并删了 —— 它当初只为 `cqh` 服务。
      ⚠️ **别再改回 cqh / vh / % 这类"随容器变"的单位** —— 一变两个页面就又不一样了。
      ⚠️ 幅度调过几轮：19px → 11.5cqh（太大）→ 7.8cqh → 6.4cqh → 现在写死 26px。
        要再调就只动下面这两个 `font-size`。

   ⚠️ 只在 **≥901px** 生效（跟 `.home-cols` 折成单栏的那个断点对齐）。
      窄屏是单栏，卡片高度由**内容**撑出来，走下面 `.stat-tight` 那套 19px。
   ⚠️ `min-height` 是兜底：万一哪天左栏特别矮、把卡片挤扁，格子也不至于被压成 0 高
      （那样数字会溢出卡片）。 */
@media (min-width: 901px) {
  .card.stat-card .stat-grid { min-height: 168px; }
  .card.stat-card .stat {
    display: flex; flex-direction: column; justify-content: center;
  }
  .card.stat-card .stat .k { font-size: 12px; }
  .card.stat-card .stat .v { font-size: 26px; }
  /* 数字后面那个小尾巴（「%」「天」）跟着一起放大，别一个大字旁边挂个小字 */
  .card.stat-card .stat .v .tiny { font-size: .55em; }
}

/* ---- 单元一览：一行一个单元（2026-10-06 重做）----
   把原来「练习」那张单元格子（旧的 `.unit-grid` + `.unit-pick`）和
   「单元掌握度」那张网格（旧的 `.mast-grid` + `.mast-cell`）**合并成这张表** ——
   那两处都在列同样的 8 个单元，是首页看着乱的主因；而且用户"找不到每个单元的正确率"。
   一行四列：单元名 / 正确率（大号 + 色条）/ 掌握·到期 / 分段开练（A·B·C）。
   ⚠️ 正确率高 = 绿、低 = 红 —— 中国习惯（红=错、绿=对）。 */
.unit-table {
  --u-cols: minmax(0, 1.15fr) minmax(0, 1fr) 138px 200px;
  display: flex;
  flex-direction: column;
}
/* 表头：让这张表一眼看出是"表"，不是一堆散落的块 */
.u-head {
  display: grid;
  grid-template-columns: var(--u-cols);
  gap: 14px;
  padding: 0 10px 7px;
  font-size: 11px;
  color: var(--text-3);
  border-bottom: 1px solid var(--line);
}
.u-row {
  display: grid;
  grid-template-columns: var(--u-cols);
  align-items: center;
  gap: 14px;
  padding: 9px 10px;
  border-radius: var(--radius-sm);
  cursor: pointer;
  border-bottom: 1px solid var(--line);
  transition: background var(--dur-2) var(--ease), box-shadow var(--dur-2) var(--ease);
}
.u-row:last-child { border-bottom: 0; }
.u-row:hover { background: var(--surface-2); }
.u-row.on { background: var(--brand-soft); box-shadow: inset 3px 0 0 var(--brand); }
.u-name {
  font-size: 14px; font-weight: 600; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.u-cnt { font-size: 11px; font-weight: 400; color: var(--text-3); margin-left: 7px; }
.u-acc { display: flex; align-items: center; gap: 9px; }
.u-acc b {
  flex: 0 0 auto; min-width: 46px; text-align: right;
  font-size: 17px; line-height: 1; font-variant-numeric: tabular-nums;
  color: var(--text-2);
}
.u-track { flex: 1 1 auto; height: 7px; background: var(--surface-3); border-radius: 999px; overflow: hidden; }
.u-fill { height: 100%; border-radius: 999px; background: var(--brand); transition: width var(--dur-3) var(--ease-out); }
.u-acc.ok b { color: var(--ok); }
.u-acc.ok .u-fill { background: var(--ok); }
.u-acc.warn b { color: var(--warn); }
.u-acc.warn .u-fill { background: var(--warn); }
.u-acc.bad b { color: var(--bad); }
.u-acc.bad .u-fill { background: var(--bad); }
.u-acc.none b { font-size: 14px; color: var(--text-3); }
.u-acc.none .u-fill { background: var(--line-strong); }
.u-meta { font-size: 12px; color: var(--text-3); display: flex; align-items: center; gap: 9px; font-variant-numeric: tabular-nums; }
.u-due { color: var(--bad); font-weight: 600; }
.u-parts { display: flex; gap: 6px; }
.u-parts .part-mini { flex: 1 1 0; }

/* 最近练习表：行高压一点 */
.card-tight .tbl th, .card-tight .tbl td { padding-top: 7px; padding-bottom: 7px; }

/* 紧凑卡里的折线图/柱状图限高 —— `.lc` 是 height:auto 跟着宽度缩的，
   在 500px 宽的格子里会渲染到 253px 高（听写统计那张卡就这么撑到 327px）。
   ⚠️ 用 max-height + 保持比例，别写死 height（会拉伸变形）。 */
.card-tight .chart-box .lc { max-height: 132px; }
.card-tight .bars { height: 112px; }

/* ---- 学科首页「左右双栏」（2026-10-01 加，方案三） ----
   左栏 = 要动手的（今天要做的 / 点一下就开练），右栏 = 看着的数据（档案 / 掌握度 / 近 7 天）。
   一屏看完不用滚。窄屏自动折成单栏。
   ⚠️ 列内卡片的间距交给 `.home-col` 的 gap —— 必须把 `.card` 自己的 margin-bottom 归零，
      否则「gap + margin」两份间距叠在一起，又变回松散。 */
.home-cols {
  display: grid;
  grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr);
  gap: 12px;
  align-items: start;
  margin-bottom: 12px;
}
.home-col { display: flex; flex-direction: column; gap: 12px; min-width: 0; align-self: start; }
.home-col > .card { margin-bottom: 0; }
/* ⚠️ 2026-10-05：两栏高度差太大（左 732 / 右 874，右边空一截），把**左栏最后一张卡**
   撑满到与右栏齐平 —— 做法是让左栏整体拉伸，再让打 `.grow-last` 的那张卡吃掉剩余空间。
   ⚠️ 只给"内容本身能被撑开"的卡打这个类（练习卡里是网格，撑开只是多留点呼吸空隙）；
   像「今天要做的」那种一行行清单撑开会变成中间一片空白，反而更难看，所以不打。 */
.home-cols.stretch { align-items: stretch; }
.home-cols.stretch .home-col { height: 100%; }
.home-col > .card.grow-last { flex: 1 1 auto; }

/* 「今天要做的」清单：一行一件事，右边跟一个动作按钮 */
.todo-row {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 0; border-bottom: 1px solid var(--line);
}
.todo-row:last-child { border-bottom: 0; }
.todo-mark {
  width: 18px; height: 18px; flex: 0 0 auto; border-radius: 50%;
  border: 1.5px solid var(--line-strong);
  display: flex; align-items: center; justify-content: center;
  font-size: 11px; line-height: 1; color: #fff;
}
.todo-row.done .todo-mark { background: var(--ok); border-color: var(--ok); }
.todo-row.done .todo-mark::after { content: "✓"; }
.todo-row.done .todo-text { color: var(--text-3); text-decoration: line-through; }
.todo-text { flex: 1; font-size: 14px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.todo-note { font-size: 12px; color: var(--text-3); flex: 0 0 auto; }
.todo-note.hot { color: var(--bad); font-weight: 600; }
.todo-act { flex: 0 0 auto; }

/* ------------------------------------------------------------------ *
 * 分段开练的小按钮 A / B / C（2026-10-03 加；2026-10-06 搬到「单元一览」行里）
 *
 * 用户要求"这里也把 ABC 部分加上"—— 跟练习页那张「单练」列表对齐，
 * 省得为了只练 1/3 还得先跳去练习页。比练习页紧凑：一排小按钮。
 * ------------------------------------------------------------------ */
.part-minis { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.part-mini {
  flex: 1 1 0;
  min-width: 54px;                 /* 太窄就换行，别把「A 22」挤成两行 */
  font: inherit;
  font-size: 12px;
  line-height: 1.2;
  padding: 5px 4px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-xs);
  background: var(--surface);
  color: var(--text-2);
  cursor: pointer;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
  transition: border-color var(--dur-2) var(--ease),
              color var(--dur-2) var(--ease),
              background var(--dur-2) var(--ease),
              box-shadow var(--dur-2) var(--ease),
              transform var(--dur-1) var(--ease);
}
.part-mini b { font-weight: 600; color: var(--text-3); }
.part-mini:hover {
  border-color: var(--brand);
  color: var(--brand);
  background: var(--brand-soft);
  box-shadow: var(--sh-1);
}
.part-mini:hover b { color: var(--brand); }
.part-mini:active { transform: scale(.97); }
/* 选中那一行的底色是品牌浅色，小按钮要浮出来一点才看得清 */
.u-row.on .part-mini { background: var(--surface); }
.u-row.on .part-mini:hover { background: var(--brand-soft); }

@media (max-width: 900px) {
  .home-cols { grid-template-columns: 1fr; }
  /* 单元一览：一行放不下四列 → 折成两行（名字 + 正确率 / 掌握 + 分段开练） */
  .u-head { display: none; }
  .unit-table { --u-cols: minmax(0, 1fr) 152px; }
  .u-row {
    grid-template-areas: "name acc" "meta parts";
    row-gap: 8px;
  }
  .u-name { grid-area: name; }
  .u-acc { grid-area: acc; }
  .u-meta { grid-area: meta; }
  .u-parts { grid-area: parts; justify-content: flex-end; }
  .u-parts .part-mini { flex: 0 1 auto; min-width: 50px; }
}

@media (max-width: 640px) {
  .page-head { padding: 24px 0 16px; }
  .page-head h1 { font-size: 22px; }
  .card { padding: 16px; }
  .card.card-tight { padding: 13px 14px; }
  /* 单元一览：名字后面那个「65 条」挤不下就收起来 */
  .u-cnt { display: none; }
  .u-acc b { min-width: 40px; font-size: 16px; }
  .part-mini { padding: 5px 4px; }
  .prompt-card { padding: 26px 16px; }
  .prompt-hint { font-size: 18px; }
  .answer-input { font-size: 20px !important; }
  .speaker { width: 88px; height: 88px; font-size: 32px; }
  .blank-text .bl { width: 92px; font-size: 16px; }
  /* 练习入口列表：单练的 A/B/C 三列在窄屏改成两列（第三段换行） */
  .prac-row { grid-template-columns: 1fr; gap: 6px; padding: 8px 10px; }
  .pr-name { white-space: normal; }
  .pr-segs { grid-template-columns: repeat(3, 1fr); gap: 6px; }
  .prac-go.seg { padding: 6px 6px; }
  .prac-go.seg .pg-n { font-size: 11px; }
  /* 题型两栏在窄屏折成一列（左栏在上、右栏在下） */
  .prac-two { grid-template-columns: 1fr; gap: 12px; }
  .prac-go { padding: 9px 10px; }
  .prac-go .pg-t { font-size: 13px; }
  .prac-go .pg-n { font-size: 11.5px; }
  /* 听写统计：录入行和两张曲线都改成上下排 */
  .dict-form { grid-template-columns: 1fr 1fr; }
  .dict-form .dict-f:first-child { grid-column: 1 / -1; }
  .dict-act { grid-column: 1 / -1; }
  .dict-act .btn { flex: 1; }
  .chart2 { grid-template-columns: 1fr; }
}
/* ================================================================== *
 * 答题页改版：参考多邻国的「答题方式」（2026-10-17）
 *
 * 三格 flex 柱：顶栏（不滚） / 题目（只有这一格滚） / 底部那条（正常流）。
 * ⚠️⚠️ **底部按钮绝对不要改成 position:fixed 浮上去** ——
 *    样板阶段踩过：窗口一矮，浮起来的按钮会把「提示 / 不会」整排压住，
 *    看起来就是"按钮凭空消失"（用户 2026-10-17 12:10 报的）。
 *    现在是文档流里的一格，永远不会盖住任何东西。
 *
 * ⚠️⚠️ **所有选择器都必须挂在 `.en-run` 下**（english/index.html 的 #pracRun 上加了这个类）。
 *    **数学页也有 `#pracRun` / `#pracSubmitBtn` / `.progress-track`** ——
 *    不加这层限定，数学的答题页会被这套版式一起改掉（2026-10-17 差点踩）。
 * ⚠️ id 一个都没改（pracSubmitBtn / pracNextBtn / pracHintBtn / pracRevealBtn /
 *    pracQuitBtn / pracKeys / pracFeedback / pracCounter ...），e2e 照旧能用。
 * ================================================================== */

/* 答题时把「练习」那个大标题和页脚都收起来 —— 全屏答题不该留一截页头/页脚，
   否则整页会多出 80 多像素的滚动条（量过：.foot 正好占 83px）。 */
#view-practice:has(.en-run:not(.hidden)) .page-head { display: none; }
body:has(.en-run:not(.hidden)) .foot { display: none; }

.en-run {
  display: flex; flex-direction: column;
  height: calc(100vh - var(--topbar-h) - 16px);
  min-height: 380px;              /* 窗口特别矮时别被压成一条缝 */
}
body.topbar-hidden .en-run { height: calc(100vh - 16px); }

/* ---------- 顶栏 ---------- */
.en-run .prac-head { flex: 0 0 auto; padding: 2px 0 6px; }
.en-run .prac-headrow { display: flex; align-items: center; gap: 14px; }
.en-run .prac-x {
  flex: 0 0 36px; width: 36px; height: 36px; padding: 0;
  display: grid; place-items: center;
  border: 0; border-radius: 50%; background: transparent;
  color: var(--text-3); font-size: 16px; line-height: 1; cursor: pointer; font-family: inherit;
  transition: background var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
}
.en-run .prac-x:hover { background: var(--surface-3); color: var(--text); }
.en-run .prac-x:focus-visible { outline: 2px solid var(--brand-ring); outline-offset: 2px; }

/* 进度条：从 6px 加粗到 14px（细条在整屏里太不显眼） */
.en-run .progress-track { flex: 1; height: 14px; margin-bottom: 0; }
.en-run .progress-fill {
  background: var(--brand);
  box-shadow: inset 0 -3px 0 rgba(0, 0, 0, .1), inset 0 2px 0 rgba(255, 255, 255, .25);
}
.en-run .prac-count {
  flex: 0 0 auto; font-size: 12.5px; color: var(--text-3);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}

/* 第二行：提示 / 不会 在左，题型标签 + 得分在右 */
.en-run .prac-headsub { display: flex; align-items: center; gap: 8px; margin-top: 8px; }
.en-run .prac-headsub .prac-aid { padding: 5px 12px; font-size: 12.5px; border-radius: 999px; }
.en-run .prac-tags {
  margin-left: auto; display: flex; align-items: center; gap: 6px;
  font-size: 12.5px; color: var(--text-3);
}
.en-run .prac-tags .pill { font-size: 11.5px; }

/* 快捷键那行：挪到顶栏，别跟着题目滚走（读单词那一关会把它换成提示语） */
.en-run #pracKeys { margin: 6px 0 0; min-height: 17px; }

/* ---------- 中间：只有这一格滚 ---------- */
.en-run .prac-scroll {
  flex: 1 1 auto; min-height: 0; overflow-y: auto;
  scrollbar-width: thin; scrollbar-color: #cbd3df transparent;
}
.en-run .prac-scroll::-webkit-scrollbar { width: 10px; }
.en-run .prac-scroll::-webkit-scrollbar-track { background: transparent; }
.en-run .prac-scroll::-webkit-scrollbar-thumb { background: #cbd3df; border-radius: 999px; border: 3px solid var(--bg); }
.en-run .prac-inner { padding: 6px 2px 10px; }

/* 题目卡：不再是一张白卡（整屏答题时"卡片套卡片"很重） */
.en-run .prompt-card {
  background: transparent; border: 0; box-shadow: none;
  padding: 18px 0 6px; margin-bottom: 6px;
}
.en-run .prompt-hint { font-size: 24px; letter-spacing: -.2px; }
.en-run .prompt-small { font-size: 14px; margin-top: 8px; }

/* 单词题的输入框：加高、圆角、focus 有品牌色边 */
.en-run .answer-input {
  height: 58px !important;
  font-size: 22px !important;
  border: 2px solid var(--line) !important;
  border-radius: 16px !important;
  box-shadow: 0 2px 0 var(--line) !important;
  transition: border-color var(--dur-2) var(--ease), box-shadow var(--dur-2) var(--ease);
}
.en-run .answer-input:focus {
  border-color: var(--brand) !important;
  box-shadow: 0 2px 0 var(--brand-strong) !important;
  outline: none;
}

/* 选项：大卡片（62px 高 / 圆角 16 / 底部 2px 硬边） */
.en-run .choice-opts { gap: 12px; }
.en-run .choice-opt {
  min-height: 62px; padding: 10px 18px; gap: 14px;
  font-size: 17px;
  border: 2px solid var(--line); border-radius: 16px;
  box-shadow: 0 2px 0 var(--line);
  transition: background var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease),
              box-shadow var(--dur-1) var(--ease), transform var(--dur-1) var(--ease),
              opacity var(--dur-2) var(--ease);
}
.en-run .choice-opt:hover:not(:disabled) { transform: none; background: var(--surface-2); }
.en-run .choice-opt:active:not(:disabled) { transform: translateY(2px); box-shadow: 0 0 0 var(--line); }
.en-run .choice-opt .co-k {
  width: 28px; height: 28px; flex: 0 0 28px;
  border: 2px solid var(--line-strong); background: #fff; color: var(--text-3);
  font-size: 12.5px;
}
.en-run .choice-opt.ok {
  border-color: var(--ok) !important; background: var(--ok-soft); color: #15805e;
  box-shadow: 0 2px 0 #15805e;
}
.en-run .choice-opt.ok .co-k { background: var(--ok); border-color: var(--ok); color: #fff; }
.en-run .choice-opt.bad {
  border-color: var(--bad) !important; background: var(--bad-soft); color: #b03636;
  box-shadow: 0 2px 0 #b03636;
}
.en-run .choice-opt.bad .co-k { background: var(--bad); border-color: var(--bad); color: #fff; }
.en-run .choice-opt.dim { opacity: .6; }
/* 判对的那张轻轻涨一下（幅度按项目规矩压到 1.018） */
.en-run .choice-opt.ok { animation: enOptPop var(--dur-3) var(--ease-out) both; }
@keyframes enOptPop {
  0%   { transform: scale(1); }
  42%  { transform: scale(1.018); }
  100% { transform: scale(1); }
}

/* 短文：不再自己滚（滚动区已经是外面那格，别弄出两条滚动轴） */
.en-run .passage-box { max-height: none; overflow: visible; margin-bottom: 14px; }

/* ---------- 底部那一条 ---------- */
.en-run .prac-dock {
  flex: 0 0 auto;
  padding: 8px 0 2px;
  border-top: 1px solid var(--line);
  background: var(--bg);
}
/* 判分结果做成"底部反馈条"：绿=对 / 红=错，紧贴在按钮上面 */
.en-run #pracFeedback:not(:empty) { padding: 8px 0; }
.en-run #pracFeedback .verdict {
  margin: 0; padding: 11px 15px; border-radius: 14px;
  font-size: 14.5px; text-align: left;
  max-height: 128px; overflow-y: auto;      /* 解析太长别把按钮顶出屏幕 */
  animation: fadeUp var(--dur-3) var(--ease-out);
}
.en-run #pracFeedback .verdict.ok  { background: var(--ok-soft);  color: #15805e; }
.en-run #pracFeedback .verdict.bad { background: var(--bad-soft); color: #b03636; }
.en-run #pracFeedback .verdict .big { font-size: 15.5px; font-weight: 700; }
.en-run #pracFeedback .verdict .right { font-size: 15.5px; margin-top: 4px; color: var(--text); }

/* 大按钮：整宽 54px + 底部 4px 硬边（按下去像真的被按下去） */
.en-run #pracSubmitBtn, .en-run #pracNextBtn {
  width: 100% !important;
  height: 54px;
  justify-content: center;
  font-size: 16px; font-weight: 700; letter-spacing: 2px;
  border-radius: 16px;
  margin-top: 6px;
  box-shadow: 0 4px 0 var(--brand-strong);
  transition: transform var(--dur-1) var(--ease), box-shadow var(--dur-1) var(--ease),
              filter var(--dur-1) var(--ease);
}
.en-run #pracSubmitBtn:hover, .en-run #pracNextBtn:hover { filter: brightness(1.05); }
.en-run #pracSubmitBtn:active, .en-run #pracNextBtn:active {
  transform: translateY(3px);
  box-shadow: 0 1px 0 var(--brand-strong);
}

/* 底部条里"两颗按钮都藏着"的时候（练选择的重做态就是这样：直接点另一个选项就行），
   整条别留一条空边框 —— 收成 0 高，省得看着像"按钮掉了"。 */
.en-run .prac-dock:has(#pracSubmitBtn.hidden):has(#pracNextBtn.hidden) {
  padding: 0; border-top-color: transparent;
}

/* 整屏答题时喇叭太大（108px 占了小半屏），压到 84px —— 只动英语答题页 */
.en-run .speaker { width: 84px; height: 84px; }
.en-run .speaker svg { width: 36px; height: 36px; }
.en-run .prompt-card { padding: 14px 0 2px; }

/* 选择题的 pane 原来是一张白卡 —— 新卡片式选项放在白底上会"白卡套白卡"、看不出边界，
   所以把容器去掉（选项卡自己就是卡片，直接落在页面底色上）。 */
.en-run #pracChoicePane {
  background: transparent; border: 0; box-shadow: none; padding: 0;
}

/* 选择题 / 阅读题的题干，在整屏里放大一点（原来是 18px，跟选项卡差不多重） */
.en-run .choice-q { font-size: 23px; font-weight: 700; letter-spacing: -.2px; margin-bottom: 18px; }

/* 挖空句子在整屏里也放大一点（原来 17px，跟按钮比太小） */
.en-run .blank-text { font-size: 21px; line-height: 2.5; }
.en-run .blank-text .bl { width: 140px; font-size: 21px; }

/* ---- 解析条里的分段排版（2026-10-17：解析从"一句话提示"改成"分段讲思路"）----
   结构由 app.js 的 rsec() / rsteps() 生成：
     .rd-sec > .rd-h（小标题）+ .rd-b（正文）
     .rd-steps（有序步骤）、.rd-note（开头那句"你有几个空没填对"）、.rd-dim（淡一点的补充） */
.redo-bar .rd-sec { margin-top: 9px; }
.redo-bar .rd-sec:first-child { margin-top: 0; }
.redo-bar .rd-h {
  font-size: 12px; font-weight: 700; color: var(--brand-strong);
  letter-spacing: .3px; margin-bottom: 2px;
}
.redo-bar .rd-b { font-size: 13.5px; line-height: 1.85; color: var(--text); }
.redo-bar .rd-note {
  margin-bottom: 8px; font-size: 13px; line-height: 1.7; color: var(--text-2);
  padding: 7px 10px; border-radius: 8px; background: rgba(24, 95, 165, .06);
}
.redo-bar .rd-dim { color: var(--text-3); font-size: 12.5px; }
.redo-bar .rd-steps { margin: 2px 0 0; padding-left: 20px; }
.redo-bar .rd-steps li { margin: 3px 0; font-size: 13.5px; line-height: 1.8; }

/* 答题页底部有一条 54px 的大按钮，toast 默认的 `bottom:32px` 正好压在它身上 ——
   提示文字把「检查 / 继续」盖住，看起来像按钮没字（2026-10-17 截图里发现的）。 */
body:has(.en-run:not(.hidden)) .toast { bottom: 104px; }

/* ---- 🎤 图标的「麦克风状态灯」（2026-10-17）----
   灰 = 识别器还没拿到音频；绿 = 正在收音；绿+放大 = 检测到说话；红 = 识别报错。
   判读方法见 app.js 里 micLamp() 的注释。
   ⚠️ 用 transform / filter 而不是 color —— `rb-mic` 里是 emoji，`color` 对彩色 emoji 不起作用。 */
.rb-mic {
  display: inline-block;
  transition: transform var(--dur-2) var(--ease), filter var(--dur-2) var(--ease),
              opacity var(--dur-2) var(--ease);
  opacity: .45;
}
.rb-mic.on  { opacity: 1; filter: drop-shadow(0 0 5px rgba(28, 160, 110, .75)); }
.rb-mic.snd { opacity: 1; transform: scale(1.22); filter: drop-shadow(0 0 9px rgba(28, 160, 110, .95)); }
.rb-mic.err { opacity: 1; filter: drop-shadow(0 0 6px rgba(200, 70, 70, .8)); }

/* ---- 「我读的」回放按钮（2026-10-18）----
   跟着读那一段会录下来，读完可以原速 / 0.7 倍速听自己刚才读的。
   没录到东西时整块 `hidden`，不占地方。 */
.rb-play {
  display: flex; gap: 8px; justify-content: center; align-items: center;
  margin-top: 10px;
}
.rb-pb {
  display: inline-flex; align-items: center; gap: 5px;
  height: 30px; padding: 0 13px;
  border: 1px solid var(--line-strong); border-radius: 999px;
  background: #fff; color: var(--text-2);
  font-family: inherit; font-size: 12.5px; font-weight: 600;
  cursor: pointer;
  transition: background var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease),
              color var(--dur-1) var(--ease), transform var(--dur-1) var(--ease);
}
.rb-pb:hover { background: var(--surface-2); border-color: var(--brand); color: var(--brand); }
.rb-pb:active { transform: translateY(1px); }
.rb-pb:focus-visible { outline: 2px solid var(--brand-ring); outline-offset: 2px; }

/* ---- 练试卷（2026-10-18）----
   ① 设置屏顶部那张"开始考试"的卡；② 成绩页顶部的总分大字。 */
.paper-hero {
  display: flex; align-items: center; justify-content: space-between;
  gap: 20px; flex-wrap: wrap;
  border-left: 3px solid var(--brand);
}
.paper-hero .ph-txt { min-width: 0; }
.paper-hero h2 { margin: 0 0 4px; }
.paper-hero .ph-btn { flex: none; }

.paper-score {
  display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap;
  padding: 18px 22px; margin-bottom: 18px;
  background: var(--surface-2); border-radius: var(--radius);
}
.paper-score .ps-num {
  font-size: 42px; font-weight: 700; line-height: 1;
  font-variant-numeric: tabular-nums; color: var(--text-1);
}
.paper-score .ps-num .ps-of {
  font-size: 18px; font-weight: 600; color: var(--text-3); margin-left: 2px;
}
.paper-score .ps-lvl { flex: none; }
.paper-score .ps-meta { flex: 1 1 100%; color: var(--text-3); }

/* ---- 练试卷右栏：选单元 + 多选 + 开始考试（2026-10-18）----
   ⚠️ 开关用"小方框打勾"的样子，别做成大按钮 —— 用户要的是"一个小按钮"。 */
.paper-pane { display: flex; flex-direction: column; gap: 12px; }
.pp-tools { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.pp-toggle {
  display: inline-flex; align-items: center; gap: 6px;
  height: 27px; padding: 0 11px;
  border: 1px solid var(--line-strong); border-radius: 999px;
  background: #fff; color: var(--text-2);
  font-family: inherit; font-size: 12.5px; font-weight: 600;
  cursor: pointer;
  transition: background var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease),
              color var(--dur-1) var(--ease);
}
.pp-toggle .pp-k {
  width: 12px; height: 12px; border-radius: 3px;
  border: 1.5px solid var(--line-strong); background: #fff;
  position: relative;
}
.pp-toggle.on { border-color: var(--brand); color: var(--brand); background: var(--brand-soft); }
.pp-toggle.on .pp-k { border-color: var(--brand); background: var(--brand); }
.pp-toggle.on .pp-k::after {
  content: ''; position: absolute; left: 3px; top: 0.5px;
  width: 4px; height: 7px; border: solid #fff; border-width: 0 1.5px 1.5px 0;
  transform: rotate(45deg);
}
.pp-toggle:hover { border-color: var(--brand); color: var(--brand); }
.pp-all {
  height: 27px; padding: 0 11px; border-radius: 999px;
  border: 1px solid var(--line-strong); background: #fff; color: var(--text-2);
  font-family: inherit; font-size: 12.5px; font-weight: 600; cursor: pointer;
}
.pp-all:hover { border-color: var(--brand); color: var(--brand); background: var(--surface-2); }
.pp-sum { margin-left: auto; font-size: 12.5px; color: var(--text-3); }

.pp-units { display: flex; flex-direction: column; gap: 6px; }
.paper-unit {
  display: flex; align-items: center; gap: 10px;
  width: 100%; padding: 9px 12px; text-align: left;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--text-1);
  font-family: inherit; font-size: 13.5px; cursor: pointer;
  transition: border-color var(--dur-1) var(--ease), background var(--dur-1) var(--ease);
}
.paper-unit:hover { border-color: var(--brand); background: var(--surface-2); }
.paper-unit .pu-box {
  flex: none; width: 15px; height: 15px; border-radius: 4px;
  border: 1.5px solid var(--line-strong); background: #fff; position: relative;
}
.paper-unit.on { border-color: var(--brand); background: var(--brand-soft); }
.paper-unit.on .pu-box { border-color: var(--brand); background: var(--brand); }
.paper-unit.on .pu-box::after {
  content: ''; position: absolute; left: 4px; top: 1px;
  width: 5px; height: 9px; border: solid #fff; border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}
.paper-unit .pu-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.paper-unit .pu-n { flex: none; font-size: 12px; color: var(--text-3); }

.pp-foot { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; padding-top: 2px; }
.pp-foot .pp-go { flex: none; }

/* ---- 右栏底部：把"等高拉出来的空白"用起来（2026-10-18 用户要求） ----
   试卷 → 「这张卷子考什么」构成表；其他题型 → 一行统计。 */
.pp-meta {
  margin-top: 4px; padding-top: 12px;
  border-top: 1px solid var(--line);
}
.pp-meta .ppm-t {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  font-size: 13px; font-weight: 700; color: var(--text-1); margin-bottom: 8px;
}
.pp-meta .ppm-t .tiny { font-weight: 500; color: var(--text-3); }
.pp-meta .ppm-t .tiny b { color: var(--brand); font-variant-numeric: tabular-nums; }
.ppm-tbl { width: 100%; border-collapse: collapse; }
.ppm-tbl td { padding: 4px 0; font-size: 12.5px; color: var(--text-2); }
.ppm-tbl td:first-child { color: var(--text-1); }
.ppm-tbl td:nth-child(2) { text-align: right; padding-right: 12px; }
.ppm-tbl td:last-child { text-align: right; font-variant-numeric: tabular-nums; width: 56px; }
.ppm-tbl tr + tr td { border-top: 1px dashed var(--line); }

/* 其他题型：列表下面那块空，放一行"这个题型一共有多少" */
.pane-fill {
  flex: 1 1 auto; min-height: 28px;
  display: flex; align-items: flex-end; justify-content: flex-end;
  padding-top: 10px;
}
.pane-fill .pf-line { font-size: 12.5px; color: var(--text-3); }
.pane-fill .pf-line b { color: var(--text-2); font-variant-numeric: tabular-nums; }

/* 右栏内容少的时候也要撑满容器，`.pane-fill` 的 flex:1 才有空间可分。
   ⚠️ 用 flex 而不是 min-height:100% —— 百分比高度在 flex 容器里解析不了（实测填充块只有 30px）。 */
.prac-pane-scroll { display: flex; flex-direction: column; }
.prac-pane-scroll > .prac-forms,
.prac-pane-scroll > .prac-list { flex: 1 1 auto; min-height: 0; }
/* ⚠️ 2026-10-18：内容比右栏矮的时候，让**列表项自己拉伸**填满 ——
   比“底下留一大块白”好得多（用户截图里就是这个）。每项设个上限，别拉得像大盶头。 */
/* 内容比右栏矮的时候，把项**均匀分开、铺满整栏**，不留大白块 */
.prac-pane-scroll > .prac-forms { justify-content: space-between; }
.prac-forms .prac-form, .paper-pane .paper-unit { flex: 0 0 auto; }

/* 试卷右栏：**列表自己滚，底部的「这张卷子考什么」固定看得到** ——
   不然那张构成表被滚到底下、用户还是觉得"下面白着"（2026-10-18）。 */
.paper-pane { height: 100%; display: flex; flex-direction: column; gap: 12px; min-height: 0; }
.paper-pane .pp-units { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; }
.paper-pane .pp-meta { flex: none; margin-top: 0; }

/* ============================================================================
   公式 / 口诀页共用版式（2026-10-18 从数学的公式页搬到公用）
   ----------------------------------------------------------------------------
   结构：左栏一列"篇章/章"导航 + 右栏"一行一条 + 点开详情"。
   数学页用的是 math.css 里同名的一套（那边还多几个数学专属小件），
   这里放的是**英语语法页要用的那部分**，两边类名一致、长得一样。
   ============================================================================ */
.fx-panes {
  display: grid;
  grid-template-columns: 236px minmax(0, 1fr);
  gap: 16px;
  align-items: start;
}
.fx-main { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
.fx-pane-h {
  padding: 12px 14px 8px;
  font-size: 12.5px; font-weight: 600;
  color: var(--text-3); letter-spacing: .4px;
}

/* ---- 左栏：篇章 ---- */
.fx-units { padding-bottom: 8px; }
.fx-panes .fx-units > div:not(.fx-pane-h) { padding: 0 8px 4px; }
.fx-unit {
  display: block; width: 100%;
  padding: 9px 11px; margin-bottom: 4px;
  border: 1px solid transparent; border-radius: var(--radius-xs);
  background: transparent; text-align: left;
  font-family: inherit; cursor: pointer;
  transition: background var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease);
}
.fx-unit:hover { background: var(--surface-2); }
.fx-unit.on { background: var(--brand-soft); border-color: var(--brand); }
.fx-unit-t { display: block; font-size: 14px; font-weight: 600; color: var(--text); line-height: 1.5; }
.fx-unit.on .fx-unit-t { color: var(--brand-strong); }
.fx-unit-s { display: block; margin-top: 2px; font-size: 12px; color: var(--text-3); line-height: 1.55; }
.fx-unit-n { display: block; margin-top: 4px; font-size: 11.5px; color: var(--text-3); opacity: .9; }

/* ---- 右栏头：标题 + 搜索 ---- */
.fx-search { display: flex; align-items: flex-end; gap: 12px; margin-top: 14px; flex-wrap: wrap; }
.fx-search .field { flex: 1; min-width: 220px; max-width: 460px; }
.fx-search .fx-count { margin-left: auto; white-space: nowrap; padding-bottom: 3px; }

/* ---- 右栏：一行一条 ---- */
.fx-rows { padding: 6px; }
.fx-sec-h {
  padding: 10px 12px 4px;
  font-size: 12px; font-weight: 600; color: var(--text-3); letter-spacing: .3px;
}
.fx-sec-h:first-child { padding-top: 5px; }
.fx-row {
  display: flex; align-items: center; gap: 11px;
  width: 100%; padding: 11px 12px;
  border: 1px solid transparent; border-bottom: 1px solid var(--line);
  border-radius: 0; background: transparent; text-align: left;
  font-family: inherit; cursor: pointer;
  transition: background var(--dur-1) var(--ease);
}
.fx-rows .fx-row:last-child { border-bottom-color: transparent; }
.fx-row:hover { background: var(--surface-2); }
.fx-row.on { background: var(--brand-soft); border-color: var(--brand); border-radius: var(--radius-xs); }
.fx-row-no {
  flex: 0 0 auto; min-width: 22px; height: 22px; padding: 0 6px;
  border-radius: 6px; background: var(--brand); color: #fff;
  font-size: 12px; font-weight: 600; line-height: 22px; text-align: center;
}
.fx-row-main { flex: 1; min-width: 0; }
.fx-row-t { display: block; font-size: 14.5px; font-weight: 600; color: var(--text); line-height: 1.55; }
.fx-row.on .fx-row-t { color: var(--brand-strong); }
.fx-row-s {
  display: block; margin-top: 2px; font-size: 12.5px; color: var(--text-3); line-height: 1.55;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.fx-row-go { flex: 0 0 auto; font-size: 12.5px; color: var(--brand-strong); opacity: .75; white-space: nowrap; }
.fx-row.on .fx-row-go { opacity: 1; }

/* ---- 详情卡（点开一条后显示） ---- */
.fx-card { overflow: hidden; }
.fx-head {
  display: flex; align-items: flex-start; gap: 12px;
  padding: 13px 16px;
  background: var(--surface-2); border-bottom: 1px solid var(--line);
}
.fx-hwrap { flex: 1; min-width: 0; }
.fx-h1 { font-size: 15.5px; font-weight: 600; color: var(--text); line-height: 1.6; }
.fx-hno {
  display: inline-block; min-width: 20px; height: 20px; margin-right: 8px; padding: 0 5px;
  border-radius: 5px; background: var(--brand); color: #fff;
  font-size: 12px; font-weight: 600; line-height: 20px; text-align: center; vertical-align: 2px;
}
.fx-crumb { margin-top: 4px; font-size: 12.5px; color: var(--text-3); }
.fx-crumb .fx-sep { margin: 0 6px; opacity: .6; }
.fx-empty-hint {
  padding: 26px 16px; border: 1px dashed var(--line-strong); border-radius: var(--radius);
  text-align: center; font-size: 13.5px; color: var(--text-3); background: var(--surface-2);
}
.fx-body { padding: 14px 16px; }
.fx-body .gra-mn { font-size: 14.5px; line-height: 1.9; }
.fx-body .gra-ex { font-size: 12.5px; color: var(--text-2); line-height: 1.95; margin-top: 8px; }
.fx-body .gra-ex b { color: var(--brand-strong); }

/* 语法页口诀有 80+ 条，列表要限高自己滚 —— 不然详情卡被顶到几屏之外，
   点了"没反应"（2026-10-18）。公式页条数少，没这问题。 */
#graList { max-height: 52vh; overflow-y: auto; overscroll-behavior: contain; }

/* 朗读得分常驻（2026-10-19）：read-bar 收起之后分数留在这儿。
   ⚠️ 用户在条子里看不到分数，就是因为条子过关即收 —— 这个必须一直在。 */
.rb-done {
  margin-top: 10px;
  font-size: 13px;
  font-weight: 600;
  color: var(--ok);
  text-align: center;
  font-variant-numeric: tabular-nums;
}
.rb-done.bad { color: var(--bad); }

/* ---- 语音区排成一行（2026-10-19）----
   原来喇叭下面竖着堆「慢速重播 / 我读的 / 朗读 N 分」三四行，又长又乱。
   现在一律横排：喇叭 · 慢速 · 拼读 · 我读的 · 分数。 */
.speak-row {
  display: flex; align-items: center; justify-content: center;
  gap: 10px; flex-wrap: wrap;
  margin-top: 14px;
}
.speak-row .speaker { width: 76px; height: 76px; margin: 0; }
.speak-row .rb-play { margin-top: 0; }
.speak-row .rb-done { margin-top: 0; }
.speak-row .voice-state { flex-basis: 100%; }

/* ---- 题干卡 / 短文卡：更柔的圆角和阴影 ---- */
.en-run .prompt-card {
  border-radius: 1.75rem;
  box-shadow: 0 1px 2px rgba(26,38,57,.03), 0 8px 22px rgba(26,38,57,.04);
}

/* ============================================================================
   答题屏 · 新外观（2026-10-24 按设计稿换皮）
   ----------------------------------------------------------------------------
   ⚠️ 只换"配色 / 圆角 / 胶囊"，**不动字号、不动 padding、不动尺寸** ——
      这屏的纵向空间已经卡得很紧（`#pracRun` 高度是算出来的），
      之前试过把题干放大到 1.5rem，直接把底部大按钮挤出屏幕，只能退回来。
   ⚠️ 全部挂在 `.en-run` 下，别外溢到数学页（那边也有 .prac-* 同名类）。
   ============================================================================ */

/* ---- 底色：雾蓝渐变 ---- */
.en-run { background: linear-gradient(158deg, #f9fbfd 0%, #f2f7fc 46%, #eef2f7 100%); }

/* ---- 进度条：蓝紫渐变 + 尾端微光 ---- */
.en-run .progress-track { height: 6px; background: #eef2f8; overflow: visible; }
.en-run .progress-fill {
  background: linear-gradient(90deg, #4b8bf5 0%, #6f7ef6 55%, #8b7cf6 100%);
  position: relative;
}
.en-run .progress-fill::after {
  content: ""; position: absolute; right: -3px; top: 50%;
  width: 10px; height: 10px; margin-top: -5px; border-radius: 50%;
  background: radial-gradient(circle, rgba(139,124,246,.8) 0%, rgba(139,124,246,0) 70%);
}

/* ---- 题号胶囊：淡蓝底 ---- */
.en-run .prac-count {
  display: inline-flex; align-items: center;
  height: 22px; padding: 0 12px;
  border-radius: 999px;
  background: #eff5ff; color: #2f6fd6;
  font-size: 12.5px; font-weight: 600; letter-spacing: .2px;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
/* 右上鼓励语（宽屏才有，窄屏会挤掉进度条） */
.en-run .prac-cheer {
  margin-left: auto; display: inline-flex; align-items: center;
  height: 20px; padding: 0 11px; border-radius: 999px;
  background: #f4f8ff; color: #8aa4c8;
  font-size: 12px; font-weight: 400; white-space: nowrap;
}
@media (max-width: 720px) { .en-run .prac-cheer { display: none; } }

/* ---- 题干：按设计稿放大到 1.5rem（上一版因为 DOM 结构错误导致溢出，
   那个 bug 已经修了 —— 底部按钮现在能正常出现） ---- */
.en-run .prompt-hint {
  /* 设计稿要 1.5rem；1.5 的时候「答错解析条」会被裁掉顶上 6px，
     退到 1.4rem（目测看不出差别）。 */
  font-size: 1.4rem;
  font-weight: 600;
  line-height: 1.62;
  color: #1a2639;
  letter-spacing: .2px;
}

/* ---- 输入框：大圆角 + 浅蓝底 ---- */
.en-run .answer-input {
  border-radius: 1.25rem !important;
  background: #f5f9ff;
  border-color: #dbe7fb !important;
  color: #1a2639;
}
.en-run .answer-input:focus {
  background: #fff;
  border-color: #4b8bf5 !important;
  box-shadow: 0 0 0 4px rgba(75, 139, 245, .14) !important;
}

/* ---- 四选一：浅蓝横条 + 圆形字母块（悬停上浮 2px / 选中填充） ---- */
.en-run .choice-opt {
  background: #f5f9ff;
  border: 2px solid #dbe7fb;
  border-radius: 1.25rem;
  box-shadow: none;
  color: #46536b;
  transition: transform .22s var(--ease), background .22s var(--ease),
              border-color .22s var(--ease), box-shadow .22s var(--ease);
}
.en-run .choice-opt:hover:not(:disabled) {
  transform: translateY(-2px);
  background: #eef5ff;
  border-color: #c7dbf8;
  box-shadow: 0 6px 18px rgba(75, 139, 245, .10);
}
.en-run .choice-opt .co-k {
  background: #fff; border: 1px solid #dbe7fb; color: #2f6fd6;
}
.en-run .choice-opt.on {
  background: #e9f2ff;
  border-color: #4b8bf5;
  box-shadow: 0 4px 14px rgba(75, 139, 245, .16);
}
.en-run .choice-opt.on .co-k { background: #4b8bf5; border-color: #4b8bf5; color: #fff; }
.en-run .choice-opt.on .co-t { color: #1a2639; font-weight: 600; }

/* ---- 底部大按钮：只换配色（尺寸由上面 2153 那条管，别在这里动） ---- */
.en-run #pracSubmitBtn, .en-run #pracNextBtn {
  background: linear-gradient(180deg, #5593f7 0%, #4b8bf5 100%);
  border-color: transparent;
}

/* ---- 题干卡：更柔的圆角 ---- */
.en-run .prompt-card { border-radius: 1.75rem; }

/* ⚠️ 2026-10-24：答题时**整页不许滚**。
   实测 `#pracRun` 算出来的高度 + 页脚会多出 24px，导致底部大按钮被挤出屏幕下沿
   （`btnInView: false`，点不到）。把 body 滚掉，那 24px 就是页脚，裁掉正好。 */
body:has(.en-run:not(.hidden)) { overflow: hidden; }

/* ⚠️ 2026-10-24 兜底：这屏算出来的高度多出 24px（`#pracRun` 752 + 页脚），
   底部那条被挤到屏幕下沿外面。用 sticky 把它钉在视口底部 ——
   **不要改成 `position: fixed`**（项目里踩过：fixed 会把「提示 / 不会」压没）。 */
.en-run .prac-dock {
  position: sticky;
  bottom: 0;
  background: #fff;
  z-index: 6;
}

/* ---- 语法页左栏的「学段」小切换（2026-10-25）----
   用户口径：多选是"这个年级里的多选"，不是选初中/小学 ——
   所以把篇章降级成这么两个小胶囊，真正的列表是下面的**小节**。 */
#graStage {
  display: flex; gap: 6px;
  padding: 2px 8px 10px;
}
.gra-stage-btn {
  flex: 1 1 0;
  height: 26px;
  padding: 0 8px;
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  background: #fff;
  color: var(--text-2);
  font-family: inherit;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
}
.gra-stage-btn:hover { border-color: var(--brand); color: var(--brand); }
.gra-stage-btn.on {
  background: var(--brand-soft);
  border-color: var(--brand);
  color: var(--brand-strong);
}

/* ---- 语法页再瘦一层（2026-10-25 用户说"太乱了"）----
   ① 那行「共 84 条口诀，其中配了练习题的有 60 条（合计 152 道题）。已掌握 0 / 152。」
      太长、又跟上面副标题重复，直接不显示（统计已在副标题里）；
   ② 搜索框顶上的「🔍 搜索口诀」占一整行，去掉 —— placeholder 已经写得很清楚了。 */
#graListHint { display: none; }
#view-grammar .fx-search .lab { display: none; }
#view-grammar .fx-search { margin-top: 10px; }
#view-grammar .fx-mainhead h2 { font-size: 17px; }
#view-grammar #graPaneSub { line-height: 1.65; }

/* ---- 语法页左栏的小工具行：多选 + 全选（2026-10-25 用户要求挪到小节栏上方） ---- */
.gra-tools {
  display: flex; align-items: center; gap: 6px;
  padding: 0 8px 8px;
}
.gra-tools .pp-toggle { height: 24px; font-size: 12px; padding: 0 10px; }
.gra-tools .pp-toggle .pp-k { width: 11px; height: 11px; }
.gra-tools .btn { height: 24px; min-height: 0; padding: 0 10px; font-size: 12px; }

/* ---- 题目区垂直居中——试过，**回滚了** ----
   居中后  变成 flex， 算不准：
   解析条会跑到可视区**上面**（barTop 60 < scTop 84），选项也点不到了。
   留白就留白，不值得拿功能换。 */

/* ---- 答题屏第三轮微调（2026-10-25 用户逐条点名）---- */

/* ① 「提示 / 不会」挪到底部、检查按钮的上方：一行小胶囊，居中 */
.en-run .prac-aidrow {
  display: flex; align-items: center; justify-content: center;
  gap: 8px;
  padding: 0 0 9px;
}
.en-run .prac-aidrow .prac-aid {
  height: 28px; padding: 0 13px;
  font-size: 12.5px; font-weight: 600;
  border-radius: 999px;
  color: var(--text-2);
}
.en-run .prac-aidrow .prac-aid:hover {
  color: var(--brand); border-color: var(--brand); background: var(--brand-soft);
}
.en-run .prac-aidrow .prac-aid kbd { font-size: 10.5px; opacity: .75; margin-left: 3px; }

/* ② 语音那三颗（播放 / 慢速 / 我读的）统一成同一种小胶囊 ——
     原来「我读的」是 .rb-pb、另外两颗是 .btn-sm，长得不一样。 */
.speak-row .btn-sm {
  height: 30px; padding: 0 13px;
  border: 1px solid var(--line-strong);
  border-radius: 999px;
  background: #fff;
  color: var(--text-2);
  font-size: 12.5px; font-weight: 600;
  box-shadow: none;
}
.speak-row .btn-sm:hover {
  background: var(--surface-2);
  border-color: var(--brand);
  color: var(--brand);
}
