/* ==========================================================================
   电子数据取证比赛平台 — 公共主题样式
   风格对齐参考页面（深色 · 战术风 · 墨绿强调色）

   ── 两条正交的视觉轴 ─────────────────────────────────────────────────────
   1) **模式轴**（本文件）：<html data-theme="dark|light">，用户自选，存本机。
   2) **皮肤轴**（skins.css）：<html data-skin="...">，管理员在后台统一选择，
      平台级生效（所有页面、所有用户）。默认皮肤 tactical = 本文件的默认值，
      skins.css 里不为它写任何覆盖块。

   ── 双主题（夜间 / 日间）的实现约定 ────────────────────────────────────────
   1) 所有颜色都收敛到下面的「语义 token」（--user-* / --tt-* / --skin-*），
      组件规则里不再直接写死颜色值。这样加一套浅色只需要换一组 token。

   2) 主题由 <html data-theme="dark|light"> 选中，由 theme-init.js 在**首次
      绘制前**写入（防深色闪屏），用户切换走 theme-toggle.js。
      <html> 上**永不**出现除 dark/light 以外的值。

   3) **选择器一律写 :root，绝不写 :root[data-theme="dark"]。**
      原因：jsdom 等测试环境只把 theme.css / admin.css 注入为 <style>，
      不会执行 theme-init.js，<html> 上也就没有 data-theme。若把深色写在
      :root[data-theme="dark"] 里，这些套件会拿到**空的**颜色 token
      （getComputedStyle 得到空串），从而出现「测试环境样式全丢」的假失败。
      改成「:root 默认深色 + 浅色覆盖」后，两种环境都正确。

   4) 浅色覆盖的选择器是 `:root[data-theme="light"]`（特异性更高），
      与「各页面 CSS 自身的选择器」无关 —— 页面 CSS 里写死的深色值
      由各页面自己的浅色块负责覆盖（见 login.css / exam.css / admin.css /
      screen.css 末尾的 `html[data-theme="light"]` 段落）。

   5) **皮肤块（skins.css）必须同时写深、浅两套完整色板。**
      原因在层叠：`:root[data-skin="x"]` 与 `:root[data-theme="light"]` 的
      特异性相同（都是 0,2,0），而 skins.css 在 theme.css 之后加载 ——
      若某个皮肤只写了深色块，它在浅色模式下会**压掉整个浅色主题**。
      因此皮肤用 `:root[data-skin="x"][data-theme="light"]`（0,3,0）覆盖，
      并且深浅两块定义同一组 token。
   ========================================================================== */

:root {
  --app-font-family: "HarmonyOS Sans SC", "MiSans", "PingFang SC", "Microsoft YaHei",
    "Noto Sans SC", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
    "Helvetica Neue", Arial, sans-serif;

  /* ---------------- 深色（默认，保持平台原有外观） ---------------- */
  --user-page-bg: #151213;
  --user-surface: rgba(200, 16, 46, 0.07);
  --user-surface-solid: #1d191a;
  --user-surface-elevated: #241f20;
  --user-text-primary: #ece7e8;
  --user-text-secondary: #a99c9e;
  --user-border: rgba(190, 150, 156, 0.42);
  --user-border-soft: rgba(190, 150, 156, 0.22);
  --user-accent: #c8102e;
  --user-accent-hover: #e6202f;
  --user-accent-alt: #e6c27a;
  --user-warn: #ffb020;
  --user-danger: #ff6b3d;
  --user-blue: #3b82f6;
  --user-header-bg: linear-gradient(180deg, rgba(21, 18, 19, 0.94) 0%, rgba(21, 18, 19, 0.88) 100%),
    linear-gradient(0deg, rgba(200, 16, 46, 0.05), rgba(200, 16, 46, 0.05));
  --user-panel-bg: linear-gradient(0deg, rgba(200, 16, 46, 0.04), rgba(200, 16, 46, 0.04)),
    linear-gradient(rgba(80, 22, 30, 0.18) 0%, rgba(60, 16, 22, 0.18) 100%);
  --user-panel-border: rgba(190, 150, 156, 0.42);
  --user-shadow: 0 12px 26px rgba(10, 4, 5, 0.46), inset 0 0 0 1px rgba(255, 214, 218, 0.07);
  --user-shadow-sm: 0 6px 16px rgba(10, 4, 5, 0.38);
  --radius: 4px;
  /* 小尺寸元素（复选框 / 标签 / chip / 行内代码 / 小图标按钮 / 徽标）的圆角。
     与 --radius 分开是有意的：neon 皮肤的 --radius 是 12px，
     套到 16px 的复选框或 20px 高的标签上会直接变成圆形。 */
  --radius-sm: 2px;

  /* --------- 布局常量 ------------------------------------------------
     --app-header-h：顶栏（.app-user-header）的**实际占位高度** =
       .app-header-inner 的 min-height（60px）+ 顶栏自己的 1px 下边框。

     为什么要有这个 token：答题页的工具条是 position: sticky 的，它必须
     「吸在顶栏正下方」。以前那里写死 top: 78px，而顶栏实际占 61px ——
     两者之间的 17px 就成了一条没人管的缝：滚动时题目卡片会从这条缝里
     滑过去（露出卡片边框），看起来就是「顶栏和工具条之间裂了一道」。

     以后改顶栏高度时，只要同步这个值（矮屏断点在文件末尾、
     .app-header-inner 改成 54px 的地方一并改），吸顶栈就不会再裂开。 */
  --app-header-h: 61px;

  /* --------- 主题 token（双主题切换的公共抓手） ---------------- */
  /* --tt-on-accent：实心强调色按钮上的文字色。
     深色主题下强调色是亮墨绿 #20ad7b，需要**深色**文字才看得清；
     浅色主题下必须换成**白色**，否则白底绿钮上的深字几乎糊在一起。 */
  --tt-on-accent: #ffffff;
  /* --tt-on-blue：蓝色状态块（题目概览「已提交」半格）上的文字色。
     它与 --user-blue 配对：皮肤改蓝的深浅时，这里的文字色要跟着换，
     否则 matrix 皮肤的亮青底 (#4cd7ff) 上白字几乎看不见。 */
  --tt-on-blue: #04101f;
  /* 页面主背景的渐变层（与 --user-page-bg 同色系，供 body 的 background-image 用） */
  --tt-page-glow-1: rgba(200, 16, 46, 0.13);
  --tt-page-glow-2: rgba(230, 194, 122, 0.07);
  --tt-page-bg-end: #0d0a0b;
  /* 输入框/内嵌区块的底色，以及聚焦态 */
  --tt-field-bg: rgba(38, 22, 25, 0.50);
  --tt-field-bg-focus: rgba(38, 22, 25, 0.70);
  --tt-field-bg-soft: rgba(38, 22, 25, 0.30);
  --tt-field-bg-strong: rgba(38, 22, 25, 0.62);
  /* 中性叠加层：hover 行、空状态、分隔等 */
  --tt-neutral-weak: rgba(190, 150, 156, 0.08);
  --tt-neutral: rgba(190, 150, 156, 0.12);
  --tt-neutral-strong: rgba(190, 150, 156, 0.18);
  /* 半透明强调色叠加（用于 tag / alert / 内嵌区块） */
  --tt-accent-weak: rgba(200, 16, 46, 0.11);
  --tt-accent-soft: rgba(200, 16, 46, 0.16);
  --tt-accent-line: rgba(200, 16, 46, 0.55);
  /* 强调色的**前景**（用于文字，浅色主题下需加深才够对比度） */
  --tt-accent-text: #ff9aa2;
  --tt-accent-text-strong: #ffb3b9;
  --tt-blue-text: #93c5fd;
  --tt-warn-text: #ffd48a;
  --tt-danger-text: #ffb3a0;
  --tt-danger-text-strong: #ff9c85;
  /* 表格表头底色（sticky 时需要不透明） */
  --tt-thead-bg: #1f1416;
  --tt-thead-bg-solid: #1f1416;
  --tt-thead-text: #dccfd1;
  /* 中间层文字：面板标题、正文等介于主/次之间的灰 */
  --user-text-strong: #ded3d5;
  --user-text-body: #cfc3c5;
  --tt-heading: #ece7e8;
  /* Toast / 悬浮元素底色 */
  --tt-float-bg: #251a1c;
  /* 模态遮罩 */
  --tt-mask: rgba(10, 4, 5, 0.72);
  /* 等宽字体栈（多处 .mono / .cell-ans 复用） */
  --app-mono-font-family: ui-monospace, SFMono-Regular, Menlo, Consolas,
    "Liberation Mono", monospace;

  /* --------- 皮肤签名 token（默认值 = 平台原有的「战术墨绿」） -----------
     这些 token 不在页面里直接写死，而是由 skins.css 按
     <html data-skin="..."> 覆盖；tactical（默认皮肤）不覆盖任何值，
     因此「不装皮肤文件」与「装了就选默认皮肤」渲染完全一致。 */
  /* 面板四角的战术角标：主色 / 渐变末端 / 发光色 */
  --skin-corner: #c8102ef2;
  --skin-corner-fade: #c8102e33;
  --skin-corner-glow: rgba(200, 16, 46, 0.42);

  /* 警示色（琥珀）家族：标记态、警示提示、动态分值等**语义**场景复用。
     只暴露一个 rgb 三元组 + 若干透明度，其余色值由 var() 组合出来 ——
     换皮肤时只需改 --tt-warn-rgb 一个值，整个琥珀家族自动跟随。
     --tt-on-warn 是「实心暖色按钮上的文字色」——深色主题下暖色很亮，
     必须配深色文字；浅色主题下暖色被压深，必须换成白字。
     （当前没有实心暖色按钮：管理员登录页已改为跟随强调色，这个 token 作为
     完整色板的一员保留，供将来的「警示类实心按钮」使用。） */
  --tt-warn-rgb: 255, 176, 32;
  --tt-warn-a-soft: 0.10;
  --tt-warn-a-tag: 0.14;
  --tt-warn-a-glow: 0.05;
  --tt-warn-a-tint: 0.30;
  --tt-warn-a-line: 0.45;
  --tt-warn-a-line-strong: 0.70;
  --tt-warn-soft: rgba(var(--tt-warn-rgb), var(--tt-warn-a-soft));
  --tt-warn-tag: rgba(var(--tt-warn-rgb), var(--tt-warn-a-tag));
  --tt-warn-glow: rgba(var(--tt-warn-rgb), var(--tt-warn-a-glow));
  --tt-warn-tint: rgba(var(--tt-warn-rgb), var(--tt-warn-a-tint));
  --tt-warn-line: rgba(var(--tt-warn-rgb), var(--tt-warn-a-line));
  --tt-warn-line-strong: rgba(var(--tt-warn-rgb), var(--tt-warn-a-line-strong));
  --tt-warn-hover: #ffc247;
  --tt-on-warn: #241701;

  /* 危险色家族：错误提示条、危险按钮、危险标签共用 */
  --tt-danger-rgb: 255, 107, 61;
  --tt-danger-a-soft: 0.12;
  --tt-danger-a-tag: 0.14;
  --tt-danger-a-weak: 0.16;
  --tt-danger-a-line: 0.45;
  --tt-danger-a-line-strong: 0.60;
  --tt-danger-soft: rgba(var(--tt-danger-rgb), var(--tt-danger-a-soft));
  --tt-danger-tag: rgba(var(--tt-danger-rgb), var(--tt-danger-a-tag));
  --tt-danger-weak: rgba(var(--tt-danger-rgb), var(--tt-danger-a-weak));
  --tt-danger-line: rgba(var(--tt-danger-rgb), var(--tt-danger-a-line));
  --tt-danger-line-strong: rgba(var(--tt-danger-rgb), var(--tt-danger-a-line-strong));

  /* 信息色（蓝）家族：已提交态、信息标签共用 */
  --tt-blue-rgb: 59, 130, 246;
  --tt-blue-a-tag: 0.14;
  --tt-blue-a-line: 0.50;
  --tt-blue-tag: rgba(var(--tt-blue-rgb), var(--tt-blue-a-tag));
  --tt-blue-line: rgba(var(--tt-blue-rgb), var(--tt-blue-a-line));

  /* --------- 题目概览的状态色（**有意不跟随皮肤**） --------------------
     只用于答题页左侧「题目概览」的方格与图例：已提交(蓝) / 已暂存(绿) /
     已标记(琥珀)。

     为什么不跟皮肤走：皮肤决定的是**品牌调性**（强调色可能是蓝、是红、
     是青、是赭），而方格是「一眼看状态」的**功能色**。一旦让它们跟品牌色
     走，就会出现分不清的组合 —— 实测 midnight 皮肤的强调色(#4c8dff)与
     它的蓝(#3b82f6)几乎同色，paper 皮肤的强调色(#c8873f)与琥珀(#ffb020)
     也几乎同色，方格上就变成「两格看起来一样」。

     因此固定成蓝 / 绿 / 琥珀三色，并保证彼此色相相差 ≥60°、明度同档：
       深色模式三色都偏亮（配深色数字），浅色模式三色都偏深（配白色数字）。
     skins.css 里**不要**覆盖它们（tests/skin-verify.mjs 会校验这一点）。 */
  --ov-submitted: #4a90ff;
  --ov-stashed: #2fbf71;
  --ov-marked: #f0a020;
  --ov-on-state: #06140e;
}

/* ==========================================================================
   浅色（日间）主题
   --------------------------------------------------------------------------
   设计取向：不做「白底黑字的反色」，而是保持平台的战术风调性 ——
   冷灰蓝的底、更深的墨绿强调色、加重的边框。暗色强调色 #20ad7b 直接放到
   白底上对比度只有 ~2.5:1，因此浅色主题把强调色压到 #0f7a57 一档。
   ========================================================================== */
:root[data-theme="light"] {
  --user-page-bg: #f6f4f5;
  --user-surface: rgba(176, 14, 34, 0.05);
  --user-surface-solid: #ffffff;
  --user-surface-elevated: #ffffff;
  --user-text-primary: #1a1214;
  --user-text-secondary: #6b5a5d;
  --user-border: rgba(120, 80, 88, 0.32);
  --user-border-soft: rgba(120, 80, 88, 0.16);
  --user-accent: #b00e22;
  --user-accent-hover: #8e0a1b;
  --user-accent-alt: #8a6a22;
  --user-warn: #b26a00;
  --user-danger: #c2410c;
  --user-blue: #1d4ed8;
  --user-header-bg: linear-gradient(180deg, rgba(255, 255, 255, 0.95) 0%, rgba(255, 255, 255, 0.90) 100%),
    linear-gradient(0deg, rgba(176, 14, 34, 0.03), rgba(176, 14, 34, 0.03));
  --user-panel-bg: #ffffff;
  --user-panel-border: rgba(120, 80, 88, 0.30);
  --user-shadow: 0 10px 24px rgba(60, 20, 26, 0.10), inset 0 0 0 1px rgba(255, 255, 255, 0.60);
  --user-shadow-sm: 0 4px 12px rgba(60, 20, 26, 0.08);
  /* 浅色下 --radius / --radius-sm 与深色同值（形状不随深浅模式变），
     这里显式写出来是为了满足「浅色块必须覆盖全部配色 token」的约定。 */
  --radius-sm: 2px;

  --tt-on-accent: #ffffff;
  --tt-on-blue: #ffffff;
  --tt-page-glow-1: rgba(176, 14, 34, 0.08);
  --tt-page-glow-2: rgba(138, 106, 34, 0.07);
  --tt-page-bg-end: #eceaea;
  --tt-field-bg: #ffffff;
  --tt-field-bg-focus: #ffffff;
  --tt-field-bg-soft: rgba(80, 40, 46, 0.035);
  --tt-field-bg-strong: rgba(80, 40, 46, 0.06);
  --tt-neutral-weak: rgba(120, 80, 88, 0.06);
  --tt-neutral: rgba(120, 80, 88, 0.10);
  --tt-neutral-strong: rgba(120, 80, 88, 0.16);
  --tt-accent-weak: rgba(176, 14, 34, 0.08);
  --tt-accent-soft: rgba(176, 14, 34, 0.12);
  --tt-accent-line: rgba(176, 14, 34, 0.45);
  --tt-accent-text: #a3101f;
  --tt-accent-text-strong: #8c0c1a;
  --tt-blue-text: #1d4ed8;
  --tt-warn-text: #92530a;
  --tt-danger-text: #a83a12;
  --tt-danger-text-strong: #8f2f0d;
  --tt-thead-bg: #f0edee;
  --tt-thead-bg-solid: #f0edee;
  --tt-thead-text: #4a3c3e;
  --user-text-strong: #2b2224;
  --user-text-body: #463a3c;
  --tt-heading: #1a1214;
  --tt-float-bg: #ffffff;
  --tt-mask: rgba(60, 20, 26, 0.40);

  /* 皮肤签名 token 的浅色默认值（战术墨绿 → 压深一档，白底上才够对比度） */
  --skin-corner: rgba(176, 14, 34, 0.95);
  --skin-corner-fade: rgba(176, 14, 34, 0.20);
  --skin-corner-glow: rgba(176, 14, 34, 0.28);

  /* 浅色下琥珀压到 #b26a00 一档，实心琥珀按钮配白字 */
  --tt-warn-rgb: 178, 106, 0;
  --tt-warn-a-soft: 0.07;
  --tt-warn-a-tag: 0.14;
  --tt-warn-a-glow: 0.05;
  --tt-warn-a-tint: 0.16;
  --tt-warn-a-line: 0.42;
  --tt-warn-a-line-strong: 0.70;
  --tt-warn-soft: rgba(var(--tt-warn-rgb), var(--tt-warn-a-soft));
  --tt-warn-tag: rgba(var(--tt-warn-rgb), var(--tt-warn-a-tag));
  --tt-warn-glow: rgba(var(--tt-warn-rgb), var(--tt-warn-a-glow));
  --tt-warn-tint: rgba(var(--tt-warn-rgb), var(--tt-warn-a-tint));
  --tt-warn-line: rgba(var(--tt-warn-rgb), var(--tt-warn-a-line));
  --tt-warn-line-strong: rgba(var(--tt-warn-rgb), var(--tt-warn-a-line-strong));
  --tt-warn-hover: #9c5c00;
  --tt-on-warn: #ffffff;

  /* 危险色：浅底上压深一档，白字/深字对比度才够 */
  --tt-danger-rgb: 194, 65, 12;
  --tt-danger-a-soft: 0.12;
  --tt-danger-a-tag: 0.14;
  --tt-danger-a-weak: 0.16;
  --tt-danger-a-line: 0.45;
  --tt-danger-a-line-strong: 0.60;
  --tt-danger-soft: rgba(var(--tt-danger-rgb), var(--tt-danger-a-soft));
  --tt-danger-tag: rgba(var(--tt-danger-rgb), var(--tt-danger-a-tag));
  --tt-danger-weak: rgba(var(--tt-danger-rgb), var(--tt-danger-a-weak));
  --tt-danger-line: rgba(var(--tt-danger-rgb), var(--tt-danger-a-line));
  --tt-danger-line-strong: rgba(var(--tt-danger-rgb), var(--tt-danger-a-line-strong));

  /* 信息色（蓝）：浅底上同样压深 */
  --tt-blue-rgb: 29, 78, 216;
  --tt-blue-a-tag: 0.14;
  --tt-blue-a-line: 0.50;
  --tt-blue-tag: rgba(var(--tt-blue-rgb), var(--tt-blue-a-tag));
  --tt-blue-line: rgba(var(--tt-blue-rgb), var(--tt-blue-a-line));

  /* 题目概览状态色：浅底上三色一起压深（色相保持不变），配白色数字 */
  --ov-submitted: #1d4ed8;
  --ov-stashed: #15803d;
  --ov-marked: #b45309;
  --ov-on-state: #ffffff;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  font-size: 14px;
  font-family: var(--app-font-family);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  color: var(--user-text-primary);
}

body.is-user {
  background-color: var(--user-page-bg);
  background-image:
    radial-gradient(1200px 600px at 20% -10%, var(--tt-page-glow-1), transparent 60%),
    radial-gradient(900px 500px at 100% 0%, var(--tt-page-glow-2), transparent 55%),
    linear-gradient(180deg, var(--user-page-bg) 0%, var(--tt-page-bg-end) 100%);
  background-attachment: fixed;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

a { color: var(--user-accent-alt); text-decoration: none; }
a:hover { color: var(--user-accent-hover); }

h1, h2, h3, h4 { margin: 0; font-weight: 700; }

::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: var(--tt-neutral-weak); }
::-webkit-scrollbar-thumb {
  background: linear-gradient(180deg, color-mix(in srgb, var(--user-accent-alt) 67%, transparent), color-mix(in srgb, var(--user-accent) 53%, transparent));
  border: 1px solid var(--user-border-soft);
  border-radius: var(--radius-sm);
}
::-webkit-scrollbar-thumb:hover { background: linear-gradient(180deg, var(--tt-accent-text), var(--user-accent-alt)); }
* { scrollbar-color: var(--user-border) var(--tt-neutral-weak); scrollbar-width: thin; }

/* ------------------------------- 容器 ---------------------------------- */
.app-container {
  width: min(1440px, 96vw);
  margin: 0 auto;
  padding: 0 20px;
}
.app-container--narrow { width: min(960px, 94vw); }

/* ------------------------------- 页头 ---------------------------------- */
.app-user-header {
  position: sticky;
  top: 0;
  z-index: 60;
  background: var(--user-header-bg);
  border-bottom: 1px solid var(--user-border);
  backdrop-filter: blur(8px);
}
.app-header-inner {
  display: flex;
  align-items: center;
  gap: 16px;
  min-height: 60px;
  flex-wrap: nowrap;
}
.app-site-title {
  font-weight: 600;
  font-size: 20px;
  color: var(--user-text-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* 顶栏左侧的品牌标识：一个 34×34 的方框（窄屏 28×28）。
   框内是品牌图案（.app-logo__img，见下），不再是「FP」两个字母。
   框本身保留描边与本色渐变底：图案是带透明通道的深蓝盾徽，
   在深色底上偏暗，靠这层底色与描边把轮廓托住 —— 深浅两种模式、
   以及 7 套皮肤下都能看清。 */
.app-logo {
  width: 34px; height: 34px; flex-shrink: 0;
  display: grid; place-items: center;
  padding: 2px;                 /* 图案与描边之间留一点气口 */
  border: 1px solid var(--user-border);
  border-radius: var(--radius);
  background: linear-gradient(140deg, var(--tt-accent-soft), var(--tt-field-bg-soft));
  overflow: hidden;             /* 圆角处不露出图片的直角（各皮肤 --radius 不同） */
}
/* 品牌图案。
   资源由 tools/logo-optimize.mjs 从原始设计稿 web/assets/logo.png（640×640，
   约 400 KB）裁切缩放而来，128×128 约 26 KB —— 顶栏只显示 34px，
   直接引用原图会让前端总体积多出一倍多。改 logo 后重新跑一次该工具即可。 */
.app-logo__img {
  width: 100%; height: 100%;
  display: block;
  object-fit: contain;
}
.app-header-spacer { flex: 1 1 auto; min-width: 0; }
.app-header-user {
  display: flex; align-items: center; gap: 12px;
  color: var(--user-text-secondary);
  font-size: 13px;
  flex-shrink: 0;
}
.app-header-user strong { color: var(--user-text-primary); }

/* ------------------------------- 面板 ---------------------------------- */
.panel {
  background: var(--user-panel-bg);
  border: 1px solid var(--user-panel-border);
  border-radius: var(--radius);
  box-shadow: var(--user-shadow);
  position: relative;
}
.panel-head {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--user-border-soft);
  min-height: 48px;
  flex-wrap: wrap;
}
.panel-title {
  font-weight: 700;
  font-size: 15px;
  color: var(--user-text-strong);
  letter-spacing: 0.5px;
  display: flex;
  align-items: center;
  gap: 8px;
}
.panel-body { padding: 16px; }
.panel-extra { margin-left: auto; display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }

/* 战术角标。
   颜色走 --skin-corner* 三个 token（默认墨绿，skins.css 按皮肤覆盖），
   因此换皮肤时角标自动跟着换色，组件规则里不再出现写死的颜色值。 */
.tactical-corners { overflow: hidden; }
.tactical-corners::before,
.tactical-corners::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 2;
}
.tactical-corners::before {
  background:
    linear-gradient(90deg, var(--skin-corner), var(--skin-corner-fade)) left top / 22px 1px no-repeat,
    linear-gradient(180deg, var(--skin-corner), var(--skin-corner-fade)) left top / 1px 22px no-repeat,
    linear-gradient(90deg, var(--skin-corner-fade), var(--skin-corner)) right top / 22px 1px no-repeat,
    linear-gradient(180deg, var(--skin-corner-fade), var(--skin-corner)) right top / 1px 22px no-repeat;
  filter: drop-shadow(0 0 6px var(--skin-corner-glow));
}
.tactical-corners::after {
  background:
    linear-gradient(90deg, var(--skin-corner), var(--skin-corner-fade)) left bottom / 22px 1px no-repeat,
    linear-gradient(180deg, var(--skin-corner-fade), var(--skin-corner)) left bottom / 1px 22px no-repeat,
    linear-gradient(90deg, var(--skin-corner-fade), var(--skin-corner)) right bottom / 22px 1px no-repeat,
    linear-gradient(180deg, var(--skin-corner-fade), var(--skin-corner)) right bottom / 1px 22px no-repeat;
  filter: drop-shadow(0 0 6px var(--skin-corner-glow));
}

/* ------------------------------- 按钮 ---------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  height: 34px;
  padding: 0 14px;
  font-family: inherit;
  font-size: 14px;
  font-weight: 500;
  color: var(--user-text-primary);
  background: transparent;
  border: 1px solid var(--user-border);
  border-radius: var(--radius);
  cursor: pointer;
  transition: border-color 0.18s, background 0.18s, color 0.18s, transform 0.1s;
  white-space: nowrap;
  user-select: none;
}
.btn:hover:not(:disabled) { border-color: var(--user-text-secondary); background: var(--tt-neutral); }
.btn:active:not(:disabled) { transform: translateY(1px); }
.btn:focus-visible { outline: 2px solid var(--user-accent); outline-offset: 2px; }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }

.btn-primary {
  background: var(--user-accent);
  border-color: var(--tt-accent-line);
  color: var(--tt-on-accent);
  font-weight: 700;
}
.btn-primary:hover:not(:disabled) { background: var(--user-accent-hover); border-color: var(--user-accent-hover); color: var(--tt-on-accent); }
.btn-danger { border-color: var(--tt-danger-line-strong); color: var(--tt-danger-text-strong); }
.btn-danger:hover:not(:disabled) { background: var(--tt-danger-weak); border-color: var(--user-danger); }
.btn-ghost { border-color: transparent; color: var(--user-text-secondary); }
.btn-ghost:hover:not(:disabled) { color: var(--user-text-primary); background: var(--tt-neutral); }
.btn-sm { height: 28px; padding: 0 10px; font-size: 13px; }
.btn-lg { height: 44px; padding: 0 22px; font-size: 15px; }
.btn-block { width: 100%; }
.btn-accent-outline {
  border-color: var(--tt-accent-line);
  color: var(--tt-accent-text);
}
.btn-accent-outline:hover:not(:disabled) { background: var(--tt-accent-soft); border-color: var(--user-accent); }
.btn.is-on { background: var(--user-accent); border-color: var(--user-accent); color: var(--tt-on-accent); }

/* ------------------------------- 表单 ---------------------------------- */
.field { margin-bottom: 18px; }
.field-label {
  display: block;
  margin-bottom: 8px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  color: var(--user-text-primary);
}
.field-label .req { color: var(--user-danger); margin-right: 4px; }
.input, .textarea, .select {
  width: 100%;
  font-family: inherit;
  font-size: 14px;
  color: var(--user-text-primary);
  background: var(--tt-field-bg);
  border: 1px solid var(--user-border);
  border-radius: var(--radius);
  padding: 0 12px;
  height: 38px;
  outline: none;
  transition: border-color 0.18s, background 0.18s;
}
.textarea { height: auto; padding: 10px 12px; line-height: 1.7; resize: vertical; }
.input:hover, .textarea:hover, .select:hover { border-color: var(--user-text-secondary); }
.input:focus, .textarea:focus, .select:focus { border-color: var(--user-accent); background: var(--tt-field-bg-focus); }
.input::placeholder, .textarea::placeholder { color: var(--user-text-secondary); opacity: 0.75; }
.input:disabled, .textarea:disabled, .select:disabled { opacity: 0.55; cursor: not-allowed; }
select.select { appearance: none; padding-right: 30px;
  background-image: linear-gradient(45deg, transparent 50%, var(--user-text-secondary) 50%), linear-gradient(135deg, var(--user-text-secondary) 50%, transparent 50%);
  /* 箭头纵向按高度居中（而不是写死 17px）—— 否则 .input-sm（28px 高）
     这类小尺寸下拉上的箭头会明显偏下。 */
  background-position: calc(100% - 16px) calc(50% - 2.5px), calc(100% - 11px) calc(50% - 2.5px);
  background-size: 5px 5px, 5px 5px; background-repeat: no-repeat;
}
.form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 0 18px; }

/* 复选框 */
.check {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  user-select: none;
  color: var(--user-text-primary);
  font-size: 14px;
}
.check input { appearance: none; width: 16px; height: 16px; margin: 0;
  border: 1px solid var(--user-border); border-radius: var(--radius-sm);
  background: var(--tt-field-bg); cursor: pointer; position: relative; flex-shrink: 0; }
.check input:checked { background: var(--user-accent); border-color: var(--user-accent); }
.check input:checked::after {
  content: ""; position: absolute; left: 5px; top: 1px; width: 4px; height: 9px;
  border: solid var(--tt-on-accent); border-width: 0 2px 2px 0; transform: rotate(45deg);
}
.check input:focus-visible { outline: 2px solid var(--user-accent); outline-offset: 2px; }

/* ------------------------------- 提示 ---------------------------------- */
.alert {
  padding: 10px 14px;
  border-radius: var(--radius);
  border: 1px solid var(--user-border-soft);
  background: var(--tt-neutral-weak);
  font-size: 13px;
  line-height: 1.7;
  margin-bottom: 14px;
}
.alert-error { border-color: var(--tt-danger-line); background: var(--tt-danger-soft); color: var(--tt-danger-text); }
.alert-ok { border-color: var(--tt-accent-line); background: var(--tt-accent-weak); color: var(--tt-accent-text-strong); }
.alert-warn { border-color: var(--tt-warn-line); background: var(--tt-warn-soft); color: var(--tt-warn-text); }
.hidden { display: none !important; }

/* ------------------------------- 标签 ---------------------------------- */
.tag {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 1px 8px;
  font-size: 12px;
  line-height: 20px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--user-border-soft);
  background: var(--tt-neutral);
  color: var(--user-text-secondary);
  white-space: nowrap;
}
.tag-accent { border-color: var(--tt-accent-line); background: var(--tt-accent-soft); color: var(--tt-accent-text); }
.tag-blue { border-color: var(--tt-blue-line); background: var(--tt-blue-tag); color: var(--tt-blue-text); }
.tag-orange { border-color: var(--tt-warn-line); background: var(--tt-warn-tag); color: var(--tt-warn-text); }
.tag-danger { border-color: var(--tt-danger-line); background: var(--tt-danger-tag); color: var(--tt-danger-text); }

/* ------------------------------- 表格 ---------------------------------- */
.table-wrap { overflow: auto; max-width: 100%; border: 1px solid var(--user-border-soft); border-radius: var(--radius); }
table.data { width: 100%; border-collapse: collapse; font-size: 13px; }
table.data th, table.data td {
  padding: 8px 10px;
  text-align: left;
  border-bottom: 1px solid var(--tt-neutral-strong);
  vertical-align: top;
}
table.data th {
  position: sticky; top: 0; z-index: 1;
  /* 两行是有意为之的「渐进增强」：
       第 1 行 —— 字面量兜底。jsdom 无法在 background/background-color 里
                 解析 var()（会保留 "var(--tt-thead-bg)" 字符串，计算值表现为透明），
                 而后台布局套件正是靠 getComputedStyle().backgroundColor 断言
                 「吸顶表头有不透明底色」。没有这一行，该断言会假失败。
       第 2 行 —— 真正生效的主题 token；支持 var() 的浏览器两者都能解析，
                 后者覆盖前者，随 data-theme 切换。
     两行都是同一个深色默认值，因此深色下行为与改动前完全一致。 */
  background-color: #131c26;
  background-color: var(--tt-thead-bg);
  color: var(--tt-thead-text);
  font-weight: 700;
  font-size: 12px;
  letter-spacing: 0.4px;
  white-space: nowrap;
}
table.data tbody tr:hover { background: var(--tt-neutral-weak); }
table.data td.num { text-align: right; font-variant-numeric: tabular-nums; }
.mono { font-family: var(--app-mono-font-family); }

/* ------------------------------ 模态框 --------------------------------- */
.modal-mask {
  position: fixed; inset: 0; z-index: 200;
  background: var(--tt-mask);
  backdrop-filter: blur(3px);
  display: flex; align-items: flex-start; justify-content: center;
  padding: 5vh 16px 40px;
  overflow: auto;
}
.modal {
  width: min(760px, 100%);
  background: var(--user-surface-elevated);
  border: 1px solid var(--user-panel-border);
  border-radius: var(--radius);
  box-shadow: 0 20px 50px rgba(0, 0, 0, 0.45);
  position: relative;
}
.modal-lg { width: min(1080px, 100%); }
.modal-head {
  display: flex; align-items: center; gap: 12px;
  padding: 14px 18px; border-bottom: 1px solid var(--user-border-soft);
}
.modal-head h3 { font-size: 16px; color: var(--tt-heading); }
.modal-close { margin-left: auto; background: none; border: none; color: var(--user-text-secondary);
  font-size: 20px; line-height: 1; cursor: pointer; padding: 2px 6px; border-radius: var(--radius-sm); }
.modal-close:hover { color: var(--user-text-primary); background: var(--tt-neutral-strong); }
.modal-close:focus-visible { outline: 2px solid var(--user-accent); outline-offset: 2px; }
.modal-body { padding: 18px; max-height: 68vh; overflow: auto; }
.modal-foot { padding: 14px 18px; border-top: 1px solid var(--user-border-soft); display: flex; gap: 10px; justify-content: flex-end; }

/* ------------------------------ 页脚 ---------------------------------- */
.site-footer {
  margin-top: auto;
  border-top: 1px solid var(--user-border);
  background: var(--user-panel-bg);
  color: var(--user-text-secondary);
  font-size: 13px;
}
.site-footer-inner { display: flex; gap: 20px; flex-wrap: wrap; align-items: center; padding: 16px 0; }
.site-footer .sep { color: var(--user-border); }

/* ------------------------------ 加载 ---------------------------------- */
.spinner {
  width: 16px; height: 16px; border-radius: 50%;
  border: 2px solid var(--user-border-soft);
  border-top-color: var(--user-accent);
  animation: spin 0.8s linear infinite;
  display: inline-block;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* -------------------------- 空状态 / 加载态 ----------------------------
   全站统一的「没有内容」与「正在加载」外观。此前同一个平台里有 6 种空态
   （表格行的 .empty-row、裸 .muted、各页面自造的 .xxx-empty…）与 3 种加载
   提示（裸文本、.spinner、面板角落文字），字号与对齐各不相同。
   这里只统一外观，不改类名 —— 各页面继续用自己的类，视觉上收敛成一套。
   ------------------------------------------------------------------------- */
.empty,
.loading,
.empty-row td,
.list-empty,
.ov-empty,
.materials-empty,
.my-subs__empty,
.reb__empty {
  color: var(--user-text-secondary);
  font-size: 13px;
}
.empty,
.loading,
.list-empty,
.ov-empty,
.materials-empty,
.my-subs__empty,
.reb__empty {
  text-align: center;
}
/* .loading 自带一个旋转指示器，避免各页面自己拼「加载中…」的裸文本 */
.loading::before {
  content: "";
  display: inline-block;
  width: 14px; height: 14px;
  margin-right: 6px;
  vertical-align: -2px;
  border: 2px solid var(--user-border-soft);
  border-top-color: var(--user-accent);
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}

/* ------------------------------ Toast --------------------------------- */
.toast-host {
  position: fixed; top: 76px; right: 24px; z-index: 400;
  display: flex; flex-direction: column; gap: 8px; pointer-events: none;
}
.toast {
  min-width: 180px; max-width: 380px;
  padding: 10px 14px;
  background: var(--tt-float-bg);
  border: 1px solid var(--user-border);
  border-left: 3px solid var(--user-accent);
  border-radius: var(--radius);
  box-shadow: var(--user-shadow);
  color: var(--user-text-primary);
  font-size: 13px;
  line-height: 1.6;
  animation: toastIn 0.18s ease-out;
}
.toast-error { border-left-color: var(--user-danger); }
.toast-warn { border-left-color: var(--user-warn); }
@keyframes toastIn { from { opacity: 0; transform: translateX(12px); } to { opacity: 1; transform: none; } }

/* --------------------------- 响应式 / 移动端 --------------------------- */
@media (max-width: 900px) {
  /* 顶栏在这个断点变矮 → 吸顶栈的偏移量必须同步（见 --app-header-h 的说明） */
  :root { --app-header-h: 55px; }
  .app-site-title { font-size: 16px; }
  .app-container { padding: 0 12px; }
  .form-grid { grid-template-columns: 1fr; }
  .app-header-inner { min-height: 54px; gap: 10px; }
  .app-logo { width: 28px; height: 28px; padding: 1px; }
}

@media (max-width: 640px) {
  .app-site-title { font-size: 14px; max-width: 40vw; }
  .app-header-user { gap: 6px; font-size: 12px; }
  /* 窄屏优先保留操作按钮，时间等次要信息可换行 */
  .app-header-inner { flex-wrap: wrap; padding-top: 6px; padding-bottom: 6px; }
  .app-header-user .tag { display: none; }
  .toast-host { right: 12px; left: 12px; top: 66px; }
  .toast { max-width: none; }
  .modal-mask { padding: 3vh 10px 24px; }
}

@media (prefers-reduced-motion: reduce) {
  * { animation-duration: 0.001ms !important; transition-duration: 0.001ms !important; }
}

/* ==========================================================================
   浅色（日间）主题的收尾修正
   --------------------------------------------------------------------------
   上面绝大多数规则已经走 token，这里只处理两类必须在浅色下单独调的东西：
     · 深色下「亮色半透明」在浅色底上不够可见的元素（战术角标、滚动条）
     · 深色下依赖发光/深色描边的细节
   角标的**颜色**已由 --skin-corner* 在浅色块里换成了压深的墨绿，
   所以这里只需去掉深色主题的发光并压低透明度。
   ========================================================================== */
:root[data-theme="light"] .tactical-corners::before,
:root[data-theme="light"] .tactical-corners::after {
  /* 浅底上降低亮度以免刺眼，同时去掉深色主题的发光 */
  filter: none;
  opacity: 0.85;
}
