/* ============================================================================
   ui2.css —— 视觉系统 v2
   在 style.css 之后加载，只覆盖「质感、动效、控件形态」，不动布局骨架。
   分三层：
     1) tokens   间距 / 字号 / 圆角 / 阴影 / 缓动
     2) themes   十七套配色，切 data-theme 就换（indigo / ivory / midnight 免费）
     3) controls 按钮 / 输入 / 卡片 / 行 / 标签 / 弹窗 / toast / 骨架 / 空态
   ========================================================================== */

/* ---------------------------------------------------------------- 1. tokens */
:root {
  /* 间距：全部是 4 的倍数。老代码里 13/17/23 这种随手写的值，
     是「看着别扭又说不清哪别扭」的主要来源之一 */
  --s1: 4px;  --s2: 8px;  --s3: 12px; --s4: 16px;
  --s5: 20px; --s6: 24px; --s7: 32px; --s8: 40px; --s9: 56px;

  /* 第三档文字色。原来有几处在用 var(--text-faint) 但从来没人定义，
     var() 无效 → 一路继承正文色，本该最淡的字看着跟正文一样重。
     十套主题都不覆盖它，这里定义一次就全站生效 */
  --text-faint: #9AA1B2;

  /* 字号：拉开层级。原来 20/14/12.5 三档撑全站，标题压不住场。

     每一档都用 clamp 跟视口宽度走，而不是钉死 px：
     固定 px 在 1080p 上正好，到了 2K/4K 屏就缩成一小撮，老师得凑近看；
     在 1366×768 的老笔记本上又挤。clamp(下限, 视口比例, 上限) 三头都兜住——
     小屏不塌、大屏跟着长，中间连续变化不跳档。

     斜率按「1440 是基准，1920 放大约 8%」定的。上限卡在 1.15 倍左右，
     再大就该分栏了，不是把字撑大能解决的。 */
  --fs-display: clamp(42px, 0.9vw + 29px, 56px);
  --fs-h1: clamp(30px, 0.6vw + 21.4px, 38px);
  --fs-h2: clamp(21px, 0.4vw + 15.2px, 26px);
  --fs-h3: clamp(16px, 0.28vw + 12px, 19px);
  --fs-body: clamp(15px, 0.22vw + 11.8px, 17px);
  --fs-sm: clamp(13.5px, 0.2vw + 10.6px, 15px);
  --fs-cap: clamp(12.5px, 0.18vw + 9.9px, 14px);

  /* 圆角：大件更大、小件更小，形成家族感 */
  --r-xs: 8px; --r-sm: 10px; --r-md: 14px; --r-lg: 20px; --r-xl: 28px; --r-pill: 999px;

  /* 阴影：分三层，越靠前越「高」 */
  --sh-1: 0 1px 2px rgba(16, 24, 40, .05);
  --sh-2: 0 2px 4px -1px rgba(16, 24, 40, .05), 0 8px 20px -12px rgba(16, 24, 40, .16);
  --sh-3: 0 4px 8px -2px rgba(16, 24, 40, .06), 0 20px 40px -20px rgba(16, 24, 40, .24);

  /* 缓动：出场用减速，按压用弹簧 */
  --ease-out: cubic-bezier(.22, 1, .36, 1);
  --ease-in-out: cubic-bezier(.4, 0, .2, 1);
  --ease-spring: cubic-bezier(.34, 1.4, .64, 1);
  --dur-1: .13s;
  --dur-2: .22s;
  --dur-3: .36s;

  --ink: var(--text);
  --ink-2: var(--text-sub);
  --line: var(--border);
}

/* ---------------------------------------------------------------- 2. themes */
/* 靛蓝（原配色，保留为默认） */
:root, html[data-theme="indigo"] {
  --brand: #5B5BF5;
  --brand-dark: #4343D6;
  --brand-soft: #EEEEFE;
  --brand-2: #7C6BFF;
  --bg: #F6F7FB;
  --card: #FFFFFF;
  --text: #1B1E28;
  --text-sub: #6B7280;
  --border: #E8EAF1;
  --green: #12B76A; --green-soft: #E7F8F0;
  --amber: #F79009; --amber-soft: #FEF4E6;
  --red: #F04438;   --red-soft: #FEECEB;
  --blue: #2E90FA;  --blue-soft: #EAF3FF;
  --track: #EEF0F6;
  --hero-a: #5B5BF5; --hero-b: #7C6BFF; --hero-c: #9B8CFF;
  --hero-shade: 32, 28, 120;
}

/* 阳光橙 */
html[data-theme="sunny"] {
  --brand: #B85700;
  --brand-dark: #994900;
  --brand-soft: #FFF1E0;
  --brand-2: #FFB020;
  --bg: #FFFBF5;
  --card: #FFFFFF;
  --text: #2B2118;
  --text-sub: #8A7968;
  --border: #F4E9DC;
  --green: #16A97A; --green-soft: #E1F8EF;
  --amber: #C77A00; --amber-soft: #FFF3D6;
  --red: #E8385C;   --red-soft: #FFEBEF;
  --blue: #2F80ED;  --blue-soft: #EAF3FF;
  --track: #F7EDE1;
  --hero-a: #FF9A33; --hero-b: #FF7A1F; --hero-c: #FFC24D;
  --hero-shade: 140, 50, 0;
}

/* 活力青 */
html[data-theme="fresh"] {
  --brand: #0B7F78;
  --brand-dark: #09635D;
  --brand-soft: #E1FAF6;
  --brand-2: #38D6C4;
  --bg: #F5FCFB;
  --card: #FFFFFF;
  --text: #10231F;
  --text-sub: #6B8781;
  --border: #DCF0EC;
  --green: #14996A; --green-soft: #DFF7EC;
  --amber: #B97C00; --amber-soft: #FFF3D1;
  --red: #E0455C;   --red-soft: #FFEAEE;
  --blue: #3B82F6;  --blue-soft: #E9F2FF;
  --track: #E4F4F1;
  --hero-a: #16C4B2; --hero-b: #0FA396; --hero-c: #5FE0CF;
  --hero-shade: 3, 62, 56;
}

/* ---------------------------------------------------------------- 后七套 */
/* 每套 21 个变量，一个不多一个不少。加主题只加这一段，
   别在别处散写覆盖——散写出来的主题切过去总有几处不跟着变。

   `--hero-shade` 是 banner 暗罩的基色，必须比 `--hero-a` 深得多：
   白字要的是「背景够暗」，不是「背景够艳」。拿 hero-a 直接当暗罩，
   阳光橙那套就会翻车（实测 4.21，差 0.3）。 */

/* 深海蓝 */
html[data-theme="ocean"] {
  --brand: #2563EB;
  --brand-dark: #1D4ED8;
  --brand-soft: #E8F0FE;
  --brand-2: #3B82F6;
  --bg: #F5F8FF;
  --card: #FFFFFF;
  --text: #12192B;
  --text-sub: #64748B;
  --border: #E2EAF8;
  --green: #12B76A; --green-soft: #E7F8F0;
  --amber: #F79009; --amber-soft: #FEF4E6;
  --red: #F04438;   --red-soft: #FEECEB;
  --blue: #2563EB;  --blue-soft: #E8F0FE;
  --track: #EDF2FC;
  --hero-a: #2563EB; --hero-b: #1E40AF; --hero-c: #60A5FA;
  --hero-shade: 14, 32, 110;
}

/* 樱花粉 */
html[data-theme="sakura"] {
  --brand: #DC2066;
  --brand-dark: #C11C5A;
  --brand-soft: #FDEBF3;
  --brand-2: #F472B6;
  --bg: #FFF8FB;
  --card: #FFFFFF;
  --text: #2B1520;
  --text-sub: #8E6B7A;
  --border: #F7E3EC;
  --green: #16A97A; --green-soft: #E1F8EF;
  --amber: #C77A00; --amber-soft: #FFF3D6;
  --red: #E8385C;   --red-soft: #FFEBEF;
  --blue: #3B82F6;  --blue-soft: #E9F2FF;
  --track: #FBEBF2;
  --hero-a: #E8508D; --hero-b: #C93B75; --hero-c: #F9A8D4;
  --hero-shade: 120, 20, 60;
}

/* 森林绿 */
html[data-theme="forest"] {
  --brand: #148345;
  --brand-dark: #106837;
  --brand-soft: #E5F7EC;
  --brand-2: #22B463;
  --bg: #F5FBF7;
  --card: #FFFFFF;
  --text: #10241A;
  --text-sub: #64806F;
  --border: #DEEFE4;
  --green: #17924E; --green-soft: #E5F7EC;
  --amber: #B97C00; --amber-soft: #FFF3D1;
  --red: #E0455C;   --red-soft: #FFEAEE;
  --blue: #3B82F6;  --blue-soft: #E9F2FF;
  --track: #E8F5EC;
  --hero-a: #17924E; --hero-b: #0F7A3F; --hero-c: #4ADE80;
  --hero-shade: 8, 60, 32;
}

/* 葡萄紫 */
html[data-theme="grape"] {
  --brand: #7C3AED;
  --brand-dark: #6D28D9;
  --brand-soft: #F1EAFE;
  --brand-2: #9F6BFF;
  --bg: #FAF7FF;
  --card: #FFFFFF;
  --text: #1D1630;
  --text-sub: #7A6E92;
  --border: #EAE1FA;
  --green: #16A97A; --green-soft: #E1F8EF;
  --amber: #C77A00; --amber-soft: #FFF3D6;
  --red: #E8385C;   --red-soft: #FFEBEF;
  --blue: #3B82F6;  --blue-soft: #E9F2FF;
  --track: #F1EAFB;
  --hero-a: #7C3AED; --hero-b: #6D28D9; --hero-c: #C4B5FD;
  --hero-shade: 50, 18, 110;
}

/* 落日红 */
html[data-theme="sunset"] {
  --brand: #D33920;
  --brand-dark: #B8321C;
  --brand-soft: #FDECE7;
  --brand-2: #F4795E;
  --bg: #FFF8F5;
  --card: #FFFFFF;
  --text: #2A1813;
  --text-sub: #8C6E64;
  --border: #F6E1D9;
  --green: #16A97A; --green-soft: #E1F8EF;
  --amber: #C77A00; --amber-soft: #FFF3D6;
  --red: #D93A2B;   --red-soft: #FFEBE8;
  --blue: #2F80ED;  --blue-soft: #EAF3FF;
  --track: #FBE9E2;
  --hero-a: #E2553C; --hero-b: #C7422C; --hero-c: #FDBA74;
  --hero-shade: 110, 32, 16;
}

/* 晴空蓝 */
html[data-theme="sky"] {
  --brand: #0E79A7;
  --brand-dark: #0C658B;
  --brand-soft: #E4F6FE;
  --brand-2: #38BDF8;
  --bg: #F4FBFF;
  --card: #FFFFFF;
  --text: #0F2231;
  --text-sub: #5E7C8E;
  --border: #DCEFF9;
  --green: #16A97A; --green-soft: #E1F8EF;
  --amber: #C77A00; --amber-soft: #FFF3D6;
  --red: #E8385C;   --red-soft: #FFEBEF;
  --blue: #0EA5E9;  --blue-soft: #E4F6FE;
  --track: #E7F4FC;
  --hero-a: #0EA5E9; --hero-b: #0284C7; --hero-c: #7DD3FC;
  --hero-shade: 6, 52, 86;
}

/* 午夜灰 */
html[data-theme="midnight"] {
  --brand: #475569;
  --brand-dark: #334155;
  --brand-soft: #EEF1F5;
  --brand-2: #64748B;
  --bg: #F6F7F9;
  --card: #FFFFFF;
  --text: #161C26;
  --text-sub: #6B7686;
  --border: #E4E8EE;
  --green: #12B76A; --green-soft: #E7F8F0;
  --amber: #F79009; --amber-soft: #FEF4E6;
  --red: #F04438;   --red-soft: #FEECEB;
  --blue: #2E90FA;  --blue-soft: #EAF3FF;
  --track: #ECF0F4;
  --hero-a: #475569; --hero-b: #334155; --hero-c: #94A3B8;
  --hero-shade: 20, 28, 42;
}

/* 象牙白。学员免费自带的三套之一。
   底色不是纯白而是带一点暖黄（#FDFCF7），纯白在教室灯下看久了发灰、刺眼。
   品牌色用古铜，配象牙白是纸和印泥的关系，比放个高饱和色稳妥 */
html[data-theme="ivory"] {
  --brand: #8A6D3B;
  --brand-dark: #6F5730;
  --brand-soft: #F5F5F5;
  --brand-2: #C4A76A;
  --bg: #FFFFFF;
  --card: #FFFFFF;
  --text: #111111;
  --text-sub: #6B6B6B;
  --border: #E8E8E8;
  --green: #3D7A2E; --green-soft: #F1F7EE;
  --amber: #A6721A; --amber-soft: #FAF5EA;
  --red: #B03A2E;   --red-soft: #FBEFED;
  --blue: #2F6390;  --blue-soft: #EFF4F9;
  --track: #F4F4F4;
  --hero-a: #FFFFFF; --hero-b: #F7F7F7; --hero-c: #FFFFFF;
  --hero-shade: 30, 30, 30;
}

/* 白纸黑字。banner 原来是白字配深底，这套底色改成纯白之后白字就没了，
   所以 banner 里的字反过来压成黑的，遮罩也跟着从「压暗」翻成「提亮」——
   底色白的主题，想让字读得清只能往白里压，不能往黑里压。
   这是十七套里唯一一套反过来的，单独写几条，不去动 .banner 的公共规则 */
html[data-theme="ivory"] .banner-body { color: #111111; }
html[data-theme="ivory"] .banner-body h1 { text-shadow: none; }
html[data-theme="ivory"] .banner-body p { opacity: 1; color: #5A5A5A; }
html[data-theme="ivory"] .banner-eyebrow {
  background: rgba(0, 0, 0, .05);
  border-color: rgba(0, 0, 0, .10);
  color: #333333;
}
html[data-theme="ivory"] .banner.has-img::after {
  background: linear-gradient(90deg,
    rgba(255, 255, 255, .95) 0%,
    rgba(255, 255, 255, .88) 30%,
    rgba(255, 255, 255, .66) 46%,
    rgba(255, 255, 255, .24) 62%,
    rgba(255, 255, 255, 0) 78%);
}
html[data-theme="ivory"] .banner:not(.has-img)::before,
html[data-theme="ivory"] .banner:not(.has-img)::after {
  background: rgba(0, 0, 0, .035);
}

/* 抹茶 */
html[data-theme="matcha"] {
  --brand: #5A7D3A;
  --brand-dark: #486430;
  --brand-soft: #EDF5E4;
  --brand-2: #8FBF5A;
  --bg: #F8FBF4;
  --card: #FFFFFF;
  --text: #222B1A;
  --text-sub: #748066;
  --border: #E6EDDC;
  --green: #4C9A2A; --green-soft: #EAF7E1;
  --amber: #C08A12; --amber-soft: #FBF3DC;
  --red: #C9453B;   --red-soft: #FBEAE8;
  --blue: #3C7FA8;  --blue-soft: #E9F2F8;
  --track: #EEF4E5;
  --hero-a: #A8C97F; --hero-b: #7FA84F; --hero-c: #CFE3AE;
  --hero-shade: 60, 80, 30;
}

/* 可可（奶茶棕） */
html[data-theme="cocoa"] {
  --brand: #8B5E3C;
  --brand-dark: #6F4A2E;
  --brand-soft: #F7EDE4;
  --brand-2: #C99A6E;
  --bg: #FDFAF6;
  --card: #FFFFFF;
  --text: #2B211A;
  --text-sub: #87735F;
  --border: #EFE5DA;
  --green: #4E8A45; --green-soft: #EDF6EA;
  --amber: #B87C1C; --amber-soft: #FBF1DB;
  --red: #BF4638;   --red-soft: #FBEAE7;
  --blue: #3A73A6;  --blue-soft: #E9F1F8;
  --track: #F5EDE3;
  --hero-a: #C89A72; --hero-b: #A5764F; --hero-c: #E3C6A8;
  --hero-shade: 110, 70, 40;
}

/* 薰衣草。跟葡萄紫的区别：葡萄紫是艳的 #7C3AED，这套压了饱和度往灰里走，
   看半小时不会腻 */
html[data-theme="lavender"] {
  --brand: #8A6FBF;
  --brand-dark: #7357A6;
  --brand-soft: #F3EFFA;
  --brand-2: #B49BE0;
  --bg: #FAF8FD;
  --card: #FFFFFF;
  --text: #241E30;
  --text-sub: #7A7189;
  --border: #ECE7F5;
  --green: #4B8C55; --green-soft: #ECF6EE;
  --amber: #B98524; --amber-soft: #FBF1DC;
  --red: #C24A50;   --red-soft: #FBEAEB;
  --blue: #4472B0;  --blue-soft: #EAF0F9;
  --track: #F2EDF9;
  --hero-a: #B49BE0; --hero-b: #8A6FBF; --hero-c: #DCCEF2;
  --hero-shade: 70, 50, 110;
}

/* 画布。唯一一套「背景是图片」的主题。
   图片地址不写死在这儿：common.js 启动时拉 /api/themes/background，
   把 url() 塞进 --theme-bg。管理员换图不用发版，也不用来改这个文件。
   没配图时退到 static/assets 里那张内置水彩纸——不指望管理员一上线就先去传图，
   但「画布」切过去总得有块布，白板一块不如没有。
   上面那层 82% 的白是有意的——图再淡也要压一层，否则遇到深色图
   正文立刻读不清。宁可图看不清，也不能让题看不清 */
html[data-theme="gallery"] {
  --brand: #5B6B7C;
  --brand-dark: #46545F;
  --brand-soft: #EEF2F5;
  --brand-2: #8FA3B5;
  --bg: #FBFAF8;
  --card: #FFFFFF;
  --text: #232B33;
  --text-sub: #6B7885;
  --border: #E8EBEE;
  --green: #4A8A5C; --green-soft: #EDF6F0;
  --amber: #B47C1E; --amber-soft: #FBF2DF;
  --red: #C0463A;   --red-soft: #FCEDEB;
  --blue: #3A73A6;  --blue-soft: #EAF1F8;
  --track: #F1F4F7;
  --hero-a: #A8B8C8; --hero-b: #7E93A8; --hero-c: #C9D6E0;
  --hero-shade: 60, 72, 84;
}
html[data-theme="gallery"] body {
  background-image:
    linear-gradient(rgba(251, 250, 248, .82), rgba(251, 250, 248, .82)),
    var(--theme-bg, url('/static/assets/theme-bg-gallery.jpg'));
  background-size: cover;
  background-position: center;
  background-attachment: fixed;
  background-repeat: no-repeat;
}

/* 彩虹。这套的诉求是「一眼就是彩虹」。
   只有顶上一根线、一个按钮，那不叫彩虹，那叫贴了个标签。所以颜色铺到
   三块大面积上去：页面底色（整页最大的一块）、banner（首屏最抢眼的一块）、
   顶部带（钉在视口上，滚动也在）。
   卡片仍留纯白——学生要在这上面读题。彩在四周、白在中间，才是能天天看的彩虹；
   满屏渐变看十分钟眼睛就累，那是烟花不是配色 */
html[data-theme="rainbow"] {
  --brand: #7C3AED;
  --brand-dark: #6D28D9;
  --brand-soft: #F5F0FF;
  --brand-2: #F472B6;
  --bg: #FCFAFF;
  --card: #FFFFFF;
  --text: #1F1B2E;
  --text-sub: #6B6580;
  --border: #EDE8F8;
  --green: #12A150; --green-soft: #E8F8EE;
  --amber: #D97706; --amber-soft: #FEF3DC;
  --red: #DC2626;   --red-soft: #FDECEC;
  --blue: #2563EB;  --blue-soft: #EAF1FE;
  --track: #F7F3FF;
  --hero-a: #FF6B9D; --hero-b: #A78BFA; --hero-c: #38BDF8;
  --hero-shade: 90, 60, 120;
}

/* 页面底色：六团彩色柔光。
   透明度压在三成上下——再浓白卡片就浮不起来，正文也发灰。
   固定定位（fixed）让光团钉在视口上，滚动时页面从光上面滑过去，
   不像一张彩纸贴在内容后面跟着跑 */
html[data-theme="rainbow"] body {
  background-color: #FCFAFF;
  background-image:
    radial-gradient(42% 34% at 6% 2%,   rgba(255, 107, 157, .34), transparent 68%),
    radial-gradient(40% 32% at 94% 4%,  rgba(255, 200, 87, .30),  transparent 68%),
    radial-gradient(44% 36% at 2% 50%,  rgba(74, 222, 128, .26),  transparent 68%),
    radial-gradient(42% 34% at 98% 48%, rgba(56, 189, 248, .30),  transparent 68%),
    radial-gradient(46% 38% at 18% 98%, rgba(192, 132, 252, .30), transparent 68%),
    radial-gradient(42% 34% at 86% 96%, rgba(255, 107, 157, .24), transparent 68%);
  background-attachment: fixed;
  background-repeat: no-repeat;
}

/* 顶上一条七像素的彩虹带，钉在视口最上面。
   动效不是「整条平移」——那是跑马灯，看两眼就廉价。改成：
   七色**原地常驻**，每一刻都能看全七色；一道柔光从左掠到右，掠完停四秒再来。
   像玻璃上反了一道光，不是 LED 屏在滚。
   原先 background-size: 200% 只显示渐变的一半，任何时刻看到的都是
   「紫→青」这半段，压根不像彩虹——顺手改成 100%。
   pointer-events: none 是为了不挡住顶上那一条边的点击 */
html[data-theme="rainbow"] body::before {
  content: '';
  position: fixed;
  top: 0; left: 0; right: 0;
  height: 7px;
  background-image:
    linear-gradient(100deg, transparent 42%, #fff 50%, transparent 58%),
    linear-gradient(90deg,
      #FF3D71, #FF8A3D, #FFD93D, #4ADE80, #38BDF8, #818CF8, #C084FC, #FF3D71);
  background-size: 30% 100%, 100% 100%;
  background-repeat: no-repeat, no-repeat;
  /* 两条动画叠着跑，互不抢属性：
     sheen 动 background-position（光带扫），glow 动 box-shadow + filter（呼吸）。
     单靠扫光在 7px 高的线上太不起眼，加了呼吸之后，
     整条带子的光晕和鲜艳度一起一伏，才看得出「是活的」。 */
  animation:
    rainbow-sheen 9s cubic-bezier(.45,.05,.35,1) infinite,
    rainbow-glow 6.5s ease-in-out infinite;
  z-index: 9999;
  pointer-events: none;
}
/* 0-55% 走完全程，55%-100% 静止 —— 「掠—停—掠」的节奏，
   不是永不停歇的匀速。缓动两头慢中间快，像真的光在扫 */
@keyframes rainbow-sheen {
  0%        { background-position: -40% 0, 0 0; }
  55%, 100% { background-position: 140% 0, 0 0; }
}
/* 光晕和鲜艳度一起呼吸。周期 6.5s 和扫光的 9s 不是整数倍，
   两个节奏错开，看上去不会循环得太整齐 */
@keyframes rainbow-glow {
  0%, 100% {
    box-shadow: 0 0 10px rgba(168, 85, 247, .32), 0 0 3px rgba(255, 61, 113, .32);
    filter: saturate(1);
  }
  50% {
    box-shadow: 0 0 26px rgba(168, 85, 247, .78), 0 0 9px rgba(255, 61, 113, .7);
    filter: saturate(1.32);
  }
}

/* banner：七色斜切，自己在流。
   面积最大、位置最显眼，这里不铺满彩虹，整套就还是「白底加个紫按钮」 */
html[data-theme="rainbow"] .banner {
  background: linear-gradient(115deg,
    #FF6B9D 0%, #FF9A5A 13%, #FFD93D 26%, #6EE7A8 41%,
    #38BDF8 58%, #818CF8 74%, #C084FC 88%, #FF6B9D 100%);
  background-size: 260% 100%;
  animation: rainbow-banner 11s ease-in-out infinite alternate;
  /* 三层彩色光环套在 banner 外面。插画一旦铺满，里面的彩虹渐变就被盖住了，
     这道光环是唯一还露在外面的彩色——首屏最显眼的那块必须带颜色 */
  box-shadow:
    0 0 0 3px rgba(255, 107, 157, .45),
    0 0 0 6px rgba(255, 217, 61, .30),
    0 0 0 9px rgba(56, 189, 248, .22),
    0 26px 60px -28px rgba(168, 85, 247, .75);
}
@keyframes rainbow-banner {
  from { background-position-x: 0%; }
  to   { background-position-x: 100%; }
}

/* 导航底线也换成彩虹。顶带被浏览器界面遮住时，这条是第二道证据。
   和顶带同一套「掠光」，周期错开（11s vs 9s），两条不会同步闪 */
html[data-theme="rainbow"] .nav {
  border-bottom: none;
  background-image:
    linear-gradient(100deg, transparent 40%, rgba(255,255,255,.85) 50%, transparent 60%),
    linear-gradient(90deg,
      #FF3D71, #FF8A3D, #FFD93D, #4ADE80, #38BDF8, #818CF8, #C084FC, #FF3D71);
  background-size: 22% 3px, 100% 3px;
  background-repeat: no-repeat, no-repeat;
  animation: rainbow-sheen-nav 11s cubic-bezier(.45,.05,.35,1) infinite;
}
@keyframes rainbow-sheen-nav {
  0%        { background-position: -40% 100%, 0 100%; }
  55%, 100% { background-position: 140% 100%, 0 100%; }
}

/* 主按钮：四色渐变常驻，一道白光斜掠过去。整页最常被点的地方 */
html[data-theme="rainbow"] .btn-primary {
  background-color: #A78BFA;
  background-image:
    linear-gradient(100deg, transparent 40%, rgba(255,255,255,.45) 50%, transparent 60%),
    linear-gradient(105deg, #F472B6, #A78BFA 36%, #60A5FA 68%, #34D399 100%);
  background-size: 40% 100%, 100% 100%;
  background-repeat: no-repeat, no-repeat;
  animation: rainbow-sheen-btn 5s ease-in-out infinite;
  border-color: transparent;
  box-shadow: 0 10px 24px -12px rgba(167, 139, 250, .85);
}
@keyframes rainbow-sheen-btn {
  0%        { background-position: -100% 0, 0 0; }
  60%, 100% { background-position: 200% 0, 0 0; }
}
html[data-theme="rainbow"] .btn-primary:hover { filter: brightness(1.08); }

/* 卡片顶上压一道三像素的彩虹条。
   卡片是整页数量最多的东西，每张都带一条，视线扫过去就是一片彩。
   用 background-image 而不是 border-image：border-image 会把圆角变成直角 */
html[data-theme="rainbow"] .card,
html[data-theme="rainbow"] .th-card,
html[data-theme="rainbow"] .sheet-card,
html[data-theme="rainbow"] .me-card,
html[data-theme="rainbow"] .fc-card {
  background-image: linear-gradient(90deg,
    #FF6B9D, #FFD93D 25%, #4ADE80 50%, #38BDF8 75%, #C084FC);
  background-size: 100% 4px;
  background-repeat: no-repeat;
  background-position: top left;
}
/* 带 card-head 的卡片，头顶那一块会盖住彩条，单独补在它上面 */
html[data-theme="rainbow"] .card-head {
  background-image: linear-gradient(90deg,
    #FF6B9D, #FFD93D 25%, #4ADE80 50%, #38BDF8 75%, #C084FC);
  background-size: 100% 4px;
  background-repeat: no-repeat;
  background-position: top left;
}

/* 筛选条上选中的那个小胶囊。整页里最常被点的地方 */
html[data-theme="rainbow"] .pc-chip.is-on,
html[data-theme="rainbow"] .pc-chip.is-on:hover {
  background: linear-gradient(105deg, #F472B6, #A78BFA 50%, #60A5FA);
  color: #fff;
}
html[data-theme="rainbow"] .pc-chip.is-on:hover { filter: brightness(1.06); }

/* 导航上「我现在在哪」那一格，和「进入教学中心」那颗按钮。
   顶带、banner 都在页面上方，这两颗在导航条里，是最常被扫到的地方 */
html[data-theme="rainbow"] .nav-links a.active {
  background-color: #A78BFA;
  /* 和主按钮同一套掠光。这两颗是导航里唯一像「按钮」的东西，
     其他导航项是纯文字，扫光会显得整条导航都在闪 */
  background-image:
    linear-gradient(100deg, transparent 42%, rgba(255,255,255,.5) 50%, transparent 58%),
    linear-gradient(105deg, #F472B6, #A78BFA 50%, #60A5FA);
  background-size: 40% 100%, 100% 100%;
  background-repeat: no-repeat, no-repeat;
  animation: rainbow-sheen-btn 5s ease-in-out infinite;
  color: #fff;
  box-shadow: 0 4px 14px -6px rgba(167, 139, 250, .85);
}
html[data-theme="rainbow"] .nav-links a:hover {
  background: rgba(167, 139, 250, .14);
  color: #6D28D9;
}
html[data-theme="rainbow"] .nav-links a.nav-admin {
  background-color: #7C3AED;
  background-image:
    linear-gradient(100deg, transparent 42%, rgba(255,255,255,.5) 50%, transparent 58%),
    linear-gradient(105deg, #7C3AED, #C084FC 52%, #F472B6);
  background-size: 40% 100%, 100% 100%;
  background-repeat: no-repeat, no-repeat;
  /* 相位错开 1.3 秒，跟旁边那颗不会一起亮 */
  animation: rainbow-sheen-btn 5s ease-in-out infinite;
  animation-delay: -1.3s;
  box-shadow: 0 6px 18px -8px rgba(168, 85, 247, .9);
}
html[data-theme="rainbow"] .nav-links a.nav-admin.active,
html[data-theme="rainbow"] .nav-links a.nav-admin:hover {
  background: linear-gradient(105deg, #6D28D9, #A855F7 52%, #EC4899);
  color: #fff;
}
/* 卡片浮起来的时候描一圈淡紫，跟头顶那道彩条呼应 */
html[data-theme="rainbow"] .card:hover {
  box-shadow: 0 0 0 1px rgba(167, 139, 250, .38), var(--sh-2);
}
html[data-theme="rainbow"] .th-card:hover { border-color: #C4B5FD; }

/* 滚动条也换成彩虹。这条最容易被忽略，但滚动条是常驻的，
   一竖条彩色比顶上那根线更常被看见 */
html[data-theme="rainbow"] * { scrollbar-color: #C084FC transparent; }
html[data-theme="rainbow"] ::-webkit-scrollbar-thumb {
  background: linear-gradient(180deg,
    #FF6B9D, #FFD93D 25%, #4ADE80 50%, #38BDF8 75%, #C084FC);
  background-clip: content-box;
  border-radius: 99px;
}
html[data-theme="rainbow"] ::selection { background: rgba(167, 139, 250, .34); }

/* 彩虹主题的鼠标。
   箭头用 SVG 内联成 data URI——图片光标在 4K 屏上会糊成马赛克，矢量不会。
   热点给 6 3，正好是箭尖：鼠标点哪儿，箭尖就得在哪儿，差一点整页都会觉得别扭。
   只改默认箭头，不动 a / button 上的 cursor: pointer——按钮该是手型还是手型，
   全站统一换掉反而让人认不出哪里能点 */
html[data-theme="rainbow"],
html[data-theme="rainbow"] body {
  cursor: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2232%22%20height%3D%2232%22%20viewBox%3D%220%200%2032%2032%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%22g%22%20x1%3D%220%22%20y1%3D%220%22%20x2%3D%22.8%22%20y2%3D%221%22%3E%3Cstop%20offset%3D%220%22%20stop-color%3D%22%23F472B6%22%2F%3E%3Cstop%20offset%3D%22.5%22%20stop-color%3D%22%23A78BFA%22%2F%3E%3Cstop%20offset%3D%221%22%20stop-color%3D%22%2360A5FA%22%2F%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Cpath%20d%3D%22M6%203%206%2025%2011.5%2020%2015.5%2028%2019%2026.5%2015%2018.5%2022%2018.5Z%22%20fill%3D%22url%28%23g%29%22%20stroke%3D%22%23FFFFFF%22%20stroke-width%3D%221.6%22%20stroke-linejoin%3D%22round%22%2F%3E%3C%2Fsvg%3E") 6 3, auto;
}

/* 极光。会动的那套。
   上一版把三团 radial 渐变挂在 body 的 background 上，动 background-position。
   两个问题：位移只有百分之十几、周期 26 秒，肉眼基本察觉不到；而且
   background-position 动画每帧重绘整页渐变，页面一长就掉帧。
   现在改成独立一层（body::before，固定定位），动的是 transform——
   transform 走合成器，不重绘，位移放大到百分之十几屏宽也不掉帧。
   光团边界本来就模糊，位移一大就是「窗外有光在慢慢移」的感觉。
   body 自己必须透明，否则会把这一层盖住 */
html[data-theme="aurora"] {
  --brand: #2F80B8;
  --brand-dark: #256994;
  --brand-soft: #EAF5FB;
  --brand-2: #5EC8D8;
  --bg: #F7FBFF;
  --card: #FFFFFF;
  --text: #16232E;
  --text-sub: #5F7385;
  --border: #DFEDF6;
  --green: #12A150; --green-soft: #E8F8EE;
  --amber: #D97706; --amber-soft: #FEF3DC;
  --red: #DC2626;   --red-soft: #FDECEC;
  --blue: #2563EB;  --blue-soft: #EAF1FE;
  --track: #EDF6FC;
  --hero-a: #34D399; --hero-b: #38BDF8; --hero-c: #A78BFA;
  --hero-shade: 30, 70, 90;
}
html[data-theme="aurora"] { background-color: #F7FBFF; }
html[data-theme="aurora"] body { background: transparent; }
html[data-theme="aurora"] body::before {
  content: '';
  position: fixed;
  inset: -25%;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(34% 30% at 26% 28%, rgba(52, 211, 153, .58), transparent 66%),
    radial-gradient(30% 28% at 72% 20%, rgba(56, 189, 248, .56), transparent 66%),
    radial-gradient(38% 32% at 54% 78%, rgba(167, 139, 250, .50), transparent 66%),
    radial-gradient(24% 22% at 88% 66%, rgba(244, 114, 182, .30), transparent 66%);
  animation: aurora-move 14s ease-in-out infinite alternate;
}
@keyframes aurora-move {
  from { transform: translate3d(-6%, -4%, 0) scale(1.04) rotate(-2deg); }
  to   { transform: translate3d(7%, 6%, 0) scale(1.18) rotate(3deg); }
}
/* banner 上的极光流动。原来 20 秒一轮、位移一屏，太含蓄了，
   现在 10 秒一轮、走两个屏宽，看两秒就知道它在动 */
html[data-theme="aurora"] .banner {
  background: linear-gradient(115deg,
    #34D399, #38BDF8 28%, #818CF8 52%, #A78BFA 72%, #34D399 100%);
  background-size: 280% 280%;
  animation: aurora-flow 10s ease-in-out infinite;
}
@keyframes aurora-flow {
  0%, 100% { background-position: 0% 50%; }
  50%      { background-position: 100% 50%; }
}
/* 系统里开了「减少动态效果」就全部停掉。有人看这种缓慢漂移会头晕，
   这是系统级的意愿，不该由主题决定 */
@media (prefers-reduced-motion: reduce) {
  html[data-theme="aurora"] body::before,
  html[data-theme="aurora"] .banner,
  html[data-theme="rainbow"] body::before,
  html[data-theme="rainbow"] .nav,
  html[data-theme="rainbow"] .banner,
  html[data-theme="rainbow"] .btn-primary { animation: none; }
}

/* ---------------------------------------------------------------- 基础 */
html { scroll-behavior: smooth; }
body {
  font-size: var(--fs-body);
  letter-spacing: .01em;
  transition: background-color var(--dur-3) var(--ease-out), color var(--dur-3) var(--ease-out);
}
body, .nav, .hero, .banner, .chip, .btn, .search, .org-table, .me-card,
.org-row, .p-row, .fc-field select, .fc-card, .sheet-card {
  transition-property: background-color, border-color, color, box-shadow, transform;
  transition-duration: var(--dur-2);
  transition-timing-function: var(--ease-out);
}

/* 键盘焦点必须看得见——现在很多控件 focus 只有 1px 边框变色，键盘用户等于摸黑 */
:where(a, button, input, select, textarea, [tabindex]):focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
  border-radius: var(--r-xs);
}

::selection { background: var(--brand); color: #fff; }

/* ---------------------------------------------------------------- 3. 按钮 */
/* 老按钮是纯色块。新做法：底部 3px 实色「底座」，按下去就压平——
   这是最省事、最有效的一招，孩子能感觉到「我按到了东西」 */
.btn {
  border-radius: var(--r-md);
  /* 字号接变量，别靠继承——继承到的是 body 的 14px 硬编码值，
     大屏上按钮文字就比周围正文小一号，看着像没做完 */
  font-size: var(--fs-body);
  padding: 11px 22px;
  font-weight: 700;
  letter-spacing: .01em;
  transition: transform var(--dur-1) var(--ease-out),
              box-shadow var(--dur-1) var(--ease-out),
              background-color var(--dur-1) var(--ease-out);
}
.btn-primary {
  /* 基础皮肤就是一块纯色，不加扫光。
     扫光是付费主题才有的东西——不做出差距，花铜币解锁就没意义了。
     彩虹那套在下面 html[data-theme="rainbow"] 里自己叠光带。 */
  background: var(--brand);
  box-shadow: 0 3px 0 var(--brand-dark), 0 8px 18px -10px var(--brand);
}
.btn-primary:hover {
  background: var(--brand);
  /* hover 是**变暗**不是变亮：白字压在主题色上，一亮对比度就掉。
     实测六套亮色主题 hover 变亮 5% 会从 4.7~4.8 掉到 4.35~4.49，
     刚好跌破 4.5。变暗 6% 反而升到 5.3 以上 */
  filter: brightness(.94);
  transform: translateY(-1px);
  box-shadow: 0 4px 0 var(--brand-dark), 0 12px 22px -10px var(--brand);
}
.btn-primary:active {
  transform: translateY(2px);
  box-shadow: 0 1px 0 var(--brand-dark), 0 4px 10px -8px var(--brand);
}
.btn-primary:disabled,
/* 彩虹主题的选择器权重是 0,2,1，比 .btn-primary:disabled 的 0,2,0 高，
   不显式写出来，禁用态下光带还会继续扫，看着像「还能点」 */
html[data-theme="rainbow"] .btn-primary:disabled {
  background: var(--track); color: var(--text-sub);
  background-image: none; animation: none;
  box-shadow: 0 3px 0 var(--border);
  transform: none; filter: none;
}
.btn-ghost {
  background: var(--card);
  border: 1.5px solid var(--border);
  box-shadow: 0 2px 0 var(--border);
  color: var(--text);
}
.btn-ghost:hover {
  background: var(--card);
  border-color: var(--brand);
  color: var(--brand);
  box-shadow: 0 2px 0 var(--brand);
  transform: translateY(-1px);
}
.btn-ghost:active { transform: translateY(1px); box-shadow: 0 0 0 var(--border); }
.btn-sm { padding: 8px 16px; font-size: var(--fs-sm); border-radius: var(--r-sm); }
.btn-danger-ghost { color: var(--red); border-color: var(--red-soft); }
.btn-danger-ghost:hover { border-color: var(--red); color: var(--red); box-shadow: 0 2px 0 var(--red); }

.org-ops .btn { transition: background-color var(--dur-1), color var(--dur-1), transform var(--dur-1); }
.org-ops .btn:active { transform: scale(.96); }

.btn.is-busy { pointer-events: none; opacity: .75; }
.btn.is-busy::after {
  content: ''; width: 13px; height: 13px; margin-left: 7px;
  border: 2px solid currentColor; border-right-color: transparent;
  border-radius: 50%; display: inline-block; vertical-align: -2px;
  animation: ui2-spin .7s linear infinite;
}
@keyframes ui2-spin { to { transform: rotate(360deg); } }

/* ---------------------------------------------------------------- 输入 */
.search, .fc-field select, input[type="text"], input[type="password"],
input[type="datetime-local"], input[type="number"], textarea, select {
  border-radius: var(--r-md);
  border: 1.5px solid var(--border);
  background: var(--card);
  transition: border-color var(--dur-1), box-shadow var(--dur-1), background-color var(--dur-1);
}
.search:focus, .fc-field select:focus, input:focus, textarea:focus, select:focus {
  outline: none;
  border-color: var(--brand);
  box-shadow: 0 0 0 4px var(--brand-soft);
}
/* 错误态：红框 + 轻微抖动。老做法只有一行红字，眼睛找不到是哪一项 */
.need, .is-error {
  border-color: var(--red) !important;
  box-shadow: 0 0 0 4px var(--red-soft) !important;
  animation: ui2-shake .32s var(--ease-out);
}
@keyframes ui2-shake {
  0%, 100% { transform: translateX(0); }
  25% { transform: translateX(-4px); }
  75% { transform: translateX(4px); }
}

.chip {
  border-width: 1.5px;
  transition: transform var(--dur-1) var(--ease-spring), background-color var(--dur-1),
              border-color var(--dur-1), color var(--dur-1), box-shadow var(--dur-1);
}
.chip:hover { transform: translateY(-1px); box-shadow: var(--sh-1); }
.chip:active { transform: scale(.96); }
.chip.active {
  background: var(--brand);
  border-color: var(--brand);
  box-shadow: 0 2px 8px -3px var(--brand);
}

/* ---------------------------------------------------------------- 卡片 / 行 */
.sheet-card, .me-card, .fc-card {
  border-radius: var(--r-lg);
  box-shadow: var(--sh-1);
  transition: transform var(--dur-2) var(--ease-out), box-shadow var(--dur-2) var(--ease-out),
              border-color var(--dur-2);
}
.sheet-card:hover {
  transform: translateY(-3px);
  box-shadow: var(--sh-3);
  border-color: var(--brand);
}
/* 列表行 hover：老版后台表格行只有一条底边框，鼠标划过去毫无反应 */
.org-row:not(.org-head):hover,
.p-row:not(.p-head):hover,
.admin-row:not(.p-head):hover,
.hw-stu:hover {
  background: var(--brand-soft);
}
.p-row, .org-row, .admin-row { transition: background-color var(--dur-1); }
.p-row:not(.p-head), .admin-row:not(.p-head) { cursor: pointer; }

.problem-table, .org-table {
  border-radius: var(--r-lg);
  box-shadow: var(--sh-2);
  overflow: hidden;
}

/* ---------------------------------------------------------------- 标签 */
.org-tag, .tag {
  border-radius: var(--r-pill);
  font-weight: 700;
  letter-spacing: .02em;
  padding: 3px 10px;
}

/* ---------------------------------------------------------------- 弹窗 */
.ui-mask, .org-mask, .me-mask {
  backdrop-filter: blur(6px);
  animation: ui2-fade var(--dur-2) var(--ease-out);
}
.ui-dialog, .org-modal {
  border-radius: var(--r-xl);
  box-shadow: 0 32px 64px -24px rgba(16, 24, 40, .4);
  animation: ui2-pop var(--dur-3) var(--ease-spring);
}
@keyframes ui2-fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes ui2-pop {
  from { opacity: 0; transform: translateY(12px) scale(.97); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}

/* ---------------------------------------------------------------- toast */
.toast, .adm-toast {
  border-radius: var(--r-pill) !important;
  padding: 11px 22px !important;
  font-weight: 700;
  box-shadow: var(--sh-3);
}

/* ---------------------------------------------------------------- 骨架屏 */
.sk-line {
  height: 13px; border-radius: var(--r-xs);
  background: linear-gradient(90deg, var(--track) 25%, var(--card) 37%, var(--track) 63%);
  background-size: 400% 100%;
  animation: ui2-shimmer 1.3s var(--ease-in-out) infinite;
}
.sk-row { display: flex; gap: var(--s3); align-items: center; padding: 15px 22px; border-bottom: 1px solid var(--border); }
.sk-row .sk-line:nth-child(1) { width: 64px; flex: none; }
.sk-row .sk-line:nth-child(2) { flex: 1; }
.sk-row .sk-line:nth-child(3) { width: 72px; flex: none; }
@keyframes ui2-shimmer {
  0% { background-position: 100% 50%; }
  100% { background-position: 0 50%; }
}

/* ---------------------------------------------------------------- 空状态 */
/* 「我的赛事」下面挂的第二个区块（真题练习）要有自己的小标题，
   不然两张卡片贴在一起，分不清哪块是哪块 */
.me-sub-head {
  margin: 22px 0 10px;
  padding-left: 2px;
}
.me-sub-head h2 {
  margin: 0 0 3px;
  font-size: 15px;
  font-weight: 800;
  color: var(--text);
  letter-spacing: -.2px;
}
.me-sub-head p {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--text-sub);
}

.me-empty, .org-empty {
  padding: var(--s9) var(--s5);
  text-align: center;
  color: var(--text-sub);
}
.me-empty b, .org-empty b {
  display: block; font-size: var(--fs-h3); color: var(--text);
  margin-bottom: var(--s1);
}

/* ---------------------------------------------------------------- Banner */
/* 每个中心页顶部的图位。有图用图，没图用渐变 + 光斑，
   两种情况都得好看——不能因为管理员忘了传图，首页就秃一块 */
.banner {
  position: relative;
  border-radius: var(--r-xl);
  overflow: hidden;
  margin-bottom: var(--s6);
  min-height: 188px;
  display: flex;
  align-items: flex-end;
  background: linear-gradient(125deg, var(--hero-a), var(--hero-b) 58%, var(--hero-c));
  box-shadow: 0 24px 48px -28px var(--brand);
  isolation: isolate;
}
.banner-img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  z-index: -2;
}
.banner.has-img::after {
  content: '';
  position: absolute; inset: 0; z-index: -1;
  /* 左侧压住让白字读得清，中间快速透出插画。
     原来 0%→52% 只从 .72 降到 .38，衰减太慢，中间一大片灰蒙蒙，
     跟右侧明亮的插画像两张图硬拼的。文字盒最多占到 banner 宽度的
     四成（1280 窄屏时），所以 45% 之前必须还有足够暗度，再往后就该让开 */
  background: linear-gradient(90deg,
    rgba(12, 14, 24, .84) 0%,
    rgba(12, 14, 24, .74) 30%,
    rgba(12, 14, 24, .48) 46%,
    rgba(12, 14, 24, .16) 62%,
    rgba(12, 14, 24, 0) 78%);
}
.banner:not(.has-img)::before {
  content: '';
  position: absolute; right: -70px; top: -90px;
  width: 320px; height: 320px; border-radius: 50%;
  background: rgba(255, 255, 255, .14);
  z-index: -1;
}
.banner:not(.has-img)::after {
  content: '';
  position: absolute; right: 120px; bottom: -110px;
  width: 220px; height: 220px; border-radius: 50%;
  background: rgba(255, 255, 255, .09);
  z-index: -1;
}
.banner-body {
  position: relative;
  padding: var(--s7) var(--s8);
  color: #fff;
  width: 100%;
}
.banner-eyebrow {
  display: inline-block;
  font-size: var(--fs-cap);
  font-weight: 800;
  letter-spacing: .16em;
  padding: 4px 11px;
  border-radius: var(--r-pill);
  /* 白纱会把底色抬亮，白字反而更糊；用品牌深色玻璃压暗，对比度才稳 */
  background: rgba(var(--hero-shade), .36);
  border: 1px solid rgba(255, 255, 255, .26);
  backdrop-filter: blur(4px);
  margin-bottom: var(--s3);
}
.banner-body h1 {
  font-size: clamp(26px, 3.4vw, var(--fs-display));
  line-height: 1.18;
  letter-spacing: -.02em;
  font-weight: 800;
  margin: 0 0 var(--s2);
  text-shadow: 0 2px 16px rgba(0, 0, 0, .18);
}
.banner-body p {
  font-size: var(--fs-body);
  opacity: .94;
  margin: 0;
  max-width: 52ch;
}
/* 副标题一长就伸到亮区，白字立刻掉到 3.7 —— 宽屏卡住宽度别让它过去；
   窄屏 ch 单位比容器还宽、卡不住，交给更浓的暗罩兜 */
@media (max-width: 900px) { .banner-body p { max-width: 100%; } }
.banner-stats {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s3);
  margin-top: var(--s5);
}
.banner-stat {
  background: rgba(var(--hero-shade), .50);
  border: 1px solid rgba(255, 255, 255, .32);
  backdrop-filter: blur(10px);
  border-radius: var(--r-md);
  padding: 10px 18px;
  min-width: 96px;
}
.banner-stat b {
  display: block;
  font-size: 24px;
  line-height: 1.2;
  font-weight: 800;
  letter-spacing: -.02em;
}
.banner-stat span { font-size: var(--fs-cap); color: rgba(255, 255, 255, .92); font-weight: 600; }

/* ---------------------------------------------------------------- 主题切换器 */
.theme-pick {
  display: flex; align-items: center; gap: 5px;
  background: var(--card);
  border: 1.5px solid var(--border);
  border-radius: var(--r-pill);
  padding: 4px 6px;
}
.theme-dot {
  width: 20px; height: 20px; border-radius: 50%;
  border: 2px solid transparent;
  cursor: pointer; padding: 0;
  background: var(--dot);
  transition: transform var(--dur-1) var(--ease-spring), border-color var(--dur-1);
}
.theme-dot:hover { transform: scale(1.18); }
.theme-dot[aria-pressed="true"] { border-color: var(--text); transform: scale(1.06); }

/* ---------------------------------------------------------------- hidden 兜底 */
/* 浏览器的 [hidden] 默认是 display:none，但它的优先级只等于一条元素选择器。
   只要组件自己写了 display（.banner{display:flex}、.banner-stats{display:flex}），
   就会把 hidden 顶掉——元素在 DOM 里标着 hidden，屏幕上照样画出来。
   这一条是全局兜底：任何写了 display 的组件，只要加 hidden 就必须真的消失 */
[hidden] { display: none !important; }

/* ---------------------------------------------------------------- 无障碍 */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}

@media (max-width: 700px) {
  .banner-body { padding: var(--s5) var(--s5); }
  .banner-stats { gap: var(--s2); }
  .banner-stat { padding: 8px 13px; min-width: 0; flex: 1 1 auto; }
  .banner-stat b { font-size: 19px; }
}

/* ============ 首页图位管理（admin_banners） ============ */

.bn-tabs { display: flex; gap: 8px; margin-bottom: var(--s5); flex-wrap: wrap; }

.bn-list { display: flex; flex-direction: column; gap: var(--s4); }

.bn-card {
  display: grid;
  grid-template-columns: 210px minmax(0, 1fr) auto;
  gap: var(--s5);
  align-items: center;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: var(--s4);
  transition: box-shadow var(--dur-2) var(--ease-out), transform var(--dur-2) var(--ease-out);
}
.bn-card:hover { box-shadow: var(--sh-2); transform: translateY(-2px); }
.bn-card.is-off { opacity: .62; }

.bn-thumb {
  height: 92px;
  border-radius: var(--r-md);
  overflow: hidden;
  background: linear-gradient(125deg, var(--hero-a), var(--hero-b) 58%, var(--hero-c));
  display: flex; align-items: center; justify-content: center;
}
.bn-thumb-img { width: 100%; height: 100%; object-fit: cover; display: block; }
.bn-thumb-empty {
  color: rgba(255, 255, 255, .9); font-size: 12.5px; text-align: center;
  line-height: 1.6; font-weight: 600;
}
.bn-thumb-empty small { font-size: 11px; opacity: .8; font-weight: 400; }

.bn-body { min-width: 0; }
.bn-title {
  font-size: var(--fs-h3); font-weight: 800; color: var(--text);
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
}
.bn-sub {
  font-size: var(--fs-sm); color: var(--text-sub); margin-top: 5px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.bn-meta {
  display: flex; gap: 14px; flex-wrap: wrap;
  font-size: var(--fs-cap); color: var(--text-sub); margin-top: 9px;
}

/* 弹窗里的图片选择：左边预览，右边选文件 */
.bn-upload { display: grid; grid-template-columns: 240px minmax(0, 1fr); gap: var(--s4); align-items: start; }
.bn-upload-preview {
  height: 76px; border-radius: var(--r-sm); overflow: hidden;
  border: 1.5px dashed var(--border); background: var(--track);
  display: flex; align-items: center; justify-content: center;
  font-size: var(--fs-cap); color: var(--text-sub);
}
.bn-upload-preview img { width: 100%; height: 100%; object-fit: cover; }
.bn-upload-side { min-width: 0; }
.bn-upload-side input { margin-top: 0 !important; }

.bn-inline { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s4); align-items: end; }

.is-busy { opacity: .6; pointer-events: none; }

@media (max-width: 860px) {
  .bn-card { grid-template-columns: 1fr; }
  .bn-thumb { height: 120px; }
  .bn-upload { grid-template-columns: 1fr; }
  .bn-inline { grid-template-columns: 1fr; }
}

.banner.is-link { cursor: pointer; }
.banner.is-link:hover .banner-img { transform: scale(1.02); }
.banner-img { transition: transform var(--dur-3) var(--ease-out); }

/* ============================================================
   排版层（ui2 第二段）
   控件换了皮之后，真正显旧的其实是「节奏」：标题小、行挤、留白紧。
   这一段只调间距、字号层级、分隔方式，不动任何 grid 列宽，
   所以不会把已经调好的表格挤坏。
   ============================================================ */

/* ---------- 内容区留白 ---------- */
.wrap { padding: 34px 26px 96px; }
.adm-content { padding: 28px 32px 56px; }

/* ---------- 页面标题区 ---------- */
.adm-page-head {
  position: relative;
  margin-bottom: var(--s6);
  padding-bottom: var(--s5);
  border-bottom: 1px solid var(--border);
}
.adm-page-head h1 {
  font-size: 26px;
  font-weight: 800;
  letter-spacing: -.6px;
  line-height: 1.2;
  display: flex;
  align-items: center;
  gap: 10px;
}
/* 标题左侧那道小竖条：一眼分清「这是页面」还是「这是板块」 */
.adm-page-head h1::before {
  content: '';
  width: 5px;
  height: 22px;
  border-radius: 999px;
  background: linear-gradient(180deg, var(--brand-2), var(--brand));
  flex: none;
}
.adm-page-head p {
  font-size: var(--fs-body);
  color: var(--text-sub);
  margin-top: 7px;
  line-height: 1.6;
  max-width: 720px;
}

/* ---------- 工具栏：抬成一块，别散在页面上 ---------- */
.org-toolbar {
  gap: var(--s3);
  margin-bottom: var(--s5);
  padding: var(--s3) var(--s4);
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-1);
}
.org-toolbar input[type="text"],
.org-toolbar .inp,
.org-toolbar select {
  height: 38px;
  padding: 0 13px;
  border: 1.5px solid var(--border);
  border-radius: var(--r-sm);
  font-size: var(--fs-sm);
  background: var(--card);
  color: var(--text);
  transition: border-color var(--dur-1) var(--ease-out),
              box-shadow var(--dur-2) var(--ease-out);
}
.org-toolbar input[type="text"]:hover,
.org-toolbar .inp:hover { border-color: var(--brand-2); }
.org-toolbar input[type="text"]:focus,
.org-toolbar .inp:focus,
.org-toolbar select:focus {
  outline: none;
  border-color: var(--brand);
  box-shadow: 0 0 0 4px var(--brand-soft);
}

/* ---------- 表格：表头压低、行距放开、hover 有线 ---------- */
.org-table {
  border-radius: var(--r-lg);
  box-shadow: var(--sh-1);
}
.org-row {
  padding: 15px 20px;
  font-size: var(--fs-body);
  transition: background var(--dur-1) var(--ease-out),
              box-shadow var(--dur-1) var(--ease-out);
}
.org-row.org-head {
  background: var(--track);
  font-size: 11.5px;
  font-weight: 800;
  letter-spacing: .8px;
  text-transform: uppercase;
  color: var(--text-sub);
  padding: 11px 20px;
  border-bottom: 1px solid var(--border);
}
/* hover 时左边亮一道：眼睛跟着这一行走，比整行变色清楚 */
.org-row:not(.org-head):hover {
  background: var(--brand-soft);
  box-shadow: inset 3px 0 0 var(--brand);
}
.org-row:not(.org-head):last-child { border-bottom: none; }
.org-name { font-weight: 800; letter-spacing: -.2px; }

/* ---------- 数字卡：加一道顶部渐变，数字再大一号 ---------- */
.adm-stats { gap: var(--s4); margin-bottom: var(--s6); }
.adm-stat {
  position: relative;
  border-radius: var(--r-lg);
  padding: 20px 22px;
  overflow: hidden;
  transition: transform var(--dur-2) var(--ease-out), box-shadow var(--dur-2) var(--ease-out);
}
.adm-stat::before {
  content: '';
  position: absolute;
  inset: 0 0 auto 0;
  height: 3px;
  background: linear-gradient(90deg, var(--brand), var(--brand-2));
  opacity: .85;
}
.adm-stat:hover { transform: translateY(-3px); box-shadow: var(--sh-2); }
.adm-stat b {
  font-size: 34px;
  letter-spacing: -1.2px;
  line-height: 1.05;
  margin-bottom: 6px;
}
.adm-stat span { font-size: var(--fs-sm); }

/* ---------- 板块卡片 ---------- */
.adm-block-wrap {
  border-radius: var(--r-lg);
  padding: 22px 24px;
  margin-bottom: var(--s5);
  box-shadow: var(--sh-1);
}
.adm-h2 {
  font-size: var(--fs-h3);
  font-weight: 800;
  margin-bottom: var(--s4);
  display: flex;
  align-items: center;
  gap: 9px;
}
.adm-h2::before {
  content: '';
  width: 3px;
  height: 15px;
  border-radius: 999px;
  background: var(--brand);
  flex: none;
}

/* ---------- 空状态：给个形状，别只丢一行灰字 ---------- */
.org-empty {
  padding: 52px 24px;
  border: 1.5px dashed var(--border);
  border-radius: var(--r-lg);
  background: var(--card);
  color: var(--text-sub);
  font-size: var(--fs-sm);
  line-height: 1.7;
}
.org-empty b { display: block; color: var(--text); font-size: var(--fs-body); margin-bottom: 4px; }

/* ---------- 标签：加一圈同色描边，看着才不是糊上去的 ---------- */
.org-tag {
  border: 1px solid color-mix(in srgb, var(--brand) 22%, transparent);
  font-weight: 700;
  letter-spacing: .1px;
}
.org-tag.blue { border-color: color-mix(in srgb, var(--brand) 26%, transparent); }
.org-tag.gray { border-color: var(--border); }
.org-tag.green { border-color: color-mix(in srgb, var(--green) 26%, transparent); }
.org-tag.amber { border-color: color-mix(in srgb, var(--amber) 26%, transparent); }
.org-tag.red { border-color: color-mix(in srgb, var(--red) 26%, transparent); }

/* ---------- 链接：hover 时才长出来的下划线 ---------- */
a { transition: color var(--dur-1) var(--ease-out); }
.wrap a:not(.btn):not(.chip),
.adm-content a:not(.btn):not(.chip) {
  text-decoration: none;
  background-image: linear-gradient(var(--brand), var(--brand));
  background-repeat: no-repeat;
  background-size: 0% 1.5px;
  background-position: 0 100%;
  transition: background-size var(--dur-2) var(--ease-out), color var(--dur-1);
}
.wrap a:not(.btn):not(.chip):hover,
.adm-content a:not(.btn):not(.chip):hover { background-size: 100% 1.5px; }

/* ---------- 焦点可见：键盘操作时不能看不出焦点在哪 ---------- */
:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
}

/* ---------- 滚动条 ---------- */
* { scrollbar-width: thin; scrollbar-color: var(--border) transparent; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-thumb {
  background: var(--border);
  border-radius: 999px;
  border: 3px solid transparent;
  background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover { background: var(--brand-2); background-clip: content-box; }
::-webkit-scrollbar-track { background: transparent; }

@media (max-width: 900px) {
  .wrap { padding: 22px 16px 72px; }
  .adm-content { padding: 20px 16px 44px; }
  .adm-page-head h1 { font-size: 22px; }
  .adm-stat b { font-size: 28px; }
  .org-toolbar { padding: 12px; }
}

/* 后台的 banner 要更扁：后台是干活的地方，一张 188px 的大图会把
   「基本情况」那几个数字顶到折叠线以下，进来先看见图、看不见数据 */
.adm-content .banner { min-height: 150px; margin-bottom: var(--s6); border-radius: var(--r-lg); }
.adm-content .banner .banner-body { padding: var(--s5) var(--s6); }
.adm-content .banner h1 { font-size: 24px; }
.adm-content .banner p { font-size: var(--fs-sm); }

/* 成长中心的 banner 也别太占地方——下面紧跟着就是练习列表。
   跟一级页面的 188px 再矮一档：这里是「我的数据」，页头是导航不是门面 */
.me-main .banner { min-height: 132px; }

/* ============================================================
   答题页（problem.html）排版
   这是学生停留最久的一页，题面挤、样例糊的话前面所有努力都白搭
   ============================================================ */

.detail-grid { gap: var(--s5); }

.card { border-radius: var(--r-lg); box-shadow: var(--sh-1); }
.card-pad { padding: 30px 32px; }
.card-head {
  background: var(--track);
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  padding: 15px 24px;
}
.card-head h3 { font-size: var(--fs-h3); font-weight: 800; }

/* 题号做成一颗小药丸，跟标题拉开层次 */
.problem-code {
  display: inline-block;
  font-size: 11.5px;
  font-weight: 800;
  letter-spacing: .8px;
  color: var(--brand);
  background: var(--brand-soft);
  border: 1px solid color-mix(in srgb, var(--brand) 20%, transparent);
  border-radius: var(--r-pill);
  padding: 2px 10px;
  margin-bottom: 10px;
}
.problem-title {
  font-size: 27px;
  font-weight: 800;
  letter-spacing: -.7px;
  line-height: 1.28;
  margin-bottom: 16px;
}

/* 题面分节：跟后台板块标题同一套语言 */
.section { margin-bottom: var(--s6); }
.section h4 {
  font-size: 13.5px;
  font-weight: 800;
  color: var(--text);
  letter-spacing: .2px;
  text-transform: none;
  margin-bottom: 10px;
  display: flex;
  align-items: center;
  gap: 8px;
}
.section h4::before {
  content: '';
  width: 3px;
  height: 13px;
  border-radius: 999px;
  background: var(--brand);
  flex: none;
}
.section-body { font-size: 15px; line-height: 1.82; }

/* 样例：代码块要像代码块，别像普通段落 */
.sample {
  background: var(--track);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: 14px 16px;
  margin-bottom: var(--s3);
}
.sample-label {
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: .9px;
  text-transform: uppercase;
  color: var(--text-sub);
  margin-bottom: 6px;
}
.sample pre {
  font-size: 13px;
  line-height: 1.7;
  background: var(--card);
  border-radius: var(--r-xs);
  padding: 10px 12px;
  border: 1px solid var(--border);
}

.hint-box {
  border-radius: var(--r-md);
  padding: 14px 18px;
  font-size: 14px;
  line-height: 1.75;
  border: 1px solid color-mix(in srgb, var(--blue) 24%, transparent);
}

/* 标签统一成「有描边」的质感，跟表格里的 org-tag 一致 */
.tag {
  border-radius: var(--r-pill);
  padding: 2px 10px;
  border: 1px solid var(--border);
  font-weight: 600;
}
.badge { border-radius: var(--r-pill); padding: 3px 11px; }

/* 收藏按钮：它原来是个裸文字，看不出能点 */
.fav-btn {
  border: 1.5px solid var(--border);
  background: var(--card);
  border-radius: var(--r-pill);
  padding: 6px 14px;
  font-size: 13px;
  font-weight: 700;
  color: var(--text-sub);
  cursor: pointer;
  transition: all var(--dur-2) var(--ease-out);
}
.fav-btn:hover { border-color: var(--amber); color: var(--amber); transform: translateY(-1px); }
.fav-btn.is-on {
  background: var(--amber-soft);
  border-color: color-mix(in srgb, var(--amber) 40%, transparent);
  color: var(--amber);
}

/* 结果条：圆角跟着卡片家族走 */
.result-head { border-radius: var(--r-lg) var(--r-lg) 0 0; font-size: 15.5px; }
.result-panel { margin-top: var(--s4); }

@media (max-width: 980px) {
  .card-pad { padding: 22px 20px; }
  .problem-title { font-size: 22px; }
  .section-body { font-size: 14.5px; }
}

/* ============================================================
   验收修四刀
   ============================================================ */

/* ① 弹窗里的输入框：旧 style.css 写死 3px 光晕，跟外面 4px 不一致。
   同一个控件在页面上和弹窗里长得不一样，是最容易被看出来「没做干净」的地方 */
.org-modal-body input:focus,
.org-modal-body select:focus,
.org-modal-body textarea:focus {
  outline: none;
  border-color: var(--brand);
  box-shadow: 0 0 0 4px var(--brand-soft);
}

/* ② 没图时的 banner：阳光橙那套的 #FF9A33 上压白字只有 2.6:1，
   达不到 4.5:1。做法是在渐变上再叠一层左深右浅的暗罩——
   跟有图时同一个思路，只是暗罩浅一些，免得橙色被压成土棕 */
.banner:not(.has-img) {
  background:
    linear-gradient(100deg,
      rgba(var(--hero-shade), .72) 0%,
      rgba(var(--hero-shade), .67) 28%,
      rgba(var(--hero-shade), .64) 50%,
      rgba(var(--hero-shade), .44) 70%,
      rgba(var(--hero-shade), .22) 88%,
      rgba(var(--hero-shade), .16) 100%),
    linear-gradient(125deg, var(--hero-a), var(--hero-b) 58%, var(--hero-c));
}
/* 手机竖屏 banner 只有 186px 高，标题 + 副标题 + 统计卡挤在一起谁也不清楚，
   副标题是氛围文案，小屏直接让位（真要留就把统计卡挪到 banner 外面） */
@media (max-width: 480px) {
  .banner-body p { display: none; }
}
/* 两颗装饰光斑在窄屏会几乎盖满整块 banner，把文字区底色抬亮 4 个点，只能关掉 */
@media (max-width: 640px) {
  .banner:not(.has-img)::before,
  .banner:not(.has-img)::after { display: none; }
}
/* 窄屏 banner 变窄，文字占比变大，暗罩要更浓才兜得住 */
@media (max-width: 900px) {
  .banner:not(.has-img) {
    background:
      linear-gradient(100deg,
        rgba(var(--hero-shade), .76) 0%,
        rgba(var(--hero-shade), .72) 58%,
        rgba(var(--hero-shade), .48) 82%,
        rgba(var(--hero-shade), .24) 100%),
      linear-gradient(125deg, var(--hero-a), var(--hero-b) 58%, var(--hero-c));
  }
}
.banner-body h1,
.banner-body p,
.banner-body .banner-eyebrow { text-shadow: 0 1px 3px rgba(0, 0, 0, .16); }

/* ③ 有图时的遮罩改成五段：原来两段过渡太短，米白安全区和插画之间
   会出现一条竖直硬边，像贴了两张图 */
/* 宽屏：右侧要留白露插画，遮罩一路放到 .02 */
@media (min-width: 641px) {
  .banner.has-img::after {
    background: linear-gradient(96deg,
      rgba(14, 11, 8, .70) 0%,
      rgba(14, 11, 8, .56) 26%,
      rgba(14, 11, 8, .34) 46%,
      rgba(14, 11, 8, .14) 64%,
      rgba(14, 11, 8, .02) 84%);
  }
}
/* 窄屏：三张统计卡横排刚好占满容器，第三张必然压到插画最亮的橙区，
   宽屏那套「右侧放到 .02」兜不住，整条加深（两条必须挨着放，否则源序会互相覆盖） */
@media (max-width: 640px) {
  .banner.has-img::after {
    background: linear-gradient(96deg,
      rgba(14, 11, 8, .80) 0%,
      rgba(14, 11, 8, .74) 34%,
      rgba(14, 11, 8, .64) 62%,
      rgba(14, 11, 8, .52) 100%);
  }
}

/* ④ 后台 banner：150px 高放进 2.33:1 的原图，纵向要裁掉三分之二，
   裁在中间会把人物拦腰截断。抬到 176px，焦点压在画面偏上一点 */
.adm-content .banner { min-height: 176px; }
.adm-content .banner .banner-img { object-position: center 42%; }

/* ============================================================
   窄屏导航抽屉
   原来 ≤900px 直接把 .nav-links 藏了，结果成长中心和教学中心
   整个消失，只能手敲 URL——手机上这是走不通的
   ============================================================ */

.nav-burger {
  display: none;
  flex-direction: column;
  justify-content: center;
  gap: 4px;
  width: 38px; height: 38px;
  padding: 0 9px;
  border: 1.5px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--card);
  cursor: pointer;
  flex: none;
  transition: border-color var(--dur-1), box-shadow var(--dur-2) var(--ease-out);
}
.nav-burger span {
  display: block; height: 2px; border-radius: 2px;
  background: var(--text);
  transition: transform var(--dur-2) var(--ease-out), opacity var(--dur-1);
}
.nav-burger[aria-expanded="true"] { border-color: var(--brand); box-shadow: 0 0 0 4px var(--brand-soft); }
.nav-burger[aria-expanded="true"] span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
.nav-burger[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.nav-burger[aria-expanded="true"] span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

.nav-drawer { display: none; }

@media (max-width: 900px) {
  .nav-burger { display: flex; }
  .nav-drawer {
    display: block;
    max-height: 0;
    overflow: hidden;
    border-top: 1px solid transparent;
    transition: max-height var(--dur-3) var(--ease-out),
                border-color var(--dur-2);
  }
  .nav-drawer.is-open {
    max-height: 340px;
    border-top-color: var(--border);
  }
  .nav-drawer-links { display: flex; flex-direction: column; padding: var(--s3) var(--s4) var(--s2); gap: 2px; }
  .nav-drawer-links a {
    padding: 11px 14px;
    border-radius: var(--r-sm);
    font-size: 15px;
    font-weight: 600;
    color: var(--text-sub);
    transition: background var(--dur-1), color var(--dur-1);
  }
  .nav-drawer-links a:hover { background: var(--brand-soft); color: var(--brand); }
  .nav-drawer-links a.active { background: var(--brand-soft); color: var(--brand); }
  .nav-drawer-links a.nav-admin {
    background: var(--brand); color: #fff; text-align: center; margin-top: 4px;
  }
  .nav-drawer-foot { padding: 0 var(--s4) var(--s4); }
  .nav-drawer-btn {
    width: 100%;
    padding: 11px;
    border: 1.5px solid var(--border);
    border-radius: var(--r-sm);
    background: var(--card);
    color: var(--text-sub);
    font: inherit; font-size: 14px; font-weight: 700;
    cursor: pointer;
  }
  .nav-drawer-btn.is-primary {
    display: block; text-align: center;
    background: var(--brand); color: #fff; border-color: transparent;
    box-shadow: 0 3px 0 var(--brand-dark);
  }
}

/* 中屏导航收紧。1024 这个宽度下六项导航 + 右侧那一排正好放不下，
   原来会折成「训练/中心」「我的成长/中心」两行，六项全变 59px 高，
   看着像布局崩了。收一收 padding 和字距就够，不用动结构 */
.nav-links a { white-space: nowrap; }
/* 顶栏按钮一律不换行。1024 那档导航挤到极限时，「退出」两个字会被压成
   上下两行，按钮跟着变高，整条顶栏往下坠一截 */
.nav-right .btn { white-space: nowrap; flex: none; }

@media (max-width: 1180px) {
  .nav-inner { gap: var(--s4); }
  .nav-links { gap: 2px; }
  .nav-links a { padding: 7px 9px; font-size: 13.5px; }
}

/* 窄屏导航收紧：右上角只留头像和配色切换，退出挪进抽屉 */
@media (max-width: 900px) {
  .nav-inner { gap: var(--s3); padding: 0 var(--s4); }
  .nav-right { margin-left: auto; gap: 8px; }
  .nav-right #logoutBtn { display: none; }
}
@media (max-width: 480px) {
  .logo { font-size: 16px; gap: 8px; }
  .logo small { display: none; }
  .theme-pick { padding: 3px 4px; gap: 4px; }
  .theme-dot { width: 17px; height: 17px; }
}

/* ---------------------------------------------------------------- 主题商店 */
/* 十张卡片摆成网格。每张卡片上方是一块「迷你界面」预览——
   只给色块不给界面，等于让人看色号买衣服；给了迷你界面，
   才知道这套配色落到按钮、卡片、标签上长什么样。

   预览块里的元素用 --c1/--c2 两个变量画，跟当前站点主题无关：
   它展示的是**那套**配色，不是你现在用的这套。 */

.theme-wallet {
  display: flex; align-items: center; gap: var(--s4);
  padding: var(--s4) var(--s5);
  margin-bottom: var(--s5);
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: var(--sh-1);
  flex-wrap: wrap;
}
.theme-wallet-coin {
  display: flex; align-items: baseline; gap: 6px;
  font-size: 26px; font-weight: 800; letter-spacing: -.02em;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.theme-wallet-coin small { font-size: 12.5px; font-weight: 600; color: var(--text-sub); }
.theme-wallet-hint { font-size: 12.5px; color: var(--text-sub); line-height: 1.6; }
.theme-wallet-hint b { color: var(--brand); font-weight: 700; }

.theme-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
  gap: var(--s4);
}

.th-card {
  display: flex; flex-direction: column;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  overflow: hidden;
  box-shadow: var(--sh-1);
  transition: box-shadow var(--dur-2) var(--ease-out), transform var(--dur-2) var(--ease-out),
              border-color var(--dur-2) var(--ease-out);
}
.th-card:hover { box-shadow: var(--sh-2); transform: translateY(-2px); }
/* 用着的那套给一圈实色描边，一眼看得出「我现在是这套」 */
.th-card.is-current { border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-soft), var(--sh-2); }

/* 预览区：一块渐变 + 三条白条 + 一个白点，模拟「banner + 按钮 + 卡片」 */
.th-preview {
  position: relative;
  height: 96px;
  background: linear-gradient(135deg, var(--c1), var(--c2));
  padding: 14px 16px;
  display: flex; flex-direction: column; justify-content: center; gap: 7px;
}
.th-preview::after {
  /* 一颗装饰圆，别让预览太规整 */
  content: ""; position: absolute; right: -18px; top: -18px;
  width: 74px; height: 74px; border-radius: 50%;
  background: rgba(255, 255, 255, .16);
}
.th-line { height: 7px; border-radius: 4px; background: rgba(255, 255, 255, .92); width: 62%; }
.th-line.th-2 { width: 40%; background: rgba(255, 255, 255, .66); height: 5px; }
.th-pillrow { display: flex; gap: 6px; margin-top: 2px; }
.th-pill {
  height: 15px; min-width: 34px; border-radius: 999px;
  background: rgba(255, 255, 255, .9);
  display: flex; align-items: center; justify-content: center;
  font-size: 8.5px; font-weight: 800; color: var(--c1); letter-spacing: .04em;
}
.th-pill.ghost { background: rgba(255, 255, 255, .22); color: #fff; }

.th-body { padding: var(--s3) var(--s4) var(--s4); display: flex; flex-direction: column; gap: 6px; flex: 1; }
.th-head { display: flex; align-items: center; gap: 6px; }
.th-name { font-size: 15px; font-weight: 700; color: var(--text); }
.th-tag {
  font-size: 10.5px; font-weight: 700; padding: 1px 6px; border-radius: 5px;
  background: var(--brand-soft); color: var(--brand-dark);
}
.th-tag.on { background: var(--green-soft); color: var(--green); }
.th-desc { font-size: 12.5px; color: var(--text-sub); line-height: 1.55; flex: 1; }

.th-foot { display: flex; align-items: center; justify-content: space-between; gap: var(--s2); margin-top: 2px; flex-wrap: wrap; }
.th-price {
  font-size: 13px; font-weight: 700; color: var(--text);
  display: flex; align-items: center; gap: 4px;
  font-variant-numeric: tabular-nums;
  /* 卡片窄到 220 左右时，「120 铜币 · 还差 120」会被逐字挤成三行。
     宁可整句换到下一行，也不要一个字一行 */
  white-space: nowrap;
}
/* 卡片里的按钮同理：折成「铜币不 / 够」比放不下更难看 */
.th-card .btn { white-space: nowrap; }
.th-price.free { color: var(--green); }
.th-price.lack { color: var(--text-sub); }
.th-price i { font-style: normal; font-size: 11px; font-weight: 600; color: var(--text-sub); }

/* 代码皮肤卡片：预览不是色块，是一小段真代码。
   买皮肤的人关心的是「关键字什么颜色、注释什么颜色」，摆两个方块说明不了这件事 */
.cs-preview {
  /* 高度按四行算：11.5 * 1.65 * 4 ≈ 76，加 12 上下内边距刚好 100。
     原来给 96 会切掉最后半行，看着像预览没画完 */
  height: 100px;
  padding: 12px 14px;
  overflow: hidden;
  font-family: var(--mono, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
  font-size: 11.5px;
  line-height: 1.65;
  white-space: pre;
  /* 深浅皮肤都靠内联的 bg/color 上色，这里只兜一层圆角 */
  border-bottom: 1px solid var(--border);
}
/* 代码皮肤卡片里「已解锁 / 使用中」的强调跟主题卡片一致，不另设一套 */

/* 窄屏：两列太挤，一列太长，用 minmax 自适应就够；
   只在 420 以下强制单列，让卡片本身撑满 */
@media (max-width: 420px) {
  .theme-grid { grid-template-columns: 1fr; }
  .th-preview { height: 84px; }
}

/* 导航里的「+N」——拥有的主题超过五个时出现 */
.theme-more {
  display: inline-flex; align-items: center; justify-content: center;
  height: 22px; min-width: 26px; padding: 0 6px;
  border-radius: 999px;
  font-size: 11px; font-weight: 800;
  color: var(--text-sub);
  background: var(--track);
  text-decoration: none;
  transition: background-color var(--dur-1), color var(--dur-1);
}
.theme-more:hover { background: var(--brand-soft); color: var(--brand-dark); }

/* ---------------------------------------------------------------- 作业管理 */
/* 页签用「下划线」而不是胶囊：这一页的三个页签是**页面分区**
   （已发布 / 作业库 / 新建），不是筛选条件。胶囊那种样式在
   「全部/已批改/未批改」这类筛选用才合适，两处长得一样会让人分不清
   哪个是换页、哪个是过滤。 */
.adm-tabs {
  display: flex; gap: var(--s5);
  border-bottom: 1px solid var(--border);
  margin-bottom: var(--s5);
  overflow-x: auto;
  scrollbar-width: none;
}
.adm-tabs::-webkit-scrollbar { display: none; }
.adm-tab {
  position: relative;
  padding: 11px 2px 12px;
  border: 0; background: none;
  font-size: 14.5px; font-weight: 600;
  color: var(--text-sub);
  cursor: pointer;
  white-space: nowrap;
  transition: color var(--dur-1);
}
.adm-tab:hover { color: var(--text); }
.adm-tab.active { color: var(--brand); font-weight: 700; }
.adm-tab.active::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -1px;
  height: 2px; border-radius: 2px 2px 0 0;
  background: var(--brand);
}

/* 两张表的列宽。跟 style.css 里 .org-row.homework 分开写——
   那套是「作业=一条记录」的老结构，现在拆成发布表和作业库表两套列 */
/* 操作列 216 → 274：加了「讲解」之后五个按钮挤不下，nowrap 会顶出去 */
/* 操作列 274 → 330：多了「分析」按钮，五个按钮加「撤回」一排要够宽 */
.org-row.hw-pub { grid-template-columns: 1.4fr 120px 62px 100px 92px 78px 330px; }
/* 范围列 78 → 128：加了「审核中 / 被驳回」小标之后挤不下了。
   操作列 210 → 292：作业库里多了「提交机构」这个按钮，四个按钮一排得够宽 */
.org-row.hw-lib { grid-template-columns: 1.7fr 128px 66px 118px 92px 292px; }

/* 新建作业：左边表单 + 右边说明卡。窄屏右边掉到下面去 */
.hw-form {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 268px;
  gap: var(--s5);
  align-items: start;
}
.hw-form-main {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: var(--s5);
  box-shadow: var(--sh-1);
}
.hw-label {
  display: block;
  font-size: 12.5px; font-weight: 700;
  color: var(--text-sub);
  margin: 0 0 6px;
  letter-spacing: .02em;
}
.hw-label:not(:first-child) { margin-top: var(--s5); }
.hw-inp {
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  font-size: 14px;
  font-family: inherit;
  color: var(--text);
  background: var(--card);
  transition: border-color var(--dur-1), box-shadow var(--dur-1);
}
.hw-inp:focus {
  outline: none;
  border-color: var(--brand);
  box-shadow: 0 0 0 4px var(--brand-soft);
}
textarea.hw-inp { resize: vertical; line-height: 1.6; }

/* 私人 / 机构：两张可点的卡片，不是下拉框。
   这两个概念老师第一次见，需要一句解释才分得清，下拉框塞不下解释 */
/* 范围不再是可选项了——建出来一律私人，想进机构库走审核。
   留一行灰底说明，比一个点了会让作业「偷偷变成公共资产」的下拉安全 */
.hw-scope-note {
  font-size: 12.5px; line-height: 1.7; color: var(--text-sub);
  background: var(--track); border-radius: 10px; padding: 10px 12px;
}
.hw-scope-note b { color: var(--text); }

.hw-pick-sel { width: 118px; flex: none; }

/* 选题：左边列表，右边题面。点题名看题，勾选单独点框——
   原来标题是 label，点一下直接勾选，想看题面只能退出去翻题库 */
.hw-pick-body {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.12fr);
  gap: var(--s3);
  align-items: start;
}
.hw-pick-body .hw-pick-list { max-height: 460px; }
/* 正在看的那道题：左边一道竖条，跟右边的题面呼应，眼睛不用来回找 */
.hw-pick-item.is-view { background: var(--brand-soft); box-shadow: inset 3px 0 0 var(--brand); }
.hw-pick-preview {
  max-height: 460px; overflow-y: auto;
  border: 1px solid var(--border); border-radius: 10px;
  background: var(--card); padding: var(--s3) var(--s4);
}
.pv-empty { color: var(--text-sub); font-size: 13px; text-align: center; padding: 44px 0; }
.pv-head { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-bottom: 7px; }
.pv-code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px; color: var(--text-sub); margin-left: auto;
}
.pv-title { font-size: 15px; font-weight: 700; margin: 0 0 5px; line-height: 1.55; }
.pv-limits { font-size: 12px; color: var(--text-sub); margin-bottom: 11px; }

/* 每个字段一块：标签是品牌色小胶囊，正文浅色底 + 左边一道色条。
   跟题目详情页一个读法，老师不用在两套版式之间切换 */
.pv-sec { margin-bottom: 11px; }
.pv-sec > b {
  display: inline-block; font-size: 12px; font-weight: 700; color: var(--brand);
  background: var(--brand-soft); border-radius: 6px; padding: 2px 8px; margin-bottom: 6px;
}
.pv-sec > p {
  margin: 0; font-size: 13px; line-height: 1.85; color: var(--text);
  background: var(--brand-soft); border-left: 3px solid var(--brand);
  border-radius: 0 8px 8px 0; padding: 9px 12px; white-space: pre-wrap;
  word-break: break-word;
}
.pv-opts { display: flex; flex-direction: column; gap: 5px; }
.pv-opt {
  display: flex; gap: 9px; font-size: 13px; line-height: 1.7;
  padding: 6px 10px; background: var(--bg); border-radius: 8px;
}
.pv-opt i { font-style: normal; font-weight: 700; color: var(--brand); flex: none; }
.pv-sample { background: var(--bg); border-radius: 8px; padding: 8px 10px; margin-bottom: 6px; }
.pv-sample-h { font-size: 12px; color: var(--text-sub); font-weight: 600; margin-bottom: 5px; }
.pv-io { display: flex; gap: 8px; align-items: flex-start; margin-top: 4px; }
.pv-io span { font-size: 12px; color: var(--text-sub); flex: none; width: 28px; padding-top: 5px; }
.pv-io pre {
  margin: 0; flex: 1; min-width: 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px; line-height: 1.65;
  background: var(--card); border: 1px solid var(--border); border-radius: 6px;
  padding: 6px 8px; overflow-x: auto; white-space: pre-wrap; word-break: break-all;
}
/* 「加进这份作业」钉在预览区底部。
   题面一长（编程题带两三个样例很常见），这排按钮就被顶到滚动区外面，
   老师看完题得先滚到底才点得到——而这时候他刚决定要不要这道题，
   按钮正好该在手边。所以 sticky 贴底，横向撑满抵消容器内边距 */
.pv-foot {
  position: sticky; bottom: calc(-1 * var(--s3)); z-index: 2;
  display: flex; align-items: center; gap: 10px;
  margin: var(--s3) calc(-1 * var(--s4)) calc(-1 * var(--s3));
  padding: 10px var(--s4);
  background: var(--card);
  border-top: 1px dashed var(--border);
}
.pv-hint { font-size: 12px; color: var(--text-sub); }

/* 窄屏（平板、分屏）两栏挤不下，题面挪到列表下面 */
@media (max-width: 1100px) {
  .hw-pick-body { grid-template-columns: 1fr; }
  .hw-pick-body .hw-pick-list, .hw-pick-preview { max-height: 320px; }
}


.hw-pick-warn {
  font-size: 12.5px; line-height: 1.7; color: #B54708;
  background: var(--amber-soft); border: 1px solid rgba(247, 144, 9, .3);
  border-radius: 10px; padding: 9px 12px; margin: 8px 0 2px;
}
.hw-pick-warn b { color: #93370D; }

.hw-scope { display: grid; gap: var(--s2); }
.hw-scope-item {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 11px 13px;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  cursor: pointer;
  transition: border-color var(--dur-1), background-color var(--dur-1);
}
.hw-scope-item:hover { border-color: var(--brand-2); }
.hw-scope-item:has(input:checked) {
  border-color: var(--brand);
  background: var(--brand-soft);
}
.hw-scope-item input { margin-top: 2px; width: 15px; height: 15px; accent-color: var(--brand); cursor: pointer; }
.hw-scope-item span { display: flex; flex-direction: column; gap: 2px; }
.hw-scope-item b { font-size: 13.5px; font-weight: 700; color: var(--text); }
.hw-scope-item small { font-size: 12px; color: var(--text-sub); line-height: 1.5; }

.hw-form-side { display: flex; flex-direction: column; gap: var(--s3); position: sticky; top: 76px; }

/* 1280 / 1366 / 1440 都是常见笔记本宽度。这几个宽度下，右边那张 268px 的
   说明卡占着位置，主表单只剩 692~852px，再切成「列表 + 题面」两栏，
   题名只剩 22~116px——被压成「打…」，老师认不出是哪道题。
   1440 尤其别扭：比 1280 还窄，因为它刚好过了断点又还没到 1920。
   所以整段提前到 1500：说明卡挪到表单下面，主表单吃满整宽。
   1920 以上两栏各自还有 600px，不用动。

   注意这条必须写在 .hw-form-side 基础规则之后——同优先级下后出现者胜，
   写在前面会被 position:sticky 覆盖掉（sticky 在这里虽不产生位移，
   但「写了没生效」本身就是隐患） */
@media (max-width: 1500px) {
  .hw-form { grid-template-columns: 1fr; }
  .hw-form-side { position: static; }
}
.hw-side-card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: var(--s4);
  box-shadow: var(--sh-1);
}
.hw-side-title { font-size: 13px; font-weight: 700; color: var(--text); margin-bottom: var(--s2); }
.hw-side-list { margin: 0; padding-left: 18px; font-size: 12.5px; color: var(--text-sub); line-height: 1.75; }
.hw-side-list b { color: var(--text); }
.hw-side-actions { display: flex; flex-direction: column; gap: var(--s2); }
.hw-side-actions .btn { width: 100%; }

/* 发布弹窗里的班级多选 */
.hw-class-list {
  max-height: 210px; overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  margin-bottom: var(--s3);
}
.hw-class-item {
  display: flex; align-items: center; gap: 9px;
  padding: 9px 12px;
  border-bottom: 1px solid var(--border);
  cursor: pointer;
  font-size: 13.5px;
}
.hw-class-item:last-child { border-bottom: none; }
.hw-class-item:hover { background: var(--bg); }
.hw-class-item input { width: 15px; height: 15px; accent-color: var(--brand); cursor: pointer; }
.hw-class-name { flex: 1; font-weight: 600; color: var(--text); }
/* 已经发过的班：勾着但禁用，灰掉。禁用的勾选框光改颜色不够，
   得把整行压暗，不然人会一直点 */
.hw-pub-count { margin-top:10px; font-size:12.5px; color:var(--brand); font-weight:600; min-height:18px; }
.hw-pub-count:empty { display:none; }
.hw-class-item.is-sent { opacity: .55; cursor: not-allowed; background: var(--bg); }
.hw-class-item.is-sent input { cursor: not-allowed; }

@media (max-width: 900px) {
  .hw-form { grid-template-columns: 1fr; }
  .hw-form-side { position: static; }
  /* 窄屏只留四列：作业 / 班级(范围) / 完成(已发) / 操作。
     题目数、截止时间、状态这些还能从别处看到，操作列没了就没法用了 */
  .org-row.hw-pub { grid-template-columns: minmax(0, 1fr) 84px 84px 210px; }
  .org-row.hw-lib { grid-template-columns: minmax(0, 1fr) 62px 78px 118px; }
  .org-row.hw-pub > :nth-child(3),
  .org-row.hw-pub > :nth-child(5),
  .org-row.hw-pub > :nth-child(6),
  .org-row.hw-lib > :nth-child(3),
  .org-row.hw-lib > :nth-child(5) { display: none; }
  /* 窄屏操作列放不下五个按钮就换行，别让它们顶出表格 */
  .org-row.hw-pub .org-ops { flex-wrap: wrap; justify-content: flex-end; }
}
/* banner 弹窗里的「背景类型」区块。下拉先禁用——选项摆在那儿是告诉人
   「这条路已经留好了」，能点却点了没反应才是最糟的体验 */
.bn-bgkind select:disabled {
  background: var(--bg);
  color: var(--text-sub);
  cursor: not-allowed;
}
.bn-bgkind .org-modal-tip { margin-top: 6px; }
.bn-bgkind code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11.5px;
  padding: 1px 4px;
  border-radius: 4px;
  background: var(--track);
  color: var(--text);
}

/* ---------------- 中文日期时间选择器 ----------------

   替代原生 datetime-local。原生那个日历的语言跟着浏览器走，改不了，
   英文环境里点开就是 October / Clear / Today。 */

.dtp-input { cursor: pointer; background-image: none; }

.dtp {
  position: fixed; z-index: 1200; width: 274px;
  background: var(--card); border: 1px solid var(--border);
  border-radius: 14px; padding: 12px 12px 10px;
  box-shadow: 0 18px 48px -16px rgba(16, 24, 40, .34);
  font-size: 13px; color: var(--text);
}
.dtp-head { display: flex; align-items: center; gap: 2px; margin-bottom: 8px; }
.dtp-title { flex: 1; text-align: center; font-weight: 700; font-size: 13.5px; letter-spacing: -.2px; }
.dtp-nav {
  border: 0; background: transparent; color: var(--text-sub); cursor: pointer;
  width: 24px; height: 24px; border-radius: 7px; font-size: 13px; line-height: 1;
  display: flex; align-items: center; justify-content: center;
}
.dtp-nav:hover { background: var(--track); color: var(--text); }

.dtp-week { display: grid; grid-template-columns: repeat(7, 1fr); margin-bottom: 4px; }
.dtp-week span { text-align: center; font-size: 11.5px; color: var(--text-sub); padding: 3px 0; }

.dtp-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.dtp-day {
  border: 0; background: transparent; cursor: pointer; color: var(--text);
  height: 30px; border-radius: 8px; font-size: 12.5px; font-variant-numeric: tabular-nums;
}
.dtp-day:hover { background: var(--track); }
.dtp-day.is-out { color: var(--text-sub); opacity: .45; }
.dtp-day.is-today { color: var(--brand); font-weight: 700; }
.dtp-day.is-cur { background: var(--brand); color: #fff; font-weight: 700; }
.dtp-day.is-cur:hover { background: var(--brand); }

.dtp-time {
  display: flex; align-items: center; gap: 6px; margin: 10px 0 8px;
  padding-top: 9px; border-top: 1px solid var(--border);
}
.dtp-time > span { font-size: 12px; color: var(--text-sub); }
.dtp-time select {
  flex: 1; min-width: 0; height: 30px; border-radius: 8px;
  border: 1px solid var(--border); background: var(--card); color: var(--text);
  font-size: 12.5px; padding: 0 6px; cursor: pointer;
}
.dtp-time b { color: var(--text-sub); font-weight: 600; }

.dtp-foot { display: flex; gap: 6px; }
.dtp-btn {
  flex: 1; height: 30px; border-radius: 9px; cursor: pointer;
  border: 1px solid var(--border); background: var(--card); color: var(--text-sub);
  font-size: 12.5px;
}
.dtp-btn:hover { color: var(--text); background: var(--track); }
.dtp-btn.is-primary {
  background: var(--brand); border-color: var(--brand); color: #fff; font-weight: 600;
}
.dtp-btn.is-primary:hover { background: var(--brand); color: #fff; opacity: .92; }

/* ---------------------------------------------------------------- 题单广场
   题单是「摆在那儿自己拿」的东西，不像作业有人催。所以这一页按逛的节奏
   排：大卡片、一眼看到题数和做过几道，点进去才是一道道题。 */

.sh-head {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: var(--s4); margin: 4px 0 var(--s5); flex-wrap: wrap;
}
.sh-head h1 { font-size: var(--fs-h1); font-weight: 800; letter-spacing: -.6px; margin: 0 0 6px; }
.sh-head p { font-size: 13.5px; color: var(--text-sub); margin: 0; line-height: 1.6; }

.sh-grid {
  display: grid; gap: var(--s4);
  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
}
.sh-card {
  /* 卡片得撑满格子高度，进度条才好贴底对齐。
     同一行里一张有描述、一张没有，用 block 排的话两张卡的进度条会一高一低 */
  position: relative;
  display: flex; flex-direction: column; height: 100%;
  text-decoration: none; color: inherit;
  background: var(--card); border: 1px solid var(--border); border-radius: var(--r-md);
  padding: var(--s4) var(--s5) var(--s4) calc(var(--s5) + 6px);
  box-shadow: var(--sh-1); overflow: hidden;
  transition: border-color var(--dur-1), transform var(--dur-1), box-shadow var(--dur-1);
}
/* 左边一条竖色条。这是给「逛」用的：一整屏卡片扫过去，绿色的是已经做完的，
   蓝色的是做了一半的，不用一张张点进去看数字。没做过的就不上色，
   免得一屏全是彩条反而没重点 */
.sh-card::before {
  content: '';
  position: absolute; left: 0; top: 0; bottom: 0; width: 4px;
  background: transparent; transition: background var(--dur-1);
}
.sh-card.is-doing::before { background: var(--brand); }
.sh-card.is-done::before { background: var(--green); }
.sh-card:hover { border-color: var(--brand); transform: translateY(-2px); box-shadow: var(--sh-2); }
/* 进度条和百分比并排：条子说明大概，百分比报确切数，两个一起才不用猜 */
.sh-card-foot { display: flex; align-items: center; gap: 10px; margin-top: auto; }
.sh-card-foot .hw-bar { flex: 1; }
.sh-pct {
  flex: none; font-size: 12px; font-weight: 800; color: var(--text-sub);
  font-variant-numeric: tabular-nums;
}
.sh-card.is-done .sh-pct { color: var(--green); }
.sh-card-top { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
.sh-card-top b {
  font-size: 15px; font-weight: 700; flex: 1; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sh-card-desc {
  font-size: 12.5px; color: var(--text-sub); line-height: 1.65; margin: 0 0 10px;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.sh-card-meta {
  display: flex; gap: 12px; font-size: 12px; color: var(--text-sub);
  margin-bottom: 9px; flex-wrap: wrap;
}

/* 一套题单的概览。原来只有一根细进度条，学生看不出「还剩几道」「有几道卡住了」，
   这四个数把「做到哪了」拆开说清楚：通过的、做过没对的、还没碰的 */
.sh-summary {
  display: flex; align-items: center; gap: var(--s6); flex-wrap: wrap;
  background: var(--card); border: 1px solid var(--border); border-radius: var(--r-md);
  padding: var(--s4) var(--s5); margin-bottom: var(--s4); box-shadow: var(--sh-1);
}
.sh-sum-item { display: flex; flex-direction: column; gap: 2px; flex: none; }
.sh-sum-item b {
  font-size: 22px; font-weight: 800; letter-spacing: -.5px; line-height: 1.15;
  font-variant-numeric: tabular-nums;
}
.sh-sum-item b.ok { color: var(--green); }
.sh-sum-item b.warn { color: var(--amber); }
.sh-sum-item span { font-size: 12px; color: var(--text-sub); font-weight: 600; }
.sh-sum-bar { flex: 1; min-width: 120px; }

@media (max-width: 640px) {
  .sh-summary { gap: var(--s4); }
  .sh-sum-item b { font-size: 19px; }
  .sh-sum-bar { flex-basis: 100%; }
}

.sh-list { background: var(--card); border: 1px solid var(--border); border-radius: var(--r-md); overflow: hidden; }
.sh-row {
  display: grid; grid-template-columns: 38px minmax(0, 1fr) 88px 96px;
  align-items: center; gap: var(--s3);
  padding: 12px var(--s5); border-bottom: 1px solid var(--border);
}
.sh-row:last-child { border-bottom: none; }
.sh-row:hover { background: var(--bg); }
.sh-row-no { font-size: 13px; color: var(--text-sub); font-weight: 600; }
.sh-row-main { min-width: 0; }
.sh-row-main b {
  display: block; font-size: 13.5px; font-weight: 600; margin-bottom: 3px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sh-row-sub { display: flex; align-items: center; gap: 7px; }
.sh-code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11.5px; color: var(--text-sub);
}
.sh-row-act { text-align: right; }

@media (max-width: 760px) {
  .sh-grid { grid-template-columns: 1fr; }
  .sh-row { grid-template-columns: 28px minmax(0, 1fr) 76px; }
  .sh-row-act { grid-column: 2 / -1; text-align: left; margin-top: 6px; }
}

/* ============================================================
   新建作业 v2：左边整份卷子，右边挑题
   ------------------------------------------------------------
   原来左栏是表单、右栏是 268px 的说明卡，题面挤在表单中间
   一块 460px 高的框里。老师加完十道题想知道「这份作业到底
   长什么样」，只能一道道点回来看，或者干脆发布出去看学生端。

   现在把关系倒过来：左栏就是这份作业本身——按题型分组、
   每道题的题干/选项/样例全摊开，跟学生拿到的那张卷子一样；
   右栏是题库，勾一道左栏长一段。加完抬头就能通读。
   ============================================================ */
.hw-doc {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 360px;
  gap: var(--s5);
  align-items: start;
}
.hw-doc-main {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: var(--s5);
  box-shadow: var(--sh-1);
  min-width: 0;
}

/* 标题行：输入框做得像标题，不像表单字段。老师在这一页只干一件事，
   顶上加个「作业标题：」的标签反而多一层噪音 */
.hw-doc-bar {
  display: flex; align-items: center; gap: var(--s3);
  margin-bottom: var(--s3);
}
.hw-doc-title {
  flex: 1; min-width: 0;
  border: none; border-bottom: 2px solid transparent;
  background: transparent;
  font-size: 19px; font-weight: 800;
  color: var(--text);
  padding: 6px 0;
  font-family: inherit;
  transition: border-color var(--dur-1);
}
.hw-doc-title::placeholder { color: var(--text-sub); font-weight: 600; }
.hw-doc-title:focus { outline: none; border-bottom-color: var(--brand); }
.hw-doc-desc {
  width: 100%; box-sizing: border-box;
  border: 1px solid var(--border); border-radius: var(--r-sm);
  padding: 9px 11px; margin-bottom: 10px;
  font-size: 13px; line-height: 1.6; font-family: inherit;
  color: var(--text); background: var(--card); resize: vertical;
  transition: border-color var(--dur-1), box-shadow var(--dur-1);
}
.hw-doc-desc:focus {
  outline: none; border-color: var(--brand);
  box-shadow: 0 0 0 4px var(--brand-soft);
}
.hw-doc-scope {
  font-size: 12.5px; line-height: 1.7; color: var(--text-sub);
  background: var(--track); border-radius: 10px;
  padding: 9px 12px; margin-bottom: var(--s4);
}
.hw-doc-scope b { color: var(--text); }

.hw-doc-body { display: flex; flex-direction: column; gap: var(--s5); }
.hw-doc-empty {
  border: 1px dashed var(--border); border-radius: 12px;
  padding: 52px 20px; text-align: center;
  color: var(--text-sub); font-size: 13px; line-height: 1.8;
}

/* 题型分组：标题 + 「3 道 · 30 分」+ 批量设分。
   分值是给老师看的账，摆在这一行比每题底下重复算一遍省眼 */
.hw-doc-sec-head {
  display: flex; align-items: center; gap: 10px;
  padding-bottom: 8px; margin-bottom: var(--s3);
  border-bottom: 1px solid var(--border);
}
.hw-doc-sec-head h3 {
  font-size: 15px; font-weight: 800; margin: 0;
  color: var(--text); letter-spacing: .01em;
}
.hw-doc-sec-meta { font-size: 12.5px; color: var(--text-sub); }
.hw-doc-sec-head .btn { margin-left: auto; flex: none; }

/* 一道题。这里刻意不折叠：老师要的就是「一眼看完」，
   折叠回列表就又变成原来那个看不清的样子了 */
.hw-doc-q {
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: var(--s4);
  margin-bottom: var(--s3);
  background: var(--card);
  transition: border-color var(--dur-1);
}
.hw-doc-q:last-child { margin-bottom: 0; }
.hw-doc-q:hover { border-color: var(--brand-line, var(--border)); }
.hw-doc-q-head {
  display: flex; align-items: center; gap: 8px;
  margin-bottom: 10px; flex-wrap: wrap;
}
.hw-doc-q-no {
  font-size: 14px; font-weight: 800; color: var(--brand);
  flex: none; min-width: 20px;
}
.hw-doc-q-title {
  font-size: 14.5px; font-weight: 700; color: var(--text);
  line-height: 1.5;
}
.hw-doc-q-code {
  font-size: 12px; color: var(--text-sub);
  font-family: var(--mono, ui-monospace, monospace);
  flex: none;
}
/* 分值框跟题号标题同一行、靠右。width 给够三位数，
   不然改成 100 的时候数字被挤成两行 */
.hw-doc-q-score {
  margin-left: auto; display: flex; align-items: center; gap: 4px; flex: none;
}
.hw-doc-q-score input {
  width: 62px; padding: 5px 8px;
  border: 1px solid var(--border); border-radius: 8px;
  font-size: 13px; font-family: inherit; font-weight: 600;
  color: var(--text); background: var(--card); text-align: right;
  transition: border-color var(--dur-1), box-shadow var(--dur-1);
}
.hw-doc-q-score input:focus {
  outline: none; border-color: var(--brand);
  box-shadow: 0 0 0 3px var(--brand-soft);
}
.hw-doc-q-score i { font-style: normal; font-size: 12.5px; color: var(--text-sub); }
.hw-doc-q-body { min-width: 0; }
.hw-doc-q-body .pv-limits { margin-bottom: 9px; }
.hw-doc-q-body .pv-sec:last-child { margin-bottom: 0; }
.hw-doc-q-loading {
  font-size: 12.5px; color: var(--text-sub);
  background: var(--track); border-radius: 8px; padding: 14px;
  text-align: center;
}

/* 右栏：题库。sticky 跟着滚，挑题的时候不用来回翻页 */
.hw-doc-side {
  display: flex; flex-direction: column; gap: var(--s3);
  position: sticky; top: 76px;
  min-width: 0;
}
.hw-pool {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  padding: var(--s4);
  box-shadow: var(--sh-1);
  display: flex; flex-direction: column; gap: 9px;
}
.hw-pool-title {
  font-size: 13.5px; font-weight: 800; color: var(--text);
}
.hw-pool-kw { font-size: 13px; padding: 8px 10px; }
.hw-pool-filters { display: flex; gap: 6px; }
.hw-pool-filters .hw-pick-sel { flex: 1; width: auto; min-width: 0; }
.hw-pool-stat {
  font-size: 12.5px; color: var(--text-sub);
  padding: 6px 0 2px;
  border-top: 1px solid var(--border);
}
.hw-pool-stat b { color: var(--brand); font-weight: 800; }
/* 列表高度按视口算：扣掉导航 76、筛选区约 150、底部留白，
   小屏笔记本上也能完整看到十来行 */
.hw-pool .hw-pick-list {
  max-height: calc(100vh - 360px);
  min-height: 240px;
  overflow-y: auto;
  padding-right: 2px;
}
.hw-side-actions { display: flex; }
.hw-side-actions .btn { flex: 1; }

/* 窄屏：右栏掉到下面。选题是操作入口，但标题得先看得见，
   所以不去调 order——先写名字，再往下挑题。
   断点定 1200 不是 1500：右栏 360px、左边侧栏 236px，1280 的笔记本
   上左栏还剩 612px，摆一份带选项和样例的卷子够用；掉成单列反而让
   老师加完一道题得往下滚才能挑下一道 */
@media (max-width: 1200px) {
  .hw-doc { grid-template-columns: 1fr; }
  .hw-doc-side { position: static; }
  .hw-pool .hw-pick-list { max-height: 380px; }
}

/* 登录页底部那句话：账号是老师发的，别让人在这儿找「注册」 */
.auth-note {
  margin: var(--s4) 0 0;
  font-size: 12.5px; line-height: 1.7;
  color: var(--text-sub); text-align: center;
}

/* 左栏整份卷子里，题面是拿来「读」的，不是拿来「被强调」的。
   浅绿底 + 绿左边框在原来那个 460px 的小预览框里是重点提示，
   摊成十道题就变成一片绿块，眼睛找不到落脚的地方。
   这里只留一条细线做结构分隔，字回到正文的样子。 */
.hw-doc-q-body .pv-sec > b {
  background: transparent;
  padding: 0;
  color: var(--text-sub);
  font-size: 12px;
  letter-spacing: .04em;
}
.hw-doc-q-body .pv-sec > p {
  background: transparent;
  border-left: 2px solid var(--border);
  border-radius: 0;
  padding: 2px 0 2px 12px;
}
/* 选项和样例本来就有底色，题面改成素底之后它们反而成了层次，
   所以选项底色再淡一档，别跟样例抢 */
.hw-doc-q-body .pv-opt { background: var(--bg); }

/* 登录页品牌字，跟上面的图标一组 */
.auth-brand {
  font-size: 21px; font-weight: 800; letter-spacing: .04em;
  color: var(--text); text-align: center;
  margin: 0 0 18px;
}

/* 弹窗高度封顶 + 内容区自己滚。
   原来 .org-modal 没有 max-height，题面一长（编程题带两三个样例很常见）
   整个弹窗就顶出视口：标题看不见、底部「加进这份作业」也看不见，
   只剩中间一段题面挂在屏幕上，想关都找不到按钮。
   封到视口减 40（上下各留 20 的 mask padding），内容区单独滚，
   头和脚固定——跟「加进这份作业」钉在底部是同一个道理。 */
.org-modal {
  max-height: calc(100vh - 40px);
  display: flex;
  flex-direction: column;
}
.org-modal > h3 { flex: none; }
.org-modal-body { overflow-y: auto; min-height: 0; }
.org-modal-foot { flex: none; }

/* 题面正文容器。原来 .pv-sec 里直接放一个 <p>，只够摆纯文本；
   现在描述走 renderMarkdown，出来的是 <p>/<pre>/<code> 混排，
   得有个容器承载背景和左边那条线。里面各元素的样式直接复用
   style.css 里 .md 那一套——学生端题面也是这么渲染的，两处一致。 */
.pv-sec > .pv-md {
  background: var(--brand-soft);
  border-left: 3px solid var(--brand);
  border-radius: 0 8px 8px 0;
  padding: 9px 12px;
  font-size: 13px; line-height: 1.85; color: var(--text);
  overflow-x: auto;
}
.pv-sec > .pv-md > p:last-child { margin-bottom: 0; }
.pv-sec > .pv-md .md-code { margin: 8px 0; font-size: 12.5px; }

/* 左栏整份卷子里换成素底：十道题的题面都顶着浅绿块，眼睛没地方落 */
.hw-doc-q-body .pv-sec > .pv-md {
  background: transparent;
  border-left: 2px solid var(--border);
  border-radius: 0;
  padding: 2px 0 2px 12px;
}

/* 品牌字不许换行。原来叫「MyCode」是一个词，断不开；改成「韦伯」两个汉字
   之后，窄屏（900 上下，侧栏收成顶部横条）空间一挤就断成「韦 / 伯」竖排，
   看着像排版坏了 */
.logo > span, .adm-brand > span { white-space: nowrap; }

/* ============================================================
   题库选择器（picker.js）
   作业编辑页和试卷组卷页共用，所以类名一律 pk- 前缀，
   不复用 hw- 那一套——免得以后改作业页样式误伤试卷页
   ============================================================ */
.pk-head {
  display: flex; align-items: center; gap: 8px;
  margin-bottom: 9px;
}
.pk-title { font-size: 13.5px; font-weight: 800; color: var(--text); }
.pk-filter-btn {
  margin-left: auto;
  display: inline-flex; align-items: center; gap: 4px;
  border: 1px solid var(--border); border-radius: 999px;
  background: var(--card); color: var(--text-sub);
  font-size: 12.5px; font-family: inherit;
  padding: 4px 11px; cursor: pointer;
  transition: all var(--dur-1);
}
.pk-filter-btn:hover { border-color: var(--brand); color: var(--brand); }
.pk-filter-btn.is-on {
  background: var(--brand-soft); border-color: var(--brand); color: var(--brand);
  font-weight: 700;
}
.pk-filter-btn i {
  font-style: normal; font-size: 11px; font-weight: 800;
  background: var(--brand); color: #fff;
  min-width: 15px; height: 15px; line-height: 15px;
  border-radius: 999px; text-align: center; padding: 0 4px;
}
.pk-search { margin-bottom: 9px; }
.pk-search .inp { width: 100%; box-sizing: border-box; font-size: 13px; padding: 8px 10px; }

/* 筛选面板：展开时压在搜索框下面，不弹窗——
   挑题是连续动作，弹窗每次都要关一遍 */
.pk-panel {
  border: 1px solid var(--border); border-radius: 10px;
  background: var(--bg);
  padding: 10px 11px;
  margin-bottom: 9px;
  display: flex; flex-direction: column; gap: 9px;
}
.pk-row { display: flex; gap: 8px; align-items: flex-start; }
.pk-row > b {
  flex: none; width: 40px; padding-top: 4px;
  font-size: 12px; font-weight: 700; color: var(--text-sub);
}
.pk-chips { display: flex; flex-wrap: wrap; gap: 5px; flex: 1; min-width: 0; }
/* 知识点可能二十几个，横向不折、纵向滚 */
.pk-chips-scroll {
  max-height: 84px; overflow-y: auto;
  padding-right: 2px;
}
.pk-chip {
  border: 1px solid var(--border); border-radius: 999px;
  background: var(--card); color: var(--text-sub);
  font-size: 12px; font-family: inherit; line-height: 1.5;
  padding: 3px 10px; cursor: pointer;
  transition: all var(--dur-1);
  white-space: nowrap;
}
.pk-chip:hover { border-color: var(--brand); color: var(--brand); }
.pk-chip.is-on {
  background: var(--brand); border-color: var(--brand); color: #fff; font-weight: 600;
}
.pk-panel-foot {
  display: flex; align-items: center; gap: 8px;
  border-top: 1px solid var(--border); padding-top: 8px;
}
.pk-panel-foot span { font-size: 11.5px; color: var(--text-sub); line-height: 1.5; }
.pk-panel-foot .btn { margin-left: auto; flex: none; }

.pk-stat {
  display: flex; align-items: center; gap: 8px;
  font-size: 12.5px; color: var(--text-sub);
  padding: 7px 0 8px;
  border-top: 1px solid var(--border);
}
.pk-stat b { color: var(--text); font-weight: 800; }
.pk-stat #pkSel { margin-left: auto; }
.pk-stat #pkSel b { color: var(--brand); }

.pk-list {
  max-height: calc(100vh - 400px);
  min-height: 220px;
  overflow-y: auto;
  padding-right: 2px;
  display: flex; flex-direction: column; gap: 5px;
}
.pk-item {
  display: flex; align-items: center; gap: 8px;
  border: 1px solid var(--border); border-radius: 9px;
  padding: 7px 9px;
  background: var(--card);
  transition: border-color var(--dur-1), background var(--dur-1);
}
.pk-item:hover { border-color: var(--brand); }
.pk-item.is-on { background: var(--brand-soft); border-color: var(--brand); }
.pk-item input[type="checkbox"] {
  flex: none; width: 15px; height: 15px; cursor: pointer; accent-color: var(--brand);
}
.pk-item-main {
  flex: 1; min-width: 0; cursor: pointer;
  display: flex; flex-direction: column; gap: 1px;
}
.pk-item-code {
  font-size: 11px; color: var(--text-sub);
  font-family: var(--mono, ui-monospace, monospace);
}
.pk-item-title {
  font-size: 13px; color: var(--text); font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pk-item-know {
  font-size: 11px; color: var(--text-sub);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pk-item .org-tag { flex: none; }

@media (max-width: 1200px) {
  .pk-list { max-height: 380px; }
}

/* ================================================================
   试卷中心 / 赛事管理 / 商城中心（2026-11-01）

   试卷和作业是同一个引擎，所以能复用的样式一律复用：
   组卷页用 .hw-doc（左卷右库），详情页用 .sh-list / .sh-row（题单广场那套行）。
   这里只补三块新东西：试卷库列表的行、发考试的表单、商场中心的分类条。
   ================================================================ */

/* ---- 试卷库列表 ---- */

.pp-row {
  display: grid;
  grid-template-columns: minmax(0, 2.4fr) 70px 70px 78px minmax(0, 1fr) 96px 210px;
  gap: var(--s3);
  align-items: center;
}

.pp-desc {
  font-size: 12px;
  color: var(--text-sub);
  margin-top: 3px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.pp-acts {
  display: flex;
  gap: 6px;
  justify-content: flex-end;
  flex-wrap: wrap;
}

.pp-scope {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  font-size: 13px;
  color: var(--text-sub);
}

.pp-scope input { width: 15px; height: 15px; accent-color: var(--brand); }

/* 卷子的「出身」：来源 / 时间 / 科目 / 考试时长。
   这四项决定它在试卷库里能不能被筛出来，所以跟标题摆在一起，不藏进二级弹窗
   ——藏起来的结果是没人填，筛选永远只有「全部」 */
.pp-meta {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 10px; margin-bottom: var(--s4);
}
.pp-f { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.pp-f > span {
  font-size: 11.5px; font-weight: 700; letter-spacing: .2px;
  color: var(--text-sub);
}
.pp-meta .inp {
  width: 100%; box-sizing: border-box;
  border: 1px solid var(--border); border-radius: var(--r-sm);
  padding: 8px 10px; font-size: 13px; font-family: inherit;
  color: var(--text); background: var(--card);
  transition: border-color var(--dur-1), box-shadow var(--dur-1);
}
.pp-meta .inp:focus {
  outline: none; border-color: var(--brand);
  box-shadow: 0 0 0 3px var(--brand-soft);
}
@media (max-width: 720px) {
  .pp-meta { grid-template-columns: 1fr 1fr; }
}

/* ---- 试卷详情（前台）：按题型分组，每组一个标题 ---- */

.pp-sec { margin-bottom: var(--s4); }

.pp-sec-head {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 0 4px 8px;
}

.pp-sec-head h3 {
  margin: 0;
  font-size: 15px;
  font-weight: 700;
}

.pp-sec-head span {
  font-size: 12.5px;
  color: var(--text-sub);
}

/* 知识点小标签，比 org-tag 更轻——一行里可能要摆三四个 */
.pk-tag-sm {
  display: inline-block;
  padding: 1px 7px;
  border-radius: 999px;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--text-sub);
  background: var(--bg-soft, #f4f6f8);
  border: 1px solid var(--border);
}

/* ---- 赛事管理：列表 ---- */

.ex-row {
  display: grid;
  grid-template-columns: minmax(0, 1.8fr) minmax(0, 1.5fr) minmax(0, 1fr) 66px 96px 92px 80px 200px;
  gap: var(--s3);
  align-items: center;
}

.ex-paper {
  font-size: 12.5px;
  color: var(--text-sub);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ex-acts {
  display: flex;
  gap: 6px;
  justify-content: flex-end;
  flex-wrap: wrap;
}

/* ---- 赛事管理：发赛事表单 ---- */

.ex-form {
  display: grid;
  gap: var(--s4);
  max-width: 780px;
}

.ex-card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r3, 14px);
  padding: var(--s4);
  box-shadow: var(--sh-1);
}

.ex-card h3 {
  margin: 0 0 12px;
  font-size: 14px;
  font-weight: 700;
}

.ex-card .inp { width: 100%; }
.ex-card textarea.inp { margin-top: 8px; resize: vertical; }

.ex-hint {
  margin-top: 8px;
  font-size: 12.5px;
  color: var(--text-sub);
  line-height: 1.6;
}

.ex-hint b { color: var(--brand); }

.ex-classes {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.ex-class {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  border: 1px solid var(--border);
  border-radius: 999px;
  font-size: 13px;
  cursor: pointer;
  transition: .15s;
}

.ex-class:hover { border-color: var(--brand); }
.ex-class input { accent-color: var(--brand); width: 15px; height: 15px; }
.ex-class i { font-style: normal; font-size: 11.5px; color: var(--text-sub); }

/* 赛事类型：五张可点的卡。用下拉也能选，但老师发赛事时最需要的是
   一眼看清「周赛/月赛/年赛」各是什么，顺带看到那句解释 */
.ex-kinds {
  display: grid;
  /* 132 是量出来的：后台内容区在 1440 屏下约 745px 宽，
     五张卡一行放得下；150 只能塞四个，「普通考试」被挤到第二行孤零零一个 */
  grid-template-columns: repeat(auto-fit, minmax(132px, 1fr));
  gap: 10px;
  margin-top: 4px;
}

/* 「发给谁」两个选项：全机构 / 指定班级。两列就够，
   跟上面五张类型卡挤同一个 auto-fit 网格会显得像还有别的选项 */
.ex-scope {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
  margin-bottom: 12px;
}

.ex-kind {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 11px 13px;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  cursor: pointer;
  transition: .15s;
  background: var(--card);
}

.ex-kind:hover { border-color: var(--brand); }
.ex-kind input { accent-color: var(--brand); width: 14px; height: 14px; margin: 0 0 2px; }
.ex-kind span { font-size: 13.5px; font-weight: 700; color: var(--text); }
.ex-kind i {
  font-style: normal; font-size: 11.5px; line-height: 1.5;
  color: var(--text-sub);
}
.ex-kind:has(input:checked) {
  border-color: var(--brand);
  background: color-mix(in srgb, var(--brand) 6%, var(--card));
  box-shadow: 0 0 0 1px var(--brand) inset;
}

.ex-time {
  display: flex;
  gap: var(--s4);
  flex-wrap: wrap;
}

.ex-time label {
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 12.5px;
  color: var(--text-sub);
  font-weight: 600;
}

.ex-foot {
  display: flex;
  gap: 10px;
}

/* ---- 商场中心 ---- */

.mall-cats {
  display: flex;
  gap: 8px;
  margin-bottom: var(--s4);
  flex-wrap: wrap;
}

.mall-cat {
  padding: 9px 18px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--card);
  font-size: 13.5px;
  font-weight: 600;
  color: var(--text-sub);
  cursor: pointer;
  transition: .15s;
}

.mall-cat:hover { border-color: var(--brand); color: var(--brand); }

.mall-cat.active {
  background: var(--brand);
  border-color: var(--brand);
  color: #fff;
}

.mall-cat.is-off { opacity: .55; cursor: default; }

.mall-cat i {
  font-style: normal;
  font-size: 11px;
  margin-left: 6px;
  padding: 1px 6px;
  border-radius: 999px;
  background: var(--bg-soft, #f4f6f8);
  color: var(--text-sub);
}

.mall-wallet {
  display: flex;
  align-items: center;
  gap: var(--s4);
  flex-wrap: wrap;
  padding: var(--s4);
  margin-bottom: var(--s4);
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r3, 14px);
  box-shadow: var(--sh-1);
}

.mall-coin {
  display: flex;
  align-items: baseline;
  gap: 6px;
  padding-right: var(--s4);
  border-right: 1px solid var(--border);
}

.mall-coin b {
  font-size: 30px;
  font-weight: 800;
  color: var(--brand);
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.mall-coin small { font-size: 12.5px; font-weight: 600; color: var(--text-sub); }

.mall-wallet-hint {
  flex: 1;
  min-width: 220px;
  font-size: 12.5px;
  color: var(--text-sub);
  line-height: 1.7;
}

.mall-wallet-hint b { color: var(--brand); font-weight: 700; }

/* 订单卡片底部那行「什么时候买的」 */
.order-time {
  margin-top: 8px;
  font-size: 11.5px;
  color: var(--text-sub);
}

/* ---- 韦伯商城：两层 + 转盘 ---- */

.ms-tabs {
  display: flex;
  gap: 10px;
  margin-bottom: var(--s4);
  flex-wrap: wrap;
}

.ms-tab {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 11px 20px;
  border-radius: var(--r2, 10px);
  border: 1px solid var(--border);
  background: var(--card);
  cursor: pointer;
  text-align: left;
  transition: .15s;
}

.ms-tab b { font-size: 14px; font-weight: 700; color: var(--text); }
.ms-tab span { font-size: 11.5px; color: var(--text-sub); }
.ms-tab:hover { border-color: var(--brand); }
.ms-tab.active {
  border-color: var(--brand);
  background: var(--brand-soft);
  box-shadow: 0 0 0 1px var(--brand) inset;
}
.ms-tab.active b { color: var(--brand); }

.mall-h2 {
  font-size: 16px;
  font-weight: 800;
  color: var(--text);
  margin: var(--s5) 0 var(--s3);
}

.mall-h2-sub {
  font-size: 12.5px;
  color: var(--text-sub);
  margin: -6px 0 var(--s4);
  line-height: 1.7;
}
.mall-h2-sub b { color: var(--brand); font-weight: 700; }

.th-tag.rare {
  background: #FFF4E0;
  color: #B76B00;
}

/* 「能买也能抽到」。用蓝，跟「仅限抽奖」的橙分开：
   橙 = 只有抽奖这一条路，蓝 = 多一条路。
   两个都用橙的话，一眼扫过去分不清这件到底能不能买 */
.th-tag.both { background: var(--blue-soft); color: var(--blue); }

/* 画布背景图的预览行。缩略图按 16:9 卡死——真图可能是任何比例，
   让它自然撑开的话这一行会忽高忽低，管理员换一张图页面就跳一下 */
.mm-bg-row { display: flex; align-items: center; gap: 14px; }
.mm-bg-thumb {
  width: 160px; height: 90px; object-fit: cover;
  border-radius: 10px; border: 1px solid var(--border); flex: none;
  background: var(--track);
}
.mm-bg-meta { flex: 1; min-width: 0; }
.mm-bg-name {
  font-size: 13px; color: var(--text); font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mm-bg-hint { font-size: 12px; color: var(--text-sub); margin-top: 4px; }

.th-price.rare { color: #B76B00; font-weight: 700; }

/* 头像/徽章还没做预览图，先给一块占位，别让卡片高度塌下去 */
.th-preview-ph {
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(135deg, var(--bg-soft, #F4F6F8), var(--card));
  color: var(--text-sub);
  font-size: 12px;
  font-weight: 600;
}

/* 转盘。**用 conic-gradient 画扇区、整体 rotate 转**——不画十二个 div
   再各自定位，那样改格子数量要重算一堆角度。 */

.wheel-stage {
  position: relative;
  display: flex;
  justify-content: center;
  align-items: center;
  padding: var(--s6) 0 var(--s5);
}

/* 外框。转盘本体嵌在里面，外面这一圈负责「看起来像个正经转盘」：
   灯珠、双层描边、落影。盘面本身不画这些，转起来才不会跟着甩 */
.wheel-rim {
  position: relative;
  width: 468px;
  height: 468px;
  border-radius: 50%;
  flex: none;
  background:
    radial-gradient(circle at 50% 36%, #FFFFFF 0%, #E7EDFA 48%, #CBD5EE 100%);
  box-shadow:
    inset 0 2px 12px rgba(255, 255, 255, .95),
    inset 0 -6px 16px rgba(91, 91, 245, .07),
    0 0 0 2px rgba(255, 255, 255, .9),
    0 0 0 3px rgba(91, 91, 245, .12),
    0 26px 54px -18px rgba(37, 42, 74, .30);
  display: flex;
  align-items: center;
  justify-content: center;
}

/* 灯珠：一圈小点，位置由 JS 按极坐标算（百分比，跟着尺寸走）。
   奇偶交错闪烁，转起来的时候整圈跟着快闪一下 */
.wheel-bulb {
  position: absolute;
  width: 8px;
  height: 8px;
  margin: -4px 0 0 -4px;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 30%, #FFF6DA, #FFC93C 62%, #E8A400);
  box-shadow: 0 0 6px rgba(255, 201, 60, .75);
  animation: bulbBlink 1.6s ease-in-out infinite;
  pointer-events: none;
}
/* 原来给的是银白，贴在同为浅色的外框上基本看不见，16 颗读起来像 8 颗。
   换成蓝灰实心：亮度压下去、颜色拉开，才是「暗着的那一半」 */
.wheel-bulb.is-off {
  background: radial-gradient(circle at 35% 30%, #C7D2EC, #93A4C9 72%);
  box-shadow: 0 0 4px rgba(120, 140, 180, .45);
  animation-delay: .8s;
}
.wheel-rim.is-spinning .wheel-bulb { animation-duration: .45s; }

@keyframes bulbBlink {
  0%, 100% { opacity: 1; }
  50% { opacity: .35; }
}

.wheel {
  position: absolute;
  /* 468 的框里留 24 的边给灯珠和外环，盘面 420。
     标签位置由 JS 按 34.5% 算，跟着盘宽自动走，不用在这儿配 --wr */
  inset: 24px;
  border-radius: 50%;
  transform: rotate(0deg);
  box-shadow: 0 0 0 3px #fff, 0 6px 18px -6px rgba(37, 42, 74, .25);
}

.wheel-face {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  overflow: hidden;
  /* 兜底：奖池还没拉回来时先是一圈浅色，别露出黑洞 */
  background: conic-gradient(from 0deg, #EEF2FF 0deg 360deg);
}

/* 左上角那块玻璃反光。画在盘面上、标签下面，字不受影响 */
.wheel-face::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: radial-gradient(circle at 33% 22%, rgba(255, 255, 255, .85), rgba(255, 255, 255, 0) 48%);
  pointer-events: none;
}

/* 扇区分隔线。--seg 由 JS 按「360 / 格数」写进来，格子数变了线也跟着变 */
.wheel-face::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: 50%;
  /* 起点必须和 .wheel-face 的 conic 用同一个 from，否则白线全落在
     格子正中间：指针压线、每格的字被线穿过、暖色格被切成两半。
     这两处是一对，改一个必须改另一个 */
  background: repeating-conic-gradient(from calc(-0.5 * var(--seg, 45deg)),
    rgba(255, 255, 255, .92) 0deg 1deg,
    transparent 1deg var(--seg, 45deg));
  pointer-events: none;
}

.wheel-labels {
  position: absolute;
  inset: 0;
  border-radius: 50%;
}

.wheel-label {
  position: absolute;
  /* 位置由 JS 按极坐标算好，写进 left/top（百分比，跟着盘尺寸走）。
     字不跟着角度转——径向排布会让下半圈的字躺着，扶正一半又会出现
     「同一盘上两种读法」，学生看不清格子。干脆全部水平摆。 */
  transform: translate(-50%, -50%);
  /* 宽度给到 118：8 格时相邻标签中心距约 111px，再宽就蹭上了。
     长文案（「谢谢参与，奖励你 1 铜币」这种）允许折两行——
     原来 nowrap + ellipsis 会把它切成「谢谢参与，奖…」，等于没说抽到啥 */
  width: 118px;
  text-align: center;
  font-size: 13.5px;
  font-weight: 700;
  color: var(--text);
  line-height: 1.22;
  pointer-events: none;
  word-break: break-word;
}

.wheel-label.is-rare { color: #B76B00; }
/* 中奖那一格。停下后只有指针，人还得自己数格子，加个记号省事 */
.wheel-label.is-win {
  color: var(--brand);
  background: rgba(255, 255, 255, .95);
  border-radius: 999px;
  padding: 4px 10px;
  box-shadow: 0 3px 10px -2px rgba(37, 42, 74, .28), 0 0 0 1px rgba(91, 91, 245, .18);
}

/* 10 格以上相邻标签会撞，收窄一档 */
.wheel-labels.is-dense .wheel-label { width: 58px; font-size: 9.5px; }

/* 指针。原来是一个纯三角，像贴了张纸。改成地图钉：圆头 + 尖脚，
   圆头上有高光点，脚下压一层落影，才有「钉子压在盘上」的感觉 */
.wheel-pin {
  position: absolute;
  top: 4px;
  left: 50%;
  width: 30px;
  height: 30px;
  margin-left: -15px;
  border-radius: 50%;
  background: radial-gradient(circle at 36% 30%, #FF9B8A 0%, #F0453C 52%, #C42424 100%);
  box-shadow: 0 6px 10px -2px rgba(210, 44, 44, .5), inset 0 -2px 4px rgba(0, 0, 0, .2);
  z-index: 4;
}
/* 尖脚 */
.wheel-pin::before {
  content: '';
  position: absolute;
  top: 22px;
  left: 50%;
  margin-left: -9px;
  border-left: 9px solid transparent;
  border-right: 9px solid transparent;
  border-top: 20px solid #CE2A2A;
  filter: drop-shadow(0 3px 3px rgba(210, 44, 44, .35));
}
/* 圆头高光 */
.wheel-pin::after {
  content: '';
  position: absolute;
  top: 7px;
  left: 9px;
  width: 8px;
  height: 6px;
  border-radius: 50%;
  background: rgba(255, 255, 255, .8);
  transform: rotate(-20deg);
}

.wheel-btn {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 112px;
  height: 112px;
  border-radius: 50%;
  border: 5px solid #fff;
  background: linear-gradient(158deg, #7C7CFF 0%, var(--brand) 46%, #4141CE 100%);
  color: #fff;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  /* 外圈光晕 + 内高光 + 底部压暗，三层叠起来才像个实体按钮而不是一个色块 */
  box-shadow:
    0 0 0 1px rgba(91, 91, 245, .25),
    0 12px 26px -8px rgba(91, 91, 245, .65),
    inset 0 2px 6px rgba(255, 255, 255, .45),
    inset 0 -5px 12px rgba(0, 0, 0, .18);
  z-index: 3;
  transition: transform .16s, box-shadow .16s;
}

.wheel-btn span { font-size: 12.5px; opacity: .92; letter-spacing: .5px; }
.wheel-btn b { font-size: 17px; font-weight: 800; font-variant-numeric: tabular-nums; }
.wheel-btn:hover:not(:disabled) {
  transform: translate(-50%, -50%) scale(1.05);
  box-shadow:
    0 0 0 1px rgba(91, 91, 245, .3),
    0 16px 32px -8px rgba(91, 91, 245, .75),
    inset 0 2px 6px rgba(255, 255, 255, .5),
    inset 0 -5px 12px rgba(0, 0, 0, .18);
}
.wheel-btn:active:not(:disabled) { transform: translate(-50%, -50%) scale(.98); }
.wheel-btn:disabled { opacity: .8; cursor: default; }

.wheel-tip {
  text-align: center;
  font-size: 12.5px;
  color: var(--text-sub);
  padding-bottom: var(--s4);
}

.wheel-log {
  margin-top: var(--s4);
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.wl-head {
  font-size: 12.5px;
  font-weight: 700;
  color: var(--text-sub);
  margin-bottom: 2px;
}

.wl-row {
  display: flex;
  gap: 10px;
  align-items: baseline;
  padding: 9px 13px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r2, 10px);
  font-size: 12.5px;
}

.wl-label { font-weight: 700; color: var(--text); flex: none; }
.wl-label.is-big { color: var(--brand); }
.wl-msg { color: var(--text-sub); }

.wheel-result { text-align: center; padding: var(--s4) 0; }
.wr-label {
  font-size: 26px;
  font-weight: 800;
  color: var(--text);
  margin-bottom: 10px;
}
.wheel-result.is-big .wr-label { color: var(--brand); }
.wr-msg { font-size: 13.5px; color: var(--text-sub); line-height: 1.7; margin: 0; }
.wr-cost { font-size: 12px; color: var(--text-sub); margin-top: 10px; }

@media (max-width: 900px) {
  .mm-row-prod, .mm-row-prize, .mm-row-draw { grid-template-columns: 1fr; gap: 6px; }
  .mm-row-prod.org-head, .mm-row-prize.org-head, .mm-row-draw.org-head { display: none; }
}

@media (max-width: 1100px) {
  .wheel-rim { width: 408px; height: 408px; }
  .wheel { inset: 21px; }
  .wheel-label { font-size: 12.5px; width: 106px; }
  .wheel-btn { width: 100px; height: 100px; }
  .wheel-btn b { font-size: 15.5px; }
}

@media (max-width: 640px) {
  .wheel-rim { width: 336px; height: 336px; }
  .wheel { inset: 18px; }
  .wheel-bulb { width: 6px; height: 6px; margin: -3px 0 0 -3px; }
  .wheel-btn { width: 88px; height: 88px; border-width: 4px; }
  .wheel-btn span { font-size: 11.5px; }
  .wheel-btn b { font-size: 14px; }
  .wheel-label { font-size: 11px; width: 88px; }
  .wheel-pin { width: 26px; height: 36px; margin-left: -13px; top: 5px; }
  .wheel-row { flex-direction: column; align-items: center; }
  .wheel-board { width: 100%; max-width: 340px; }
}

/* ---- 转盘 + 大奖榜并排 ---- */

.wheel-row {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--s8);
  flex-wrap: wrap;
}

.wheel-board {
  width: 300px;
  flex: none;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: var(--s4) var(--s4) var(--s3);
  box-shadow: var(--sh-1);
}

.wb-head b { font-size: 14px; font-weight: 800; letter-spacing: -.2px; }
.wb-head span {
  display: block; margin-top: 2px;
  font-size: 11.5px; color: var(--text-sub);
}

/* 定高视口 + 溢出裁掉，里面那条轨道才能滚起来。
   高度必须是「整行」的倍数（3 行 = 48×3 + 6×2 = 156），否则滚动时
   上下会各切半行，看着像有幽灵行 */
.wb-viewport {
  margin: var(--s3) 0 var(--s2);
  height: 156px;
  overflow: hidden;
  mask-image: linear-gradient(180deg, transparent, #000 16%, #000 84%, transparent);
}

.wb-track { display: flex; flex-direction: column; gap: 6px; }

/* 2 条就滚（JS 控制）。内容重复 N 份接在后面，滚完一份的距离正好接上，
   位移由 JS 写进 --roll（-100% / N）——固定 -50% 只在正好 2 份时成立，
   2 条大奖凑不够一屏高度，硬用 -50% 会露出空档 */
.wb-track.is-roll { animation: wbRoll 15s linear infinite; }
.wb-track.is-roll:hover { animation-play-state: paused; }

@keyframes wbRoll {
  from { transform: translateY(0); }
  to { transform: translateY(var(--roll, -50%)); }
}

.wb-item {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 2px 8px;
  height: 48px;
  box-sizing: border-box;
  padding: 6px 10px;
  border-radius: var(--r-md);
  /* 原来写的是 var(--bg-sub)，这个变量全站从没定义过，行一直是透明的，
     整块读起来不像「榜」，像几行散字 */
  background: var(--bg-soft, #F4F6F8);
  border: 1px solid rgba(37, 42, 74, .06);
  align-content: center;
}
.wb-who { font-size: 12.5px; font-weight: 700; color: var(--text); }
.wb-worth {
  font-size: 12.5px; font-weight: 800; color: #B76B00;
  font-variant-numeric: tabular-nums;
  /* 只写 grid-row:span 2 会自动摆进第 1 列，金额掉到昵称底下。
     显式锁到第 2 列、跨两行 */
  grid-column: 2;
  grid-row: 1 / span 2;
  align-self: center;
  justify-self: end;
}
.wb-worth::after { content: " 铜币"; font-size: 10px; font-weight: 600; color: var(--text-sub); }
.wb-what { font-size: 11.5px; color: var(--text-sub); }

.wb-empty {
  text-align: center; font-size: 12.5px; color: var(--text-sub);
  padding: 44px 8px; line-height: 1.7;
}
.wb-empty span { font-size: 11.5px; color: var(--text-faint); }

.wb-foot {
  text-align: center; font-size: 11.5px; color: var(--text-faint);
  border-top: 1px dashed var(--border); padding-top: var(--s2);
}

/* ---- 我的订单里的流水行 ---- */

.ord-list { display: flex; flex-direction: column; }

.ord-row {
  display: grid;
  grid-template-columns: 1.2fr 1.4fr auto;
  align-items: center;
  gap: var(--s3);
  padding: 9px 0;
  border-bottom: 1px dashed var(--border);
  font-size: 13px;
}
.ord-row:last-child { border-bottom: 0; }

.ord-name { font-weight: 600; color: var(--text); display: flex; align-items: center; gap: 6px; }
.ord-mid { font-size: 12px; color: var(--text-sub); }
.ord-num {
  font-variant-numeric: tabular-nums; font-weight: 700; font-size: 13px;
  color: var(--text-sub); text-align: right; white-space: nowrap;
}
.ord-num.plus { color: #1B9A5B; }
.ord-num.minus { color: var(--text); }

.ord-badge {
  font-size: 10px; font-weight: 700; color: #B76B00;
  background: #FFF4E0; border-radius: 999px; padding: 1px 6px;
}

@media (max-width: 720px) {
  .ord-row { grid-template-columns: 1fr auto; gap: 2px var(--s2); }
  .ord-mid { grid-column: 1 / -1; order: 3; }
}

/* ---- 商城管理（后台） ---- */

.chip-row {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: var(--s4);
}

.mm-sec {
  margin-bottom: var(--s6);
}

.mm-sec-title {
  font-size: 15px;
  font-weight: 800;
  color: var(--text);
  margin: 0 0 4px;
}

.mm-sec-sub {
  font-size: 12.5px;
  color: var(--text-sub);
  margin: 0 0 var(--s3);
  line-height: 1.7;
}

.mm-row-prod {
  grid-template-columns: 1.6fr 96px 100px 104px 84px 60px 190px;
}

.mm-row-prize {
  grid-template-columns: 1.6fr 96px 74px 80px 84px 170px;
}

.mm-row-draw {
  grid-template-columns: 120px 1.1fr 1.6fr 70px 140px;
}

.mm-name {
  display: flex;
  align-items: baseline;
  gap: 8px;
  min-width: 0;
}

.mm-name b {
  font-size: 13.5px;
  font-weight: 700;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.mm-name code {
  font-size: 11px;
  padding: 1px 6px;
  border-radius: 5px;
  background: var(--bg-soft, #F4F6F8);
  color: var(--text-sub);
  flex: none;
}

.mm-num {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  color: var(--text);
}

.mm-num i {
  font-style: normal;
  font-size: 11px;
  font-weight: 600;
  color: var(--text-sub);
  margin-left: 3px;
}

.mm-foot {
  margin-top: 10px;
  font-size: 12px;
  color: var(--text-sub);
  line-height: 1.7;
}

.mm-warn {
  padding: 10px 14px;
  margin-bottom: var(--s3);
  border-radius: var(--r2, 10px);
  background: #FFF8E8;
  border: 1px solid #F0D9A8;
  color: #8A5A00;
  font-size: 12.5px;
  line-height: 1.7;
}

.mm-set {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  padding: 12px 16px;
  margin-bottom: var(--s3);
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r2, 10px);
  font-size: 13px;
}

.mm-set label {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text-sub);
  margin: 0;
}

.mm-set input[type="number"] { margin: 0; }

.mm-switch {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  cursor: pointer;
}

.mm-switch input { width: auto; margin: 0; }

.mm-row-prod.is-off,
.mm-row-prize.is-off { opacity: .55; }

.mm-hint {
  font-size: 11.5px;
  color: var(--text-sub);
  line-height: 1.6;
  margin: 4px 0 8px;
}

/* 七列表格在中等宽度先收列宽，别急着堆叠 */
@media (max-width: 1240px) {
  /* 窄一点的时候先把「商品」列的地板撑住：1.3fr 在 1042px 下只算出 100px，
     里面还塞着 key 的 code 小标签，名字被挤成「樱…」。所以这里藏掉 code（key
     是内部标识，管理员在窄屏上看不到不影响操作），并给名字列留 minmax 地板 */
  .mm-row-prod { grid-template-columns: minmax(112px, 1.3fr) 74px 84px 86px 68px 44px 150px; }
  .mm-name code { display: none; }
  .mm-row-prize { grid-template-columns: 1.4fr 78px 62px 68px 70px 148px; }
  .mm-row-draw { grid-template-columns: 104px 1fr 1.3fr 58px 124px; }
}

/* ---- 窄屏：表格一律改成堆叠 ---- */

@media (max-width: 1100px) {
  .pp-row, .ex-row { grid-template-columns: 1fr; gap: 6px; }
  .pp-row.org-head, .ex-row.org-head { display: none; }
  .pp-acts, .ex-acts { justify-content: flex-start; }
  .mall-coin { border-right: 0; padding-right: 0; }
}

/* ============================================================================
   试卷中心（/papers）—— 2026-11-01 按题库站的版式重做

   版式是照着一套成熟的题库站抄的：面包屑 → 居中标题的筛选卡片 → 表格；
   详情页是「左栏题号导航 + 右栏分节题目」。

   配色没有跟着抄橙色。全站有十套主题，把这一页钉死成橙的，用户一换主题
   就会觉得这页跟别处不是一家的。所以凡是截图里用橙的地方一律换成
   var(--brand)——在阳光橙主题下就是那个橙，在别的主题下自动跟着变。
   ========================================================================== */

/* ---------- 面包屑 ---------- */
.pc-crumb {
  display: flex; align-items: center; flex-wrap: wrap; gap: 4px 7px;
  font-size: 13px; color: var(--text-sub);
  /* 跟下面的卡片、表格用同一个 1080 容器，否则面包屑贴 wrap 边、
     内容块居中，两块左边缘差 74px */
  max-width: 1080px;
  margin: 0 auto var(--s4);
}
.pc-crumb svg { width: 15px; height: 15px; flex: none; color: var(--brand); }
/* 390 下容器不够宽时，原来是在词中间折——「您当前位 / 置：」。
   让每个子项自己不许断，折行交给 flex 容器整项折 */
.pc-crumb > * { white-space: nowrap; }
.pc-crumb b {
  font-weight: 700; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; max-width: 62vw;
}
.pc-crumb i { font-style: normal; color: var(--text-sub); opacity: .5; }

/* ---------- 筛选卡片 ---------- */
.pc-panel {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: var(--s8) var(--s9) var(--s6);
  /* 这块原来限宽 1080 居中，因为那时页面顶上没有 banner，1228 宽的卡片里
     只放三行 chip，右边空出六百多像素，看着像没写完。
     可 2026-11-01 傍晚门面换成整幅 banner（1228）之后，卡片比 banner 窄 148、
     左右各缩 74，一眼就看得出没对齐——宽度统一交给 .wrap，这里不再自己限。
     但铺满之后右半边又空了，所以改成两栏：标题在左当栏头，三行筛选在右，
     卡片里每一块都有主，不再是「左边一坨字、右边一大片白」 */
  display: grid;
  grid-template-columns: 188px minmax(0, 1fr);
  column-gap: var(--s7);
  align-items: start;
  margin: 0 0 var(--s6);
  box-shadow: var(--sh-1);
}

/* 居中标题 + 底下一小截色条。色条是这块的视觉重心，
   没有它整张卡片就只剩一堆字，看着像后台表单 */
.pc-title {
  position: relative;
  /* 两栏布局里标题是左栏头，横跨所有筛选行，别再往下推距离 */
  grid-column: 1;
  grid-row: 1 / -1;
  margin: 0;
  padding-bottom: 16px;
  /* 原来居中，可下面六行筛选全从左边起——居中标题压在一堆左对齐内容上，
     像两段拼起来的。标题跟筛选行对齐到同一条竖线 */
  text-align: left;
  font-size: clamp(22px, .5vw + 16px, 28px);
  font-weight: 800;
  letter-spacing: -.4px;
  color: var(--text);
}
.pc-title::after {
  content: ''; position: absolute; left: 0; bottom: 0;
  width: 56px; height: 3px;
  border-radius: 2px; background: var(--brand);
}

.pc-frow {
  grid-column: 2;
  display: flex; align-items: flex-start; gap: 10px; padding: 5px 0;
  /* 2026-11-01 去掉 620px 限宽。原来三行 chip 的时候限宽是为了不左重右空，
     现在筛选项有六行、每行 chip 数量不一，限宽反而让右侧空出六百多像素，
     整张卡像没写完。铺满之后每行从左边同一个 x 起，看着是张表 */
}
/* 标签定宽右对齐，「数据范围」「题　　数」「排　　序」才能对上一条竖线。
   用全角空格凑宽度比 letter-spacing 稳——letter-spacing 会连标点一起拉开 */
.pc-frow > label {
  flex: none; width: 84px; text-align: left;
  font-size: 13.5px; font-weight: 700; color: var(--text-sub);
  line-height: 30px;
}
.pc-chips { display: flex; flex-wrap: wrap; gap: 2px; flex: 1; min-width: 0; }

/* 搜索框。跟 .pc-chips 一样占右栏，但自己收窄到 340px——拉满一整行，
   眼睛要从左栏标题横跨一千多像素去右边看结果，读起来是散的。
   340 够放十六七个汉字，够写「GESP 一级 条件分支」 */
.pc-search {
  position: relative; flex: 1; min-width: 0; max-width: 340px;
  display: flex; align-items: center;
}
.pc-search input {
  width: 100%; height: 32px; box-sizing: border-box;
  padding: 0 30px 0 12px;
  border-radius: 6px; border: 1.5px solid var(--border);
  background: var(--card);
  font: inherit; font-size: 13.5px; color: var(--text);
  outline: none; transition: border-color var(--dur-1), box-shadow var(--dur-1);
}
.pc-search input::placeholder { color: var(--text-faint); }
.pc-search input:focus {
  border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-soft);
}
/* 原生清除叉 Chrome 里是黑的、Safari 里干脆不出现，两家长得不一样。
   自己画一个，位置颜色都在手里 */
.pc-search input::-webkit-search-cancel-button { display: none; }
.pc-search-x {
  position: absolute; right: 5px; top: 50%; transform: translateY(-50%);
  width: 20px; height: 20px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  border: 0; border-radius: 50%; background: transparent;
  color: var(--text-faint); font-size: 15px; line-height: 1;
  cursor: pointer; transition: color var(--dur-1), background var(--dur-1);
}
.pc-search-x:hover { color: var(--text); background: var(--border); }
.pc-chip {
  appearance: none; border: 0; background: transparent;
  padding: 5px 13px; border-radius: 5px;
  font: inherit; font-size: 14px; font-weight: 600;
  color: var(--text-sub); cursor: pointer; line-height: 20px;
  transition: color var(--dur-1), background var(--dur-1);
}
.pc-chip:hover { color: var(--brand); background: var(--brand-soft); }
.pc-chip.is-on { background: var(--brand); color: #fff; }
.pc-chip.is-on:hover { background: var(--brand-dark); color: #fff; }

/* ---------- 表格 ---------- */
.pc-table {
  /* 跟 banner、筛选卡片同宽：都交给 .wrap，谁也不许自己限宽 */
  margin: 0;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  overflow: hidden;
  box-shadow: var(--sh-1);
}
.pc-tr {
  display: grid;
  grid-template-columns: 84px minmax(0, 1fr) 76px 84px 88px 196px;
  align-items: center; gap: 14px;
  padding: 0 var(--s6);
  min-height: 66px;
  border-bottom: 1px solid var(--border);
}
.pc-tr:last-child { border-bottom: 0; }
.pc-tr.pc-th {
  min-height: 50px;
  /* 页面底色本身就浅（青绿主题的 --bg 是 #F5FCFB），压到白卡上几乎分不出来。
     再掺一点正文色才立得住 */
  background: color-mix(in srgb, var(--brand) 8%, var(--bg));
  font-size: 13.5px; font-weight: 700; color: var(--text-sub);
}
.pc-tr:not(.pc-th):hover { background: color-mix(in srgb, var(--brand) 3%, var(--card)); }
.pc-tr.pc-th > span:nth-child(3),
.pc-tr.pc-th > span:nth-child(4) { text-align: center; }
.pc-tr.pc-th > span:nth-child(6) { text-align: right; }

.pc-no { font-size: 14px; color: var(--text-sub); font-variant-numeric: tabular-nums; }

.pc-name { display: flex; align-items: center; gap: 9px; min-width: 0; }
.pc-name > svg { width: 18px; height: 18px; flex: none; color: var(--brand); }
/* 全站链接的 hover 下划线是「背景色条生长」做的，方框类链接要关掉，
   否则题号格底下会莫名长出一根线 */
.wrap .pc-name a {
  display: block;
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 15px; font-weight: 600; color: var(--text);
  background-image: none;
}
.wrap .pc-name a:hover { color: var(--brand); }
/* 名称列 580px 而卷名常常只有 200px，中间一大段白。补一行副信息
   把空白填成有用的东西，也让「谁建的、什么时候动的」一眼可见 */
.pc-name-txt { min-width: 0; display: flex; flex-direction: column; }
.pc-name-sub {
  display: flex; gap: 6px; margin-top: 2px; flex-wrap: wrap;
  font-size: 12px; color: var(--text-sub); opacity: .78;
}
/* 390 下这一行塞不下「GESP · 2025年6月 · 刘老师 · 2026-11-01 15:19」，
   不锁 nowrap 的话浏览器会把词硬拆开——「刘老师」被拆成「刘老 / 师」。
   允许整段换行，但不许拆段 */
.pc-name-sub > span { white-space: nowrap; }
.pc-name-sub i { font-style: normal; opacity: .55; }

.pc-num { text-align: center; font-size: 14px; color: var(--text); font-variant-numeric: tabular-nums; }
.pc-num small { font-size: 12px; font-weight: 600; color: var(--text-sub); margin-left: 2px; }

.pc-src {
  justify-self: start;
  padding: 2px 9px; border-radius: var(--r-pill);
  font-size: 12px; font-weight: 700;
  background: var(--brand-soft); color: var(--brand);
}
/* --text-faint 在白底上只有 2.5:1，落在灰底上更低，「私人」读起来像禁用态 */
.pc-src.is-priv { background: var(--bg); color: var(--text-sub); }

.pc-acts { display: flex; gap: 8px; justify-content: flex-end; }

/* 「共 N 份 / 合计 N 道题」那一行也要跟表格对齐，不能贴着 wrap 的边 */
.pc-listbar {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px;
  margin: 0 0 10px;
}

/* 空态。原来是往 #pcRows 里塞一个 .pc-tr，既占了一行表格，
   又让「行数」这种口径算错 */
.pc-empty {
  padding: 64px 20px; text-align: center;
  font-size: 13.5px; color: var(--text-faint);
}

/* ---------- 详情页 ---------- */
.pc-exam {
  display: grid;
  grid-template-columns: 258px minmax(0, 1fr);
  gap: var(--s6);
  align-items: start;
  /* 顶栏（.pc-exam-bar）是铺满 .wrap 的，网格原来限宽 1080 居中，
     两者左边缘差 74px——顶栏从 26 开始，正文从 100 开始。
     网格跟着顶栏铺满，右侧多出来的 148 全给题面和代码框 */
  margin: 0;
}

.pc-side {
  /* 顶栏贴住视口顶之后，左栏不能再留 82px 的空档，不然它比顶栏低一截 */
  position: sticky; top: var(--s4);
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: var(--s5) var(--s4) var(--s4);
  box-shadow: var(--sh-1);
  max-height: calc(100vh - var(--s6));
  overflow-y: auto;
}

.pc-who { text-align: center; padding-bottom: var(--s4); border-bottom: 1px solid var(--border); }
.pc-who-av {
  width: 56px; height: 56px; margin: 0 auto 9px;
  border-radius: 50%;
  display: grid; place-items: center;
  font-size: 22px; font-weight: 800; color: #fff;
  background: linear-gradient(152deg, color-mix(in srgb, var(--brand) 62%, #fff), var(--brand));
  box-shadow: 0 8px 18px -8px var(--brand);
}
.pc-who b { display: block; font-size: 15px; font-weight: 700; color: var(--text); }
.pc-who span {
  display: block; margin-top: 2px;
  font-size: 12px; color: var(--text-faint);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.pc-navg { margin-top: var(--s4); }
.pc-navg h4 {
  margin: 0 0 9px;
  font-size: 13px; font-weight: 700; color: var(--text);
  display: flex; align-items: baseline; gap: 6px;
}
.pc-navg h4 em {
  font-style: normal; font-size: 11.5px; font-weight: 600; color: var(--text-faint);
}
.pc-navgrid { display: grid; grid-template-columns: repeat(6, 1fr); gap: 6px; }
.wrap .pc-navgrid a.pc-navno {
  display: grid; place-items: center;
  height: 30px; border-radius: 4px;
  /* --border 在白底上只有 1.19:1，六个题号看着像六个散落的数字，没有格子感 */
  border: 1px solid color-mix(in srgb, var(--text) 26%, transparent);
  font-size: 12.5px; font-weight: 600; color: var(--text-sub);
  font-variant-numeric: tabular-nums;
  background-color: var(--card); background-image: none;
  transition: border-color var(--dur-1), color var(--dur-1), background var(--dur-1);
}
.wrap .pc-navgrid a.pc-navno:hover { border-color: var(--brand); color: var(--brand); }
.wrap .pc-navgrid a.pc-navno.is-cur {
  background: var(--brand); border-color: var(--brand); color: #fff;
}

.pc-legend { margin-top: var(--s5); padding-top: var(--s4); border-top: 1px solid var(--border); }
.pc-legend h4 { margin: 0 0 9px; font-size: 13px; font-weight: 700; color: var(--text); }
.pc-legend-row {
  display: flex; align-items: center; gap: 8px;
  font-size: 12.5px; color: var(--text-sub); padding: 3px 0;
}
.pc-legend-dot { width: 14px; height: 14px; border-radius: 3px; flex: none; border: 1px solid color-mix(in srgb, var(--text) 18%, transparent); }
.pc-legend-dot.on { background: var(--brand); border-color: var(--brand); }

.pc-main { min-width: 0; padding-bottom: 24px; }

.pc-mhead {
  position: relative;
  text-align: center;
  padding: var(--s6) var(--s7) var(--s5);
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  margin-bottom: var(--s5);
  box-shadow: var(--sh-1);
}
.pc-mhead h1 {
  /* 右上角那颗 ✕ 是绝对定位的，标题不给让位的话窄屏会压上去 */
  margin: 0 auto 9px; padding: 0 40px;
  font-size: clamp(20px, .45vw + 14px, 26px);
  font-weight: 800; letter-spacing: -.4px; color: var(--text);
}
.pc-mmeta { display: flex; justify-content: center; flex-wrap: wrap; gap: 6px var(--s6); font-size: 13.5px; color: var(--text-sub); }
.pc-mmeta b { color: var(--text); font-weight: 800; font-variant-numeric: tabular-nums; }
/* 卷子说明单独一行、更淡。原来塞在「总分/题数」那排里，
   读起来像第四个统计数字 */
.pc-mdesc {
  margin: 10px auto 0; max-width: 720px;
  font-size: 12.5px; color: var(--text-faint); line-height: 1.65;
}
.wrap .pc-mclose {
  position: absolute; top: 13px; right: 13px;
  width: 30px; height: 30px; border-radius: 50%;
  display: grid; place-items: center;
  /* 原来是整页唯一的近黑色块，比标题还抢眼；压成页面底色之后又淡到找不着。
     用品牌浅底 + 品牌字，既不抢也知道那是个能点的东西 */
  background: var(--brand-soft); color: var(--brand);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--brand) 22%, transparent);
  font-size: 14px; line-height: 1;
  background-image: none;
  transition: color var(--dur-1), transform var(--dur-1), background var(--dur-1);
}
.wrap .pc-mclose:hover { color: #fff; background: var(--brand); transform: rotate(90deg); }

/* 退出：钉在左栏「状态说明」上面。
   原来它是顶栏左上角一个 30px 的圆 ✕，顶栏钉到视口顶上以后，
   ✕ 和标题挤在同一行，看着像「关掉这个弹窗」，不像「退出这场作业」。
   挪到左栏、写成一句完整的话，点之前就知道点完去哪 */
.pc-exit {
  display: flex; align-items: center; justify-content: center; gap: 7px;
  margin-top: var(--s5);
  padding: 9px 12px;
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  background: var(--card);
  color: var(--text-sub);
  font-size: 13px; font-weight: 600;
  transition: color var(--dur-1), border-color var(--dur-1), background var(--dur-1);
}
.pc-exit span { font-size: 11px; opacity: .7; }
.pc-exit:hover {
  color: var(--red);
  border-color: color-mix(in srgb, var(--red) 38%, var(--border));
  background: color-mix(in srgb, var(--red) 6%, var(--card));
}

.pc-qsec { margin-bottom: var(--s5); }
.pc-qbar {
  margin: 0; padding: 11px var(--s5);
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  border-radius: var(--r-md) var(--r-md) 0 0;
  /* 原来用的是 text 混白的中灰，跟主题色完全脱节——青绿主题下一条深灰横在
     页面里，像别处截来的。换成品牌色往深里压一档，跟着主题走 */
  background: linear-gradient(96deg, var(--brand) 0%, color-mix(in srgb, var(--brand) 86%, #000) 100%);
  color: #fff;
  font-size: 14.5px; font-weight: 700;
}
.pc-qbar b { font-weight: 700; }
.pc-qbar span { font-size: 12.5px; font-weight: 600; opacity: .84; flex: none; }
.pc-qwrap {
  background: var(--card);
  border: 1px solid var(--border); border-top: 0;
  border-radius: 0 0 var(--r-md) var(--r-md);
  overflow: hidden;
}

.pc-q {
  display: grid;
  grid-template-columns: 30px minmax(0, 1fr) auto;
  gap: 12px; align-items: start;
  padding: var(--s4) var(--s5);
  border-bottom: 1px solid var(--border);
  scroll-margin-top: 90px;
  transition: background var(--dur-2);
}
.pc-q:last-child { border-bottom: 0; }
/* 4% 太淡，滚到那一题时几乎看不出和别的行有区别 */
.pc-q.is-cur { background: color-mix(in srgb, var(--brand) 7%, var(--card)); }
/* 从「改这题」改完回来，落回原题时闪一下边框：位置跳过去了，但眼睛不一定
   跟得上，给个两秒的提示。用 outline 不用 box-shadow，免得顶掉 .pc-q 自己的
   阴影层级 */
.pc-q-flash { outline: 2px solid var(--brand); outline-offset: 3px; border-radius: 10px;
  animation: pcqFlash 2.2s ease-out 1 forwards; }
@keyframes pcqFlash {
  0%, 45% { outline-color: var(--brand); }
  100% { outline-color: transparent; }
}
.pc-q-no {
  width: 28px; height: 28px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--brand-soft); color: var(--brand);
  font-size: 13px; font-weight: 800; font-variant-numeric: tabular-nums;
}
.pc-q-body { min-width: 0; }
.pc-q-title {
  margin: 0 0 7px;
  font-size: 15px; font-weight: 600; color: var(--text); line-height: 1.55;
}
.pc-q-tags { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
/* 右侧一列：分值在上、按钮在下。原来是「分值飘在最右、按钮混在标签里」，
   按钮跟着标签长短左右乱跑，六道题看下来按钮位置全不一样 */
.pc-q-right {
  display: flex; align-items: center; gap: 12px;
  flex: none;
}
/* 没有 nowrap 的话，窄屏下按钮会被挤成 min-content = 一个字宽，
   「去做这题」竖着排成一列 */
.pc-q-right .btn { white-space: nowrap; flex: none; }
/* btn-ghost 的 --border 在白底上太淡，这个按钮看着像一行浮着的字 */
.pc-q-right .btn {
  border-color: color-mix(in srgb, var(--brand) 28%, transparent);
  color: var(--brand);
}
.pc-q-right .btn:hover { background: var(--brand-soft); border-color: var(--brand); }

.pc-q-score {
  display: flex; align-items: baseline; gap: 3px;
  font-size: 12px; font-weight: 600;
  /* --text-sub 落在题行的浅底上只有 3.5:1，13px 的小字读着费劲 */
  color: color-mix(in srgb, var(--text) 62%, transparent);
  white-space: nowrap;
  /* 跟题干第一行对齐，不是跟整块顶对齐——顶对齐会让「20 分」
     比标题高出小半行，扫一眼以为两行不是一回事 */
  padding-top: 4px; line-height: 1.55;
}
.pc-q-score b { font-size: 15px; font-weight: 800; color: var(--text); font-variant-numeric: tabular-nums; }

@media (max-width: 1080px) {
  .pc-panel { padding: var(--s6) var(--s6) var(--s5); }
  /* 原来是五列、把「来源」整列 display:none 了——机构卷还是私人卷
     是这一行最要紧的归属信息，宁可变窄也别消失 */
  .pc-tr { grid-template-columns: 52px minmax(0, 1fr) 52px 64px 60px 152px; gap: 10px; }
  .pc-tr.pc-th > span:nth-child(3),
  .pc-tr.pc-th > span:nth-child(4) { text-align: center; }
  /* 收窄左栏而不是直接堆叠。堆叠之后左栏会拉成通栏，
     题号格跟着 1fr 摊成一个个大方块，很难看 */
  .pc-exam { grid-template-columns: 208px minmax(0, 1fr); gap: var(--s4); }
  .pc-navgrid { grid-template-columns: repeat(5, 1fr); }
}

@media (max-width: 860px) {
  .pc-exam { grid-template-columns: 1fr; }
  .pc-side { position: static; max-height: none; padding: var(--s4) var(--s4) var(--s3); }
  /* 堆叠时题号格给固定宽，别让 1fr 把它们摊成大方块 */
  .pc-navgrid { grid-template-columns: repeat(auto-fill, 34px); }
  /* 左栏一占就是六百多像素高，手机上第一道题要滚一屏多才看得到。
     头像和分组间距收一收，把整块压到一半 */
  .pc-who { display: flex; align-items: center; gap: 10px; text-align: left; padding-bottom: var(--s3); }
  .pc-who-av { width: 40px; height: 40px; margin: 0; font-size: 17px; }
  .pc-who b { font-size: 14px; }
  .pc-who span { font-size: 11.5px; }
  /* 四组竖排下来左栏一千多像素，手机上第一道题要滚一屏多才见着。
     分组标题和题号改成同一行，一块从 66px 压到 40px */
  .pc-navg {
    margin-top: var(--s3);
    display: flex; align-items: center; gap: 10px;
  }
  .pc-navg h4 {
    margin: 0; flex: none; width: 88px;
    font-size: 12.5px; flex-wrap: wrap;
  }
  /* 「2 道」这种统计挤在 88px 里会折成两行，每块多占 21px。
     题号格数本身就看得出几道，手机上省掉 */
  .pc-navg h4 em { display: none; }
  .pc-navgrid { flex: 1; min-width: 0; }
  /* 就两个状态，手机上一屏放不下这些说明，藏掉 */
  .pc-legend { display: none; }
  .pc-side > .btn { margin-top: var(--s4) !important; }
}

@media (max-width: 720px) {
  /* 两栏在手机上塌成一栏。注意 .pc-frow 的 grid-column: 2 也要一起改回来——
     只改列数不改列位，浏览器会为它开一条隐式列，标题被挤成一字一行 */
  .pc-panel { padding: var(--s5) var(--s4); grid-template-columns: minmax(0, 1fr); }
  .pc-title { grid-column: 1; grid-row: auto; margin-bottom: var(--s5); }
  .pc-frow { grid-column: 1; }
  /* 三组筛选在手机上竖着排占掉首屏六成。标签缩成小字跟 chip 同行 */
  .pc-frow { flex-direction: row; align-items: flex-start; gap: 8px; padding: 4px 0; }
  .pc-frow > label {
    width: auto; text-align: left;
    font-size: 12.5px; line-height: 30px;
    color: var(--text-sub); font-weight: 600;
  }
  /* 手机上 340 的限宽会空出右边一截，搜索框看着像没画完。
     一行放得下就让它放满——手机上打字本来就少，宽一点好点准 */
  .pc-search { max-width: none; }
  /* 表格在手机上撑不住六列，改成一条一条堆 */
  /* 六列在手机上塞不下，改成一条一条堆。每一块都显式锁行列——
     只写 grid-column 不写 grid-row 的话，自动放置会把「题数/总分」和
     「操作」都算到第 3 行，两组控件叠在一起 */
  .pc-tr { grid-template-columns: 1fr auto; gap: 4px 10px; padding: var(--s3) var(--s4); min-height: 0; }
  .pc-tr.pc-th { display: none; }
  .pc-tr .pc-no { grid-column: 1; grid-row: 1; font-size: 12px; }
  .pc-tr .pc-src { grid-column: 2; grid-row: 1; display: inline-block; justify-self: end; }
  .pc-tr .pc-name { grid-column: 1 / -1; grid-row: 2; }
  /* 手机上表头整排没了，「6」单摆着不知道是题数还是编号，补个量词 */
  .pc-tr .pc-num { grid-column: 1; grid-row: 3; font-size: 12.5px; color: var(--text-sub); }
  .pc-tr .pc-num:not(.pc-num-score)::after { content: ' 题'; font-size: 11.5px; }
  /* 桌面端「题数」列居中，卡片化之后居中的数字会浮在中间，
     跟左边对齐的编号、名称不在一条线上 */
  .pc-tr .pc-num { text-align: left; }
  .pc-tr .pc-num.pc-num-score { text-align: right; }
  .pc-tr .pc-num.pc-num-score { grid-column: 2; grid-row: 3; justify-self: end; }
  .pc-tr .pc-acts { grid-column: 1 / -1; grid-row: 4; justify-content: flex-start; margin-top: 6px; }
  /* 原来只把 .pc-q 收成两列，第三格 .pc-q-right 被自动摆到第 2 行第 1 列——
     那一列只有 28px 宽，按钮直接被挤成竖排单字 */
  .pc-crumb i { display: none; }
  .pc-q { grid-template-columns: 28px minmax(0, 1fr); }
  .pc-q-right {
    grid-column: 1 / -1;
    justify-content: space-between;
    width: 100%; margin-top: 4px;
  }
  .pc-q-score { padding-top: 0; }
}

/* ================================================================ 卷内作答

   2026-11-01：题目从「点进去做」改成「就在卷子里做」。这组样式只服务
   卷子里的作答区，跟单题页 /problem 那套互不影响——那边是 monaco 全屏
   编辑器，这里是嵌在题行里的一块，两者尺寸和留白要求完全不同。 */

/* 左栏「已做对 2 / 6」。夹在用户卡和题号组之间，一眼知道还剩多少 */
.pc-prog {
  margin: 0 0 var(--s4);
  padding: 9px 12px;
  border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--brand) 7%, transparent);
  font-size: 12.5px; font-weight: 600; color: var(--text-sub);
  text-align: center;
}
.pc-prog b { font-size: 15px; font-weight: 800; color: var(--brand); font-variant-numeric: tabular-nums; }
/* 进度条。数字要读、要换算，横条不用——扫一眼就知道还剩多少 */
.pc-prog-bar {
  height: 5px; margin-top: 7px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--brand) 14%, transparent);
  overflow: hidden;
}
.pc-prog-bar i {
  display: block; height: 100%; width: 0;
  border-radius: 999px;
  background: var(--brand);
  transition: width var(--dur-2, .24s) ease;
}

/* 已做对的题号换个底色。六个格子里哪些做完了，不用挨个点开看 */
.wrap .pc-navgrid a.pc-navno.is-done {
  background: var(--green-soft);
  border-color: color-mix(in srgb, var(--green) 45%, transparent);
  color: var(--green);
}
.wrap .pc-navgrid a.pc-navno.is-done.is-cur {
  background: var(--green);
  border-color: var(--green);
  color: #fff;
}
.pc-legend-dot.done { background: var(--green); border-color: var(--green); }

/* 图形化题交上去，等老师批改。这个状态不能跟「做错了」共用一个颜色：
   红叉挂上去，学生会以为作品有问题，回头重做一份本来没毛病的东西。
   琥珀色是「排着队呢」，不是结论 */
.wrap .pc-navgrid a.pc-navno.is-wait {
  background: var(--amber-soft);
  border-color: color-mix(in srgb, var(--amber) 50%, transparent);
  color: var(--amber);
  font-weight: 800;
}
.pc-legend-dot.wait { background: var(--amber); border-color: var(--amber); }

/* 题干。题库里 description 是带换行的纯文本，不是 HTML——
   用 pre-wrap 保住作者的分行，不引 markdown 渲染，免得混进标签 */
.pc-q-desc {
  margin: 10px 0 0;
  font-size: 14px; line-height: 1.9; color: var(--text);
  white-space: pre-wrap; word-break: break-word;
}
.pc-q-io {
  display: flex; flex-wrap: wrap; gap: 4px 20px;
  margin-top: 9px; font-size: 12.5px; line-height: 1.75; color: var(--text-sub);
}
.pc-q-io b { color: var(--text); font-weight: 700; margin-right: 5px; }
.pc-q-hint {
  margin-top: 9px; padding: 7px 11px;
  border-left: 3px solid color-mix(in srgb, var(--amber, #F79009) 55%, transparent);
  background: color-mix(in srgb, var(--amber, #F79009) 7%, transparent);
  border-radius: 0 var(--r-xs) var(--r-xs) 0;
  font-size: 12.5px; line-height: 1.7; color: var(--text-sub);
}

/* 样例。两栏并排，窄屏自动落成一栏；输入和输出用同一种等宽字，
   学生要照着抄数据、对格式，字不对齐很容易看错空格 */
.pc-samples { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 10px; }
.pc-sample {
  flex: 1 1 210px; min-width: 0;
  border: 1px solid var(--border); border-radius: var(--r-sm);
  overflow: hidden;
  background: color-mix(in srgb, var(--text) 3%, var(--card));
}
.pc-sample h6 {
  margin: 0; padding: 5px 10px;
  font-size: 11.5px; font-weight: 700; color: var(--text-sub);
  background: color-mix(in srgb, var(--text) 4%, transparent);
  border-bottom: 1px solid var(--border);
}
/* 一行「输入 3 5」。标签窄、数据宽，标签定宽 28px 才能让两块的数据
   左边缘对齐——不对齐的话一眼看不出是同一段数据 */
.pc-sample-row { display: flex; align-items: baseline; gap: 8px; padding: 6px 10px; }
.pc-sample-row + .pc-sample-row { border-top: 1px dashed var(--border); }
.pc-sample-row b {
  flex: none; width: 28px;
  font-size: 11.5px; font-weight: 700; color: var(--text-faint);
}
.pc-sample-row pre {
  flex: 1; min-width: 0;
  margin: 0; padding: 0;
  font-family: var(--mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: 12.5px; line-height: 1.65; color: var(--text);
  white-space: pre-wrap; word-break: break-all;
}
.pc-sample pre {
  margin: 0; padding: 8px 10px;
  font-family: var(--mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: 12.5px; line-height: 1.65; color: var(--text);
  white-space: pre-wrap; word-break: break-all;
}
/* 输出那块左边拉一条绿线，跟输入区分开——两块上下叠着，
   不加标记的话分不清哪块是「我该给出的」 */
.pc-sample-row pre.pc-sample-out { color: var(--green); }

/* 作答区。.opt / .opt-letter / .opt-body 整套是 style.css 里现成的，
   这里只把容器留白收掉：单题页那边是通栏卡片，卷子里嵌在题行中间，
   20px 的左右内边距会把选项挤得比题干还窄 */
.pc-ans { margin-top: 14px; }
.pc-ans .opt-list { padding: 0; gap: 8px; }
.pc-ans .opt {
  padding: 10px 13px;
  /* style.css 把底色写死成 #fff，深色主题下这一块会白得晃眼 */
  background: var(--card);
  border-width: 1px;
}
/* 选中态要盖回来。style.css 的 `.opt:has(input:checked)` 只给浅色底，
   而上面 `.pc-ans .opt` 把 background 定成了 --card，两者特异度相同、
   ui2.css 后加载，于是选中了跟没选中长得一模一样——只有左边那个小圆点变了，
   学生根本看不出自己选的是哪个 */
.pc-ans .opt:has(input:checked) {
  background: var(--brand-soft);
  border-color: var(--brand);
}
.pc-ans .opt:has(input:checked) .opt-letter {
  background: var(--brand);
  color: #fff;
}
.pc-ans .opt:not(.opt-correct):not(.opt-wrong):hover {
  background: color-mix(in srgb, var(--brand) 5%, var(--card));
  border-color: color-mix(in srgb, var(--brand) 40%, transparent);
}
/* 交卷后的判分色得压过选中态。上面那条 `.pc-ans .opt:has(input:checked)` 跟
   style.css 的 `.opt.opt-correct:has(input:checked)` 特异度一样，而 ui2.css 后加载，
   结果「选对了」的选项还是紫色的——学生看不出自己哪题对了，只看到一片蓝 */
.pc-ans .opt.opt-correct,
.pc-ans .opt.opt-correct:has(input:checked) {
  background: var(--green-soft);
  border-color: var(--green);
}
.pc-ans .opt.opt-correct .opt-letter { background: var(--green); color: #fff; }
.pc-ans .opt.opt-wrong,
.pc-ans .opt.opt-wrong:has(input:checked) {
  background: var(--red-soft);
  border-color: var(--red);
}
.pc-ans .opt.opt-wrong .opt-letter { background: var(--red); color: #fff; }
.pc-ans .opt input:disabled + .opt-letter { opacity: .75; }
.pc-ans .opt:has(input:disabled) { cursor: default; }
.pc-ans .opt-text { font-size: 14px; line-height: 1.7; white-space: pre-wrap; word-break: break-word; }

.pc-ans-bar { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; margin-top: 11px; }
.pc-ans-tip { font-size: 12.5px; color: var(--text-faint); }

/* 代码框。**不引 monaco**：一份卷子可能十几道编程题，每道挂一个
   完整编辑器，光加载就要好几秒，滚动还会卡。这里用等宽 textarea，
   要缩进就自己敲 Tab（下面 tab-size 设成 4，Tab 进去正好一个缩进） */
.pc-code { border: 1px solid var(--border); border-radius: var(--r-sm); overflow: hidden; }
.pc-code-bar {
  display: flex; align-items: center; gap: 10px;
  padding: 7px 10px;
  background: color-mix(in srgb, var(--text) 4%, var(--card));
  border-bottom: 1px solid var(--border);
}
.pc-code-bar select { height: 30px; font-size: 12.5px; padding: 0 8px; }
.pc-code-tip { margin-left: auto; font-size: 12px; color: var(--text-faint); }
.pc-code-ta {
  display: block; width: 100%; box-sizing: border-box;
  min-height: 190px; padding: 12px 14px;
  border: 0; outline: none; resize: vertical;
  font-family: var(--mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: 13px; line-height: 1.75;
  background: var(--card); color: var(--text);
  tab-size: 4;
  transition: min-height var(--dur-2), background var(--dur-2);
}
.pc-code-ta:focus { background: color-mix(in srgb, var(--brand) 3%, var(--card)); }
.pc-code-msg {
  margin: 8px 0 0; padding: 9px 12px;
  max-height: 200px; overflow: auto;
  border-radius: var(--r-xs);
  background: color-mix(in srgb, var(--text) 4%, var(--card));
  font-family: var(--mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: 12.5px; line-height: 1.7; color: var(--text-sub);
  white-space: pre-wrap; word-break: break-word;
}

/* 判题结果条 */
.pc-vslot:empty { display: none; }
.pc-verdict {
  margin-top: 10px; padding: 9px 13px;
  border-radius: var(--r-sm);
  font-size: 13.5px; font-weight: 700;
}
.pc-verdict.ok { background: var(--green-soft); color: var(--green); }
.pc-verdict.bad { background: var(--red-soft); color: var(--red); }
.pc-verdict.wait { background: var(--brand-soft); color: var(--brand); }

/* 右侧一列改成竖排：分值在上、状态徽标在下。
   原来只有分值，横排就够了；现在多了「已做对/做错了」，横着摆会把
   标题挤成两行 */
.pc-q-right { flex-direction: column; align-items: flex-end; gap: 6px; }
/* 题型小标：卷子按题型分了组，但单题上再标一次更省事——
   学生跳到中间某道题时不用往上翻分组标题 */
.pc-q-type {
  display: inline-block; font-size: 11.5px; font-weight: 700;
  color: var(--text-sub); background: var(--track);
  border-radius: var(--r-pill); padding: 2px 9px;
}

.pc-q-state {
  padding: 2px 8px;
  border-radius: var(--r-pill);
  font-size: 11.5px; font-weight: 700; white-space: nowrap;
  background: color-mix(in srgb, var(--text) 6%, transparent);
  color: var(--text-sub);
}
.pc-q-state.is-accepted { background: var(--green-soft); color: var(--green); }
.pc-q-state.is-wrong_answer { background: var(--red-soft); color: var(--red); }
.pc-q-state.is-pending_manual { background: var(--amber-soft, #FEF4E6); color: var(--amber, #F79009); }
.pc-q-state.is-pending,
.pc-q-state.is-judging { background: var(--brand-soft); color: var(--brand); }

@media (max-width: 720px) {
  /* 720 下 .pc-q-right 被铺成整行，竖排会变成「左边分值右边状态」——
     改成横排一行靠右，跟原来的样子一致 */
  .pc-q-right { flex-direction: row; align-items: center; gap: 10px; }
  .pc-q-desc { font-size: 13.5px; line-height: 1.85; }
  .pc-samples { gap: 8px; }
  .pc-sample { flex: 1 1 100%; }
  .pc-code-ta { min-height: 150px; font-size: 12.5px; }
  .pc-code-bar { flex-wrap: wrap; }
  .pc-code-tip { display: none; }
}

/* 题干里的代码块和行内代码。老师写题时敲的是 markdown，卷子上得是代码，
   不能把反引号原样摆给学生看 */
.pc-q-desc .pc-desc-code {
  margin: 9px 0; padding: 10px 13px;
  border-radius: var(--r-xs);
  background: color-mix(in srgb, var(--text) 5%, var(--card));
  border-left: 3px solid color-mix(in srgb, var(--brand) 32%, transparent);
  font-family: var(--mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: 13px; line-height: 1.72; color: var(--text);
  white-space: pre; overflow-x: auto;
}
.pc-q-desc .pc-desc-inline {
  padding: 1px 5px; border-radius: 5px;
  background: color-mix(in srgb, var(--text) 7%, transparent);
  font-family: var(--mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: .92em;
}

/* 题干里的图。老师录题写的是 ![](/uploads/x.png)，卷面上得是真图——
   原来只 escapeHtml 摆原文，学生在卷子上看到的是「![...](...)」这串字符，
   图根本没出来，还以为题目坏了。
   cursor: zoom-in 是给 common.js 那个事件委托看的：任何 .md-img 点一下就
   铺满屏幕，流程图缩到卡片宽度后字太小，孩子看不清 */
.pc-q-desc .md-img {
  display: block; max-width: 100%; height: auto;
  margin: 10px 0; border-radius: var(--r-xs);
  border: 1px solid var(--border); background: #fff;
  cursor: zoom-in;
}
.pc-q-desc a {
  color: var(--brand); font-weight: 600;
  text-decoration: underline; text-underline-offset: 2px;
}

/* ================= 试卷中心：整卷作答（2026-11-01 第三稿） =================
   这一稿把「每题独立提交」改成「整卷一次交卷」，多出来的东西：
   开始页、考试顶栏（倒计时 + 交卷）、底部交卷条、成绩卡、逐题结果条。
   颜色一律用主题变量，十套配色都能跟着走。 */

/* ---------- 答题顶栏：钉在视口最顶上，滚到哪都能交卷 ---------- */
/* 原来写的是 sticky top:82px 的一张浮动卡片。那个 82 是按导航条高度估的，
   可导航条本身并不真粘（父容器跟它一样高，粘不住），一滚就走，
   于是顶上留下 82px 的空档：正文从这条缝里露出来，卡片自己又压在正文中间，
   编程题的代码框正好被它切掉一截。
   现在 top 归零、去掉上边框和上面的圆角，它就是一条顶栏——
   正文从它下面开始滚，不再有一条缝，也不再有一张飘在题面上的卡 */
.pc-exam-bar {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  gap: var(--s4);
  padding: 9px 14px;
  margin: 0 0 var(--s5);
  background: var(--card);
  border: 1px solid var(--border);
  border-top: 0;
  border-radius: 0 0 var(--r-lg) var(--r-lg);
  box-shadow: 0 14px 26px -24px rgba(15, 23, 42, .55);
}
.pc-exam-h { min-width: 0; flex: 1; }
.pc-exam-h h1 {
  margin: 0;
  font-size: 16px;
  font-weight: 800;
  letter-spacing: -.2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.pc-exam-h .pc-mmeta {
  display: flex;
  gap: 12px;
  margin-top: 3px;
  font-size: 12px;
  color: var(--text-faint);
}
.pc-exam-h .pc-mmeta b { color: var(--text-sub); font-weight: 700; }

.pc-exam-act {
  display: flex;
  align-items: center;
  gap: 12px;
  flex: none;
}
.pc-answered { font-size: 12.5px; color: var(--text-sub); white-space: nowrap; }
.pc-answered b { font-weight: 800; color: var(--brand); font-variant-numeric: tabular-nums; }

.pc-timer {
  flex: none;
  font-size: 13.5px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  color: var(--brand);
  background: var(--brand-soft);
  padding: 5px 12px;
  border-radius: 999px;
  white-space: nowrap;
}
.pc-timer.is-warn { color: var(--red); background: var(--red-soft); }
.pc-timer.is-off { color: var(--text-faint); background: transparent; font-weight: 600; }

/* 成绩页的顶栏不需要操作区，标题居中一点 */
.pc-exam.is-result .pc-exam-bar { background: var(--card); }

/* ---------- 左栏：已答标记 ----------
   选择器必须写全 `.wrap .pc-navgrid a.pc-navno.is-ans`（0,4,1）：
   上面那条基础格子样式是 (0,3,1)，只写 `.pc-navno.is-ans`（0,2,0）会被它整个盖掉，
   底色、边框、字色全都不生效，最后只剩下一个 5px 的小圆点——
   这就是「已答题标记不明显」的真正原因，不是颜色不够重，是规则根本没赢。

   样子也重做了：已答整格填品牌浅底 + 品牌描边 + 右上角绿色对勾，
   扫一眼就能数清还剩几个白格子。学生交卷前就靠这个找漏题。 */
.wrap .pc-navgrid a.pc-navno { position: relative; }
.wrap .pc-navgrid a.pc-navno.is-ans {
  background-color: color-mix(in srgb, var(--brand) 15%, var(--card));
  border-color: var(--brand);
  border-width: 1.5px;
  color: var(--brand);
  font-weight: 800;
}
.wrap .pc-navgrid a.pc-navno.is-ans::after {
  content: "✓";
  position: absolute;
  right: -5px;
  top: -5px;
  width: 13px;
  height: 13px;
  border-radius: 50%;
  background: var(--green);
  color: #fff;
  font-size: 8.5px;
  line-height: 13px;
  text-align: center;
  font-weight: 700;
  /* 一圈底色描边，把对勾从格线里摘出来，六个挨着也不会糊成一片 */
  box-shadow: 0 0 0 2px var(--card);
}
.wrap .pc-navgrid a.pc-navno.is-ans.is-cur {
  background: var(--brand);
  border-color: var(--brand);
  color: #fff;
}
.wrap .pc-navgrid a.pc-navno.is-wrong {
  background-color: color-mix(in srgb, var(--red) 13%, var(--card));
  border-color: var(--red);
  border-width: 1.5px;
  color: var(--red);
  font-weight: 800;
}
.wrap .pc-navgrid a.pc-navno.is-wrong::after {
  content: "✕";
  position: absolute;
  right: -5px; top: -5px;
  width: 13px; height: 13px; border-radius: 50%;
  background: var(--red); color: #fff;
  font-size: 8.5px; line-height: 13px; text-align: center; font-weight: 700;
  box-shadow: 0 0 0 2px var(--card);
}
.pc-legend-dot.ans { background: color-mix(in srgb, var(--brand) 15%, var(--card)); border-color: var(--brand); }
.pc-legend-dot.wrong { background: var(--red); border-color: var(--red); }

/* 右栏徽标：作答阶段的「未答 / 已答」。已答给实底，未答留灰——
   灰底小字是「还没轮到你」的观感，实底白字才是「这条已经落定」 */
.pc-q-state.is-answered {
  background: var(--brand);
  color: #fff;
  font-weight: 800;
}

/* 题行本身也留一道痕：滚到中间时，右栏徽标可能已经划出屏幕，
   左边这 3px 竖线还在。已答用品牌色，未答透明（占位不跳行） */
.pc-q { border-left: 3px solid transparent; }
.pc-q.is-answered { border-left-color: var(--brand); }

/* ---------- 底部交卷条 ---------- */
.pc-submit-bar {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 12px;
  margin: var(--s6) 0 var(--s5);
  padding: var(--s5);
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-1);
}
.pc-submit-tip { margin-right: auto; font-size: 12.5px; color: var(--text-faint); }

/* ---------- 开始页 ---------- */
.pc-intro {
  max-width: 720px;
  margin: 0 auto;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  box-shadow: var(--sh-1);
  padding: var(--s7);
}
.pc-intro-head { text-align: center; padding-bottom: var(--s5); border-bottom: 1px solid var(--border); }
.pc-intro-head svg { width: 30px; height: 30px; color: var(--brand); }
.pc-intro-head h1 { margin: 8px 0 6px; font-size: 21px; font-weight: 800; letter-spacing: -.4px; }
.pc-intro-head p { margin: 0; font-size: 13px; color: var(--text-sub); line-height: 1.7; }

.pc-intro-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1px;
  background: var(--border);
  border-radius: var(--r-md, 12px);
  overflow: hidden;
  margin: var(--s5) 0;
}
.pc-intro-grid > div {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 14px 10px;
  background: var(--card);
  text-align: center;
}
.pc-intro-grid span { font-size: 11.5px; color: var(--text-faint); }
.pc-intro-grid b { font-size: 19px; font-weight: 800; color: var(--text); font-variant-numeric: tabular-nums; }

.pc-intro-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  padding: 10px 2px;
  border-bottom: 1px dashed var(--border);
  font-size: 13.5px;
}
.pc-intro-row b { font-weight: 700; }
.pc-intro-row span { color: var(--text-faint); font-size: 12.5px; }

.pc-intro-tip {
  margin: var(--s5) 0;
  padding: 12px 14px;
  background: var(--brand-soft);
  border-radius: var(--r-md, 12px);
  font-size: 12.5px;
  line-height: 1.75;
  color: var(--text-sub);
}
.pc-intro-act { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }

/* ---------- 成绩卡 ---------- */
.pc-scorecard {
  display: flex;
  align-items: center;
  gap: var(--s5);
  margin-bottom: var(--s5);
  padding: var(--s6);
  border-radius: var(--r-lg);
  border: 1px solid var(--border);
  background: var(--card);
  box-shadow: var(--sh-1);
}
.pc-scorecard.is-excellent { background: linear-gradient(135deg, var(--green-soft), var(--card) 62%); }
.pc-scorecard.is-pass { background: linear-gradient(135deg, var(--brand-soft), var(--card) 62%); }
.pc-scorecard.is-fail { background: linear-gradient(135deg, var(--red-soft), var(--card) 62%); }
.pc-sc-num { display: flex; align-items: baseline; gap: 4px; flex: none; }
.pc-sc-num b {
  font-size: 46px;
  font-weight: 800;
  line-height: 1;
  letter-spacing: -2px;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}
.pc-sc-num span { font-size: 14px; color: var(--text-faint); font-weight: 600; }
.pc-sc-main { flex: 1; min-width: 0; }
.pc-sc-main h2 { margin: 0 0 5px; font-size: 16px; font-weight: 800; }
.pc-sc-main p { margin: 0; font-size: 12.5px; color: var(--text-sub); line-height: 1.7; }
.pc-sc-pct {
  flex: none;
  font-size: 22px;
  font-weight: 800;
  color: var(--brand);
  font-variant-numeric: tabular-nums;
}
.pc-sc-pct small { font-size: 12px; font-weight: 600; margin-left: 1px; }

/* ---------- 逐题结果 ---------- */
.pc-result-note { margin-top: 10px; }
/* 题里压根没写解析时，别留一片空白让学生以为界面卡了 */
.pc-noexp { margin-top: 8px; color: var(--muted); font-size: 12.5px; }

.pc-explain {
  margin-top: 10px;
  padding: 12px 14px;
  background: var(--bg-soft, color-mix(in srgb, var(--text) 4%, transparent));
  border-radius: var(--r-md, 12px);
  font-size: 12.5px;
  line-height: 1.8;
  color: var(--text-sub);
}
.pc-explain b { display: block; margin-bottom: 4px; color: var(--text); font-size: 12px; }

/* 老师评语。跟解析不是一个东西：解析讲题，评语讲人。
   配色也分开——解析是灰底，评语给一层淡绿，学生一眼知道「这是老师对我说的话」 */
.pc-tcomment {
  margin-top: 10px;
  padding: 12px 14px;
  background: color-mix(in srgb, var(--green, #12B76A) 9%, transparent);
  border-radius: var(--r-md, 12px);
  font-size: 12.5px;
  line-height: 1.8;
  color: var(--text);
}
.pc-tcomment b { display: block; margin-bottom: 4px; font-size: 12px; color: var(--green, #12B76A); }
/* 作品名后面跟一个入口，学生交完想回去改改，不用自己翻创作中心。
   :has 只在带这个按钮的提示条上开 flex，别的题的提示条还是原来的竖排 */
.pc-q-hint:has(.pc-work-open) { display: flex; align-items: center; gap: 10px; }
.pc-work-open { margin-left: auto; flex: 0 0 auto; }

/* 成绩页里代码框是只读的，别让它看着像能改 */
.pc-exam.is-result .pc-code-ta { background: color-mix(in srgb, var(--text) 3%, var(--card)); cursor: default; }
.pc-code-lang {
  font-size: 12.5px;
  font-weight: 700;
  color: var(--text-sub);
  padding: 3px 10px;
  border-radius: 6px;
  background: color-mix(in srgb, var(--text) 6%, transparent);
}

/* ---------- 窄屏 ---------- */
@media (max-width: 900px) {
  .pc-exam-bar { top: 0; flex-wrap: wrap; gap: 6px 10px; padding: 8px 12px; }
  /* 手机上顶栏一折行就占掉 150px，快赶上半个题目区了。
     「总分 / 题数 / 不限 / 截止」这四段在小屏上没人看，先收掉，
     留下标题、倒计时、已答、交卷——这四样才是做题时要瞟的 */
  .pc-exam-bar .pc-mmeta { display: none; }
  /* 一折行就是三行，占掉 150px。小屏上改成一行：标题（自动省略）+
     倒计时 + 交卷，「已答 x/N」收进左栏——它本来在那儿 */
  .pc-exam-bar { flex-wrap: nowrap; }
  .pc-exam-h { flex: 1 1 auto; min-width: 0; }
  .pc-exam-h h1 { font-size: 15px; }
  .pc-exam-act { width: auto; flex: none; gap: 8px; justify-content: flex-end; }
  .pc-exam-act .pc-answered { display: none; }
  .pc-scorecard { flex-wrap: wrap; gap: var(--s4); padding: var(--s5); }
  .pc-sc-num b { font-size: 36px; }
  .pc-intro { padding: var(--s5); }
  .pc-intro-grid { grid-template-columns: repeat(2, 1fr); }
  .pc-submit-bar { flex-wrap: wrap; }
  .pc-submit-tip { flex-basis: 100%; margin-bottom: 4px; }
}

/* ---------------------------------------------------------------- 训练中心（2026-11-01） */

/* 题型按钮：四颗胶囊 → 四张卡。
   原来是四颗字一样大、只靠底色区分选没选中，四颗排开分不清主次；
   现在每张卡带一个图标和一句「这个题型是怎么做的」，选中时整张卡换浅底 + 描边，
   扫一眼就知道自己在哪一类。JS 那边还按 .chip 和 .active 找元素，所以类名照旧 */
.tbar { align-items: center; }
.ttype {
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 10px; flex: 1; min-width: 0;
}
.tchip {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 14px; border-radius: 13px; text-align: left;
  background: var(--card); border: 1.5px solid var(--border);
  color: var(--text-sub);
}
.tchip-ic {
  width: 34px; height: 34px; flex: none; border-radius: 10px;
  display: grid; place-items: center;
  background: var(--track); color: var(--text-sub);
  transition: background var(--dur-1), color var(--dur-1);
}
.tchip-ic svg { width: 17px; height: 17px; }
.tchip-tx { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.tchip-tx b {
  font-size: 14px; font-weight: 800; color: var(--text); line-height: 1.35;
}
.tchip-tx em {
  font-style: normal; font-size: 11.5px; color: var(--text-sub); line-height: 1.35;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.tchip:hover {
  transform: translateY(-1px); box-shadow: var(--sh-1);
  border-color: var(--brand);
}
.tchip:hover .tchip-tx b { color: var(--brand); }
/* .chip.active 在 style.css 里是品牌色实底。四张卡都实底的话整排像一排按钮，
   选中和没选中只差颜色深浅；这里改成浅底 + 描边，靠图标那一块实色来指认 */
.tchip.active {
  background: var(--brand-soft); border-color: var(--brand);
  box-shadow: 0 2px 10px -4px var(--brand);
}
.tchip.active .tchip-ic { background: var(--brand); color: #fff; }
.tchip.active .tchip-tx b { color: var(--brand-dark); }
.tchip.active .tchip-tx em { color: var(--brand); opacity: .85; }

.tbar .search { flex: none; width: 240px; margin-left: 0; }

/* 五个 select 收起来，用户看到的是下面 fLines 里的可点选项。
   收起来而不是删掉：filterParams / 字典灌值 / 清空 全挂在 select.value 上 */
.fc-src { display: none; }
/* 「已筛出 N 道」没筛过时不显示。空 span 留着圆角底会画出一小块灰疙瘩 */
.fc-count:empty { display: none; }
.fc-lines .pc-frow { padding: 4px 0; }
.fc-lines .pc-frow > label { font-size: 13px; width: 56px; }
.fc-lines .pc-chip { font-size: 13.5px; padding: 4px 12px; }

@media (max-width: 900px) {
  .tbar { flex-wrap: wrap; }
  .ttype { grid-template-columns: repeat(2, minmax(0, 1fr)); flex-basis: 100%; }
  .tbar .search { width: 100%; }
  .fc-lines .pc-frow { flex-direction: column; gap: 2px; }
  .fc-lines .pc-frow > label { width: auto; line-height: 1.6; }
}

/* 窄屏的 banner 遮罩要换个方向。
   上面那层是 90deg 横向渐变——宽屏时文字只占左边四成，右边正好让开；
   390 宽时文字盒横跨整个宽度，右边几行白字直接压在插画上，读不清。
   改成上下渐变：文字在上半部，上半压暗，下半把图透出来 */
@media (max-width: 700px) {
  .banner.has-img::after {
    background: linear-gradient(180deg,
      rgba(12, 14, 24, .86) 0%,
      rgba(12, 14, 24, .74) 40%,
      rgba(12, 14, 24, .34) 70%,
      rgba(12, 14, 24, .12) 100%);
  }
}

/* 窄屏 banner 的高度要跟着内容收，不然图被 cover 放大两三倍，人物正好压在标题上。
   390 宽时把内边距和三个数字块都缩一号，整条压到 220 上下——这时候 16:9 的图
   按宽度铺满、几乎不裁，人物退到文字后面当背景，不再是「字叠在人脸上」 */
@media (max-width: 700px) {
  .banner { min-height: 0; }
  .banner-body { padding: var(--s4) var(--s5); }
  .banner-body h1 { font-size: 22px; }
  .banner-eyebrow { font-size: 10.5px; padding: 3px 9px; margin-bottom: var(--s2); }
  .banner-stats { margin-top: var(--s3); gap: 6px; }
  .banner-stat { padding: 7px 12px; min-width: 74px; }
  .banner-stat b { font-size: 19px; }
  .banner-stat span { font-size: 10.5px; }
}

/* ---------------------------------------------------------------- 站内确认框 */
.cf-mask {
  position: fixed; inset: 0; z-index: 1200;
  display: grid; place-items: center;
  padding: 20px;
  background: rgba(12, 16, 26, .42);
  backdrop-filter: blur(3px);
  opacity: 0; transition: opacity .16s ease;
}
.cf-mask.is-in { opacity: 1; }
.cf-card {
  width: min(420px, 100%);
  background: var(--card);
  border-radius: var(--r-lg);
  box-shadow: 0 30px 60px -24px rgba(12, 16, 26, .45);
  padding: 24px 24px 18px;
  transform: translateY(6px) scale(.985);
  transition: transform .18s cubic-bezier(.2, .9, .3, 1);
}
.cf-mask.is-in .cf-card { transform: none; }
.cf-card h3 {
  margin: 0 0 10px; font-size: 17px; font-weight: 800; color: var(--text);
  letter-spacing: -.2px;
}
.cf-body {
  font-size: 14px; line-height: 1.75; color: var(--text-sub);
  margin-bottom: 20px;
}
.cf-act { display: flex; justify-content: flex-end; gap: 10px; }
.cf-act .btn { min-width: 88px; justify-content: center; }
/* 交卷这种不可逆的动作，主按钮压成红调，跟「保存」区分开 */
.cf-act .cf-danger {
  background: var(--red, #F04438); border-color: var(--red, #F04438);
}
.cf-act .cf-danger:hover { filter: brightness(1.06); }

/* ---------------------------------------------------------------- 样式管理页 */
.bn-sec {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: 22px 24px 18px;
  margin-bottom: 16px;
}
.bn-sec-h { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.bn-sec-h h2 {
  margin: 0; font-size: 16px; font-weight: 800; color: var(--text); letter-spacing: -.2px;
}
.bn-spec {
  font-size: 12px; font-weight: 700; color: var(--brand);
  background: var(--brand-soft); border-radius: var(--r-pill);
  padding: 3px 10px;
}
.bn-sec-tip {
  margin: 6px 0 16px; font-size: 12.5px; line-height: 1.7; color: var(--text-sub);
}
.bn-cur {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin: 12px 0 4px; padding: 9px 14px;
  background: var(--track); border-radius: var(--r-md);
  font-size: 12.5px; color: var(--text-sub);
}
.bn-cur-name { font-weight: 800; color: var(--text); font-size: 13px; }
.bn-cur-size { margin-left: auto; }
.bn-cur-size b { color: var(--brand); font-weight: 800; }
.bn-sec-plan { opacity: .72; }


/* ---------- 整卷/整份作业批量上传（/admin/sheets/import） ----------
   卷头字段用两列网格：七个字段竖着排要滚两屏，
   而它们都是短值（标题、来源、年份、时长），横着放一行两个正好 */
.sh-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 12px;
  margin-top: 4px;
}
.sh-grid label { display: flex; flex-direction: column; gap: 5px;
  font-size: 12.5px; font-weight: 600; color: var(--text-sub); }
.sh-wide { display: flex; flex-direction: column; gap: 5px; margin-top: 12px;
  font-size: 12.5px; font-weight: 600; color: var(--text-sub); }
/* 每题的分值跟在题型徽标后面，扫一眼总分怎么来的 */
.sh-score {
  font-size: 11.5px; font-weight: 800; color: var(--brand);
  background: var(--brand-soft); border-radius: var(--r-pill); padding: 2px 8px;
}


/* ---------- 前台「作业练习」（/homework） ----------
   一张表，照学生最熟的样子排：编号、名称、起止时间、客观/主观题数、得分、操作。
   为什么用表格不用卡片：作业是「一排一排对着看」的东西，学生关心的是
   「哪份快到期了」「我上次考了多少」，横向对齐比纵向卡片好扫。 */
/* 门面之后的那句说明。标题和两个数都收进 banner 里了（跟别的中心页一样），
   但「做一半退出答案会留着」这句得留着——学生最怕的就是关了浏览器白做 */
.hw-note {
  margin: 0 0 14px;
  font-size: 13px;
  color: var(--text-sub);
}

.hw-tabs { display: flex; gap: 6px; margin-bottom: 12px; }
.hw-tab {
  border: 1px solid var(--line); background: var(--card);
  border-radius: var(--r-pill); padding: 6px 16px;
  font-size: 13px; font-weight: 600; color: var(--text-sub); cursor: pointer;
  transition: all var(--dur-1, .16s) ease;
}
.hw-tab:hover { color: var(--brand); border-color: color-mix(in srgb, var(--brand) 40%, transparent); }
.hw-tab.is-on { background: var(--brand); border-color: var(--brand); color: #fff; }

.hw-tablewrap {
  border: 1px solid var(--line); border-radius: var(--r-lg);
  background: var(--card); overflow: hidden;
}
.hw-table { width: 100%; border-collapse: collapse; }
.hw-table th {
  text-align: left; font-size: 12px; font-weight: 700; color: var(--text-sub);
  padding: 12px 14px; background: color-mix(in srgb, var(--brand) 5%, var(--card));
  border-bottom: 1px solid var(--line); white-space: nowrap;
}
.hw-table td {
  padding: 13px 14px; border-bottom: 1px solid var(--line);
  font-size: 13.5px; vertical-align: middle;
}
.hw-table tr:last-child td { border-bottom: 0; }
.hw-table tbody tr:hover { background: color-mix(in srgb, var(--brand) 3%, transparent); }
/* 过期的整行压暗：不是禁用，是「这事翻篇了」的观感 */
.hw-table tr.is-dead-row td { color: var(--text-faint, var(--text-sub)); }
.hw-no { color: var(--text-sub); font-variant-numeric: tabular-nums; }
.hw-title-cell { max-width: 420px; }
.hw-name { font-weight: 700; color: var(--text); text-decoration: none; }
.hw-name:hover { color: var(--brand); }
.hw-name.is-dead { color: var(--text-sub); font-weight: 600; cursor: default; }
.hw-sub { margin-top: 4px; font-size: 12px; color: var(--text-sub);
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.hw-time { color: var(--text-sub); font-size: 12.5px; white-space: nowrap;
  font-variant-numeric: tabular-nums; }
.hw-num { text-align: center; font-variant-numeric: tabular-nums; }
.hw-score { color: var(--brand); font-weight: 800; }
.hw-score-quiet { color: var(--text-sub); }
.hw-act { text-align: right; white-space: nowrap; }
.hw-act .is-dead { opacity: .62; }

.hw-empty {
  border: 1px dashed var(--line); border-radius: var(--r-lg);
  background: var(--card); padding: 54px 20px; text-align: center;
}
.hw-empty b { display: block; font-size: 15px; margin-bottom: 6px; }
.hw-empty span { font-size: 13px; color: var(--text-sub); }

/* 只读查看态（过期作业）顶部那条说明 */
.pc-view-tip {
  display: flex; flex-direction: column; gap: 3px;
  margin: 0 0 14px; padding: 13px 16px;
  border: 1px solid color-mix(in srgb, var(--amber, #d97706) 30%, transparent);
  border-radius: var(--r-lg);
  background: color-mix(in srgb, var(--amber, #d97706) 8%, var(--card));
}
.pc-view-tip b { font-size: 14px; }
.pc-view-tip span { font-size: 12.5px; color: var(--text-sub); }
/* 老师预览用的是同一条提示条，但意思完全相反：不是「过期了」，是
   「现在就能看」。跟着用琥珀色，老师会以为这作业出问题了 */
.pc-view-tip.is-preview {
  border-color: color-mix(in srgb, var(--brand) 30%, transparent);
  background: color-mix(in srgb, var(--brand) 7%, var(--card));
}

/* ---------- 作业练习：窄屏 ---------- */
/* 这张表是照电脑屏抄的：八列排开，手机上会被压成一列一列竖着写的字。
   小屏上留「名称 / 结束时间 / 得分 / 操作」四列——编号是给我们看的，
   开始时间和客观/主观题数做作业的时候用不上，收掉 */
@media (max-width: 720px) {
  .hw-tablewrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .hw-table th:nth-child(1), .hw-table td:nth-child(1),
  .hw-table th:nth-child(3), .hw-table td:nth-child(3),
  .hw-table th:nth-child(5), .hw-table td:nth-child(5),
  .hw-table th:nth-child(6), .hw-table td:nth-child(6) { display: none; }
  /* 固定列宽：不然「名称」那列被挤成一字一行——它的最小宽度只有一个汉字 */
  .hw-table { table-layout: fixed; }
  .hw-table th:nth-child(2) { width: auto !important; }
  .hw-table .hw-sub { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .hw-table th:nth-child(4) { width: 100px !important; }
  .hw-table th:nth-child(7) { width: 58px !important; }
  .hw-table th:nth-child(8) { width: 82px !important; }
  .hw-table th, .hw-table td { padding: 10px 8px; font-size: 11.5px; }
  /* 日期挤到得分列上去过：两列都得钉死，不能靠内容撑 */
  .hw-table .hw-time, .hw-table .hw-num { white-space: nowrap; }
  .hw-table .hw-name { white-space: normal; }
  .hw-table .hw-sub { font-size: 11.5px; }
  .hw-note { font-size: 12px; }
}

/* ---------------------------------------------------------------- 站内消息 */
/* 铃铛挂在顶栏右侧。未读数用小圆点压在铃铛右上角——
   放数字条会把铃铛撑成一个胶囊，在只有「退出」和头像的右半边显得很重 */
.nav-right { position: relative; }
.nav-bell {
  position: relative;
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; flex: none;
  border: 1px solid var(--border); border-radius: 50%;
  background: var(--card); color: var(--text-sub);
  cursor: pointer; transition: color var(--dur-1) var(--ease-out),
    border-color var(--dur-1) var(--ease-out), background var(--dur-1) var(--ease-out);
}
.nav-bell:hover { color: var(--brand); border-color: var(--brand); background: var(--brand-soft); }
.nav-bell-dot {
  position: absolute; top: -4px; right: -4px;
  min-width: 16px; height: 16px; padding: 0 4px;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 10px; font-style: normal; font-weight: 700; line-height: 1;
  color: #fff; background: var(--red);
  border-radius: var(--r-pill);
  box-shadow: 0 0 0 2px var(--card);
}
.nav-drawer-badge {
  margin-left: auto; min-width: 18px; height: 18px; padding: 0 5px;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: 700; color: #fff; background: var(--red);
  border-radius: var(--r-pill);
}

.msg-pop {
  position: absolute; top: calc(100% + 12px); right: 0;
  width: 340px; max-width: calc(100vw - 32px);
  display: flex; flex-direction: column;
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--r-md); box-shadow: var(--sh-3);
  z-index: 70; overflow: hidden;
}
.msg-pop[hidden] { display: none; }
.msg-pop-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 12px 14px; border-bottom: 1px solid var(--border);
}
.msg-pop-head b { font-size: var(--fs-sm); }
.msg-pop-act {
  border: 0; background: none; cursor: pointer; padding: 2px 4px;
  font-size: var(--fs-cap); color: var(--brand);
}
.msg-pop-act:hover { text-decoration: underline; }
.msg-pop-list { max-height: 380px; overflow-y: auto; }
.msg-item {
  display: flex; flex-direction: column; gap: 3px;
  padding: 11px 14px; border-bottom: 1px solid var(--border);
  text-decoration: none; color: inherit; position: relative;
}
.msg-item:last-child { border-bottom: 0; }
.msg-item:hover { background: var(--brand-soft); }
.msg-item.is-unread::before {
  content: ''; position: absolute; left: 5px; top: 17px;
  width: 5px; height: 5px; border-radius: 50%; background: var(--red);
}
.msg-item.is-unread .msg-item-t { font-weight: 650; }
.msg-item-t { font-size: var(--fs-sm); color: var(--text); line-height: 1.4; }
.msg-item-b {
  font-size: var(--fs-cap); color: var(--text-sub); line-height: 1.45;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
.msg-item-d { font-size: 11.5px; color: var(--text-faint); }
.msg-pop-foot {
  display: block; padding: 10px; text-align: center;
  font-size: var(--fs-cap); color: var(--text-sub);
  border-top: 1px solid var(--border); text-decoration: none;
}
.msg-pop-foot:hover { color: var(--brand); background: var(--brand-soft); }
.msg-empty { padding: 26px 16px; text-align: center; font-size: var(--fs-cap); color: var(--text-sub); }

@media (max-width: 900px) {
  /* 窄屏导航右侧只留头像，铃铛留着——红点是「有事找你」，比退出重要 */
  .msg-pop { position: fixed; left: 12px; right: 12px; width: auto; top: 64px; }
}

/* 消息中心页 */
.msg-head {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: var(--s4); flex-wrap: wrap; margin: 0 0 var(--s5);
}
.msg-head h1 { margin: 0 0 4px; font-size: var(--fs-h1); }
.msg-sub { margin: 0; color: var(--text-sub); font-size: var(--fs-sm); }
.msg-actions { display: flex; gap: var(--s2); flex-wrap: wrap; }

.msg-list {
  background: var(--card); border: 1px solid var(--border);
  border-radius: var(--r-lg); overflow: hidden;
}
.msg-row { border-bottom: 1px solid var(--border); position: relative; }
.msg-row:last-child { border-bottom: 0; }
.msg-row.is-unread { background: color-mix(in srgb, var(--brand-soft) 45%, var(--card)); }
.msg-row-main { padding: 14px var(--s5) 14px 22px; }
.msg-row.is-unread .msg-row-main::before {
  content: ''; position: absolute; left: 9px; top: 21px;
  width: 6px; height: 6px; border-radius: 50%; background: var(--red);
}
.msg-row-top { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }
.msg-row-t { font-size: var(--fs-body); font-weight: 600; color: var(--text); }
.msg-row.is-unread .msg-row-t { font-weight: 700; }
.msg-row-b {
  margin: 5px 0 0; font-size: var(--fs-sm); color: var(--text-sub);
  line-height: 1.55; white-space: pre-wrap;
}
.msg-row-foot {
  display: flex; align-items: center; gap: var(--s4);
  margin-top: 7px; font-size: 12.5px; color: var(--text-faint);
}
.msg-row-foot a { color: var(--brand); text-decoration: none; }
.msg-row-foot a:hover { text-decoration: underline; }
.msg-row-del {
  margin-left: auto; border: 0; background: none; cursor: pointer;
  font-size: 12.5px; color: var(--text-faint); padding: 2px 4px;
}
.msg-row-del:hover { color: var(--red); }

.msg-kind {
  flex: none; padding: 1px 7px; border-radius: var(--r-pill);
  font-size: 11.5px; font-weight: 600; line-height: 1.7;
}
.msg-kind.is-work { color: var(--brand); background: var(--brand-soft); }
.msg-kind.is-grade { color: var(--green); background: var(--green-soft); }
.msg-kind.is-review { color: var(--amber); background: var(--amber-soft); }
.msg-kind.is-sys { color: var(--blue); background: var(--blue-soft); }

.msg-pager {
  display: flex; align-items: center; justify-content: center; gap: var(--s3);
  margin: var(--s5) 0 0; font-size: var(--fs-sm); color: var(--text-sub);
}
.nav-bell-link { text-decoration: none; }


/* ── 训练中心首页的两个入口（题单广场 / 试卷中心）────────────────────────
   从顶部导航收进来的。两张卡并排，跟 banner 同宽，
   左边一个图标块提亮，右边箭头指出「这是能点进去的」。
   不用纯色大块：站内其他地方都是白卡 + 品牌色点缀，
   这里突然出现两块饱和色，看着像广告位，不像导航 */
.hub-row{
  display:grid; grid-template-columns:1fr 1fr; gap:var(--s4);
  margin:var(--s5) 0 var(--s3);
}
.hub-card{
  display:flex; align-items:center; gap:var(--s4);
  padding:var(--s4) var(--s5);
  background:var(--card); border:1px solid var(--border);
  border-radius:var(--r-md); box-shadow:var(--sh-1);
  text-decoration:none; color:inherit;
  transition:transform var(--dur-2) var(--ease-out),
             box-shadow var(--dur-2) var(--ease-out),
             border-color var(--dur-2) var(--ease-out);
}
.hub-card:hover{
  transform:translateY(-2px);
  border-color:color-mix(in srgb, var(--brand) 34%, var(--border));
  box-shadow:var(--sh-2);
}
.hub-ic{
  flex:0 0 46px; height:46px; border-radius:var(--r-sm);
  display:grid; place-items:center;
}
.hub-ic svg{ width:24px; height:24px; }
.hub-ic-sheet{ background:var(--brand-soft); color:var(--brand); }
.hub-ic-paper{ background:var(--blue-soft); color:var(--blue); }
.hub-tx{ display:flex; flex-direction:column; gap:3px; min-width:0; }
.hub-tx b{ font-size:var(--fs-h3); font-weight:650; letter-spacing:-.01em; }
.hub-tx em{
  font-style:normal; font-size:var(--fs-cap); color:var(--text-sub);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.hub-go{
  flex:0 0 18px; height:18px; margin-left:auto;
  color:var(--text-faint);
  transition:transform var(--dur-2) var(--ease-out), color var(--dur-2) var(--ease-out);
}
.hub-card:hover .hub-go{ transform:translateX(3px); color:var(--brand); }

@media (max-width:720px){
  .hub-row{ grid-template-columns:1fr; gap:var(--s3); }
  .hub-card{ padding:var(--s3) var(--s4); }
  .hub-tx em{ white-space:normal; }
}
