/* ===== tokens.css ===== */
/**
 * 设计 Token —— 体验设计/01-设计系统.md 的代码化落地。
 *
 * 原计划在 T9.1.1 落地；教师端知识库页（T2.6.2）是第一处需要成体系视觉的页面，
 * 与其把色值散落在各组件里，不如提前把这个文件建起来。
 *
 * 使用方式：组件内一律写 `var(--accent)` 这类语义变量，不写死色值 ——
 * 这样深色模式与后续换肤都是改这一处。
 */

:root {
  /* ---------- 色彩：浅色模式（默认） ---------- */
  --bg-base: #f5f5f7;
  --bg-surface: #ffffff;
  --bg-elevated: rgba(255, 255, 255, 0.72);
  --bg-subtle: #f5f5f7;

  --text-primary: #1d1d1f;
  --text-secondary: #6e6e73;
  --text-tertiary: #aeaeb2;
  --text-on-accent: #ffffff;

  --accent: #0071e3;
  --accent-hover: #0077ed;
  --accent-soft: rgba(0, 113, 227, 0.1);
  --accent-ring: rgba(0, 113, 227, 0.15);

  /* 辅助主色（活力科技风用三主色）：活力橙 / 薄荷绿。
     默认给中性值，被外观设置覆盖成主题色。 */
  --accent-2: #ff9f1c;
  --accent-3: #34d399;
  --accent-2-soft: rgba(255, 159, 28, 0.12);
  --accent-3-soft: rgba(52, 211, 153, 0.12);

  /* 渐变（发送按钮、选中描边、logo）。写在这里而不是组件里，
     这样换主题时渐变会跟着强调色一起变，不会留下突兀的旧配色。 */
  --gradient-primary: linear-gradient(135deg, var(--accent) 0%, #8b5cf6 100%);
  --gradient-vibrant: linear-gradient(
    135deg,
    var(--accent) 0%,
    var(--accent-3) 55%,
    var(--accent-2) 100%
  );

  /* 磨砂玻璃：半透明 + 背景模糊。低端设备上 backdrop-filter 开销可观，
     所以只用在少数「浮层」上（侧栏、气泡、悬浮按钮），不铺满全站。 */
  --glass-blur: saturate(180%) blur(18px);
  --glass-bg: rgba(255, 255, 255, 0.62);
  --gradient-brand: linear-gradient(135deg, #0a84ff 0%, #5e5ce6 100%);

  --success: #34c759;
  --success-soft: rgba(52, 199, 89, 0.12);
  --warning: #ff9500;
  --warning-soft: rgba(255, 149, 0, 0.12);
  --danger: #ff3b30;
  --danger-soft: rgba(255, 59, 48, 0.1);

  /* ---------- 难度/来源徽章（知识点校对页） ----------
   * 这五对颜色原先只以 `var(--badge-x-fg, #hex)` 的**回退值**形式存在 ——
   * 变量从未声明，等于「用 var 包着的硬编码色值」：能显示，但改主题时它们不动。
   * 现在正式声明为一等 Token。
   *
   * ⚠️ 它们声明在 `:root` 而不是教师端块里，因为表达的是**语义**
   * （基础/进阶/难/易混/考点），不是主题色 —— 语义不随主题变。
   * 五个都不跟随 `--accent`：全染成绿色就等于没有难度区分了。
   */
  --badge-basic-fg: #1a6b3c;
  --badge-basic-bg: #e3f6e9;
  --badge-advanced-fg: #1a4d8f;
  --badge-advanced-bg: #e4eefc;
  --badge-hard-fg: #8a3d0f;
  --badge-hard-bg: #fdeee2;
  --badge-confusing-fg: #7a2e6b;
  --badge-confusing-bg: #f8e8f4;
  --badge-exam-fg: #8a1f1f;
  --badge-exam-bg: #fde8e8;

  --divider: rgba(0, 0, 0, 0.06);
  --border: rgba(0, 0, 0, 0.08);
  /* 拖拽区虚线边框：单独一档，因为它比普通边框明显、比强调色淡。
     直接复用 `--accent` 会让虚线抢走整个页面的注意力。 */
  --dropzone-border: rgba(0, 0, 0, 0.18);

  /* ---------- 圆角（体验设计/01 §3） ---------- */
  --radius-lg: 20px;
  --radius-md: 16px;
  /**
   * 最小触达区（T9.3.2，WCAG 2.5.5 / 平台人机指南的 44×44）。
   *
   * ⚠️ **它是「命中区」的下限，不是「视觉尺寸」**。密集排布的图标按钮
   * （例如整卷预览里的 ↑↓换移）视觉上只有 20px 见方 —— 强行把它们放大到 44
   * 会把一行挤爆。那种场合用 `::after` 把命中区撑到 44×44，视觉保持不变
   * （见 `.paper__op`）。**缩小命中区来省空间是不允许的**：
   * 手指点不中比界面丑严重得多。
   */
  --tap-min: 44px;
  --radius-sm: 12px;
  --radius-icon: 10px;
  --radius-pill: 999px;
  /* 卡片统一 20px（活力科技风的圆角基线）。`--radius-sm` 保留给紧凑元素
     （标签、小按钮）—— 全部 20px 会让密集区域显得松散。 */
  --radius-card: 20px;

  /* ---------- 阴影（§4） ---------- */
  --shadow-card: 0 1px 2px rgba(0, 0, 0, 0.04), 0 8px 24px rgba(0, 0, 0, 0.06);
  --shadow-hover: 0 4px 12px rgba(0, 0, 0, 0.06), 0 16px 40px rgba(0, 0, 0, 0.1);
  --shadow-popover: 0 8px 24px rgba(0, 0, 0, 0.12), 0 24px 60px rgba(0, 0, 0, 0.12);
  /* 多层柔和阴影（活力科技风）：三层叠加 —— 贴地的一层很紧、中间一层撑开体积、
     最外一层带**主题色**做环境光。单层阴影永远是「贴纸」，多层才有厚度。 */
  --shadow-soft:
    0 1px 2px rgba(15, 23, 42, 0.04), 0 4px 12px rgba(15, 23, 42, 0.05),
    0 18px 40px -12px rgba(59, 130, 246, 0.18);
  --shadow-lift:
    0 2px 6px rgba(15, 23, 42, 0.06), 0 12px 28px rgba(15, 23, 42, 0.08),
    0 28px 60px -16px rgba(59, 130, 246, 0.26);
  /* 选中项的柔光描边 */
  --glow-focus: 0 0 0 3px var(--accent-ring), 0 0 22px -4px var(--accent);

  /* ---------- 间距（8pt 网格，§6） ---------- */
  --space-xs: 4px;
  --space-sm: 8px;
  --space-md: 12px;
  --space-lg: 16px;
  --space-xl: 24px;
  --space-2xl: 32px;
  --space-3xl: 48px;

  /* ---------- 字体（§2） ----------
     `PingFang SC` 管 macOS/iOS，`HarmonyOS Sans` 管华为系，
     `Microsoft YaHei` 兜 Windows。三者都是中文屏显体，
     顺序在 `-apple-system` 之后：拉丁字符优先用系统 UI 字体，
     中文才落到这几档（系统的中文回退体往往不是屏显优化的）。 */
  --font-sans:
    -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'SF Pro Display', 'PingFang SC',
    'HarmonyOS Sans SC', 'HarmonyOS Sans', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  --font-weight-regular: 400;
  --font-weight-medium: 500;
  --font-weight-semibold: 600;

  /* 字号缩放系数。**必须给默认值**：`calc(15px * var(--font-scale))` 里的变量
     一旦未定义，整条声明会失效，字号退回继承 —— 表现是所有文字都变成浏览器默认大小。
     运行时由 `stores/appearance.ts` 覆盖。 */
  --font-scale: 1;

  /* 字阶：Display 34 → Caption 11（成对给出字号与行高，避免各处自行估算行高）
     全部乘 `--font-scale`：用户在设置里调字号时，行高要跟着缩放，
     否则字变大而行距不变，密排文字会挤在一起。
     缩放系数由 `stores/appearance.ts` 写在 `<html>` 的内联样式上。 */
  --text-display: calc(34px * var(--font-scale));
  --text-display-lh: calc(41px * var(--font-scale));
  --text-title-1: calc(28px * var(--font-scale));
  --text-title-1-lh: calc(34px * var(--font-scale));
  --text-title-2: calc(22px * var(--font-scale));
  --text-title-2-lh: calc(28px * var(--font-scale));
  --text-headline: calc(17px * var(--font-scale));
  --text-headline-lh: calc(24px * var(--font-scale));
  --text-body: calc(15px * var(--font-scale));
  --text-body-lh: calc(22px * var(--font-scale));
  --text-footnote: calc(13px * var(--font-scale));
  --text-footnote-lh: calc(18px * var(--font-scale));
  --text-caption: calc(11px * var(--font-scale));
  --text-caption-lh: calc(14px * var(--font-scale));

  /* ---------- 动效（体验设计/02 §2） ---------- */
  /* 曲线语义（一条曲线只表达一件事，换页/悬浮/弹层各用各的）：
     - standard：通用状态变化（hover、颜色、边框），两端都缓
     - out：**入场**与「减速停住」的动作（页面切入、列表入场）
     - in：**离场**与「加速离开」的动作（元素淡出、被移除）
     - spring：需要「弹一下」的场合（弹层、成功反馈） */
  --ease-standard: cubic-bezier(0.25, 0.1, 0.25, 1);
  --ease-out: cubic-bezier(0, 0, 0.58, 1);
  --ease-in: cubic-bezier(0.42, 0, 1, 1);
  --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);

  /* 时长按「动作类型」分档，而不是按大小分档（体验设计/02 §1：微交互 150~200ms，
     过渡 250~300ms，入场弹簧 400~600ms）。取值落在区间内。 */
  --duration-fast: 120ms; /* 按压、hover 等即时反馈 */
  --duration-base: 200ms; /* 微交互（侧边栏高亮条、淡入淡出） */
  --duration-transition: 250ms; /* 页面切换 */
  --duration-slow: 320ms; /* 较大的状态变化 */
  --duration-spring: 500ms; /* 弹簧入场（弹层上滑） */

  /* 列表交错入场的单步延迟（体验设计/02 §2：逐张 40ms） */
  --stagger-step: 40ms;
}

/* ---------- 色彩：深色模式（§1.2） ----------
 *
 * ⚠️ **下面两个深色块是同一套值，必须同步维护。**
 * 这是纯 CSS 的固有限制：没法把一段声明同时挂到「媒体查询」和「属性选择器」上。
 *
 *   · 第一块：系统是深色，且用户**没有**手动指定浅色 → 跟随系统
 *   · 第二块：用户在设置里**手动**选了深色（哪怕系统是浅色）
 *
 * `stores/appearance.ts` 负责在 `<html>` 上写 `data-theme`：
 * 手动选浅色/深色时写死，选「跟随系统」时**移除属性**（把决定权还给媒体查询）。
 *
 * ⚠️ **两个块都要排除教师端作用域**（`:not([data-teacher-fresh])`）。
 * 教师端是一套**自足的浅色主题**，若被系统深色模式沾上，只有那些教师块
 * 没声明的变量会变成深色值（`--success-soft` / `--warning-soft` / `--danger-soft`）——
 * 症状是「老师的页面是浅绿的，但成功提示的底色是深色的」这种半深半浅的怪相。
 * 排除掉之后，教师块只需要声明**与浅色默认值不同**的那些变量。
 */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']):not([data-teacher-fresh]) {
    --bg-base: #000000;
    --bg-surface: #1c1c1e;
    --bg-elevated: rgba(28, 28, 30, 0.72);
    --bg-subtle: #2c2c2e;

    --text-primary: #f5f5f7;
    --text-secondary: #a1a1a6;
    --text-tertiary: #6e6e73;

    --accent: #0a84ff;
    --accent-hover: #409cff;
    --accent-soft: rgba(10, 132, 255, 0.18);
    --accent-ring: rgba(10, 132, 255, 0.25);

    --success-soft: rgba(52, 199, 89, 0.18);
    --warning-soft: rgba(255, 149, 0, 0.18);
    --danger-soft: rgba(255, 59, 48, 0.18);

    --divider: rgba(255, 255, 255, 0.08);
    --border: rgba(255, 255, 255, 0.12);

    --shadow-card: 0 1px 2px rgba(0, 0, 0, 0.4), 0 8px 24px rgba(0, 0, 0, 0.35);
    --shadow-hover: 0 4px 12px rgba(0, 0, 0, 0.45), 0 16px 40px rgba(0, 0, 0, 0.4);
    --shadow-popover: 0 8px 24px rgba(0, 0, 0, 0.5), 0 24px 60px rgba(0, 0, 0, 0.5);
  }
}

:root[data-theme='dark']:not([data-teacher-fresh]) {
  --bg-base: #000000;
  --bg-surface: #1c1c1e;
  --bg-elevated: rgba(28, 28, 30, 0.72);
  --bg-subtle: #2c2c2e;

  --text-primary: #f5f5f7;
  --text-secondary: #a1a1a6;
  --text-tertiary: #6e6e73;

  --accent: #0a84ff;
  --accent-hover: #409cff;
  --accent-soft: rgba(10, 132, 255, 0.18);
  --accent-ring: rgba(10, 132, 255, 0.25);

  --success-soft: rgba(52, 199, 89, 0.18);
  --warning-soft: rgba(255, 149, 0, 0.18);
  --danger-soft: rgba(255, 59, 48, 0.18);

  --divider: rgba(255, 255, 255, 0.08);
  --border: rgba(255, 255, 255, 0.12);

  --shadow-card: 0 1px 2px rgba(0, 0, 0, 0.4), 0 8px 24px rgba(0, 0, 0, 0.35);
  --shadow-hover: 0 4px 12px rgba(0, 0, 0, 0.45), 0 16px 40px rgba(0, 0, 0, 0.4);
  --shadow-popover: 0 8px 24px rgba(0, 0, 0, 0.5), 0 24px 60px rgba(0, 0, 0, 0.5);
}

/* ---------- 教师端专用：清新教育科技风（Fresh Edu Tech） ----------
 *
 * ## 为什么是**作用域切换**而不是又一个外观预设
 *
 * 学生端是「活力科技青春风”，教师端是「清新教育科技风」——
 * 这是**两套并行的视觉语言**，按角色区分，不是同一套主题的深浅两版。
 *
 * 而外观设置（`stores/appearance.ts`）是**全站唯一一份**、写在 `<html>` 内联样式上。
 * 若把教师端也做成一个预设，同一浏览器里切角色就会互相覆盖，
 * 且「老师选了深空」会把学生端也改掉。所以这里用属性作用域：
 * 教师端工作区挂载时给 `<html>` 加 `data-teacher-fresh`，卸载时移除。
 *
 * 背景层（渐变 + 微网格 + 径向光斑）不在这里定义 —— 它由 `useTeacherFreshTheme()`
 * 以**内联变量**写入，因为内联样式的优先级高于本文件，
 * 在样式表里写 `--app-backdrop` 会被外观设置的内联值盖掉。
 *
 * ## 为什么这套 Token 里**没有** `color-scheme: dark`
 *
 * 这套是**浅色**主题。曾经这里是 Dark Pro（`#0f172a` + 青色强调 + `color-scheme: dark`），
 * 换成浅色时必须显式**不写** `color-scheme` —— 否则浏览器仍按深色渲染原生控件
 * （滚动条、下拉箭头、日期选择器），页面是浅色而滚动条是黑的。
 * `:root` 默认就是 `light`，所以「不声明」就是正确行为，不需要再写一条覆盖。
 */
:root[data-teacher-fresh] {
  /* 背景：#F7FBF7 → #E8F5E9 的渐变由 `--app-backdrop` 承担，
     这里给的是**兜底纯色**（未挂载背景层时、以及卡片之外的裸露区域） */
  --bg-base: #f7fbf7;
  --bg-surface: #ffffff;
  --bg-elevated: rgba(255, 255, 255, 0.86);
  --bg-subtle: #eef7ee;

  /* 文字：深绿做正文，绿灰做次要 —— 纯灰在绿色调背景上会显脏 */
  --text-primary: #1b5e20;
  --text-secondary: #4e6b52;
  --text-tertiary: #7c947f;
  --text-on-accent: #ffffff;

  /* 主色：草绿 */
  --accent: #4caf50;
  --accent-hover: #43a047;
  --accent-soft: rgba(76, 175, 80, 0.12);
  --accent-ring: rgba(76, 175, 80, 0.28);
  /* 辅助色：蓝（规范要求，用于次要按钮点缀） */
  --accent-2: #1976d2;
  --accent-3: #2e7d32;
  --accent-2-soft: rgba(25, 118, 210, 0.12);
  --accent-3-soft: rgba(46, 125, 50, 0.12);

  /* 边框用**绿色系**而不是中性灰：浅绿底上的灰边框看起来像脏了 */
  --border: #dcecdc;
  --divider: #e8f3e8;
  /* 拖拽区虚线：一档浅绿（Material Green 200），比主色淡、比边框明显。
     虚线本来就比实线弱，再淡就看不见了。 */
  --dropzone-border: #a5d6a7;

  /* 圆角：规范要求卡片 16px。`--radius-sm` 跟到 10px ——
     16px 卡片里嵌 12px 的小标签会显得「圆到没有棱角」，
     10px 与 16px 的对比才是同一套语言。 */
  --radius-card: 16px;
  --radius-lg: 16px;
  --radius-md: 16px;
  /**
   * 最小触达区（T9.3.2，WCAG 2.5.5 / 平台人机指南的 44×44）。
   *
   * ⚠️ **它是「命中区」的下限，不是「视觉尺寸」**。密集排布的图标按钮
   * （例如整卷预览里的 ↑↓换移）视觉上只有 20px 见方 —— 强行把它们放大到 44
   * 会把一行挤爆。那种场合用 `::after` 把命中区撑到 44×44，视觉保持不变
   * （见 `.paper__op`）。**缩小命中区来省空间是不允许的**：
   * 手指点不中比界面丑严重得多。
   */
  --tap-min: 44px;
  --radius-sm: 10px;

  /* 玻璃拟态：浅色底的磨砂要更亮、模糊更少，
     否则透上来的是背景的绿，白色卡片会显得发青 */
  --glass-blur: saturate(160%) blur(10px);
  --glass-bg: rgba(255, 255, 255, 0.72);

  /* 主按钮：草绿渐变（规范给的 135° 两档绿） */
  --gradient-primary: linear-gradient(135deg, #66bb6a 0%, #4caf50 100%);
  --gradient-vibrant: linear-gradient(135deg, #4caf50 0%, #1976d2 100%);

  /* 阴影：规范给的 0 2px 12px rgba(0,0,0,0.06) 是**静止卡片**的基准，
     越靠下越淡 —— 浅色主题里阴影重一点就显脏，宁可淡 */
  --shadow-card: 0 2px 12px rgba(0, 0, 0, 0.06);
  --shadow-hover: 0 6px 20px rgba(0, 0, 0, 0.1);
  --shadow-popover: 0 12px 32px rgba(0, 0, 0, 0.12), 0 2px 8px rgba(0, 0, 0, 0.06);
  /* 多层柔和阴影：最外一层带**绿色**环境光，让卡片像浮在绿调空气里 */
  --shadow-soft:
    0 1px 2px rgba(27, 94, 32, 0.04), 0 4px 12px rgba(27, 94, 32, 0.05),
    0 16px 36px -12px rgba(76, 175, 80, 0.16);
  --shadow-lift:
    0 2px 6px rgba(27, 94, 32, 0.06), 0 10px 24px rgba(27, 94, 32, 0.08),
    0 24px 52px -16px rgba(76, 175, 80, 0.24);
  /* 选中态的柔光描边：浅色底上不需要发光，只要一圈干净的绿环 */
  --glow-focus: 0 0 0 3px var(--accent-ring);
  --glow-accent: 0 6px 18px -6px rgba(76, 175, 80, 0.55);
}

/* ---------- 全局基底 ---------- */
html,
body,
#app {
  height: 100%;
}

body {
  margin: 0;
  font-family: var(--font-sans);
  font-size: 15px;
  line-height: 22px;
  color: var(--text-primary);
  /* 背景分两层：
     - `--bg-base` 纯色兜底（老浏览器、未选科技风时）
     - `--app-backdrop` 由外观设置写入的**多层渐变/网格**，用来做科技风氛围
     `fixed` 很关键：不固定的话长页面滚动时光斑会跟着跑，
     看起来像贴在内容上的贴纸，而不是环境光。 */
  background-color: var(--bg-base);
  background-image: var(--app-backdrop, none);
  /* 网格底需要 size/repeat 才看得出格子；纯渐变时这两项由 JS 留空、用默认值 */
  background-size: var(--app-backdrop-size, auto);
  background-repeat: var(--app-backdrop-repeat, repeat);
  background-attachment: fixed;
  -webkit-font-smoothing: antialiased;
  /* 深色模式切换不跳变（体验设计/01 §1.2） */
  transition:
    background-color var(--duration-base) var(--ease-out),
    color var(--duration-base) var(--ease-out);
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

/* 数字统一等宽，避免统计数字跳动（§2） */
.tabular {
  font-variant-numeric: tabular-nums;
}

/* 可访问性：全键盘可操作，焦点态可见（§9） */
:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--accent-ring);
  border-radius: var(--radius-sm);
}

/* 尊重系统的「减少动态效果」设置（§9） */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* ===== components.css ===== */
/**
 * 共享组件样式（按钮族）。
 *
 * ## 为什么这些要放到全局，而不是各页 scoped 一份
 *
 * 原先 `.btn` 在 `teacher/KnowledgeBase.vue` 与 `teacher/ThesisReview.vue`
 * 里各写了一份**逐字相同**的规则。写第三份（知识点校对页）的时候必须停下来：
 * 同一组按钮样式复制三份，改一处忘一处就会出现「这个页面的主按钮是灰的」这类
 * 只在某个页面复现的问题，而且 scoped 样式不会互相报错。
 *
 * ## 为什么是普通 CSS 文件，而不是全局 Vue 组件
 *
 * 这些是**样式**而不是结构：按钮的标签、图标、行为各页不同，
 * 强行抽成 `<BaseButton>` 会把差异都堆成 props。而用全局类名，
 * 各页仍可自由组合（`class="btn btn--primary btn--sm"`）。
 *
 * ## 与 scoped 样式的关系
 *
 * Vue 的 scoped 样式会给选择器加上属性选择器（`[data-v-x]`），特异性更高，
 * 因此页面里**若还残留**同名规则会覆盖这里。迁移时要连同页面里的旧规则一起删掉，
 * 否则会出现「改了全局没生效」的困惑。
 *
 * 加载顺序：`build-esm.mjs` 把 `src/styles/*.css` 合并成一个全局样式表，
 * `tokens.css` 固定排在最前（组件样式依赖其中的变量）。
 */

.btn {
  display: inline-flex;
  gap: 6px;
  align-items: center;
  padding: 10px 18px;
  font-family: inherit;
  font-size: var(--text-body);
  font-weight: var(--font-weight-medium);
  cursor: pointer;
  border: none;
  border-radius: var(--radius-sm);
  transition:
    background var(--duration-fast) var(--ease-out),
    transform var(--duration-fast) var(--ease-out),
    box-shadow var(--duration-fast) var(--ease-out);
}

.btn:disabled {
  cursor: not-allowed;
  opacity: 0.55;
}

.btn:active:not(:disabled) {
  transform: scale(0.97);
}

.btn--primary {
  color: var(--text-on-accent);
  background: var(--accent);
}

.btn--primary:hover:not(:disabled) {
  background: var(--accent-hover);
  box-shadow: var(--shadow-card);
}

.btn--ghost {
  color: var(--text-primary);
  background: var(--bg-surface);
  border: 1px solid var(--border);
}

.btn--ghost:hover:not(:disabled) {
  background: var(--bg-subtle);
}

/* 危险操作（删除）：红色只在「真要删」时出现，避免整页都是红的 */
.btn--danger {
  color: var(--text-on-accent);
  background: var(--danger);
}

.btn--danger:hover:not(:disabled) {
  filter: brightness(1.06);
  box-shadow: var(--shadow-card);
}

/* 列表行内的次要操作 —— 用默认尺寸会把行撑得很高 */
.btn--sm {
  padding: 5px 12px;
  font-size: var(--text-footnote);
}

/* 按钮里的状态标签（如「待接入」） */
.btn__tag {
  padding: 1px 6px;
  font-size: var(--text-caption);
  color: var(--text-tertiary);
  background: var(--bg-subtle);
  border-radius: var(--radius-pill);
}

/**
 * 只给读屏软件看的文字。
 *
 * 骨架屏 / 转圈动画对视觉用户是「说明正在加载」，对读屏用户却是**一片空白** ——
 * 屏幕上明明有内容在变，读屏念出来什么都没有。这类地方必须补一句
 * `role="status"` 的真文字，再用这个类把它藏起来。
 *
 * 不能用 `display: none` / `visibility: hidden`：两者都会把节点从无障碍树里
 * 一并摘掉，读屏同样看不到。必须让它**可见但不可见**（1px + 裁剪）。
 */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  white-space: nowrap;
  border: 0;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
}

/**
 * 原生 `<button>` 的**基色**：一律用本项目的前景色，不留给浏览器。
 *
 * ## 这不是「美化」，是修一个真实的看不见
 *
 * `<button>` 的 `color` 初值是 UA 的关键字 **`buttontext`**，而它会跟随
 * **`prefers-color-scheme`** —— 与"本页用了什么主题"**毫无关系**。
 *
 * 本项目的深色模式**不是**靠 `prefers-color-scheme` 实现的，而是靠
 * `data-*` 属性 + CSS 变量（教师端还有 `data-teacher-fresh` 这套浅绿主题）。
 * 于是两者在**系统深色 + 浅色主题**下正好错位：
 *
 * | | 系统浅色 | 系统深色 |
 * |---|---|---|
 * | 未显式设色的 `<button>` 文字 | 黑 | **白** |
 * | `--bg-surface`（浅色主题里） | 白 | 仍然是**白** |
 *
 * 结果就是**白字白底** —— 老师实测报的「智能组卷 / 手动选题 / 让 AI 出新题
 * 这几个白色字体，和底色没差异度，看不清楚」，以及「状态页签很不美观」，
 * 都是这一条引起的（那两处当时都没有显式 `color`）。
 *
 * 修在全局而不是逐个页面打补丁：只要有一条 `<button>` 忘了写颜色，
 * 它在深色系统下就会消失 —— 这种"漏一个就瞎一个"的东西不能靠自觉。
 * `.btn` / `.btn--primary` / 各页自己的类**都写在它后面**，照样覆盖。 */
button {
  color: inherit;
  font: inherit;
}

/**
 * 表单控件的**文字色**同样必须由本项目决定 —— 理由与上面 `button` 那条**完全一样**。
 *
 * `<input>` / `<textarea>` 的 `color` 初值是 UA 关键字 **`fieldtext`**，
 * 它跟随**系统的** `prefers-color-scheme`；而本项目深色模式走的是 `data-*` + CSS 变量。
 * 错位的结果就是**白字白底**：
 *
 * 实测（老师报的「每一题右上角的卷面分值看不出数字」）——
 * `.pitem__score-input` 写死了 `background: #fff` 但没写 `color`，于是
 * 系统深色下 `color` 变成 `rgb(255,255,255)`，与自己的白底**完全同色**，
 * 框里那个「5」是看不见的（浅色系统下一切正常，所以只在部分机器上暴露）。
 *
 * 用 `var(--text-primary)` 而不是 `inherit`：输入框多数落在 `#fff` / `--bg-surface` 上，
 * 而它们的父级常常是**次要色**的标签容器（`inherit` 会把输入内容也变灰）。
 * 全库没有"深底输入框"，所以这一条不会误伤（已实测确认）。
 * 各页自己的 `.xx__input { color: ... }` 特异性更高，照样覆盖。
 */
input,
textarea {
  color: var(--text-primary);
}

/**
 * 原生 `<select>` 的统一样式。
 *
 * ## 为什么需要它（这不是「美化」，是修一个真实的丑）
 *
 * `<select>` **不会继承 `input` 的样式**。各页普遍只写了 `.field input { … }`，
 * 于是同一个表单里 `<input>` 已被样式化、`<select>` 却是浏览器默认外观：
 * 更高、字更大、圆角和边框都不一致 —— 并排放在一起就是「这个下拉怎么这么丑」。
 * 「加入班级」那个下拉正是如此（它连 class 都没有，没命中任何规则）。
 *
 * ## 为什么放在全局
 *
 * 与 `.btn` 同一个理由：一旦每个页面各写一份，就会漂移成
 * 「有的页面下拉带自定义箭头、有的是系统箭头」。这里给一份基线，
 * 各页仍可用自己的类覆盖尺寸等细节。
 *
 * ## 三个关键点
 *
 * 1. **`appearance: none`** 去掉操作系统自带的箭头 —— 它无法调色、
 *    在深色模式下经常是黑的、贴边，跟我们的设计系统格格不入；
 * 2. 自带箭头去掉后**必须自己补一个**，否则下拉看起来跟输入框一模一样、
 *    点不出「这里可以展开」。这里用内联 SVG 当背景图（`currentColor` 在
 *    data URI 里拿不到，所以用中灰 `%236b7280`，深浅两套底色上都可辨）；
 * 3. **右侧留出箭头的空间**（`padding-right` + `background-position`），
 *    否则选到较长的班级名时文字会钻到箭头底下。
 */
select {
  /* 去掉系统自带箭头，否则会与下面自绘的那个**重复成两个箭头**。
     `-webkit-appearance` 是给老 WebKit 的，两者并存无副作用。 */
  appearance: none;
  -webkit-appearance: none;
  /* 触达区走 token（T9.3.2）：原来是 36px，低于 44 的下限 */
  min-height: var(--tap-min);
  padding: 8px 30px 8px 10px;
  font-family: inherit;
  font-size: var(--text-body);
  color: var(--text-primary);
  background-color: var(--bg-base);
  /* 只设 background-image，不覆盖各页可能设的 background-color */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M4 6.5 8 10.5 12 6.5' fill='none' stroke='%236b7280' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 8px center;
  background-size: 14px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  /* 长班级名不撑破布局 */
  max-width: 100%;
  text-overflow: ellipsis;
}

select:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

select:disabled {
  cursor: not-allowed;
  opacity: 0.6;
}

/* 下拉展开的选项列表由系统绘制，这里只能保证底色与文字色跟着主题走 */
select option {
  color: var(--text-primary);
  background: var(--bg-surface);
}


/* ======================================================================
 * 对话气泡（**学生端两处对话界面共用**）
 *
 * ## 为什么放到全局而不是各页 scoped 一份
 *
 * 与文件头的按钮理由相同，但这里多了一层代价：**复制样式会把 bug 一起复制**。
 * 「教学陪练」第一版是照抄 `pages/student/Chat.vue` 的气泡样式，
 * 抄的时候把 `overflow: hidden`（强调条需要它）一并抄了过去 ——
 * 而陪练页的气泡是**滚动容器的直接子项**（答疑页多包了一层 `.row`），
 * 于是：
 *
 *   `overflow: hidden` ⇒ flex 子项的自动最小尺寸变成 0（规范如此）
 *   ⇒ `.tut__chat`（flex column 滚动容器）把它**压扁**
 *   ⇒ 超过两三行的回复被裁掉，而且容器 `scrollHeight == clientHeight`
 *   ⇒ **既看不到滚动条、也看不到被裁的字**（用户实测报的就是这两条）。
 *
 * 所以这一份规则里同时做两件事：给出统一外观，且**从结构上不允许被压扁**。
 *
 * ## 结构约定（两页必须一致）
 *
 *   <div class="bubble-row bubble-row--student|assistant">
 *     <div class="bubble bubble--student|assistant">…</div>
 *   </div>
 *
 * `.bubble-row` 是滚动容器的子项，负责对齐；`.bubble` 只负责外观。
 * 多这一层不是装饰：**行的 `overflow` 保持 visible**，它的自动最小尺寸
 * 就等于内容高度，气泡因此永远压不扁。直接把 `.bubble` 放进滚动容器，
 * 就必须自己记住 `flex: 0 0 auto`（那条也留着，作为第二道保险）。
 * ====================================================================== */

.bubble-row {
  display: flex;
  /* 关键：不要变成可压缩项。`flex-shrink: 1`（默认值）在内容超出容器时
     会把气泡压扁 —— 这就是「回复超过三行显示不全」的根因。 */
  flex: 0 0 auto;
  margin-bottom: var(--space-lg);
}

.bubble-row--student {
  justify-content: flex-end;
}

.bubble-row--assistant {
  justify-content: flex-start;
}

.bubble {
  /* 同样不能压扁。留着它，即使有人把 `.bubble` 直接塞进滚动容器也不会被裁。 */
  flex: 0 0 auto;
  max-width: min(76%, 640px);
  padding: 12px 16px;
  font-size: var(--text-body);
  line-height: var(--text-body-lh);
  border-radius: var(--radius-card);
}

/* 学生气泡：主渐变底 + 右下角收一个圆角（指向自己） */
.bubble--student {
  color: var(--text-on-accent);
  white-space: pre-wrap;
  word-break: break-word;
  background: var(--gradient-primary);
  border-bottom-right-radius: 6px;
  box-shadow: 0 6px 18px -6px rgba(59, 130, 246, 0.5);
  animation: bubble-in var(--duration-slow) var(--ease-spring) both;
}

/* AI 气泡：毛玻璃 + 左侧渐变强调条。
   强调条用伪元素而不是 `border-left` + `background-clip`：
   后者在只有一层背景却给两个 clip 值时会失效（渐变根本显示不出来），
   而且 `border-image` 会吃掉圆角。伪元素能贴合圆角、也不影响正文。

   ⚠️ 这里的 `overflow: hidden` 是为了把强调条裁在圆角内。
   它让本元素成为**滚动容器**，自动最小尺寸变为 0 ——
   所以它**必须**配合上下的 `flex: 0 0 auto`，否则会被压扁到零高度。 */
.bubble--assistant {
  position: relative;
  padding-left: 20px;
  color: var(--text-primary);
  background: var(--glass-bg);
  border: 1px solid var(--border);
  border-bottom-left-radius: 6px;
  box-shadow: var(--shadow-soft);
  backdrop-filter: var(--glass-blur);
  overflow: hidden;
  animation: bubble-in var(--duration-slow) var(--ease-spring) both;
}

.bubble--assistant::before {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 4px;
  content: '';
  background: var(--gradient-vibrant);
}

/* 思考占位（`ThinkingDots` 自带卡片外观）：
   把气泡外观清掉，否则会「卡片里套卡片」。 */
.bubble--bare {
  padding: 0;
  background: transparent;
  border: none;
  box-shadow: none;
  backdrop-filter: none;
  animation: none;
}

.bubble--bare::before {
  display: none;
}

@keyframes bubble-in {
  from {
    opacity: 0;
    transform: translateY(8px) scale(0.98);
  }

  to {
    opacity: 1;
    transform: none;
  }
}

@media (max-width: 640px) {
  .bubble {
    max-width: 88%;
  }
}

/* 尊重系统的「减弱动态效果」 */
@media (prefers-reduced-motion: reduce) {
  .bubble--student,
  .bubble--assistant {
    animation: none;
  }
}
/* ======================================================================
   打印 / 另存为 PDF（T3P.4.5）

   需求明确走**浏览器打印**（`技术设计/14` ADR-025），服务端不参与 ——
   `GET /exam/:id/export?format=pdf` 仍然返回 400，既有断言因此不用改。

   ## 为什么不给每个元素写 `@media print`

   应用外壳（侧边栏、菜单、按钮）在打印稿里必须**完全不出现**，而
   "逐条 hide" 的写法每加一个组件就漏一处。这里用一条总开关：
   `body.qb-print` 下只保留 `[data-card-kind="paper_preview"]` 这一棵子树。

   ## `visibility` 还是 `display`

   用 `display: none`。`visibility: hidden` 会**保留布局占位**，
   打印出来是一大片空白页（因为外壳仍然占着高度）。
   ====================================================================== */
@media print {
  /* 只留卷面本身 */
  body.qb-print .ws__side,
  body.qb-print .qb__side,
  body.qb-print .split__side,
  body.qb-print .split__handle,
  body.qb-print .split__reveal {
    display: none !important;
  }

  /* 卡片区里除整卷预览之外一张卡片都不打印 */
  body.qb-print .qb__cards > *:not([data-card-kind='paper_preview']) {
    display: none !important;
  }

  /* 预览卡上的操作按钮（上移/下移/换/移/复制/打印）不是卷面内容 */
  body.qb-print [data-card-kind='paper_preview'] button,
  body.qb-print .paper__ops {
    display: none !important;
  }

  body.qb-print .qb__cards {
    display: block;
    height: auto;
    padding: 0;
    overflow: visible;
  }

  body.qb-print [data-card-kind='paper_preview'] {
    border: none;
    border-radius: 0;
    background: #fff;
  }

  /* A4 + 页边距；`@page` 必须写在这条媒体查询里才生效 */
  @page {
    size: A4;
    margin: 16mm;
  }

  /* 打印时不要彩色背景（省墨，也更接近"纸卷子"） */
  body.qb-print * {
    background: transparent !important;
    color: #000 !important;
  }
}
