/* ============================================================
   PC 宽屏适配层（css/desktop.css）

   ## 为什么单独一层

   本项目原本是**手机优先**的：`.phone` 容器锁死 430px、导航在顶部、
   主菜单是底部 TabBar。这套在手机上是对的（也是微信里的主战场），
   原样搬到 PC 上就只剩中间一条窄柱子 —— 两边大片空白，信息密度极低。

   所以这里**只在宽屏下覆盖布局**，窄屏一行都不动：

     ≥1024px  →  侧边栏 + 顶栏 + 内容区（多列）的桌面应用外壳
     <1024px  →  完全不介入，保持原来的手机布局

   ## 为什么用 1024px 而不是更小的断点

   平板竖屏（768px 左右）仍是触屏单手操作，底部 TabBar 比侧边栏更顺手；
   只有到了 PC / 平板横屏，鼠标 + 宽视口才真正需要侧边栏。
   把断点定在 1024，是为了让「手机 / 平板」完全维持原体验。

   ## 这个断点不只是外观（重要）

   `1024px` 现在是**功能断点**，不再只是布局断点：
   `js/exam.js` 的 `canTakeExam()` 用同一个判据（`Desktop.isDesktop()`），
   在 `<1024px` 时把考试页判成「请用电脑作答」的门禁态 —— 因为作答形态
   （听力音频、原文与题目左右对照、键盘写作、录音、切屏检测）在这一档
   根本不存在，下面那些 `.mode-exam` 规则也全都不会命中。
   所以**改这里的断点必须同步改 `js/desktop.js` 的 `MIN_W`**，
   否则会出现「样式放行了、功能还在拦」或反过来的半截状态。
   同理：给窄屏补任何作答相关的样式之前，先想清楚要不要一并撤掉那道门禁。

   ## 不要做的事

   - ❌ 不要在窄屏下改动任何尺寸 —— 微信里那套是验证过的主路径。
   - ❌ 不要把 `--side-w` 改得太大：内容区要留给数据表与四科矩阵。
   - ❌ 不要给考试页（exam.html）加侧边栏 —— 考试中不该出现导航入口。
        注意这条禁的是「导航入口」，**不是「分栏」**：阅读作答页
        「左原文 / 右题目」两栏各自滚动，就是机考本来的样子
        （见下面「二·B」段，那边的规则全部只在 `≥1024px` 且
        `.mode-exam` 时才生效，考前须知与成绩单仍是单列）。
   ============================================================ */

:root {
  /* 侧边栏宽度：**111px** —— 照搬参照站（idictation /main/book）左侧竖导航
     的实测宽度（元素盒 0,0 111×900）。111px 放不下「图标在左 + 中文在右」
     的整行条目，所以导航项改成「图标在上、文字在下」的 56px 方块
     （与手机端 TabBar 同构，窄栏里点击目标也更方正）。
     窄下来的 125px 全部让给内容区：内容区从 1204 涨到 1329，
     正好够铺参照站那种 633×152 的双列密卡流。 */
  --side-w: 111px;
  /* 教师端 / 学生端内容区上限 */
  --desk-max: 1240px;
  /* 考试作答页：偏窄更利于阅读题干 */
  --desk-read: 880px;
  /* 桌面端顶栏高度（比手机端 52px 高一些，鼠标点击目标更舒适） */
  --desk-nav-h: 60px;
}

/* 桌面端整体底色：手机端是「深灰底 + 白色手机壳」，
   桌面上没有壳，改用浅灰底衬托白色卡片。 */
@media (min-width: 1024px) {
  body {
    background: #f0f2f5;
  }
}

/* ============================================================
   零、几个「只给桌面看」的元素：默认一律不显示

   · `.side-brand` / `.side-foot` / `.nav-actions` 由 `js/desktop.js` 注入；
   · `.exam-meta` / `.exam-actions` / `.part-bar` 直接写在 exam.html 里，
     内容由 `js/exam.js` 填（顶栏的科目标签与答题卡/交卷、底栏题号条）。

   之所以**先隐藏**再在宽屏断点里放开，而不是等宽屏时才注入/渲染 ——
   是为了让注入是幂等的、和视口变化解耦：注入只管把结构放好，
   「显示与否」完全交给 CSS 决定。否则拖动窗口跨过断点时，
   DOM 里就会留下一堆看得见的残留。

   这几条必须在任何 @media 之外，且不能被子选择器覆盖。
   ============================================================ */
.side-brand,
.side-foot,
.nav-actions,
.exam-meta,
.exam-actions,
.part-bar {
  display: none;
}

/* ============================================================
   一、有 TabBar 的页面 → 桌面应用外壳（侧边栏 + 顶栏 + 内容）
   ============================================================ */

@media (min-width: 1024px) {
  /* 只有存在 TabBar 的页面才上侧边栏外壳。
     登录页 / 考试页没有 TabBar，走下面的「无侧边栏」分支。 */
  .phone:has(> .tabbar) {
    max-width: none;
    width: 100%;
    height: 100vh;
    min-height: 0;
    display: grid;
    grid-template-columns: var(--side-w) minmax(0, 1fr);
    /* 三行：微信提示条 / 顶栏 / 内容。
       提示条由 wechat.js 插在 .nav 之后，桌面上必须给它一个具名栅格区，
       否则它会成为「没有归属的游离栅格项」被自动放置，把整个外壳挤错位。
       不在微信里时这一行没有内容，auto 行自然塌成 0 高，不占地方。 */
    grid-template-rows: auto var(--desk-nav-h) minmax(0, 1fr);
    grid-template-areas:
      "side bar"
      "side nav"
      "side page";
    box-shadow: none;
    background: #f0f2f5;
    overflow: hidden;
  }

  .phone:has(> .tabbar) .wx-bar {
    grid-area: bar;
  }

  /* ---------- 侧边栏（由底部 TabBar 改造而来） ---------- */
  .phone:has(> .tabbar) .tabbar {
    grid-area: side;
    position: static;
    height: auto;
    /* 手机端是横排 flex；桌面端改成竖排 */
    flex-direction: column;
    align-items: stretch;
    justify-content: flex-start;
    /* 左右不留 padding —— 导航项要**撑满 111px**。
       参照站的菜单项实测就是 111×56（整栏宽、无左右留白），
       留白会让点击热区缩水，看着也不像侧栏而像一排小按钮。 */
    gap: 0;
    padding: 0 0 8px;
    background: #fff;
    border-top: none;
    border-right: 1px solid var(--line);
    overflow-y: auto;
  }

  /* 手机端每个 tab 是「图标在上文字在下」的竖排小块；
     桌面端**保持这个竖排**，只是放大成 56px 高、撑满栏宽的条目 ——
     111px 放不下「图标在左 + 中文在右」的整行（会挤到贴边），
     而 56px 正是参照站菜单项的实测高度。 */
  .phone:has(> .tabbar) .tabbar .tab {
    flex: 0 0 auto;
    width: 100%;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 4px;
    height: 56px;
    padding: 0 2px;
    /* 撑满栏宽时圆角没有意义（左右都顶着边），所以收回直角 */
    border-radius: 0;
    font-size: 12.5px;
    line-height: 1.2;
    color: #4a4a4a;
    transition: background 0.14s, color 0.14s;
  }
  .phone:has(> .tabbar) .tabbar .tab .ico {
    font-size: 18px;
    width: auto;
    text-align: center;
  }
  .phone:has(> .tabbar) .tabbar .tab:hover {
    background: #f5f6f8;
  }
  .phone:has(> .tabbar) .tabbar .tab.active {
    background: var(--brand-light);
    color: var(--brand-dark);
    font-weight: 600;
  }
  .phone:has(> .tabbar) .tabbar .tab.active .ico {
    filter: none;
    /* 手机端有个上浮 1px 的动效，桌面端整行高亮已经足够，去掉位移 */
    transform: none;
  }

  /* 侧边栏品牌区（由 js/desktop.js 注入）
     111px 窄栏放不下一行「🎓 雅思托福考务」，改成上下两行居中。 */
  .side-brand {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    padding: 14px 6px;
    border-bottom: 1px solid var(--line);
  }
  .side-brand .sb-logo {
    font-size: 26px;
    line-height: 1;
  }
  .side-brand .sb-text {
    font-size: 11.5px;
    font-weight: 600;
    line-height: 1.25;
    text-align: center;
    letter-spacing: 0;
    /* 栏窄，允许折成两行（「雅思托福考务」→ 雅思托福 / 考务） */
    white-space: normal;
    word-break: break-all;
  }

  /* 侧边栏底部用户区（由 js/desktop.js 注入）：同样改成竖排居中。
     父级 tabbar 的左右 padding 已归零，所以这里自己留 6px。 */
  .side-foot {
    display: block;
    margin-top: auto;
    padding: 10px 6px 0;
    border-top: 1px solid var(--line);
  }
  .side-foot .sf-user {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 5px;
    padding: 8px 2px;
    border-radius: var(--radius-sm);
    cursor: pointer;
    transition: background 0.14s;
  }
  .side-foot .sf-user:hover {
    background: #f5f6f8;
  }
  /* 注入的 HTML 里名字块带 flex:1 1 auto，窄栏下要让它自撑、居中 */
  .side-foot .sf-user > div {
    flex: 0 0 auto !important;
    width: 100%;
    text-align: center;
  }
  .side-foot .sf-name {
    min-width: 0;
    font-size: 12.5px;
    font-weight: 500;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .side-foot .sf-role {
    font-size: 11px;
    color: var(--text-gray);
  }
  /* 头像也是 inline 元素，窄栏里居中 */
  .side-foot .sf-user .avatar,
  .side-foot .sf-user > .av,
  .side-foot .sf-user > span {
    display: block;
    margin: 0 auto;
  }

  /* ---------- 顶栏 ---------- */
  .phone:has(> .tabbar) .nav {
    grid-area: nav;
    position: static;
    height: var(--desk-nav-h);
    justify-content: flex-start;
    padding: 0 24px;
    background: #fff;
    border-bottom: 1px solid var(--line);
  }
  .phone:has(> .tabbar) .nav .nav-title {
    font-size: 17px;
    font-weight: 600;
  }
  /* 返回箭头不再浮在绝对定位上，改为跟着标题流向排布 */
  .phone:has(> .tabbar) .nav .nav-back {
    position: static;
    transform: none;
    margin-right: 6px;
    margin-left: -6px;
  }
  /* 「··· ◉」是微信小程序的原生胶囊，PC 上没有对应概念，隐藏 */
  .capsule {
    display: none;
  }

  /* 顶栏右侧操作区（由 js/desktop.js 注入） */
  .nav-actions {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-left: auto;
  }
  .nav-actions .na-user {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 13.5px;
    color: var(--text-gray);
  }
  .nav-actions .na-name {
    color: var(--text);
    font-weight: 500;
  }

  /* ---------- 内容区 ---------- */
  .phone:has(> .tabbar) .page {
    grid-area: page;
    /* 手机端给底部 TabBar 留的空白，桌面端没有 TabBar 了 */
    padding-bottom: 0;
  }
  .phone:has(> .tabbar) .page.has-tab {
    padding-bottom: 0;
  }
  .phone:has(> .tabbar) .page.has-footer {
    padding-bottom: 0;
  }

  /* 内容横向多列：手机端一列到底，桌面端两列并排。
     `align-items: start` 让同一行里较矮的卡片不被拉高。 */
  .phone:has(> .tabbar) .page .wrap {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 16px;
    align-items: start;
    align-content: start;
    max-width: var(--desk-max);
    margin: 0 auto;
    padding: 22px 28px 56px;
  }
  /* 卡片的 margin-bottom 由 grid 的 gap 接管，否则会叠加出双倍间距 */
  .phone:has(> .tabbar) .page .wrap > * {
    margin-bottom: 0 !important;
  }

  /* 需要通栏的元素：这类内容并排会读不下去或被压扁 */
  .phone:has(> .tabbar) .page .wrap > .hero,
  .phone:has(> .tabbar) .page .wrap > .stats,
  .phone:has(> .tabbar) .page .wrap > .seg,
  .phone:has(> .tabbar) .page .wrap > .boot,
  .phone:has(> .tabbar) .page .wrap > .empty,
  .phone:has(> .tabbar) .page .wrap > .btn.block,
  /* 筛选条（.tabs 下划线页签 / .chips 胶囊）。它们本来是一组横向排开的
     选项，被塞进半栏会跟相邻条错位到不同行上，通栏后各占一整行、左右对齐。
     （.tabs 是 2026-09-18 第十七轮加的：题库 / 试卷 / 工作台三处的一级筛选） */
  .phone:has(> .tabbar) .page .wrap > .tabs,
  /* 散卷区的小标题：半栏宽会折成三四行窄条，很难读 */
  .phone:has(> .tabbar) .page .wrap > .loose-hd,
  .phone:has(> .tabbar) .page .wrap > .chips,
  /* 含表格 / 横向滚动区的卡片通栏 —— 靠 :has() 反查，避免逐个改视图代码 */
  .phone:has(> .tabbar) .page .wrap > .card:has(.tbl),
  .phone:has(> .tabbar) .page .wrap > .card:has(.scroll-x),
  /* 只有一个孩子的 wrap：两列里它左边、右边空一大片，纯属浪费 */
  .phone:has(> .tabbar) .page .wrap > *:only-child {
    grid-column: 1 / -1;
  }

  /* ---------- 单列模式：给「以单一主体内容为主」的视图 ----------
     详情页（题库/试卷/成绩/学员报告）和排行榜，主体都是**一整块**
     读不完的内容，硬拆两列只会一边很高一边空着。这类视图自己挂
     `.wrap.single` 声明意图 —— 用显式类而不是靠猜 DOM 结构，
     这样看视图代码就知道它是单列。 */
  .phone:has(> .tabbar) .page .wrap.single {
    grid-template-columns: minmax(0, 1fr);
  }

  /* ---------- 卡片在宽屏下的呼吸感 ---------- */
  .phone:has(> .tabbar) .page .card {
    padding: 18px 20px;
  }
  .phone:has(> .tabbar) .page .card-title {
    font-size: 16px;
  }
  /* 色带卡头（题库分组 / 散卷分组）要贴到卡片边缘：
     上面给所有 .card 加了 18/20 的内边距，这条必须盖回去 ——
     否则色带四周会多出一圈白边，就不是「整条色底」了。
     特异性要够，所以写全 .phone:has(...) 前缀，别图短。 */
  .phone:has(> .tabbar) .page .card.bandcard {
    padding: 0;
  }
  .phone:has(> .tabbar) .page .band {
    padding: 13px 18px;
  }
  .phone:has(> .tabbar) .page .band .b-t {
    font-size: 15.5px;
  }
  .phone:has(> .tabbar) .page .card.bandcard .list .item {
    padding: 14px 18px;
  }

  /* ---------- 套卷矩阵（试卷列表）----------
     两列网格里每列约 570px：单列套卷（1~2 个 Test）刚好铺得开，
     所以 1~2 套就留在半栏里（列表两列并排一览无余）；
     3 套以上挂 `.wide` 通栏 —— 四列科目在半栏里只能压成一条缝。
     `.note` 也通栏：说明文字半栏宽会折成四五行的窄条，很难读。 */
  .phone:has(> .tabbar) .page .wrap > .note,
  .phone:has(> .tabbar) .page .wrap > .suite-card.wide {
    grid-column: 1 / -1;
  }
  .phone:has(> .tabbar) .page .suite-band {
    padding: 15px 20px;
  }
  .phone:has(> .tabbar) .page .sb-name {
    font-size: 17px;
  }
  .phone:has(> .tabbar) .page .suite-matrix {
    padding: 12px 16px 14px;
  }
  .phone:has(> .tabbar) .page .sm-row {
    grid-template-columns: 96px repeat(var(--tests), minmax(0, 1fr));
    gap: 10px;
  }
  .phone:has(> .tabbar) .page .suite-card.wide .sm-row {
    grid-template-columns: 96px repeat(var(--tests), minmax(0, 1fr));
  }
  /* 通栏之后不再需要横向滚动（列宽足够了） */
  .phone:has(> .tabbar) .page .suite-card.wide .suite-matrix {
    overflow-x: visible;
  }
  .phone:has(> .tabbar) .page .sm-test,
  .phone:has(> .tabbar) .page .sm-paper {
    padding: 11px 12px;
  }
  .phone:has(> .tabbar) .page .sm-paper.empty {
    min-height: 74px;
  }

  /* 四科矩阵 / 统计块在宽屏下放宽内边距，数字更有存在感 */
  .phone:has(> .tabbar) .stat {
    padding: 16px 12px;
  }
  .phone:has(> .tabbar) .stat .v {
    font-size: 26px;
  }
  .phone:has(> .tabbar) .skill-cell {
    padding: 14px 8px;
  }
  .phone:has(> .tabbar) .skill-cell .sc-score {
    font-size: 22px;
  }

  /* 表格：宽屏下不需要横向滚动，给单元格更多呼吸空间 */
  .phone:has(> .tabbar) .tbl th,
  .phone:has(> .tabbar) .tbl td {
    padding-left: 12px;
    padding-right: 12px;
  }
  .phone:has(> .tabbar) .scroll-x {
    overflow-x: visible;
  }

  /* ============================================================
     试卷列表：照搬参照站的「真题库骨架」
     （idictation /main/book?lid=-1，1440×900 实测）

     参照站的骨架由三件东西构成，这里逐条对齐：

       ① 顶部 sticky 筛选区     —— 实测 top-menu 1277×129，白底不滚
       ② 双列密卡流             —— 实测 grid gap 4px、卡片 633×152
       ③ 卡片 = 色带 + 矩阵     —— 实测色带 h-18（72px）整卡宽横带，
                                   下方 4 列各 w-[24%]，列头深色底，
                                   列内 3 行科目 py-1 text-xs

     我们和参照站的数据结构是天然同构的（我们的 suite 就是它的
     「一册的一个 Test」），所以这里只换版式、不动数据：
       系列(group) → 一张卡；Test(testNo) → 一列；科目 → 列内一行。

     仅 ≥1024px 生效 —— `viewExams()` 用 Desktop.isDesktop() 决定
     渲染骨架还是原来的手机卡片流，窄屏一行样式都不会命中。
     ============================================================ */

  /* 骨架容器：通栏单列，内部自己管双列网格。
     宽度取 1280 —— 参照站内容区实测 1277（1440 - 111 侧栏 - 2×26 内边距）。 */
  .phone:has(> .tabbar) .page .wrap.bk-wrap {
    grid-template-columns: minmax(0, 1fr);
    gap: 0;
    max-width: 1280px;
    padding: 0 26px 48px;
  }

  /* ---------- ① 顶部筛选区（sticky，不跟着卡片滚走） ---------- */
  .bk-top {
    position: sticky;
    top: 0;
    z-index: 5;
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 14px 0 10px;
    background: #fff;
    border-bottom: 1px solid var(--line);
    /* 上边贴住 .page 顶部时的留白交给 padding，不要再加 margin */
    margin-bottom: 14px;
  }

  /* 页签：激活项深色字 + 主题色下划线指示条，未激活灰字。
     指示条用 2px 透明边框占位，切换时不会因为突然多出一条线而跳动。 */
  .bk-tabs {
    display: flex;
    align-items: center;
    gap: 26px;
    flex-wrap: wrap;
  }
  .bk-tab {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 2px 0 8px;
    font-size: 15px;
    line-height: 1.3;
    color: #5a6b7c;
    white-space: nowrap;
    cursor: pointer;
    border-bottom: 2px solid transparent;
    transition: color 0.15s, border-color 0.15s;
  }
  .bk-tab:hover {
    color: #2c3e50;
  }
  .bk-tab.on {
    color: var(--text);
    font-weight: 600;
    border-bottom-color: var(--brand);
  }
  /* 第二排（科目）比第一排（考试类型）矮一档，形成层级 */
  .bk-tabs.sub .bk-tab {
    font-size: 14px;
    padding-bottom: 6px;
  }
  .bk-tab .n {
    font-size: 12px;
    color: var(--text-light);
    font-weight: 400;
  }
  .bk-tab.on .n {
    color: var(--text-gray);
  }

  /* ---------- ② 双列密卡流（gap 4px，照搬实测） ---------- */
  .bk-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 4px;
    align-content: start;
  }
  /* 小标题 / 空态 / 说明 这类元素不该被塞进半栏 */
  .bk-grid > .bk-hd,
  .bk-grid > .bk-empty,
  .bk-grid > .bk-note {
    grid-column: 1 / -1;
  }

  .bk-hd {
    display: flex;
    align-items: baseline;
    gap: 10px;
    padding: 16px 2px 8px;
    font-size: 15px;
    font-weight: 600;
    color: var(--text);
  }
  .bk-hd .n {
    font-size: 12.5px;
    font-weight: 400;
    color: var(--text-gray);
  }
  .bk-empty {
    grid-column: 1 / -1;
    padding: 40px 0;
  }
  .bk-note {
    padding: 10px 2px 0;
    font-size: 12.5px;
    line-height: 1.7;
    color: var(--text-gray);
  }

  /* ---------- ③ 卡片：72px 色带 + 矩阵 ---------- */
  .bk-card {
    height: 152px;
    display: flex;
    flex-direction: column;
    /* 卡本身不上底色/圆角 —— 参照站是「圆角色带 + 浮在上面的白列」，
       底色由色带自己承担，这样白列的阴影不会被父级裁掉。 */
    background: transparent;
  }

  /* 色带：整卡宽、72px 高、write 系列名与册号 */
  .bk-band {
    flex: 0 0 72px;
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 0 10px;
    border-radius: var(--radius-sm);
    background: var(--bk-c);
    color: #fff;
    overflow: hidden;
  }
  .bk-band .bk-name {
    font-size: 18px;
    line-height: 1.2;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .bk-band .bk-no {
    flex: 0 0 auto;
    font-size: 24px;
    font-weight: 600;
    line-height: 1;
  }
  .bk-band .bk-tip {
    margin-left: auto;
    flex: 0 0 auto;
    font-size: 12px;
    color: rgba(255, 255, 255, 0.85);
  }

  /* 矩阵区：与色带留 4px，横向平分各 Test 列。
     参照站固定 w-[24%]，我们改用 flex 平分 —— 系列下只有 1~2 套时
     也铺得开，不会在右边空出三列的位置。 */
  .bk-tests {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    gap: 4px;
    padding: 4px 1px 0;
  }
  /* 一列 = 一个 Test */
  .bk-test {
    flex: 1 1 0;
    min-width: 0;
    display: flex;
    flex-direction: column;
    background: #fff;
    border-radius: var(--radius-sm);
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.14);
    overflow: hidden;
  }
  /* 列头：深一档的同系列色，照搬实测 rgb(152,38,30) 之于 rgb(254,34,43) */
  .bk-test-hd {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 6px;
    padding: 4px 8px;
    background: var(--bk-c2);
    color: #fff;
    font-size: 12.5px;
    line-height: 1.4;
  }
  .bk-test-hd .t {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .bk-test-hd .s {
    flex: 0 0 auto;
    font-size: 11px;
    color: rgba(255, 255, 255, 0.85);
  }

  /* 列内科目行 */
  .bk-rows {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    padding: 3px 4px;
  }
  .bk-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 6px;
    padding: 5px 6px;
    border-radius: 4px;
    font-size: 12px;
    line-height: 1.3;
    color: #2c3e50;
    cursor: pointer;
    transition: background 0.12s;
  }
  .bk-row:hover {
    background: #f3f4f6;
  }
  /* 没编入本科目的占位：明确写出来，不留空白让人猜 */
  .bk-row.none {
    color: var(--text-light);
    cursor: default;
  }
  .bk-row.none:hover {
    background: transparent;
  }
  .bk-row .sk {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .bk-row .st {
    flex: 0 0 auto;
    font-size: 11px;
    color: var(--text-gray);
  }
  /* 状态：未开始（浅灰）/ 进行中（橙）/ 已交（品牌绿）/ 未发布（浅灰） */
  .bk-row .st.doing {
    color: var(--warn);
    font-weight: 600;
  }
  .bk-row .st.done {
    color: var(--brand-dark);
    font-weight: 600;
  }
  .bk-row .st.lock {
    color: #c8ccd2;
  }
  /* 顺序锁：前面的科没交，这一科打不开 —— 要看得出来是「因为顺序」 */
  .bk-row.locked {
    opacity: 0.55;
    cursor: default;
  }
  .bk-row.locked:hover {
    background: transparent;
  }
  /* 当前该作答的那一科：左侧品牌色竖条，一眼看到从哪继续 */
  .bk-row.cur {
    background: var(--brand-soft);
  }
  .bk-row.cur .sk {
    font-weight: 600;
    color: var(--brand-dark);
  }

  /* ---------- 散卷（没被编进任何系列的卷子） ----------
     单科卷本身是正当用法，不能因为上了系列骨架就把它藏起来。
     散卷没有「Test 列」可言，套进矩阵会空掉大半张卡 ——
     改成色带 + 一整行操作条，卡高与网格间距仍与系列卡一致，
     列表密度不会因为混排而参差。 */
  .bk-grid.loose {
    margin-top: 4px;
  }
  .bk-foot {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    margin-top: 4px;
    padding: 0 14px;
    background: #fff;
    border-radius: var(--radius-sm);
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.14);
    font-size: 13.5px;
    color: #2c3e50;
    cursor: pointer;
    transition: background 0.12s;
  }
  .bk-foot:hover {
    background: #f7f8fa;
  }
  .bk-foot .lf {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
  }
  .bk-foot .st {
    flex: 0 0 auto;
    font-size: 13px;
    font-weight: 600;
    color: var(--brand-dark);
  }
  .bk-foot .st.doing {
    color: var(--warn);
  }
  .bk-foot .st.plain {
    color: var(--text-gray);
    font-weight: 400;
  }
  /* 散卷上仍要说清「手机上考不了」——不过 PC 骨架只在宽屏出现，
     这条其实用不上，留着是为了整段规则在窄屏被误命中时的兜底一致性 */
  .bk-foot .ml {
    flex: 0 0 auto;
    font-size: 12px;
    color: var(--text-light);
  }

  /* 底部固定操作条：手机端贴着屏幕底；桌面端要落在「内容列」里居中 */
  .phone:has(> .tabbar) .footer-bar {
    left: calc(50% + var(--side-w) / 2);
    transform: translateX(-50%);
    max-width: var(--desk-max);
    padding: 14px 28px;
    border-radius: var(--radius) var(--radius) 0 0;
    box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.06);
  }
}

/* ============================================================
   二、无 TabBar 的页面（登录页 / 考试页）
   ============================================================ */

@media (min-width: 1024px) {
  .phone:not(:has(> .tabbar)) {
    max-width: none;
    width: 100%;
    min-height: 100vh;
    box-shadow: none;
    background: transparent;
  }
  .phone:not(:has(> .tabbar)) .nav {
    height: var(--desk-nav-h);
    padding: 0 32px;
  }
  .phone:not(:has(> .tabbar)) .nav .nav-title {
    font-size: 17px;
  }
}

/* ---------- 登录页：底图 + 右侧登录卡（与言之谷登录页同构） ---------- */
@media (min-width: 1024px) {
  /* 登录页导航栏是微信小程序的壳（返回 + capsule），桌面上没有意义，隐藏 */
  .phone.login-shell .nav {
    display: none;
  }

  .phone.login-shell .page {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 480px);
    grid-template-rows: auto auto;
    gap: 20px 40px;
    align-content: center;
    align-items: start;
    max-width: 1120px;
    /* `width: 100%` 不能少！.page 是 .phone(flex 容器) 的子项，
       而 `margin: 0 auto` 会取消 flex 的 stretch —— 没有显式宽度时，
       .page 会退化成 shrink-to-fit（只有内容的宽度）。
       实测 1440px 下只占 ~765px：左栏被挤到 208px、卡片贴着屏幕中间，
       屏幕两侧各空出 300 多像素，"宽屏适配"形同虚设。
       写上 width:100% 后由 max-width 兜住上限，才能既撑满又居中。 */
    width: 100%;
    /* 高度由 flex 撑满（.page 本身是 flex:1），不要写 100vh ——
       微信提示条占掉一条高度时，100vh 会顶出竖向滚动条。 */
    margin: 0 auto;
    padding: 40px 32px;
  }

  /* display:contents 把 .wrap 这一层「拆掉」，
     让里面的卡片直接参与上面的栅格排版 —— 这样登录卡能落到右栏，
     而不用改动 index.html 的嵌套结构。 */
  .phone.login-shell .page > .wrap {
    display: contents;
  }

  /* 品牌区只在手机端出现。PC 上它是「多余的一层」：
     系统名已经写在登录卡标题里，而左栏空出来正好让底图透气
     （言之谷登录页 PC 端也是纯底图 + 右侧登录框，没有品牌块）。
     要求是「手机保留、PC 不要」，靠断点切，不维护两份 HTML。 */
  .phone.login-shell .login-hero {
    display: none;
  }

  /* 栅格定位（PC 端只有两块）：左栏空着只露底图 / 右栏 登录卡 → 快速体验。

     左栏必须保留：言之谷的登录框就落在屏幕右侧（Login.vue 里
     `right: calc((100% - 70%) - 200px)`，1440px 下卡片约在 x 808~1208），
     我们右栏的卡片落在 x 768~1248，位置基本重合。把卡片改成居中反而会偏离。

     DOM 顺序是 品牌区 → 登录卡 → 快速体验，与栅格位置不同，
     所以这里用显式的 grid-row / grid-column 重新摆位。 */
  .phone.login-shell .card-login {
    grid-column: 2;
    grid-row: 1;
  }
  .phone.login-shell .card-demo {
    grid-column: 2;
    grid-row: 2;
  }
}

/* ---------- 考试页（考前须知 / 成绩单）：单列聚焦，限宽阅读 ---------- */
@media (min-width: 1024px) {
  .phone.exam-shell .page {
    max-width: var(--desk-read);
    margin: 0 auto;
    padding: 24px 0 120px;
  }
  .phone.exam-shell .footer-bar {
    max-width: var(--desk-read);
    left: 50%;
    transform: translateX(-50%);
    padding: 16px 24px;
    border-radius: var(--radius) var(--radius) 0 0;
    box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.06);
  }
}

/* ---------- 考前须知：与作答页同一副「顶 56 / 主区 / 底 92」骨架 ----------
   从试卷列表点进须知、再开考，视觉是连续的 ——
   不该出现「须知像一篇文章、作答像一个应用」的断裂。
   所以须知在宽屏下也整屏铺开：主区两列（左考试信息 / 右考试规则），
   底部操作条固定 92px，与作答页底栏同高。

   成绩单（mode-result）**不跟这条走**：它是读不完的长报告，
   限宽居中才读得下去，所以下面全部限定在 mode-intro 上。 */
@media (min-width: 1024px) {
  .phone.exam-shell.mode-intro .page {
    max-width: none;
    margin: 0;
    padding: 0;
    overflow-y: auto;
  }
  .phone.exam-shell.mode-intro .wrap {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 16px;
    align-items: start;
    max-width: 1080px;
    margin: 0 auto;
    /* 底部 132px：底栏 92px + 一点呼吸，别让最后一行的字压在操作条下 */
    padding: 22px 24px 132px;
  }
  /* hero（时长 + 科目）与末尾那句说明通栏 —— 它们本来就是一整行的话 */
  .phone.exam-shell.mode-intro .wrap > .hero,
  .phone.exam-shell.mode-intro .wrap > .note {
    grid-column: 1 / -1;
  }
  .phone.exam-shell.mode-intro .footer-bar {
    max-width: 1080px;
    height: var(--exam-bar-h);
    padding: 0 24px;
    display: flex;
    align-items: center;
    gap: 12px;
  }
}

/* ============================================================
   二·B、考试作答页 —— PC 机考（computer-delivered）界面

   实测参照（idictation `/ielts/v2`、`/ielts/read`，1440×900，
   原始产物见上一轮抽取的 `_ref/REFERENCE-NOTES.md`）：

     顶栏 56px / 主区自适应 / 底栏 92px → 合计正好一屏，整页不滚
     阅读页：左「原文」570×720、右「题目」854×720，**两栏各自独立滚动**
     正文 15.4px / 行高 1.75 / Arial；表格 td 1px 灰边；输入框直角
     底栏是 Part 1–4 题号条，每段带「已答 n of m」

   这里只搬「机考为什么长这样」的那部分，配色仍用本项目的微信绿，
   不跟着参照站换成橙 —— 换品牌色不在本次改造范围内。

   三个前提，改之前先读：

   ① 手机端一行都不动。下面全部规则都在 `@media (min-width:1024px)` 里，
      而 `js/exam.js` 挂的 `.mode-*` / `.skill-*` 类在窄屏下不命中任何样式。
   ② 「一屏不滚」靠 `.mode-exam .page` 变成 flex 列 + `overflow:hidden`，
      真正的滚动条落在 `.m-col` / `.q-col` 两个内层容器上。
   ③ `renderQuestion()` 统一产出 `.m-col`（材料）+ `.q-col`（题目）两层：
      窄屏下它们只是普通 div（等于没加），宽屏下才由这里摆成
      「阅读左右分栏 / 听力音频置顶 / 写作单列」三种形态
      —— 手机与 PC 共用一份 DOM，不要为 PC 另写一套模板。

   断点必须与 `js/desktop.js` 的 MIN_W 一致（1024）。
   ============================================================ */

@media (min-width: 1024px) {
  .phone.exam-shell {
    --exam-nav-h: 56px;
    --exam-bar-h: 92px;
  }

  /* ---------- 顶栏 56px：考号 + 科目标签 + 倒计时 / 已保存 + 功能图标 ----------
     形态取自参照站机考页：左侧是「身份 + 时间」，右侧是「状态 + 图标」。
     卷名挪去底栏信息条（那里宽、也不跟题号抢视线），返回键在作答中
     没有意义（退出走底栏 Exit 语义），所以这两块在 mode-exam 下都不出现。 */
  .phone.exam-shell .nav {
    height: var(--exam-nav-h);
    padding: 0 20px;
    justify-content: flex-start;
    gap: 10px;
  }
  .phone.exam-shell .nav .nav-back {
    position: static;
    transform: none;
    margin: 0 0 0 -8px;
  }
  .phone.exam-shell .nav .nav-title {
    font-size: 15.5px;
    max-width: 30vw;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .phone.exam-shell.mode-exam .nav .nav-back,
  .phone.exam-shell.mode-exam .nav .nav-title {
    display: none;
  }
  /* 考号：机考里学生第一个要确认的信息（「这卷子是我的吗」） */
  .phone.exam-shell .exa-no {
    font-size: 13px;
    color: var(--text-gray);
    white-space: nowrap;
  }
  .phone.exam-shell .exa-no b {
    color: var(--text);
    font-family: ui-monospace, "SFMono-Regular", Consolas, monospace;
    font-size: 13.5px;
    letter-spacing: 0.3px;
    font-weight: 600;
  }
  /* 「已保存」：一个小小的绿点 + 文字，存盘时亮一下（见 js/exam.js flashSaved） */
  .phone.exam-shell .exa-saved {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 30px;
    padding: 0 11px;
    border: 1px solid var(--line);
    border-radius: 4px;
    background: #fff;
    color: var(--text-gray);
    font-size: 12.5px;
    white-space: nowrap;
    transition: color 0.2s, border-color 0.2s, background 0.2s;
  }
  .phone.exam-shell .exa-saved i {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: #c9ced6;
    transition: background 0.2s;
  }
  .phone.exam-shell .exa-saved.on {
    color: var(--brand-dark);
    border-color: #bfe9d1;
    background: var(--brand-light);
  }
  .phone.exam-shell .exa-saved.on i {
    background: var(--brand);
  }
  /* 功能图标按钮：32×32 的方图标，跟参照站一样「小而稳」 */
  .phone.exam-shell .exa-ico {
    width: 32px;
    height: 32px;
    padding: 0;
    border: 1px solid var(--line-strong);
    border-radius: 4px;
    background: #fff;
    color: var(--text-gray);
    font-size: 14px;
    line-height: 1;
    font-family: inherit;
    cursor: pointer;
    transition: background 0.15s, border-color 0.15s, color 0.15s;
  }
  .phone.exam-shell .exa-ico:hover {
    background: #f5f6f8;
    color: var(--text);
  }
  .phone.exam-shell .exa-ico.primary {
    background: var(--brand);
    border-color: var(--brand);
    color: #fff;
  }
  .phone.exam-shell .exa-ico.primary:hover {
    background: var(--brand-dark);
    border-color: var(--brand-dark);
  }
  /* 卷名后面跟「考种 + 科目」两个标签，一眼知道在做哪一科 */
  .phone.exam-shell .exam-meta {
    display: flex;
    align-items: center;
    gap: 6px;
    flex: 0 0 auto;
  }
  /* 计时器从手机端的「居中悬浮胶囊」改成左侧常驻、紧跟考号 ——
     参照站顶栏就是「考号 + 倒计时」并排靠左。圆角收 4px 跟其他控件统一。
     （注意：这里**不能**再写 margin-left:auto，否则它会跟「已保存」
       抢右边，考号和倒计时被拆到两头，跟参照站形态就不一样了。） */
  .phone.exam-shell .timer {
    position: static;
    transform: none;
    flex: 0 0 auto;
    height: 34px;
    padding: 0 14px;
    border-radius: 4px;
    font-size: 15px;
    letter-spacing: 0.4px;
  }
  /* 「已保存 + 图标」整体推到最右 */
  .phone.exam-shell .exam-actions {
    display: flex;
    align-items: center;
    gap: 8px;
    flex: 0 0 auto;
    margin-left: auto;
  }
  .phone.exam-shell .exa-btn {
    height: 34px;
    padding: 0 16px;
    border: 1px solid var(--line-strong);
    border-radius: 4px;
    background: #fff;
    color: var(--text);
    font-size: 13.5px;
    font-weight: 500;
    font-family: inherit;
    cursor: pointer;
    transition: background 0.15s, border-color 0.15s;
  }
  .phone.exam-shell .exa-btn:hover {
    background: #f5f6f8;
  }
  .phone.exam-shell .exa-btn.primary {
    background: var(--brand);
    border-color: var(--brand);
    color: #fff;
  }
  .phone.exam-shell .exa-btn.primary:hover {
    background: var(--brand-dark);
    border-color: var(--brand-dark);
  }

  /* ---------- 作答中：一屏不滚 ---------- */
  .phone.exam-shell.mode-exam {
    height: 100vh;
    overflow: hidden;
  }
  .phone.exam-shell.mode-exam .page {
    max-width: none;
    margin: 0;
    padding: 0;
    min-height: 0;
    overflow: hidden;
    display: flex;
    flex-direction: column;
  }
  .phone.exam-shell.mode-exam .progress-line {
    flex: 0 0 auto;
    height: 4px;
  }
  .phone.exam-shell.mode-exam .violation-bar {
    flex: 0 0 auto;
  }
  .phone.exam-shell.mode-exam .wrap {
    flex: 1 1 auto;
    min-height: 0;
    max-width: none;
    margin: 0;
    padding: 0;
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: minmax(0, 1fr);
  }
  /* 没有材料（写作 / 部分听力）时材料栏整块抽掉，别留一条空白列 */
  .phone.exam-shell.mode-exam .m-col:empty {
    display: none;
  }
  .phone.exam-shell.mode-exam .q-col {
    min-height: 0;
    overflow-y: auto;
    padding: 22px 32px calc(var(--exam-bar-h) + 28px);
  }
  .phone.exam-shell.mode-exam .q-col > .card {
    max-width: 880px;
    margin: 0 auto 12px;
  }
  /* 作答中不再叠手机端那条浮动底栏（答题卡 / 上一题 / 交卷都已在顶栏） */
  .phone.exam-shell.mode-exam .footer-bar {
    display: none;
  }

  /* ---------- 阅读：左原文 / 右题目，两栏各自独立滚动，中间可拖拽 ----------
     实测参照 570 / 854 ≈ 5 : 7.5，这里取 5fr : 7fr 作为拖拽前的默认值。
     两栏各自 `overflow-y:auto` 的关键收益：读原文时**不会把题目顶走**。

     中间那条 7px 是分隔条（手抓的宽度），视觉上是 1px 的分隔线。
     一旦拖动过，js/exam.js 会把 grid-template-columns 改写成像素值，
     并把占比记在内存里，重建后写回来。 */
  .phone.exam-shell.mode-exam.skill-reading .wrap {
    grid-template-columns: minmax(0, 5fr) 7px minmax(0, 7fr);
  }
  .phone.exam-shell .splitter {
    position: relative;
    z-index: 5;
    cursor: col-resize;
    background: #fff;
    border-left: 1px solid var(--line);
    border-right: 1px solid var(--line);
    /* 触屏笔记本 / 触屏一体机上拖动时不要被浏览器当成滚动 */
    touch-action: none;
  }
  /* 抓手：默认是一条短横杠，悬停 / 拖动时变长变亮 */
  .phone.exam-shell .splitter::after {
    content: "";
    position: absolute;
    left: 50%;
    top: 50%;
    width: 3px;
    height: 34px;
    border-radius: 2px;
    background: var(--line-strong);
    transform: translate(-50%, -50%);
    transition: background 0.15s, height 0.15s;
  }
  .phone.exam-shell .splitter:hover::after,
  .phone.exam-shell .splitter.on::after {
    height: 56px;
    background: var(--brand);
  }
  /* 分隔线交给分隔条，原文栏就不必再画一条 border-right 了 */
  .phone.exam-shell.mode-exam.skill-reading .m-col {
    min-height: 0;
    overflow-y: auto;
    padding: 20px 26px calc(var(--exam-bar-h) + 20px) 32px;
    background: #fff;
    border-right: none;
  }
  /* 原文栏本身就是滚动容器了：卡片外壳、暖底色、折叠按钮全部去掉 ——
     机考里原文就是「一页纸」，不该再套一层卡片。 */
  .phone.exam-shell.mode-exam.skill-reading .m-col .passage {
    background: transparent;
    border: none;
    border-radius: 0;
    padding: 0;
    margin: 0;
  }
  /* Passage 标题吸顶：长文滚到中段也知道自己在哪一篇 */
  .phone.exam-shell.mode-exam.skill-reading .m-col .passage-hd {
    position: sticky;
    top: 0;
    z-index: 5;
    margin-bottom: 12px;
    padding: 4px 0 10px;
    background: #fff;
    border-bottom: 1px solid var(--line);
  }

  /* ---------- 听力：音频盒固定在主区顶部，题目在下面滚 ---------- */
  .phone.exam-shell.mode-exam.skill-listening .wrap {
    grid-template-rows: auto minmax(0, 1fr);
  }
  .phone.exam-shell.mode-exam.skill-listening .m-col {
    padding: 14px 32px 0;
    background: #fff;
    border-bottom: 1px solid var(--line);
  }
  .phone.exam-shell.mode-exam.skill-listening .m-col .audio-box {
    max-width: 880px;
    margin: 0 auto 14px;
  }

  /* ---------- 正文排版：贴近机考（Arial / 15.5px / 1.75） ---------- */
  .phone.exam-shell.mode-exam .passage-bd {
    font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
    font-size: 15.5px;
    line-height: 1.75;
    color: #2c3e50;
    /* 折叠是手机端的权宜之计（一屏放不下整篇）；PC 分栏后由栏自己滚。
       这里必须 !important —— 展开状态是 passageHTML 写在元素上的 inline
       max-height，普通声明盖不掉。 */
    max-height: none !important;
    overflow: visible !important;
  }
  .phone.exam-shell.mode-exam .passage-bd .p-title {
    font-size: 17px;
  }
  .phone.exam-shell.mode-exam .passage-more {
    display: none;
  }
  .phone.exam-shell.mode-exam .q-stem {
    font-size: 16px;
    line-height: 1.7;
  }
  .phone.exam-shell.mode-exam .opt .txt {
    font-size: 15px;
    line-height: 1.7;
  }

  /* ============================================================
     阅读单 Part 页的版式（对标 idictation `/ielts/read/read-jy-part`）

     参照站有**两套字号**，彼此不同源，别混着抄：
       · 整套机考页 `/ielts/v2|read/suite-jy`：根字号 1.1em = **15.4px**
         （上面那几条 15.5px / 16px 就是照它量的）
       · 单 Part 练习页 `/ielts/read/read-jy-part`：根字号 1.2em = **16.8px**
         （下面这一段全部照它量）
     我们现在对齐的是**单 Part 页**（「总结有选项题」就是从那个页面搬过来的），
     所以这一档只在 .skill-reading 下生效：阅读页整套字号统一到 16.8 /
     行高 1.75，与那页逐项对得上；听力页维持上面那一档，不动。

     所有数字都来自 `_ref/dumps/readpartlive-styles.json` 的 getComputedStyle
     实测（1440×900），不是目测。改之前先回去看那份 dump。
     ============================================================ */
  .phone.exam-shell.mode-exam.skill-reading .m-col .passage-bd,
  .phone.exam-shell.mode-exam.skill-reading .q-col {
    font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
    font-size: 16.8px;
    line-height: 1.75;
    /* 参照站整页带 `tracking-wide`（0.025em，16.8px 下算出 0.42px） */
    letter-spacing: 0.025em;
    color: #000;
  }
  /* 文章标题：参照站是 `<h4 class="text-center pt-3">`，
     16.8 / 700 / 居中 / padding 12px 0 0。 */
  .phone.exam-shell.mode-exam.skill-reading .m-col .passage-bd .p-title {
    font-size: 16.8px;
    font-weight: 700;
    line-height: 1.75;
    text-align: center;
    padding: 12px 0 0;
    margin: 0;
  }
  /* 正文段落：参照站是 `<p class="pt-6">`，**段间靠 padding-top 24px 拉开**，
     不是靠 margin —— 段落盒子的起点因此与上一段的末尾紧贴。
     照抄这个做法：末段不留尾巴（原来的 margin-bottom 要清掉）。 */
  .phone.exam-shell.mode-exam.skill-reading .m-col .passage-bd p {
    padding: 24px 0 0;
    margin: 0;
  }
  .phone.exam-shell.mode-exam.skill-reading .q-stem {
    font-size: 16.8px;
    font-weight: 600;     /* 参照站题干是 `font-semibold` */
    line-height: 1.75;
  }
  .phone.exam-shell.mode-exam.skill-reading .q-no {
    font-weight: 600;
  }
  /* 题组说明：参照站是 `div.italic`，整段斜体 */
  .phone.exam-shell.mode-exam.skill-reading .q-desc {
    font-size: 16.8px;
    line-height: 1.75;
    color: #000;
    font-style: italic;
    margin-bottom: 16px;
  }
  /* 选项行：参照站 `<label class="flex items-center cursor-pointer font-normal">`
     —— flex 居中对齐、字重 400、文字与控件间距 8px（内层 span 的 `px-2`）。 */
  .phone.exam-shell.mode-exam.skill-reading .q-opt {
    font-size: 16.8px;
    font-weight: 400;
    line-height: 1.75;
    color: #000;
  }
  .phone.exam-shell.mode-exam.skill-reading .q-opt span {
    padding: 0 8px;
  }

  /* ---------- 「总结有选项题」：小标题 / 带空段落 / 词池 ----------
     卷面三个部件的实测值（readpartlive-styles.json）：
       h2.text-center        25.2px / 700 / 行高 44.1px / 居中 / #000
       p（段内嵌空）          16.8px / 行高 29.4px
       词池外壳               `w-[50%] mt-4` → 半宽 + 上边距 16px
       ul.drag               #efefef / padding 4px 20px
       li                    #fff / shadow rgba(0,0,0,.16) 0 0 2px / padding 0 6px / margin 10px 0
       空框 input.drop-target 223×35 / padding 4px 8px / Arial 16.8px / 行高 25.2px /
                             1px solid #808080 / 直角 / inline-block
     词池宽 50% 不是随手取的：参照站 ul 实测 411.188px，而题目栏内容区 822px
     —— 正好一半，而且放在正文框**外面**（不是框里的一张表）。
     正文框本身（`space-y-2 border-solid border-1 border-gray-400 p-4`）：
     Tailwind gray-400 = #9ca3af、p-4 = 16px、space-y-2 = 相邻子元素 8px。 */
  .phone.exam-shell.mode-exam.skill-reading .q-sum-title {
    font-size: 25.2px;
    font-weight: 700;
    line-height: 1.75;
    text-align: center;
    color: #000;
    margin: 4px 0 14px;
  }
  .phone.exam-shell.mode-exam.skill-reading .q-sum-box {
    border: 1px solid #9ca3af;
    border-radius: 0;
    padding: 16px;
  }
  .phone.exam-shell.mode-exam.skill-reading .q-sum-box > * + * {
    margin-top: 8px;
  }
  /* 段落之间是 16px 而不是 8px —— 参照站的 `collect` 原文里段落之间夹着一个
     **空 `<p></p>`**（占了 space-y 的两档：8 + 8），我们的识别层把空段并掉了，
     所以要在这里把那 8px 补回来。少这一条，正文会挤成一块，读起来像一段。 */
  .phone.exam-shell.mode-exam.skill-reading .q-sum-box p + p {
    margin-top: 16px;
  }
  .phone.exam-shell.mode-exam.skill-reading .q-sum-box p {
    font-size: 16.8px;
    line-height: 1.75;
    color: #000;
  }
  .phone.exam-shell.mode-exam.skill-reading .q-pool {
    width: 50%;
    margin-top: 16px;
  }
  .phone.exam-shell.mode-exam.skill-reading .q-pool li {
    font-size: 16.8px;
    line-height: 1.75;
    padding: 0 6px;
    margin: 10px 0;
  }
  .phone.exam-shell.mode-exam .q-pool li:hover {
    background: #f6f6f6;
  }

  /* 内联空框：整段话里嵌的那种空（`.q-inline-in`）。
     宽度必须显式给死 —— 它继承的 `.input` 是 `width:100%`，
     不收回来的话一个空会把整行撑满，句子被切碎、题没法读。
     参照站实测：阅读单 Part 页 223×35，整套页 211×33（两者根字号不同源）。

     ⚠️ 选择器写成 `.input.fill-in.q-inline-in`（而不是单个 `.q-inline-in`）是
     必要的：下面那条 `.input` 的字号/内边距与本条同权重，谁在后面谁生效，
     靠位置去赢会在下次挪动样式块时静默失效（实测过一次：字号被 15.5px 盖掉，
     量出来才知道）。多带一个类，权重就压过去了。 */
  .phone.exam-shell.mode-exam .input.fill-in.q-inline-in {
    display: inline-block;
    width: 223px;
    max-width: 100%;
    height: 35px;
    padding: 4px 8px;
    font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
    font-size: 16.8px;
    line-height: 1.5;
    vertical-align: baseline;
  }
  .phone.exam-shell.mode-exam.skill-listening .input.fill-in.q-inline-in {
    width: 211px;
    height: 33px;
    font-size: 15.4px;
  }

  /* ---------- 填空 / 选择控件：**直角**，贴机考观感 ----------
     手机端是「浅灰填充 + 8px 圆角」的微信风；机考里输入框是
     「白底 + 1px 灰边」的方框 —— 参照站实测就是 **直角** + 1px solid
     rgb(128,128,128)。

     这里曾经收到 4px（「比直角柔和，又明显区别于 App 里的圆润控件」），
     现已按参照站实测改回 0：作答区整套控件（输入框 / 选项 / 字母格 /
     答题卡格）统一直角，与真机考答题卡一致。
     ⚠️ 不要再改回圆角 —— 这一档是照着参照站量出来的，不是审美偏好。 */
  .phone.exam-shell.mode-exam .input,
  .phone.exam-shell.mode-exam .textarea {
    border-radius: 0;
    background: #fff;
    border: 1px solid #808080;
    font-size: 15.5px;
  }
  .phone.exam-shell.mode-exam .input:focus,
  .phone.exam-shell.mode-exam .textarea:focus {
    background: #fff;
    border-color: var(--brand);
    box-shadow: 0 0 0 3px rgba(7, 193, 96, 0.14);
  }
  /* ⚠️ 现行选项行是 `.q-opt`（原生 radio，无边框 → 本来就没有圆角要清）。
     这里点名的 `.opt` 只剩地图题的字母格（`App.letterGridHTML`）与解析回放用 ——
     别看到「.opt 好像不生效」就把它删掉：字母格在 PC 机考上就靠这条变直角。 */
  .phone.exam-shell.mode-exam .opt,
  .phone.exam-shell.mode-exam .letter-grid .opt {
    border-radius: 0;
  }
  .phone.exam-shell.mode-exam .letter-grid .key {
    border-radius: 0;
  }
  .phone.exam-shell.mode-exam .sheet-cell {
    border-radius: 0;
  }
  /* 写作作答框在 PC 上给足高度（HTML 里写死的 min-height:220px，得 !important 盖） */
  .phone.exam-shell.mode-exam .essay-in {
    min-height: 46vh !important;
  }

  /* ---------- 底栏 92px：Part 题号条，常驻不滚 ---------- */
  .phone.exam-shell.mode-exam .part-bar {
    display: block;
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 90;
    height: var(--exam-bar-h);
    background: #fff;
    border-top: 1px solid var(--line);
    box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.05);
    /* 两排：58（题号）+ 34（信息条） = 92 = --exam-bar-h */
    display: flex;
    flex-direction: column;
  }

  /* ---------- 听力开播前遮罩：盖住整个主区（含题目） ---------- */
  .phone.exam-shell .exam-mask {
    position: absolute;
    left: 0;
    right: 0;
    top: var(--exam-nav-h);
    bottom: var(--exam-bar-h);
    z-index: 80;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
    background: rgba(248, 249, 250, 0.94);
    backdrop-filter: blur(2px);
  }
  .phone.exam-shell .exam-mask.hide {
    display: none;
  }
  .phone.exam-shell .mask-card {
    max-width: 420px;
    padding: 34px 32px 28px;
    border: 1px solid var(--line);
    border-radius: 8px;
    background: #fff;
    box-shadow: 0 10px 40px rgba(0, 0, 0, 0.08);
    text-align: center;
  }
  .phone.exam-shell .mask-ico {
    font-size: 34px;
    line-height: 1;
    margin-bottom: 14px;
  }
  .phone.exam-shell .mask-t {
    font-size: 17px;
    font-weight: 600;
    color: var(--text);
    margin-bottom: 8px;
  }
  .phone.exam-shell .mask-d {
    font-size: 13px;
    line-height: 1.75;
    color: var(--text-gray);
    margin-bottom: 22px;
  }
  .phone.exam-shell .mask-btn {
    height: 44px;
    font-size: 15px;
    letter-spacing: 1px;
  }

  /* ---------- 第一排 58px：题号 ---------- */
  .phone.exam-shell .pb-row {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    align-items: stretch;
    gap: 0;
    padding: 0 28px;
    overflow-x: auto;
    overflow-y: hidden;
  }
  /* 展开的当前段：占据自己需要的宽度，不参与平分 */
  .phone.exam-shell .pb-part {
    flex: 0 0 auto;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 6px;
    padding-right: 22px;
  }
  /* 收起的段：平分剩余宽度。段数不定，写死 25% / 33% 在 2 段或 5 段时
     都会溢出或留白，`flex:1 1 0` 在任何段数下都对。 */
  .phone.exam-shell .pb-seg {
    flex: 1 1 0;
    min-width: 88px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 3px;
    padding: 0 12px;
    border: none;
    border-left: 1px solid var(--line);
    background: transparent;
    font-family: inherit;
    text-align: center;
    cursor: pointer;
    transition: background 0.15s;
  }
  .phone.exam-shell .pb-seg:hover {
    background: #f7f8fa;
  }
  .phone.exam-shell .pb-seg-t {
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 12.5px;
    color: var(--text);
    font-weight: 600;
  }
  .phone.exam-shell .pb-seg-c {
    font-size: 11.5px;
    color: var(--text-light);
    font-variant-numeric: tabular-nums;
  }

  /* ---------- 第二排 34px：卷名 + 已作答 / 状态 ---------- */
  .phone.exam-shell .pb-foot {
    flex: 0 0 34px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 14px;
    padding: 0 28px;
    background: #f8f9fa;
    border-top: 1px solid var(--line);
    font-size: 12px;
    color: var(--text-gray);
  }
  .phone.exam-shell .pb-foot-l {
    display: flex;
    align-items: center;
    gap: 12px;
    min-width: 0;
  }
  .phone.exam-shell .pb-title {
    max-width: 42vw;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--text);
    font-weight: 600;
  }
  .phone.exam-shell .pb-done {
    flex: 0 0 auto;
    white-space: nowrap;
  }
  .phone.exam-shell .pb-done b {
    color: var(--brand-dark);
    font-variant-numeric: tabular-nums;
  }
  .phone.exam-shell .pb-foot-r {
    display: flex;
    align-items: center;
    gap: 10px;
    flex: 0 0 auto;
  }
  /* 纯装饰状态位（参照站信息条上也是这两个图标），用 CSS 画，不引图片 */
  .phone.exam-shell .pb-stat {
    display: inline-flex;
    align-items: center;
    gap: 7px;
  }
  .phone.exam-shell .pb-stat i {
    display: inline-block;
    background: #c9ced6;
  }
  .phone.exam-shell .pb-stat .ic-wifi {
    width: 13px;
    height: 11px;
    clip-path: polygon(50% 100%, 0 42%, 12% 30%, 30% 18%, 50% 13%, 70% 18%,
      88% 30%, 100% 42%);
  }
  .phone.exam-shell .pb-stat .ic-batt {
    width: 20px;
    height: 11px;
    border-radius: 2px;
    box-shadow: inset 0 0 0 1.5px #c9ced6;
    background: linear-gradient(to right, #9fd9b7 0 78%, transparent 78%);
  }
  .phone.exam-shell .pb-hd {
    display: flex;
    align-items: baseline;
    gap: 8px;
    font-size: 12.5px;
    color: var(--text-gray);
    white-space: nowrap;
  }
  .phone.exam-shell .pb-name {
    color: var(--text);
    font-weight: 600;
  }
  .phone.exam-shell .pb-cnt b {
    color: var(--brand-dark);
    font-variant-numeric: tabular-nums;
  }
  /* 题号：参照站是「一条横线 + 线下的数字」，没有边框和底色。
     线是段内连续的（`gap:0` + 线宽 = 按钮宽），当前题那一段线变品牌色，
     已答的题数字加深 —— 一眼就能看出「这段做到哪了」。 */
  .phone.exam-shell .pb-nums {
    display: flex;
    gap: 0;
  }
  .phone.exam-shell .pb-num {
    position: relative;
    width: 32px;
    height: 30px;
    padding: 7px 0 0;
    border: none;
    border-radius: 0;
    background: transparent;
    color: var(--text-light);
    font-family: inherit;
    font-size: 12px;
    font-variant-numeric: tabular-nums;
    cursor: pointer;
    transition: color 0.13s;
  }
  .phone.exam-shell .pb-num::before {
    content: "";
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    height: 2px;
    background: var(--line-strong);
    transition: background 0.13s;
  }
  .phone.exam-shell .pb-num:hover {
    color: var(--brand-dark);
  }
  .phone.exam-shell .pb-num:hover::before {
    background: var(--brand);
  }
  .phone.exam-shell .pb-num.done {
    color: var(--text);
    font-weight: 600;
  }
  .phone.exam-shell .pb-num.cur {
    color: var(--brand-dark);
    font-weight: 700;
  }
  .phone.exam-shell .pb-num.cur::before {
    background: var(--brand);
    height: 2.5px;
  }
}

/* ============================================================
   三、中等宽度（768–1023）：保持手机布局，仅放宽内容上限
   —— 平板竖屏仍是触屏，底部 TabBar 更顺手，所以不切侧边栏。
   ============================================================ */

@media (min-width: 768px) and (max-width: 1023px) {
  .phone {
    max-width: 620px;
  }
  .page .wrap {
    padding: 16px 18px 32px;
  }
}
