/* ==========================================================================
   CourseBuddy（作业提交与管理系统）—— 界面主题
   --------------------------------------------------------------------------
   扁平风格浅色主题：页底和卡面都是白的，层次靠一道细描边加上几档浅灰底
   分出来，卡片和按钮上不放投影。配色整体仍是浅色，但语义色各自占一个分得开的
   色相（蓝、绿、琥珀、红、青、紫、蓝绿），一页里并排十几个徽章时不会糊成一片。

   改色仍然靠 Bootstrap 5.3 自带的 CSS 变量：.alert-success、.table 这些组件
   把颜色抽成了 --bs-*-* 变量，在这里重新赋值就能整体换肤，不用去动 30 个模板
   里的类名。唯一的例外是**实心按钮**——5.3 编译出来的 .btn-primary 里
   --bs-btn-bg 是写死的字面值，读不到 --bs-primary，所以第 5 节按色相逐个重写。

   语义色一律用「浅底 + 深字」的 tonal 写法，而不是 Bootstrap 默认的饱和底白字——
   后者在一页十几个徽章的成绩单里会非常刺眼。
   ========================================================================== */

:root {
    /* 只有浅色一套。写死 color-scheme 可以挡住系统深色模式对原生控件
       （滚动条、下拉箭头、日期选择器）的接管。 */
    color-scheme: light;

    /* 主色：比 Material 的 Google Blue 再饱和一点，扁平色块下更立得住 */
    --gc-primary: #2563eb;
    --gc-primary-hover: #1d4ed8;
    --gc-primary-active: #1e40af;
    --gc-primary-soft: #e8effd;
    --gc-primary-soft-strong: #d2e2fc;

    /* 文字与灰阶。都带一点蓝，不用纯灰——纯灰挨着白卡片会显脏。
       页底和卡面都是白的，中间只隔一道 1px 描边，所以分层全靠 --gc-border
       这道线和下面几档浅灰底：卡片头、表头、徽章各浅一档，在白底上才分得出来。
       这道描边不能省，省了整页就糊成一块。
       页底单独留一个变量（现在和 --gc-surface 同值），是为了以后想整体调浅
       页底时只动这一处。 */
    --gc-text: #0f172a;
    --gc-text-secondary: #5a6579;
    --gc-text-disabled: #98a1b3;
    --gc-border: #d7dee9;
    --gc-border-soft: #e8edf5;
    --gc-surface: #ffffff;
    --gc-surface-2: #f5f8fc;
    --gc-surface-3: #eaf0f8;
    --gc-page: #ffffff;

    /* 页脚是整站唯一一块深色。浅浅的页面压一条深色底边，比再画一道灰线更能
       把版面收住。取的是带蓝的深灰而非纯黑——它和上面这套灰阶同一个色系，
       摆在 #eef2f8 下面不会像另一套东西。 */
    --gc-footer: #1e293b;
    --gc-footer-text: #cbd5e1;

    /* 语义色：主色 / 浅底 / 深字。
       浅底取「饱和色与白之间」，深字取该色相 700 号上下——这样并排放在一起时
       色相分得开，明度又齐平，不会有的徽章重、有的发飘。 */
    --gc-green: #16a34a;
    --gc-green-soft: #e6f6ec;
    --gc-green-text: #15803d;
    --gc-yellow: #d97706;
    --gc-yellow-soft: #fdf2e3;
    --gc-yellow-text: #b45309;
    --gc-red: #dc2626;
    --gc-red-soft: #fdeaea;
    --gc-red-text: #b91c1c;
    --gc-cyan: #0891b2;
    --gc-cyan-soft: #e3f5fa;
    --gc-cyan-text: #0e7490;
    --gc-violet: #7c3aed;
    --gc-violet-soft: #f1eafe;
    --gc-violet-text: #6d28d9;
    --gc-teal: #0d9488;
    --gc-teal-soft: #e2f5f2;
    --gc-teal-text: #0f766e;

    --gc-radius: 12px;
    --gc-radius-sm: 8px;

    /* 只有真正浮在内容之上、和底下那层没有共同边界的层才留一道很浅的投影：
       下拉菜单、文件抽屉。卡片、按钮、提示条一律不用——它们和背景有描边相接，
       再投影就只是脏。 */
    --gc-shadow-float: 0 6px 16px rgba(15, 23, 42, .10);

    /* 正文用 Roboto（装了就用），其余回落到各平台自带的 UI 字体；
       中文按顺序落到苹方 / 微软雅黑 / Noto，避免落到衬线上。
       刻意不引 Google Fonts 的 CDN：局域网或离线部署时它加载不出来，
       页面会先闪一下默认字体。 */
    --gc-font: Roboto, "Google Sans", -apple-system, BlinkMacSystemFont, "Segoe UI",
    "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans CJK SC",
    "Helvetica Neue", Arial, sans-serif;
    --gc-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, "Roboto Mono", monospace;
}

/* --------------------------------------------------------------------------
   1. 把 Bootstrap 自己的变量接到上面这套色板上
   -------------------------------------------------------------------------- */

:root {
    --bs-body-font-family: var(--gc-font);
    --bs-body-color: var(--gc-text);
    --bs-emphasis-color: var(--gc-text);
    --bs-emphasis-color-rgb: 15, 23, 42;
    --bs-secondary-color: var(--gc-text-secondary);
    --bs-tertiary-color: var(--gc-text-disabled);
    --bs-border-color: var(--gc-border);
    --bs-border-color-translucent: var(--gc-border-soft);
    --bs-secondary-bg: var(--gc-surface-2);
    --bs-tertiary-bg: var(--gc-surface-3);

    --bs-primary: var(--gc-primary);
    --bs-primary-rgb: 37, 99, 235;
    --bs-success: var(--gc-green);
    --bs-success-rgb: 22, 163, 74;
    --bs-warning: var(--gc-yellow);
    --bs-warning-rgb: 217, 119, 6;
    --bs-danger: var(--gc-red);
    --bs-danger-rgb: 220, 38, 38;
    --bs-info: var(--gc-cyan);
    --bs-info-rgb: 8, 145, 178;

    --bs-link-color: var(--gc-primary);
    --bs-link-color-rgb: 37, 99, 235;
    --bs-link-hover-color: var(--gc-primary-hover);
    --bs-link-hover-color-rgb: 29, 78, 216;

    --bs-border-radius: var(--gc-radius-sm);
    --bs-border-radius-sm: 6px;
    --bs-border-radius-lg: var(--gc-radius);
    --bs-focus-ring-color: rgba(37, 99, 235, .25);

    /* Bootstrap 的 -subtle / -emphasis 工具类（bg-success-subtle、
       text-danger-emphasis、border-warning-subtle 这些）走的是另一套变量，
       不跟着 --bs-success 变。不接的话，同一个「绿」在徽章里是一回事、
       在 bg-success-subtle 的块里又是另一回事，两处摆在一起能看出色差。 */
    --bs-primary-text-emphasis: var(--gc-primary-active);
    --bs-secondary-text-emphasis: var(--gc-text-secondary);
    --bs-success-text-emphasis: var(--gc-green-text);
    --bs-warning-text-emphasis: var(--gc-yellow-text);
    --bs-danger-text-emphasis: var(--gc-red-text);
    --bs-info-text-emphasis: var(--gc-cyan-text);

    --bs-primary-bg-subtle: var(--gc-primary-soft);
    --bs-secondary-bg-subtle: var(--gc-surface-3);
    --bs-success-bg-subtle: var(--gc-green-soft);
    --bs-warning-bg-subtle: var(--gc-yellow-soft);
    --bs-danger-bg-subtle: var(--gc-red-soft);
    --bs-info-bg-subtle: var(--gc-cyan-soft);

    --bs-primary-border-subtle: #a8c3f7;
    --bs-secondary-border-subtle: var(--gc-border);
    --bs-success-border-subtle: #b7e2c8;
    --bs-warning-border-subtle: #f0d3a5;
    --bs-danger-border-subtle: #f2bcbc;
    --bs-info-border-subtle: #a8dbe8;

    /* 表单校验。Bootstrap 这套变量是独立的，不跟着 --bs-danger 走，
       不接的话「邮箱格式不对」的输入框是 5.3 那个 #dc3545，
       旁边同一页的 alert-danger 却是本主题的 #dc2626，两个红差一点点，
       并排看就是没对齐。 */
    --bs-form-invalid-color: var(--gc-red-text);
    --bs-form-invalid-border-color: var(--gc-red);
    --bs-form-valid-color: var(--gc-green-text);
    --bs-form-valid-border-color: var(--gc-green);
}

/* 页面底色单独写在 body 上，而不是改 --bs-body-bg。
   因为 --bs-body-bg 同时是卡片、列表组、下拉菜单的默认底色，
   一旦改成灰色，这些组件会跟着一起变灰，得逐个再改回来。 */
body {
    background-color: var(--gc-page);
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

/* 选中文字用主色系的浅底，而不是浏览器默认那道饱和蓝——
   成绩单、代码预览里经常要整段框选，默认蓝配这页的浅色底太跳。 */
::selection {
    background-color: var(--gc-primary-soft-strong);
    color: var(--gc-text);
}

/* 宽屏布局。Bootstrap 的 .container 在 xl 以上封顶 1320px，
   成绩单、用户管理这些列多的表格很快就被挤成横向滚动，
   屏幕越大两边留白反而越多。这里统一放宽。
   模板里 .container 只出现在 layout 的三处（导航、正文、页脚），
   所以改这一条就同时管到三个区域，宽度也自然对齐。
   app.css 在 bootstrap.min.css 之后引入，同样都是 .container，
   靠源顺序就能盖过 Bootstrap 各断点里的 max-width。 */
.container {
    max-width: 1800px;
}

/* 宽了以后两侧留白要跟上，否则内容会贴着屏幕边缘 */
@media (min-width: 992px) {
    .container {
        padding-inline: 2rem;
    }
}

/* 登录、注册关闭、错误页这几张是"居中单栏"，栅格按比例算出来的宽度
   会跟着容器一起变大（col-lg-5 在 1800px 容器里接近 750px），
   一行只有一个输入框却拉那么长，读起来反而费劲，所以给它们封个顶。
   选择器只匹配这几个页面实际用的列组合（见 auth/、error/ 下的模板），
   宽表格那类页面不受影响。 */
.row.justify-content-center > .col-md-6.col-lg-5,
.row.justify-content-center > .col-md-8.col-lg-6 {
    max-width: 560px;
}

h1, h2, h3, h4, h5, h6,
.h1, .h2, .h3, .h4, .h5, .h6 {
    font-weight: 500;
    letter-spacing: -.01em;
    color: var(--gc-text);
}

/* 页面标题下面那行说明文字 */
.text-muted {
    color: var(--gc-text-secondary) !important;
}

/* --------------------------------------------------------------------------
   2. 顶部导航
   -------------------------------------------------------------------------- */

.app-header {
    background-color: var(--gc-surface);
    /* 导航是 sticky 的：页面向下滚时正文会从它下面穿过去，所以这条底边要用
       正常深浅的描边而不是 border-soft，否则滚动的文字和导航糊在一起 */
    border-bottom: 1px solid var(--gc-border);
    z-index: 1030;
}

.app-header .navbar {
    padding-block: .5rem;
}

.app-header .navbar-brand {
    display: inline-flex;
    align-items: center;
    gap: .625rem;
    font-size: 1.0625rem;
    font-weight: 500;
    letter-spacing: -.01em;
    color: var(--gc-text);
}

/* 校徽位置的小方块。用内联 SVG 画，不引图标字体——
   vendor 目录里只有 Bootstrap，没有 bootstrap-icons。
   扁平化后不再是斜向渐变，直接一块实心主色：渐变在小色块上本来就看不出来，
   只会让边缘发灰。 */
.brand-mark {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    border-radius: 10px;
    background-color: var(--gc-primary);
    color: #fff;
    flex-shrink: 0;
}

/* 导航项之间留出可见的空隙。每个 .nav-link 自己还有 .875rem 的内边距，
   所以按钮边缘之间实际是「这个 gap + 两侧内边距」；早先 gap 只有 .125rem，
   一排文字挨得太紧，看着像一整块而不是几个可以点的按钮。 */
.app-header .navbar-nav {
    gap: .5rem;
}

/* 导航项：平时是灰字，悬停浅灰底，当前栏目浅蓝底蓝字。
   圆角用 --gc-radius-sm（8px）而不是整颗胶囊，和按钮保持一致，
   宽屏下一排导航项也不会因为圆角显得过于圆润。 */
.app-header .nav-link {
    padding: .4rem .875rem;
    border-radius: var(--gc-radius-sm);
    font-size: .9375rem;
    font-weight: 500;
    color: var(--gc-text-secondary);
    transition: background-color .15s ease, color .15s ease;
}

.app-header .nav-link:hover,
.app-header .nav-link:focus {
    background-color: var(--gc-surface-3);
    color: var(--gc-text);
}

.app-header .nav-link.active,
.app-header .nav-link.show {
    background-color: var(--gc-primary-soft);
    color: var(--gc-primary);
}

/* 右上角用户区：头像圆圈 + 姓名，和 Google 的账号胶囊一个意思 */
.user-chip {
    display: inline-flex;
    align-items: center;
    gap: .5rem;
}

.user-avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.75rem;
    height: 1.75rem;
    border-radius: 50%;
    background-color: var(--gc-primary-soft);
    color: var(--gc-primary-active);
    font-size: .8125rem;
    font-weight: 500;
    line-height: 1;
    flex-shrink: 0;
    text-transform: uppercase;
}

.user-chip .badge {
    font-weight: 400;
}

.app-header .navbar-toggler {
    border: 0;
    padding: .35rem .5rem;
    border-radius: var(--gc-radius-sm);
}

.app-header .navbar-toggler:focus {
    box-shadow: none;
    background-color: var(--gc-surface-3);
}

.app-header .dropdown-menu {
    margin-top: .5rem;
}

@media (max-width: 991.98px) {
    /* 折叠成竖排以后导航项之间不需要横向间距，改成上下留白 */
    .app-header .navbar-nav {
        gap: 0;
        padding-block: .5rem;
    }
}

/* --------------------------------------------------------------------------
   3. 页脚
   -------------------------------------------------------------------------- */

.app-footer {
    margin-top: auto;
    padding-block: 1.5rem;
    background-color: var(--gc-footer);
}

.app-footer p {
    margin-bottom: 0;
    text-align: center;
    font-size: .8125rem;
    color: var(--gc-footer-text);
}

/* 页脚以后要挂链接（比如实验室主页）时得用浅色：主色蓝压在 #1e293b 上
   只有 2.9:1，基本看不清。现在页脚里还没有链接，先把这条定下来。 */
.app-footer a {
    color: #93c5fd;
}

/* --------------------------------------------------------------------------
   4. 卡片
   -------------------------------------------------------------------------- */

.card {
    --bs-card-border-color: var(--gc-border);
    --bs-card-border-radius: var(--gc-radius);
    --bs-card-inner-border-radius: calc(var(--gc-radius) - 1px);
    /* 卡头给一层极浅的底色，和卡体分开。原来两边都是纯白，中间只隔一条 1px
       的浅边，一页几张卡片叠下来像一块整的；有了这层底色，卡头才是一块「头」。 */
    --bs-card-cap-bg: var(--gc-surface-2);
    --bs-card-cap-padding-x: 1.25rem;
    --bs-card-cap-padding-y: .875rem;
    /*
     * 把卡片内部裁到圆角里去。
     *
     * 卡片里的表格是「card > card-body.p-0 > table-responsive > table」，
     * 而 <thead> 自己带一块浅灰底。Bootstrap 只给 .card > .list-group
     * 补了圆角，没管这种套在 card-body 里的表格，于是表头那块灰底是个
     * 方角矩形，直接盖住卡片左上/右上两处圆角——看起来就像卡片那两处
     * 描边掉了颜色。给 <th> 单独写圆角也没用：.table 是
     * border-collapse: collapse，折叠边框下单元格的圆角不生效。
     * 交给 overflow 裁是最省事也最可靠的做法。
     *
     * 代价是卡片内部溢出的浮层会被裁掉。全站的下拉菜单只在顶部导航里，
     * 不在卡片里，所以这里安全。
     */
    overflow: hidden;
    /* 扁平：卡片只用描边立住，不投阴影。Bootstrap 5.3 的 .card 本来也没有
       默认投影，这一行是为了盖掉早先在这里加过的那一道。 */
    box-shadow: none;
}

.card-header {
    border-bottom: 1px solid var(--gc-border-soft);
    font-weight: 500;
}

.card-body {
    padding: 1.25rem;
}

.card-footer {
    border-top: 1px solid var(--gc-border-soft);
}

/* 统计卡片：标签、大数字、入口链接。
   一页里最显眼的就是排在最上面这一行数字，扁平设计没有阴影帮它「浮起来」，
   改用顶部一道 3px 色条把每一格框出来。色相由模板上的修饰类给，不给就是主色。
   色条是压在圆角上的，看着仍属于这张卡片，而不是贴上去的一条。 */
.stat-card {
    height: 100%;
    --stat-accent: var(--gc-primary);
    border-top: 3px solid var(--stat-accent);
}

/* 需要在一行里区分几格时用。只在颜色本身能对上含义的地方加，
   纯粹为了好看而随机配色，教师会当成某种状态去读。 */
.stat-card--violet { --stat-accent: var(--gc-violet); }
.stat-card--teal   { --stat-accent: var(--gc-teal); }
.stat-card--green  { --stat-accent: var(--gc-green); }
.stat-card--amber  { --stat-accent: var(--gc-yellow); }

.stat-card .card-body {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: .25rem;
}

.stat-card .stat-value {
    font-size: 1.75rem;
    font-weight: 500;
    letter-spacing: -.02em;
    line-height: 1.2;
    /* 数字跟着色条走，整张卡片才是一套颜色 */
    color: var(--stat-accent);
    /* 等宽数字，四个卡片的数字才会上下对齐 */
    font-variant-numeric: tabular-nums;
}

.stat-card a {
    margin-top: auto;
    font-weight: 500;
}

.empty-state {
    padding: 3rem 1.25rem;
    text-align: center;
    color: var(--gc-text-secondary);
}

/* 直接摆在外面的空状态（教学管理的课程列表就是），给它一块浅底和一圈虚线，
   否则一屏白底上飘着一行灰字，像是页面没加载完。
   卡片里面的那种（.card-body.empty-state）不加：卡片本身已经有描边，
   再套一层框，两道边会并在一起。 */
.empty-state:not(.card-body) {
    background-color: var(--gc-surface-2);
    border: 1px dashed var(--gc-border);
    border-radius: var(--gc-radius);
}

.empty-state .btn {
    margin-top: 1rem;
}

/* --------------------------------------------------------------------------
   5. 按钮
   -------------------------------------------------------------------------- */

/* 按钮圆角。Bootstrap 把圆角放在 --bs-btn-border-radius 里，
   .btn-sm / .btn-lg 会各自覆盖这个变量，所以要一起列上。 */
.btn,
.btn-sm,
.btn-group-sm > .btn,
.btn-lg,
.btn-group-lg > .btn {
    --bs-btn-border-radius: var(--gc-radius-sm);
    --bs-btn-font-weight: 500;
    --bs-btn-padding-y: .4rem;
    --bs-btn-padding-x: 1rem;
    --bs-btn-focus-box-shadow: 0 0 0 3px rgba(37, 99, 235, .22);
    letter-spacing: .01em;

    /* 按钮文字不许折行。中文可以在任意两字之间断行，表格列一旦被压窄，
       「评分」这种两个字也会被拆成上下两行，按钮长到两倍高、整行跟着变高
       （成绩单每行 83px 就是这么来的）。宁可让列宽一点，也不要按钮折行。 */
    white-space: nowrap;

    /* 让按钮里的文字始终垂直居中。
       Bootstrap 的 .btn 是 inline-block，高度一被外面拉高（表格里
       .d-flex 默认 align-items:stretch，同一行的下拉框比按钮高就会这样），
       多出来的高度全落在文字下方，看着就是「文字贴顶、没居中」。
       换成 inline-flex 居中就不受外面拉伸影响了。 */
    display: inline-flex;
    align-items: center;
    justify-content: center;
}

.btn-sm,
.btn-group-sm > .btn {
    --bs-btn-padding-y: .3rem;
    --bs-btn-padding-x: .75rem;
}

/* 实心按钮：扁平，不带投影——原来那一道是 Material 悬浮按钮的做法，四周全都是
   描边式的层次，只有按钮投着影，看着像没改完。

   5.3 编译出来的 .btn-primary 里 --bs-btn-bg 是写死的字面值（#0d6efd 那种），
   读不到上面那套 --bs-* 变量，所以每个色相都得在这里重接一遍。少接一个，
   那个按钮就是 Bootstrap 的老色，和旁边的蓝不是同一个蓝。 */
.btn-primary {
    --bs-btn-bg: var(--gc-primary);
    --bs-btn-border-color: var(--gc-primary);
    --bs-btn-hover-bg: var(--gc-primary-hover);
    --bs-btn-hover-border-color: var(--gc-primary-hover);
    --bs-btn-active-bg: var(--gc-primary-active);
    --bs-btn-active-border-color: var(--gc-primary-active);
    --bs-btn-disabled-bg: #a8c3f7;
    --bs-btn-disabled-border-color: #a8c3f7;
    --bs-btn-color: #fff;
    --bs-btn-hover-color: #fff;
    --bs-btn-active-color: #fff;
    --bs-btn-disabled-color: #fff;
    box-shadow: none;
}

.btn-success {
    --bs-btn-bg: var(--gc-green);
    --bs-btn-border-color: var(--gc-green);
    --bs-btn-hover-bg: var(--gc-green-text);
    --bs-btn-hover-border-color: var(--gc-green-text);
    --bs-btn-active-bg: #166534;
    --bs-btn-active-border-color: #166534;
    --bs-btn-color: #fff;
    --bs-btn-hover-color: #fff;
    --bs-btn-active-color: #fff;
    --bs-btn-disabled-bg: #8fd3a8;
    --bs-btn-disabled-border-color: #8fd3a8;
    --bs-btn-disabled-color: #fff;
}

/* 琥珀底配白字对比度只有 3:1，小字号读不清，所以实心的这颗压深一档当底色，
   白字才立得住。认的还是同一个色相，和 .btn-outline-warning 的字色一致。 */
.btn-warning {
    --bs-btn-bg: var(--gc-yellow-text);
    --bs-btn-border-color: var(--gc-yellow-text);
    --bs-btn-hover-bg: #92400e;
    --bs-btn-hover-border-color: #92400e;
    --bs-btn-active-bg: #78350f;
    --bs-btn-active-border-color: #78350f;
    --bs-btn-color: #fff;
    --bs-btn-hover-color: #fff;
    --bs-btn-active-color: #fff;
    --bs-btn-disabled-bg: #e0b184;
    --bs-btn-disabled-border-color: #e0b184;
    --bs-btn-disabled-color: #fff;
}

.btn-danger {
    --bs-btn-bg: var(--gc-red);
    --bs-btn-border-color: var(--gc-red);
    --bs-btn-hover-bg: var(--gc-red-text);
    --bs-btn-hover-border-color: var(--gc-red-text);
    --bs-btn-active-bg: #991b1b;
    --bs-btn-active-border-color: #991b1b;
    --bs-btn-color: #fff;
    --bs-btn-hover-color: #fff;
    --bs-btn-active-color: #fff;
    --bs-btn-disabled-bg: #eda3a3;
    --bs-btn-disabled-border-color: #eda3a3;
    --bs-btn-disabled-color: #fff;
}

.btn-info {
    --bs-btn-bg: var(--gc-cyan);
    --bs-btn-border-color: var(--gc-cyan);
    --bs-btn-hover-bg: var(--gc-cyan-text);
    --bs-btn-hover-border-color: var(--gc-cyan-text);
    --bs-btn-active-bg: #155e75;
    --bs-btn-active-border-color: #155e75;
    --bs-btn-color: #fff;
    --bs-btn-hover-color: #fff;
    --bs-btn-active-color: #fff;
    --bs-btn-disabled-bg: #86cfe0;
    --bs-btn-disabled-border-color: #86cfe0;
    --bs-btn-disabled-color: #fff;
}

/* 描边按钮：同色相描边 + 悬停时灌进对应的浅底（Bootstrap 5.3 的写法）。
   以前一律是灰描边配彩色字，一页里几十个按钮边框全是灰的，颜色只落在字上，
   整页看着就只有蓝和灰两种颜色。描边用中间调而不是满饱和的那个色，
   一列按钮排下来才不至于刺眼。

   次级按钮保持灰的——它表示「取消 / 返回 / 查看更多」这类不改变状态的动作，
   给它上色反而会和旁边真正的主操作抢注意力。 */
.btn-outline-primary,
.btn-outline-success,
.btn-outline-warning,
.btn-outline-danger,
.btn-outline-info {
    --bs-btn-disabled-bg: transparent;
}

.btn-outline-primary {
    --bs-btn-color: var(--gc-primary);
    --bs-btn-border-color: #a8c3f7;
    --bs-btn-hover-bg: var(--gc-primary-soft);
    --bs-btn-hover-border-color: #7ba4f4;
    --bs-btn-hover-color: var(--gc-primary-active);
    --bs-btn-active-bg: var(--gc-primary-soft-strong);
    --bs-btn-active-border-color: #7ba4f4;
    --bs-btn-active-color: var(--gc-primary-active);
    --bs-btn-disabled-color: var(--gc-text-disabled);
    --bs-btn-disabled-border-color: var(--gc-border);
}

.btn-outline-secondary {
    --bs-btn-color: var(--gc-text-secondary);
    --bs-btn-border-color: var(--gc-border);
    --bs-btn-hover-bg: var(--gc-surface-3);
    --bs-btn-hover-border-color: #b9c3d4;
    --bs-btn-hover-color: var(--gc-text);
    --bs-btn-active-bg: #dfe6f0;
    --bs-btn-active-border-color: #b9c3d4;
    --bs-btn-active-color: var(--gc-text);
    --bs-btn-disabled-color: var(--gc-text-disabled);
    --bs-btn-disabled-border-color: var(--gc-border-soft);
    --bs-btn-focus-box-shadow: 0 0 0 3px rgba(90, 101, 121, .18);
}

.btn-outline-success {
    --bs-btn-color: var(--gc-green-text);
    --bs-btn-border-color: #9fd9b6;
    --bs-btn-hover-bg: var(--gc-green-soft);
    --bs-btn-hover-border-color: #6cc48f;
    --bs-btn-hover-color: var(--gc-green-text);
    --bs-btn-active-bg: #cdeed9;
    --bs-btn-active-border-color: #6cc48f;
    --bs-btn-active-color: var(--gc-green-text);
    --bs-btn-disabled-color: var(--gc-text-disabled);
    --bs-btn-disabled-border-color: var(--gc-border);
}

.btn-outline-warning {
    --bs-btn-color: var(--gc-yellow-text);
    --bs-btn-border-color: #f0cb9a;
    --bs-btn-hover-bg: var(--gc-yellow-soft);
    --bs-btn-hover-border-color: #e3ab63;
    --bs-btn-hover-color: var(--gc-yellow-text);
    --bs-btn-active-bg: #fbe3c3;
    --bs-btn-active-border-color: #e3ab63;
    --bs-btn-active-color: var(--gc-yellow-text);
    --bs-btn-disabled-color: var(--gc-text-disabled);
    --bs-btn-disabled-border-color: var(--gc-border);
}

.btn-outline-danger {
    --bs-btn-color: var(--gc-red-text);
    --bs-btn-border-color: #f0adad;
    --bs-btn-hover-bg: var(--gc-red-soft);
    --bs-btn-hover-border-color: #e57f7f;
    --bs-btn-hover-color: var(--gc-red-text);
    --bs-btn-active-bg: #fbd5d5;
    --bs-btn-active-border-color: #e57f7f;
    --bs-btn-active-color: var(--gc-red-text);
    --bs-btn-disabled-color: var(--gc-text-disabled);
    --bs-btn-disabled-border-color: var(--gc-border);
}

.btn-outline-info {
    --bs-btn-color: var(--gc-cyan-text);
    --bs-btn-border-color: #9bd4e2;
    --bs-btn-hover-bg: var(--gc-cyan-soft);
    --bs-btn-hover-border-color: #67bed2;
    --bs-btn-hover-color: var(--gc-cyan-text);
    --bs-btn-active-bg: #c9e9f1;
    --bs-btn-active-border-color: #67bed2;
    --bs-btn-active-color: var(--gc-cyan-text);
    --bs-btn-disabled-color: var(--gc-text-disabled);
    --bs-btn-disabled-border-color: var(--gc-border);
}

.btn-link {
    --bs-btn-color: var(--gc-primary);
    --bs-btn-hover-color: var(--gc-primary-hover);
}

/* --------------------------------------------------------------------------
   6. 表单
   -------------------------------------------------------------------------- */

.form-label {
    margin-bottom: .35rem;
    font-size: .875rem;
    font-weight: 500;
    color: var(--gc-text-secondary);
}

.form-control,
.form-select {
    border-color: var(--gc-border);
    border-radius: var(--gc-radius-sm);
    font-size: .9375rem;
    color: var(--gc-text);
    /* 5.3 给输入框默认加了一道 inset 内阴影，扁平化后没必要——
       它让输入框看着是「凹进去」的，和这一版的描边式层次不是一套语言。 */
    box-shadow: none;
    /* 悬停时边框先深一档，让人知道这块是能点的；焦点环留给键盘和正在输入的那一个。
       扁平设计里没有内阴影帮忙，输入框的「可交互」全靠边框这点变化。 */
    transition: border-color .15s ease, box-shadow .15s ease;
}

.form-control:hover:not(:focus):not(.is-invalid),
.form-select:hover:not(:focus):not(.is-invalid) {
    border-color: #b9c3d4;
}

/*
 * 尺寸单独写，而且普通和 -sm 各列一条，原因有两个：
 *
 * 1) 不能用 padding 简写盖 .form-select。Bootstrap 把下拉箭头画成
 *    "right .75rem" 处的一张背景图，右边那 2.25rem 留白就是给箭头腾的位置。
 *    简写会把右边一起改成 .75rem，箭头就压到「草稿/已发布/已关闭」文字上了
 *    （教学管理的作业列表踩的正是这个坑）。
 *
 * 2) .form-control-sm / .form-select-sm 只改 padding 和字号，优先级和普通项
 *    一样是 (0,1,0)，纯靠源顺序决胜负。app.css 在 bootstrap 之后引入，
 *    所以必须为 -sm 再写一遍，否则表格里的小控件会被撑回普通尺寸——
 *    它一高，旁边 .btn-sm 就被 flex 的 align-items:stretch 拉高，
 *    按钮看着变大、文字还贴顶。
 */
.form-control {
    padding: .5rem .75rem;
}

.form-select {
    /* 右边 2.25rem 是下拉箭头的位置，别用简写 */
    padding: .5rem 2.25rem .5rem .75rem;
}

.form-control-sm {
    padding: .25rem .5rem;
    font-size: .875rem;
}

.form-select-sm {
    padding: .25rem 2rem .25rem .5rem;
    font-size: .875rem;
}

.form-control:focus,
.form-select:focus {
    border-color: var(--gc-primary);
    box-shadow: 0 0 0 3px rgba(37, 99, 235, .18);
}

.form-control::placeholder {
    color: var(--gc-text-disabled);
}

.form-text {
    font-size: .8125rem;
    color: var(--gc-text-secondary);
}

.form-check-input:checked {
    background-color: var(--gc-primary);
    border-color: var(--gc-primary);
}

.form-check-input:focus {
    border-color: var(--gc-primary);
    box-shadow: 0 0 0 3px rgba(37, 99, 235, .18);
}

.input-group-text {
    border-color: var(--gc-border);
    background-color: var(--gc-surface-2);
    color: var(--gc-text-secondary);
}

/* 密码框右侧的"显示/隐藏"按钮（结构由 app.js 的 setupPasswordReveal 生成）。
   包裹层只装输入框，所以按钮可以相对它做垂直居中——按钮是额外插进去的兄弟节点，
   不是换成 .input-group，避免动到已有模板里 label / invalid-feedback 的位置。 */
.pw-field {
    position: relative;
    display: block;
}

.pw-field > .form-control {
    /* 给按钮腾地方，否则长密码会钻到图标下面 */
    padding-right: 2.75rem;
}

.pw-toggle {
    position: absolute;
    top: 50%;
    right: .4375rem;
    transform: translateY(-50%);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.75rem;
    height: 1.75rem;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: transparent;
    color: var(--gc-text-secondary);
}

.pw-toggle:hover {
    background-color: var(--gc-surface-3);
    color: var(--gc-text);
}

.pw-toggle:focus-visible {
    outline: 2px solid var(--gc-primary);
    outline-offset: 1px;
}

.pw-toggle svg {
    width: 18px;
    height: 18px;
}

/* 输入框被包了一层，Bootstrap 的 .is-invalid ~ .invalid-feedback 就接不上了，
   这里按"包裹层里有没有 .is-invalid"把服务端回填的错误文案重新显示出来。 */
.pw-field:has(> .form-control.is-invalid) ~ .invalid-feedback {
    display: block;
}

/* --------------------------------------------------------------------------
   7. 表格
   -------------------------------------------------------------------------- */

.table {
    --bs-table-color: var(--gc-text);
    --bs-table-border-color: var(--gc-border-soft);
    --bs-table-hover-bg: var(--gc-surface-2);
    --bs-table-hover-color: var(--gc-text);
    font-size: .9375rem;
}

/* <thead class="table-light"> 在这里会命中，
   所以不用去改那 13 个模板里的 thead 类名。
   底色比卡头再深一档：卡头是 --gc-surface-2，两者紧挨着时表头得能看出是另一层，
   否则一整块浅灰连成一片。 */
.table > thead,
.table > thead.table-light {
    --bs-table-bg: var(--gc-surface-3);
    --bs-table-color: var(--gc-text-secondary);
}

.table > thead th {
    padding: .75rem 1rem;
    border-bottom: 1px solid var(--gc-border);
    font-size: .75rem;
    font-weight: 500;
    letter-spacing: .04em;
    color: var(--gc-text-secondary);
    white-space: nowrap;
}

.table > tbody td {
    padding: .875rem 1rem;
}

/* 悬停底色淡入。成绩单那种几十行的表，鼠标划过去时整行「啪」地变色很晃眼，
   有个 0.12s 的过渡就顺下来了。 */
.table > tbody > tr {
    transition: background-color .12s ease;
}

.table > tbody tr:last-child > td {
    border-bottom: 0;
}

/* 操作列里挤着一排放按钮，间距靠 gap 控制，别让它们贴在一起 */
td .btn + .btn,
td form + .btn,
td .btn + form {
    margin-left: .25rem;
}

/* 成绩单这类宽表自己横向滚动，不撑破页面 */
.table-scroll {
    overflow-x: auto;
}

.table-scroll table {
    min-width: 44rem;
}

/* --------------------------------------------------------------------------
   8. 徽章 / 状态标签
   -------------------------------------------------------------------------- */

/* 胶囊改成小圆角方块：一列状态标签全是胶囊时，圆头会和旁边的按钮圆角打架，
   整页看着偏软；5.3 自己的徽章也是方圆角。 */
.badge {
    --bs-badge-padding-x: .5rem;
    --bs-badge-padding-y: .25rem;
    --bs-badge-font-size: .75rem;
    --bs-badge-font-weight: 500;
    --bs-badge-border-radius: 6px;
    letter-spacing: .01em;
}

/* Bootstrap 的 bg-* / text-bg-* 颜色工具类带 !important，
   所以这里也必须用 !important 才盖得住；选择器多带一个 .badge 提高优先级，
   顺便避免影响到卡片、进度条上用到的同名工具类。 */
.badge.text-bg-secondary,
.badge.bg-secondary,
.badge.text-bg-light,
.badge.bg-light {
    background-color: var(--gc-surface-3) !important;
    color: var(--gc-text-secondary) !important;
}

.badge.text-bg-dark,
.badge.bg-dark {
    background-color: var(--gc-surface-3) !important;
    color: var(--gc-text) !important;
}

.badge.text-bg-primary,
.badge.bg-primary {
    background-color: var(--gc-primary-soft) !important;
    color: var(--gc-primary-active) !important;
}

.badge.text-bg-success,
.badge.bg-success {
    background-color: var(--gc-green-soft) !important;
    color: var(--gc-green-text) !important;
}

.badge.text-bg-warning,
.badge.bg-warning {
    background-color: var(--gc-yellow-soft) !important;
    color: var(--gc-yellow-text) !important;
}

.badge.text-bg-danger,
.badge.bg-danger {
    background-color: var(--gc-red-soft) !important;
    color: var(--gc-red-text) !important;
}

.badge.text-bg-info,
.badge.bg-info {
    background-color: var(--gc-cyan-soft) !important;
    color: var(--gc-cyan-text) !important;
}

/* --------------------------------------------------------------------------
   9. 提示条
   -------------------------------------------------------------------------- */

/* 提示条：浅底 + 同色相一圈细描边（原来是整块无描边的色带）。
   页面底色本身带灰，一条纯色带浮在上面边界发虚；有描边才像一块完整的提示。 */
.alert {
    --bs-alert-border: 1px solid var(--bs-alert-border-color);
    --bs-alert-border-radius: var(--gc-radius-sm);
    --bs-alert-padding-x: 1rem;
    --bs-alert-padding-y: .875rem;
    font-size: .9375rem;
}

.alert-success {
    --bs-alert-color: var(--gc-green-text);
    --bs-alert-bg: var(--gc-green-soft);
    --bs-alert-border-color: #b7e2c8;
}

.alert-danger {
    --bs-alert-color: var(--gc-red-text);
    --bs-alert-bg: var(--gc-red-soft);
    --bs-alert-border-color: #f2bcbc;
}

.alert-warning {
    --bs-alert-color: var(--gc-yellow-text);
    --bs-alert-bg: var(--gc-yellow-soft);
    --bs-alert-border-color: #f0d3a5;
}

.alert-info {
    --bs-alert-color: var(--gc-cyan-text);
    --bs-alert-bg: var(--gc-cyan-soft);
    --bs-alert-border-color: #a8dbe8;
}

.alert-secondary {
    --bs-alert-color: var(--gc-text-secondary);
    --bs-alert-bg: var(--gc-surface-3);
    --bs-alert-border-color: var(--gc-border);
}

/* .alert-light 也得接一下。上面给 .alert 加的那圈描边读的是
   --bs-alert-border-color，而 Bootstrap 给 light 配的是 #fdfdfe——
   在带灰的页底上等于没有边框，整条提示会糊成一片白。这里按「中性提示」
   处理，和 .alert-secondary 一致。 */
.alert-light {
    --bs-alert-color: var(--gc-text-secondary);
    --bs-alert-bg: var(--gc-surface-2);
    --bs-alert-border-color: var(--gc-border);
}

.alert-link {
    font-weight: 500;
    color: inherit;
    text-decoration: underline;
}

/* --------------------------------------------------------------------------
   10. 列表组、面包屑、分页、下拉菜单、进度条
   -------------------------------------------------------------------------- */

.list-group {
    --bs-list-group-color: var(--gc-text);
    --bs-list-group-border-color: var(--gc-border-soft);
    --bs-list-group-action-hover-bg: var(--gc-surface-2);
    --bs-list-group-action-hover-color: var(--gc-text);
    --bs-list-group-action-active-bg: var(--gc-primary-soft);
    --bs-list-group-action-active-color: var(--gc-primary-active);
    --bs-list-group-item-padding-x: 1.25rem;
    --bs-list-group-item-padding-y: .875rem;
}

/* 面包屑做成一小串浅色胶囊：祖先项平时只是次要文字，悬停时浮出一层主色浅底；
   当前项自己带一层灰底，一眼能看出「你在这儿」。整条链一个语义色都不用
   （绿 / 琥珀 / 红）——那些色在这套界面里表示状态，摆在这里会被读成
   「这门课有问题」。

   分隔符从 Bootstrap 默认的「/」换成 V 形折角：中文标题后面挂一个斜杠，
   十有八九会被当成标题的一部分。折角是画进 ::before 的一张背景图，
   图片取不到 currentColor，所以它的颜色只能写死在这儿（等于 --gc-text-disabled）。
   也正因为它带着自己的颜色，Bootstrap 那个 --bs-breadcrumb-divider-color 就没用了。 */
.breadcrumb {
    --bs-breadcrumb-item-active-color: var(--gc-text);
    --bs-breadcrumb-item-padding-x: .375rem;
    --bs-breadcrumb-divider: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 16 16' fill='none' stroke='%2398a1b3' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 3.5 10.5 8 6 12.5'/%3E%3C/svg%3E");
    margin-bottom: 1rem;
    font-size: .875rem;
}

/* 每一项改成 flex，只为让折角和文字居中对齐。
   Bootstrap 是把 ::before 浮动到行盒左边，而浮动元素贴着行盒顶部，胶囊有上下
   内边距、比纯文字高一截，折角就会显得偏高；成为 flex 项之后浮动失效，改用
   居中对齐（顺带一提，这也是这里不用再写 float: none 的原因）。 */
.breadcrumb-item {
    display: flex;
    align-items: center;
    margin-right: .2rem;
}

.breadcrumb-item > a {
    display: inline-block;
    padding: .125rem .5rem;
    border-radius: 6px;
    color: var(--gc-text-secondary);
    /* 全站链接默认带下划线，这里去掉：面包屑是一串导航，不是正文里的引用，
       可点性交给悬停时那层底色来讲。 */
    text-decoration: none;
    transition: background-color .12s ease, color .12s ease;
}

.breadcrumb-item > a:hover {
    background-color: var(--gc-primary-soft);
    color: var(--gc-primary-active);
}

.breadcrumb-item > a:focus-visible {
    outline: 2px solid var(--gc-primary);
    outline-offset: 1px;
}

.breadcrumb-item.active {
    padding: .125rem .5rem;
    border-radius: 6px;
    background-color: var(--gc-surface-3);
    font-weight: 500;
}

.pagination {
    --bs-pagination-color: var(--gc-text-secondary);
    --bs-pagination-bg: transparent;
    --bs-pagination-border-color: transparent;
    --bs-pagination-hover-color: var(--gc-text);
    --bs-pagination-hover-bg: var(--gc-surface-3);
    --bs-pagination-hover-border-color: transparent;
    --bs-pagination-active-color: var(--gc-primary-active);
    --bs-pagination-active-bg: var(--gc-primary-soft);
    --bs-pagination-active-border-color: transparent;
    --bs-pagination-disabled-color: var(--gc-text-disabled);
    --bs-pagination-disabled-bg: transparent;
    --bs-pagination-disabled-border-color: transparent;
    gap: .25rem;
}

/* 分页也是按钮，圆角跟 .btn 保持一致。
   必须连 --bs-pagination-border-radius 一起改，光写 border-radius 不够：
   Bootstrap 另有 .pagination .page-item:first-child .page-link 这类规则
   （优先级 0,3,0，比这里的 0,1,0 高）单独设置左边两个角，
   它们读的是这个变量。只改 border-radius 的话首尾按钮会变成
   左边 6px、右边 8px 的歪角——.pagination-sm 会把变量压到 --bs-border-radius-sm。
   把变量直接写在 .page-link 上，那几条高优先级规则读到的就是 8px。 */
.page-link {
    --bs-pagination-border-radius: var(--gc-radius-sm);
    border-radius: var(--gc-radius-sm);
    min-width: 2rem;
    text-align: center;
}

.page-link:focus {
    box-shadow: 0 0 0 3px rgba(37, 99, 235, .22);
}

/* 下拉菜单是浮层：它和底下的内容没有共同边界，光靠描边分不出前后，
   所以全站只有它和文件抽屉留着投影。描边取正常深浅的那一档，
   浅描边配投影会显得脏。 */
.dropdown-menu {
    --bs-dropdown-border-color: var(--gc-border);
    --bs-dropdown-border-radius: var(--gc-radius-sm);
    --bs-dropdown-link-color: var(--gc-text);
    --bs-dropdown-link-hover-bg: var(--gc-surface-2);
    --bs-dropdown-link-hover-color: var(--gc-text);
    --bs-dropdown-link-active-bg: var(--gc-primary-soft);
    --bs-dropdown-link-active-color: var(--gc-primary-active);
    --bs-dropdown-font-size: .9375rem;
    padding: .375rem;
    box-shadow: var(--gc-shadow-float);
}

.dropdown-item {
    padding: .5rem .75rem;
    border-radius: 6px;
}

.dropdown-divider {
    border-color: var(--gc-border-soft);
}

.progress {
    --bs-progress-height: .5rem;
    --bs-progress-bg: var(--gc-surface-3);
    --bs-progress-bar-bg: var(--gc-primary);
    --bs-progress-border-radius: 999px;
}

/* --------------------------------------------------------------------------
   11. 页面里用到的几个小工具类
   -------------------------------------------------------------------------- */

/* 表格里的路径、学号等需要能整段复制，长路径允许折行 */
.mono {
    font-family: var(--gc-mono);
    font-size: .875em;
    word-break: break-all;
}

.path-cell {
    max-width: 22rem;
}

/* 成绩单的「反馈摘要」这类列：整列只占一行，长的用省略号截掉，完整内容挂在 title 上做悬停气泡。
   max-width 必须加在 td 上而不是内层 span 上——表格是自动布局，会按 span 里 nowrap 之后的
   完整文本去撑列宽，只在 span 上截断压不住列宽，省略号就永远等不到出现的机会。 */
.cell-clip {
    max-width: 16rem;
}

.cell-clip > span {
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* 提交说明、反馈等长文本保留换行，但不撑破卡片 */
.pre-wrap {
    white-space: pre-wrap;
    word-break: break-word;
}

.deadline-overdue {
    color: var(--gc-red-text);
    font-weight: 500;
}

.deadline-soon {
    color: var(--gc-yellow-text);
    font-weight: 500;
}

.file-preview {
    max-height: 16rem;
    overflow-y: auto;
}

.file-preview .list-group-item {
    padding: .25rem .75rem;
    font-size: .8125rem;
}

/* 智能体评分建议：现在只是脚手架，用左侧色条和正文区分开 */
.suggestion-card {
    border-left: 4px solid var(--gc-cyan);
}

/* 历史建议会一条条攒起来：每点一次「请求智能评分」留一条，全班评分也会往这里写。
   不限高的话这一栏越拖越长，把成绩表单和页面下半部分一直往下推。
   给个上限让它自己滚——约莫三条带反馈的建议那么高。
   外面加一圈边框和内边距：有边界才看得出来「滚到头了」还是「下面还有」。 */
.suggestion-history {
    max-height: 24rem;
    overflow-y: auto;
    padding-inline: .75rem;
    border: 1px solid var(--gc-border-soft);
    border-radius: .5rem;
}

/* --------------------------------------------------------------------------
   12. 文件预览抽屉
   -------------------------------------------------------------------------- */

/* 文件清单里的路径现在可以点开看内容了，得让人看出来能点 */
.file-link {
    color: var(--gc-primary);
    text-decoration: none;
    cursor: pointer;
}

.file-link:hover,
.file-link:focus {
    color: var(--gc-primary-hover);
    text-decoration: underline;
}

/* 抽屉要够宽才看得下源码。Bootstrap 默认 400px，代码一行就折成三截。
   用 min() 兜住窄屏：手机上仍占满宽度，不会被这个值顶出横向滚动。 */
.file-drawer {
    --bs-offcanvas-width: min(920px, 100vw);
}

.file-drawer .offcanvas-header {
    border-bottom: 1px solid var(--gc-border-soft);
    padding-block: .75rem;
}

/* 路径很长时标题要能折行，而不是把关闭按钮挤出屏幕 */
.file-drawer .min-w-0 {
    min-width: 0;
}

.file-drawer .offcanvas-title {
    font-size: .9375rem;
    font-weight: 500;
    line-height: 1.4;
    word-break: break-all;
}

/* 正文自己滚，标题栏和滚动条不跟着跑 */
.file-drawer .offcanvas-body {
    padding: 0;
    overflow: auto;
}

/* 源码视图。行号用 CSS 计数器画，不写进文本里——否则整段复制出去会带上行号 */
.file-view {
    margin: 0;
    padding: .75rem 0 .75rem 0;
    background-color: var(--gc-surface);
    font-family: var(--gc-mono);
    font-size: .8125rem;
    line-height: 1.6;
    tab-size: 4;
}

.file-view code {
    display: block;
    counter-reset: fline;
}

.file-view .fline {
    display: block;
    /* 空行没有内容，不给个最小高度就会被压成 0 高、行号挤在一起 */
    min-height: 1.6em;
    padding-inline: 4.5rem .75rem;
    /* 长行折行时悬挂缩进对齐到正文，不钻到行号底下 */
    text-indent: -3.25rem;
    white-space: pre-wrap;
    word-break: break-word;
}

.file-view .fline::before {
    counter-increment: fline;
    content: counter(fline);
    display: inline-block;
    width: 3.25rem;
    margin-right: .75rem;
    text-indent: 0;
    text-align: right;
    color: var(--gc-text-disabled);
    user-select: none;
}

.file-view .fline:hover {
    background-color: var(--gc-surface-2);
}

/* 图片预览：撑满可用宽度，但不放大到失真 */
.file-image {
    max-width: 100%;
    height: auto;
    border: 1px solid var(--gc-border-soft);
    border-radius: var(--gc-radius-sm);
}

/* PDF 预览：内容交给浏览器自带的阅读器，这里只负责给它一块够大的地方。
   抽屉正文是 flex 项（高度已定），所以这一层能直接 height: 100% 撑满，
   再由 flex 把减去小字提示之后的高度让给 iframe——min-height: 0 不能省，
   否则 flex 项的默认下限是内容高度，iframe 会把这一栏顶高而不是自己变矮。
   下面那行小字是给「不在页面里显示 PDF」的浏览器留的退路。 */
.file-pdf {
    display: flex;
    flex-direction: column;
    height: 100%;
    min-height: 0;
}

.file-pdf-frame {
    flex: 1 1 auto;
    width: 100%;
    min-height: 0;
    border: 0;
    background-color: var(--gc-surface);
}

.file-pdf-hint {
    flex: 0 0 auto;
    padding: .5rem .75rem;
    border-top: 1px solid var(--gc-border-soft);
}

.file-pdf-hint a {
    color: var(--gc-primary);
}

/* --------------------------------------------------------------------------
   Markdown 预览（抽屉里的「渲染」一档）

   这一块是学生写的排版，所以自己的字号、行距、颜色都要重新定一遍：抽屉里
   没有 .container，Bootstrap 那套正文样式在这儿本来也不生效，不定就是
   浏览器默认的 16px 宋体加满宽的 h1。

   原生的元素选择器（h1、table 这些）就够用——这段 HTML 是 commonmark 生成的，
   标签种类是固定的，不需要给每个元素挂类名（挂了也得回头去改渲染器）。
   相对字号用 em：整体缩放时标题跟着走。
   -------------------------------------------------------------------------- */

.file-markdown {
    padding: 1rem 1.25rem 2rem;
    font-size: .9375rem;
    line-height: 1.7;
    color: var(--gc-text);
    word-wrap: break-word;
}

.file-markdown > :first-child {
    margin-top: 0;
}

/* 标题之间的空档比段落大，否则一屏看不出层级 */
.file-markdown h1,
.file-markdown h2,
.file-markdown h3,
.file-markdown h4,
.file-markdown h5,
.file-markdown h6 {
    margin: 1.5em 0 .6em;
    font-weight: 600;
    line-height: 1.35;
}

.file-markdown h1 {
    font-size: 1.5em;
}

.file-markdown h2 {
    font-size: 1.25em;
    padding-bottom: .25em;
    border-bottom: 1px solid var(--gc-border-soft);
}

.file-markdown h3 {
    font-size: 1.075em;
}

.file-markdown h4,
.file-markdown h5,
.file-markdown h6 {
    font-size: 1em;
}

.file-markdown p,
.file-markdown ul,
.file-markdown ol,
.file-markdown blockquote,
.file-markdown table {
    margin-bottom: .875rem;
}

.file-markdown ul,
.file-markdown ol {
    padding-left: 1.5rem;
}

.file-markdown li {
    margin-block: .2rem;
}

.file-markdown li > ul,
.file-markdown li > ol {
    margin-bottom: 0;
}

/* 正文里的链接默认就带下划线：这一段里全是学生写的内容，光靠颜色区分链接
   和加粗文字不够看（而且颜色是最容易被背景吃掉的一种区分方式）。 */
.file-markdown a {
    color: var(--gc-primary);
    text-decoration: underline;
    text-underline-offset: 2px;
}

.file-markdown a:hover {
    color: var(--gc-primary-active);
}

/* 截图。宽度卡在容器内是必须的：一张 2560px 的截图不限制就会把抽屉顶宽，
   而抽屉宽度是 offcanvas 定的，撑不出第二条滚动条，结果是右边半张图看不见。
   描边用最淡的那一档：截图本身大多是白底，没这道线会和抽屉糊在一起。 */
.file-markdown img {
    display: block;
    max-width: 100%;
    height: auto;
    margin: .25rem 0 .875rem;
    border: 1px solid var(--gc-border-soft);
    border-radius: var(--gc-radius-sm);
}

/* 图片外面那层「点开看原图」的链接（见 MarkdownRenderer#embedImage）：
   下划线该落在字上，落在一张图上只会显得脏。
   选择器要带上 .file-markdown a，光写 .file-md-image-link 会被上面那条
   「正文里的链接默认带下划线」按优先级压过去。 */
.file-markdown a.file-md-image-link {
    display: inline-block;
    max-width: 100%;
    text-decoration: none;
}

.file-markdown a.file-md-image-link:hover img {
    border-color: var(--gc-primary);
}

.file-markdown code {
    padding: .1em .35em;
    background-color: var(--gc-surface-3);
    border-radius: 4px;
    font-family: var(--gc-mono);
    font-size: .875em;
}

/* 代码块：浅灰底、不描边（扁平风里描边和底色同时上会显脏），
   横向溢出的长行就地滚动而不是折行——代码折行后缩进全乱。 */
.file-markdown pre {
    margin-bottom: .875rem;
    padding: .75rem 1rem;
    background-color: var(--gc-surface-2);
    border-radius: var(--gc-radius-sm);
    overflow-x: auto;
}

.file-markdown pre code {
    padding: 0;
    background-color: transparent;
    font-size: .8125rem;
    line-height: 1.6;
}

.file-markdown blockquote {
    padding: .25rem 0 .25rem .875rem;
    border-left: 3px solid var(--gc-border);
    color: var(--gc-text-secondary);
}

.file-markdown blockquote > :last-child {
    margin-bottom: 0;
}

.file-markdown hr {
    margin-block: 1.5rem;
    border: 0;
    border-top: 1px solid var(--gc-border);
}

/* 任务列表：commonmark 的 task-list-items 吐出来的是带 disabled 的复选框，
   缩进和项目符号那一档对齐，别让方框飘在列表外面 */
.file-markdown li.task-list-item {
    list-style: none;
    margin-left: -1.25rem;
}

.file-markdown li.task-list-item input {
    margin-right: .4rem;
    vertical-align: middle;
}

/* 表格外面这层是脚本包的（见 app.js 的 renderMarkdownBody）：
   抽屉宽度是有限的，宽表格得自己滚，不能把整张抽屉顶开 */
.file-md-table {
    margin-bottom: .875rem;
    overflow-x: auto;
}

.file-md-table table {
    margin-bottom: 0;
    border-collapse: collapse;
}

/* 描边用 --gc-border-soft，和站内那些 .table 一个色：表格线是最该退到背景里的
   一道线。表头压一档 --gc-surface-3，跟站内表头同一档底色。
   这里不画斑马纹——站内的表也都没有。 */
.file-markdown th,
.file-markdown td {
    padding: .35rem .6rem;
    border: 1px solid var(--gc-border-soft);
}

.file-markdown th {
    background-color: var(--gc-surface-3);
    font-weight: 600;
    text-align: left;
}

/* --------------------------------------------------------------------------
   13. 登录页
   -------------------------------------------------------------------------- */

/* 未登录时导航里一个菜单项都不渲染，整页只剩一张卡片。宽屏下把这张卡片抻成
   左右两栏，左栏交代这是什么系统、能干什么，右栏才是表单——原先一屏白底上
   孤零零两个输入框，第一眼给不出任何信息。

   这里没有复用第 4 节给居中单栏页面封顶的那条规则（.col-md-6.col-lg-5），
   因为登录页现在不是那一套栅格，宽度由 .auth-card 自己定。 */
.auth-page {
    display: flex;
    justify-content: center;
    padding-block: 1rem;
}

.auth-card {
    /* .card 是纵向 flex，这里改成横排；圆角靠 .card 已有的 overflow: hidden 裁 */
    flex-direction: row;
    width: 100%;
    max-width: 920px;
    /* 扁平：全站卡片都不投阴影，登录页这张也不破例，靠左栏那块色把版面撑起来 */
}

/* 左栏原来是 150° 的蓝渐变，扁平化后取一块纯主色：渐变的过渡方向在小面积里
   本来也读不出来，只会在右下角堆出一片脏灰。全站只有这一块是满色的深底，
   其余仍是浅色——登录页要一眼认出「这是入口」。 */
.auth-aside {
    flex: 0 0 22rem;
    display: flex;
    flex-direction: column;
    gap: 1rem;
    padding: 3rem 2.25rem;
    background-color: var(--gc-primary);
    color: #fff;
}

/* 蓝底上的校徽反过来做成白方块 + 蓝字：底本身已经是主色了，
   再放一块半透明的蓝，两蓝叠在一起只会糊成一团。 */
.auth-aside .brand-mark {
    width: 3rem;
    height: 3rem;
    border-radius: 14px;
    background-color: #fff;
    color: var(--gc-primary);
}

.auth-aside-title {
    margin: .5rem 0 0;
    font-size: 1.5rem;
    font-weight: 500;
    letter-spacing: -.01em;
    color: #fff;
}

.auth-aside-tagline {
    margin: 0;
    font-size: .9375rem;
    color: rgba(255, 255, 255, .82);
}

/* 特性说明推到卡片底部，标题那一组留在顶上，中间留白 */
.auth-points {
    display: flex;
    flex-direction: column;
    gap: .9rem;
    margin: auto 0 0;
    padding: 0;
    list-style: none;
}

.auth-points li {
    display: flex;
    align-items: flex-start;
    gap: .625rem;
    font-size: .875rem;
    line-height: 1.55;
    color: rgba(255, 255, 255, .9);
}

.auth-points svg {
    flex-shrink: 0;
    margin-top: .1rem;
}

.auth-form {
    /* 输入框是块级元素，min-width: 0 不能省，否则长内容会把这一栏顶宽 */
    flex: 1 1 auto;
    min-width: 0;
    padding: 3rem 2.25rem;
}

/* 窄屏改成上下排。这时左栏那三条只是挡路的——把登录框推到屏幕下面去了，
   所以只留品牌那一块，当成一条横幅。 */
@media (max-width: 991.98px) {
    .auth-card {
        flex-direction: column;
    }

    .auth-aside {
        flex: none;
        gap: .35rem;
        padding: 1.75rem 1.5rem;
    }

    .auth-aside .brand-mark {
        width: 2.5rem;
        height: 2.5rem;
        border-radius: 12px;
    }

    .auth-aside-title {
        margin-top: .35rem;
        font-size: 1.25rem;
    }

    .auth-points {
        display: none;
    }

    .auth-form {
        padding: 2rem 1.5rem;
    }
}

/* --------------------------------------------------------------------------
   14. 打印
   -------------------------------------------------------------------------- */

@media print {
    .app-header,
    .app-footer,
    .navbar,
    footer,
    .no-print {
        display: none !important;
    }

    body {
        background-color: #fff;
    }

    .card {
        box-shadow: none;
        border-color: #ccc;
    }
}
