@charset "UTF-8";
/* ============================================================================
   班级事务管理系统 —— 统一设计系统 (v1.21.0)
   ----------------------------------------------------------------------------
   首页 / 管理后台 / 接龙页 / 404 / 文件浏览 / 文件管理 / 使用说明 共用本文件的
   「设计令牌 + 基础组件」，页面各自的私有块统一放在文件末尾的 `页面：xxx` 分区里。

   风格约定（简约可爱）：
     · 配色：奶油底色 + 白色卡片 + 一个主色（雾蓝）+ 四组柔和语义色，
       语义色只做「浅底 + 深字」的柔和胶囊，不用高饱和大色块；
       v1.7.0 起四个级别的书脊色各有身份：置顶=琥珀、紧急=粉红、普通=雾蓝、已完成=薄荷；
       v1.10.0 补的雾青现在用在「有附件」胶囊与代码类文件的图标上
       （它原本是「定时未开始」专用的，那套定时显示已在 v1.21.0 取消）；
     · 形状：一切皆圆角，卡片 18px 起步，胶囊一律全圆；
     · 描边：1px 暖色描边取代重投影，卡片投影都极浅且有暖调；
     · 图标：内联 SVG（static/icons.svg），圆头描边，无外部 CDN 依赖；
     · 动效：只在 hover / active / 状态切换上做小幅位移与回弹，克制、可爱；
     · 字号：v1.6.0 起整体收小约 6%（原来偏大，一屏放不下几条）。
       所有 font-size 都是 px，要再调就按同一比例统一改，别只改个别几处；
     · 信息块：v1.8.0 起卡片里的级别徽标 / 标签 / 剩余时间合并进 `.item-tags`
       排成一行，一行放不下才整体换行（以前每块各占一行，列表又长又散）；
     · 弹窗：v1.10.0 起全站不用系统 confirm / alert，统一走 `.ui-*`（static/ui.js），
       样式与页面同源，危险操作有独立的玫红实心按钮。
     · 计时：v1.12.0 起首页与管理页的剩余时间都由浏览器每秒重算；管理页的计时
       渲染成独立的 `.item-countdown` 节点（不再混在 `.m` 的一句话里），
       颜色跟着行上的 `data-level` 走，与首页卡片同一套语义色。
     · 预览：v1.12.0 起图片可以单击预览，`.file-preview` 遮罩沿用 `.ui-*` 的
       圆角 / 动效 / 滚动锁，只是把内容从"一句问话"换成"一张图 + 下载"。
    · 文件类型：v1.13.0 起文件列表左侧的小方块按**真实类型**换图标 + 换色
       （图片 / 视频 / 音频 / Word / Excel / PPT / PDF / 压缩包 / 代码 / 文本），
       扩展名徽标同色，元信息第一段写中文类型名；表在 static/files.js 的 TYPES，
       配色在下面「页面：文件浏览」分区的 `.file-thumb.k-*`，两处一一对应。
    · 空栏目：v1.14.0 起首页栏目"一条待办都没有、只剩已完成的历史"时显示
       `.is-all-done`（薄荷绿虚线卡「全部任务已完成🎉」），与"这里本来就空着"
       的「暂无通知」区分开。
    · 说明：v1.16.0 起使用说明页（`/help`）里的示例**直接复用首页那几个组件**
       （`.list-item` / `.item-tag` / `.roll-cta` / `.fold-bar`），不另画一套 ——
       所以尾部那个分区只加本页独有的 `.help-*`，**不覆盖公共组件**：
       一旦覆盖，说明里的"截图"就不再等于实际页面了。
   ========================================================================== */

/* ---------------------------------------------------------------- 1. 设计令牌 */
:root {
    /* 主色：雾蓝。比原来的 #3498db 更浅更柔，长时间看不累 */
    --primary: #6ea8fe;
    --primary-dark: #4b8bf5;
    --primary-soft: #eaf2ff;
    --primary-line: #cfe0ff;

    /* 语义色：浅底 / 深字 / 描边 三件套，保证任何一处的颜色都能对上。
       没有 `--lilac` / `--peach` 这两个基准色 —— 它们只以三件套的形式被用到，
       多留一个用不上的基准色只会让「令牌是否都用上了」的自检一直有噪音。 */
    --rose: #ff8fa3;
    --rose-ink: #cf5372;
    --rose-soft: #ffeef1;
    --rose-line: #ffd7de;

    --mint: #5fc9a4;
    --mint-ink: #2f8f6e;
    --mint-soft: #e8f8f1;
    --mint-line: #c6ecdd;

    --honey: #f7b955;
    --honey-ink: #b0771b;
    --honey-soft: #fff5e2;
    --honey-line: #ffe2b4;

    --lilac-ink: #7257c9;
    --lilac-soft: #f2edff;
    --lilac-line: #ded3ff;

    --peach-ink: #c46a2c;
    --peach-soft: #fff1e6;
    --peach-line: #ffdcc2;

    /* 雾青（v1.10.0）：为「定时还没开始」这类"尚未生效"的状态引入的第三支中性色 ——
       它既不是紧急（粉红）也不是普通（雾蓝），一眼分得开，又和暖色的分类标签、
       淡紫的接龙标签互不打架。定时显示在 v1.21.0 取消之后，这支颜色继续用在
       「有附件」胶囊与代码类文件的图标上（都是"中性补充信息"的语义）。
       与 --lilac / --peach 一样只留三件套，不设基准色：基准色没人用，
       只会让「令牌是否都用上了」的自检一直有噪音。 */
    --teal-ink: #21757f;
    --teal-soft: #e6f6f8;
    --teal-line: #c2e8ee;

    /* 表格（v1.13.0）：叶绿。给文件列表里的 Excel 用 ——
       "表格是绿的"是所有人共同的直觉，而薄荷（--mint）已经给了图片，
       两者又偏偏最常出现在同一个文件夹里，共用一种颜色等于白分。
       与前几支一样只留三件套，不设基准色。 */
    --leaf-ink: #4f8f3d;
    --leaf-soft: #eef8e8;
    --leaf-line: #d0e8c3;

    /* 中性色：暖调灰阶，底色不用冷灰，避免"系统后台感" */
    --bg: #fdf9f4;
    --bg-lines: #f3ece3;
    --surface: #ffffff;
    --surface-soft: #fbf7f1;
    --line: #efe7dd;
    --line-strong: #e5dbcf;
    --text: #4a4048;
    --text-mid: #7d7378;
    --text-soft: #a89fa4;
    --text-faint: #c2b8bb;

    /* 圆角：卡片 / 控件 / 胶囊 三档 */
    --r-lg: 20px;
    --r-md: 16px;
    --r-sm: 12px;
    --r-pill: 999px;

    /* 投影：非常浅，且带暖色，才有"纸片"的柔软感 */
    --sh-xs: 0 1px 2px rgba(120, 96, 72, .05);
    --sh-sm: 0 3px 10px rgba(120, 96, 72, .06);
    --sh-md: 0 6px 18px rgba(120, 96, 72, .08);
    --sh-primary: 0 6px 16px rgba(110, 168, 254, .30);

    --ease: cubic-bezier(.34, 1.28, .64, 1);   /* 轻微回弹，可爱 */
    --speed: .22s;

    --nav-height: 68px;

    /* 详细通知的折叠行数（v1.22.0）：事项页上超过这么多行就先折起来。
       默认值定义在这里是为了让 check_css.py 能查到它 —— 真正生效的那个数由
       notice.html 在 .detail-body 上行内覆盖（来源是 detailhtml.DETAIL_CLAMP_LINES）。 */
    --detail-lines: 8;

    /* 内容栏宽度：手机上是满宽，平板 / 桌面上收成一条居中的窄栏。
       不设限的话，宽屏下卡片会被拉得很长、一行塞 40 多个字，很难读。 */
    --content-max: min(720px, 100%);

    /* 详细通知编辑框（v1.23.0 起"大而不满屏"，v1.24.2 起"几乎满屏 + 跟着可见区"）：
       --detail-h    弹窗高度。默认 100%（手机=整屏，桌面=遮罩内上下各留 10px）。
                     用户拖过之后由管理页按拖出来的像素值覆盖（写在 .detail-dialog
                     的行内 style 上），下次打开还是那个大小；手机端不采纳（见媒体查询）。
       --vv-top      --vv-height / --vv-top 由管理页按 visualViewport 实时写，
       --vv-height   遮罩钉在"看得见的那块区域"上：手机弹软键盘时可见区变矮，
                     弹窗跟着变矮、位置一动不动（v1.24.2，取代了原来的 --kb-inset）。 */
    --detail-h: 100%;
    --vv-top: 0px;
    --vv-height: 100%;

    /* 下拉框的自绘小箭头（v1.12.0 抽成令牌）。
       抽成令牌是为了两处共用同一份 data URI：普通表单控件（select）与
       文件页那颗小小的排序下拉。v1.20.0 起聚焦态只改 background-color
       （见下面的 input:focus / select:focus），这个箭头不会再被聚焦清掉。 */
    --chevron-down: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23a89fa4' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6.4 9.6 12 15.2l5.6-5.6'/%3E%3C/svg%3E");
}

/* ---------------------------------------------------------------- 2. 基础重置 */
* { margin: 0; padding: 0; box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

html { -webkit-text-size-adjust: 100%; }

/* hidden 必须压得住组件自己的 display：
   .btn / .notice-bar 都声明了 display:flex|inline-flex，类选择器优先级高于浏览器
   默认的 [hidden]{display:none}，于是"隐藏"的按钮反而会明晃晃地显示出来
   （管理页的「取消编辑」、接龙页的错误提示条都踩过这个坑）。 */
[hidden] { display: none !important; }

body {
    /* 只用系统字体栈：Google Fonts 在国内校园网经常超时，
       而且系统字体（苹方 / 微软雅黑 / Noto）本就比网页字体更清晰。 */
    font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB",
                 "Microsoft YaHei", "Noto Sans SC", "Source Han Sans SC", sans-serif;
    background: var(--bg);
    color: var(--text);
    line-height: 1.6;
    font-size: 14px;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    overflow-x: hidden;
}

img, svg { display: block; max-width: 100%; }
button, input, select, textarea { font: inherit; color: inherit; }
button { cursor: pointer; border: none; background: none; }

/* 全局链接：继承颜色，去掉下划线（原本靠一条通配规则实现，容易误伤按钮） */
a { color: inherit; text-decoration: none; }

/* 焦点可见性：键盘用户也要看得见，但不用浏览器默认的蓝框 */
:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; border-radius: 6px; }

/* 内联 SVG 图标：尺寸跟随 font-size，颜色跟随 currentColor */
.ic {
    width: 1em;
    height: 1em;
    flex: none;
    /* 让图标与相邻文字在基线上对齐得更自然 */
    vertical-align: -.14em;
}

/* ---------------------------------------------------------------- 3. 卡片与列表 */
.card {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    box-shadow: var(--sh-sm);
}

/* 一条事项卡片：左侧一条语义色「书脊」，比整块变色更克制 */
.list-item {
    position: relative;
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 14px;
    background: var(--surface);
    border: 1px solid var(--line);
    border-left: 4px solid var(--line-strong);
    border-radius: var(--r-md);
    padding: 15px 17px;
    margin-bottom: 12px;
    box-shadow: var(--sh-xs);
    transition: box-shadow var(--speed) ease, transform var(--speed) var(--ease),
                background var(--speed) ease, border-color var(--speed) ease;
}
/* 可点击卡片（外层是 <a>）按下时轻微下沉 */
a > .list-item:active { transform: scale(.988); }

.list-item.urgent { border-left-color: var(--rose); background: #fffcfc; }
.list-item.done { border-left-color: var(--mint-line); background: var(--surface-soft); }
.list-item.done .item-name { color: var(--text-mid); }

/* ---- 长按标记完成（v1.24.3）----
   按住一条事项 0.5 秒：整块内容虚化（filter: blur），中间浮出一颗确认按钮。
   为什么虚化而不是弹出对话框：这件事发生在列表里，用户想确认的是"**这一条**"，
   把整块磨开、按钮落在正中间，视线不用离开那张卡片。
   松开手指后按钮**仍然留着**（要再点一下才生效）：长按本身是个很容易误触的手势，
   没有第二道确认的话，会有人在滑列表时把一堆事项标成已完成。 */
.list-item.lp-on {
    /* 虚化的同时把底色压干净一点，按钮周围才不会花 */
    background: var(--surface-soft);
    box-shadow: var(--sh-md);
}
.list-item.lp-on .item-content,
.list-item.lp-on .item-meta {
    filter: blur(2.5px);
    opacity: .45;
    transition: filter var(--speed) ease, opacity var(--speed) ease;
    /* 虚化的内容不许再吃到点击：底下那层 <a> 也不该被点开 */
    pointer-events: none;
    user-select: none;
}
/* 遮罩层：铺满整张卡片，按钮居中 */
.lp-overlay {
    position: absolute;
    inset: 0;
    z-index: 2;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: inherit;
    animation: lpOverlayIn .18s var(--ease);
}
.lp-ask {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 7px 9px 7px 14px;
    background: var(--surface);
    border: 1px solid var(--line-strong);
    border-radius: var(--r-pill);
    box-shadow: var(--sh-md);
}
.lp-ask-text { font-size: 11.5px; color: var(--text-soft); white-space: nowrap; }
.lp-btn {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 8px 16px;
    border: none;
    border-radius: var(--r-pill);
    font-size: 12.5px;
    font-weight: 700;
    color: #fff;
    background: var(--mint-ink);
    box-shadow: 0 4px 12px rgba(47, 143, 110, .28);
    transition: transform var(--speed) var(--ease), background var(--speed) ease;
}
.lp-btn:active { transform: scale(.96); }
.lp-btn:disabled { opacity: .6; }
/* 取消完成那颗按钮换成中性灰：它是"撤销"，不该跟确认一样醒目 */
.lp-btn.is-undo { background: var(--text-mid); box-shadow: var(--sh-sm); }
@keyframes lpOverlayIn {
    from { opacity: 0; transform: scale(.96); }
    to   { opacity: 1; transform: scale(1); }
}
/* 长按过程里的"正在按住"反馈：书脊微微亮一下，让人知道按住有效 */
.list-item.lp-hold { border-left-color: var(--mint); }

/* 普通：雾蓝书脊 + 极浅蓝底（v1.7.0 换的配色）。
   换之前普通事项是一条米白卡 + 暖灰书脊，跟「我的」栏目里的静态卡片长得一模一样，
   整页扫下来只能靠一枚蓝色小徽标认出「普通」；现在四个级别各有各的颜色身份：
   置顶=琥珀金、紧急=粉红、普通=雾蓝、已完成=薄荷。
   只挂 .normal 类，所以不带动「我的」栏目与空栏目占位卡（那两类不是事项状态）。 */
.list-item.normal { border-left-color: var(--primary); background: linear-gradient(180deg, #f7fbff 0%, #ffffff 62%); }
.list-item.normal .item-time { color: var(--primary-dark); }
.list-item.normal .item-countdown { background: var(--primary-soft); color: var(--primary-dark); border-color: var(--primary-line); }

/* 置顶：琥珀金而非红色 —— 既要醒目，又要与「紧急」的粉红一眼区分。
   置顶优先级高于紧急，故写在 urgent 之后覆盖同权重的规则。
   置顶项即使截止时间已过也不会降级成 done，级别恒为「置顶」。 */
.list-item.pinned {
    border-left-color: var(--honey);
    border-left-width: 5px;
    border-color: var(--honey-line);
    background: linear-gradient(180deg, #fffaf0 0%, #fffdf9 100%);
    box-shadow: 0 3px 12px rgba(247, 185, 85, .16);
    opacity: 1;
}
.list-item.pinned .item-name { font-weight: 700; color: var(--honey-ink); }
.list-item.pinned .item-note { border-top-color: var(--honey-line); color: #a8802f; }
.list-item.pinned .item-time { color: var(--honey-ink); }
.list-item.pinned .item-time-sub { color: #d3a24a; }
.list-item.pinned .item-countdown { background: var(--honey-soft); color: var(--honey-ink); border-color: var(--honey-line); }

/* 图钉图标：置顶项标题前的小装饰 */
.pin-icon { color: var(--honey); font-size: 13px; }

.item-content { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 6px; }

.item-name {
    font-size: 15px;
    font-weight: 600;
    line-height: 1.5;
    color: var(--text);
    word-break: break-word;
    display: flex;
    align-items: baseline;
    gap: 5px;
    flex-wrap: wrap;
}
.item-name .ic { color: var(--honey); }

/* 小信息块（级别徽标 / N 人接龙 / 有附件 / 标签 / 剩余时间）：默认排成一行，
   一行装不下才整体换行（flex + wrap）。这些是同一层级的"补充信息"，
   挤在一排才好扫；标题不并进来 —— 它是主体，而且常常很长。
   v1.19.0：「普通」不再挂胶囊，所以只要没有标签 / 接龙 / 附件 / 倒计时，
   模板与 app.js 连这个容器一起不输出 —— 空的 flex 行照样会撑出一个 gap 的空隙。 */
.item-tags {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
}

.item-note {
    font-size: 12px;
    color: var(--text-soft);
    line-height: 1.6;
    padding-top: 7px;
    margin-top: 3px;
    border-top: 1px dashed var(--bg-lines);
    word-break: break-word;
}

.item-meta {
    flex: none;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 6px;
    min-width: 84px;
}
.item-time { font-size: 14px; font-weight: 700; color: var(--text-mid); text-align: right; }
.item-time-sub { font-size: 11.5px; color: var(--text-soft); text-align: right; }
.list-item.urgent .item-time { color: var(--rose-ink); }
.list-item.urgent .item-time-sub { color: #e08fa2; }

.item-location {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 11.5px;
    font-weight: 500;
    color: var(--peach-ink);
    background: var(--peach-soft);
    border: 1px solid var(--peach-line);
    padding: 3px 9px;
    border-radius: var(--r-pill);
    max-width: 100%;
}
.item-location .ic { font-size: 11.5px; }

/* ---------------------------------------------------------------- 4. 标签与倒计时 */
/* 统一胶囊：首页的 .item-tag 与管理页的 .tag 共用同一套外观，
   避免同一种「级别徽标」在两个页面长得不一样。 */
.item-tag,
.tag {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    font-size: 10.5px;
    font-weight: 700;
    line-height: 1;
    padding: 4px 9px;
    border-radius: var(--r-pill);
    border: 1px solid transparent;
    width: fit-content;
    margin-left: 0;
    vertical-align: middle;
    white-space: nowrap;
}
.list-item .item-tag,
.item .tag { margin-left: 0; }
.item-name .item-tag { margin-left: 0; }

/* 级别配色：浅底 + 深字 + 同色描边 */
.tag-pinned, .tag.pinned { background: var(--honey-soft); color: var(--honey-ink); border-color: var(--honey-line); }
.tag-urgent, .tag.urgent { background: var(--rose-soft); color: var(--rose-ink); border-color: var(--rose-line); }
.tag-done,   .tag.done   { background: var(--mint-soft); color: var(--mint-ink); border-color: var(--mint-line); }
.tag-normal, .tag.normal { background: var(--primary-soft); color: var(--primary-dark); border-color: var(--primary-line); }
/* 分类色：notice 用中性灰而不是蓝色 ——
   蓝色留给「级别徽标」和导航选中态，否则一个「重要」标签和「普通」挤在一起全是蓝的。 */
.tag-notice   { background: var(--surface-soft); color: var(--text-mid); border-color: var(--line); }
.tag-homework { background: var(--peach-soft); color: var(--peach-ink); border-color: var(--peach-line); }
.tag-online   { background: var(--lilac-soft); color: var(--lilac-ink); border-color: var(--lilac-line); }
.tag.cate { background: var(--surface-soft); color: var(--text-mid); border-color: var(--line); }
/* 接龙徽标：淡紫（与「网课」同色系的那支，但语义不同 —— 它只出现在有接龙的事项上） */
.tag-roll, .tag.roll { background: var(--lilac-soft); color: var(--lilac-ink); border-color: var(--lilac-line); }
/* 详细通知徽标（v1.22.0）：琥珀金 —— 与「置顶」同一支的那支暖色。
   为什么复用这一支：详细通知是班委亲手写的那段"真正要说的事"，在卡片上它就该
   最显眼；而置顶那枚是"图钉 + 标题"，位置在标题里，两者不会混。
   它只表示"这一条有详细通知"，正文一个字都不在卡片上（见 _cards.html 的说明）。 */
.tag-detail, .tag.detail { background: var(--honey-soft); color: var(--honey-ink); border-color: var(--honey-line); }
/* 定时徽标（v1.10.0～v1.20.0）：`.tag-sched` 随定时显示一起删掉了（v1.21.0）——
   它只出现在管理页，学生端根本看不到未开始的事项。
   雾青（--teal-*）没有跟着废掉：「有附件」胶囊与代码类文件的图标仍用它。 */

/* 接龙 / 附件那两行「点击卡片…」提示（.roll-cta / .attach-cta）在 v1.18.0 整体删掉了：
   它们和「N 人接龙」「有附件」「文件收集」几颗胶囊说的是同一件事，一条事项同时开着
   接龙又带着附件时还会并排出现两行，把卡片撑得老长。现在"这张卡可以点"由胶囊本身
   表达，点进去都是同一个事项页（/notice/{id}），样式不再需要。 */

/* 剩余时间 / 已超时：与级别同色的浅胶囊 */
.item-countdown {
    display: inline-flex;
    align-items: center;
    width: fit-content;
    font-size: 10.5px;
    font-weight: 700;
    line-height: 1;
    padding: 4px 9px;
    border-radius: var(--r-pill);
    background: var(--surface-soft);
    color: var(--text-mid);
    border: 1px solid var(--line);
}
.list-item.urgent .item-countdown { background: var(--rose-soft); color: var(--rose-ink); border-color: var(--rose-line); }
.list-item.done .item-countdown { background: var(--mint-soft); color: var(--mint-ink); border-color: var(--mint-line); }
/* 「距开始 X」（v1.22.0）：**固定用雾青**，不跟着级别变色。
   它是"还没开始"的意思，与"离截止还剩多久"是两种心情；跟着级别变色会让人
   以为那是同一件事的两个阶段（其实开始时间一到，这一颗就换成另一句了）。 */
.item-countdown.start-countdown,
.list-item.urgent .item-countdown.start-countdown,
.list-item.done .item-countdown.start-countdown {
    background: var(--teal-soft);
    color: var(--teal-ink);
    border-color: var(--teal-line);
}

/* ---------------------------------------------------------------- 5. 按钮 */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 7px;
    font-size: 13px;
    font-weight: 600;
    line-height: 1;
    padding: 11px 20px;
    border-radius: var(--r-pill);
    border: 1px solid transparent;
    background: var(--surface-soft);
    color: var(--text-mid);
    text-decoration: none;
    transition: transform var(--speed) var(--ease), box-shadow var(--speed) ease,
                background var(--speed) ease, opacity var(--speed) ease;
}
.btn .ic { font-size: 15px; }
.btn:active { transform: scale(.96); }
.btn-primary {
    background: var(--primary);
    color: #fff;
    box-shadow: var(--sh-primary);
}
.btn-primary:hover { background: var(--primary-dark); box-shadow: 0 8px 20px rgba(110, 168, 254, .38); }
.btn-ghost {
    background: var(--surface-soft);
    color: var(--text-mid);
    border-color: var(--line);
}
.btn-ghost:hover { background: var(--bg-lines); }
.btn:disabled { opacity: .55; cursor: not-allowed; }

/* 小圆点按钮（列表里的置顶 / 删除） */
.btn-sm {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 5px;
    font-size: 11.5px;
    font-weight: 600;
    line-height: 1;
    padding: 7px 11px;
    border-radius: var(--r-pill);
    border: 1px solid transparent;
    background: var(--surface-soft);
    color: var(--text-mid);
    transition: transform var(--speed) var(--ease), background var(--speed) ease, opacity var(--speed) ease;
}
.btn-sm .ic { font-size: 12px; }
.btn-sm:active { transform: scale(.94); }
.btn-sm:disabled { opacity: .5; cursor: not-allowed; }

.btn-soft { background: var(--primary-soft); color: var(--primary-dark); border-color: var(--primary-line); }
.btn-honey { background: var(--honey); color: #fff; box-shadow: 0 3px 10px rgba(247, 185, 85, .32); }
.btn-danger-soft { background: var(--rose-soft); color: var(--rose-ink); border-color: var(--rose-line); }
.btn-danger-soft:hover { background: var(--rose); color: #fff; }
/* 实心玫红：自绘弹窗里"删除 / 撤销"这类不可撤销的操作专用（v1.10.0） */
.btn-danger { background: var(--rose); color: #fff; box-shadow: 0 6px 16px rgba(255, 143, 163, .34); }
.btn-danger:hover { background: var(--rose-ink); }

/* ---------------------------------------------------------------- 6. 表单控件 */
.field { display: block; margin-bottom: 14px; }
.field-label {
    display: block;
    font-size: 12px;
    font-weight: 600;
    color: var(--text-mid);
    margin-bottom: 6px;
}
.field-label .req { color: var(--rose); }

input[type=text], input[type=url], input[type=datetime-local],
input[type=search], input:not([type]), select, textarea {
    width: 100%;
    padding: 10px 13px;
    font-size: 13px;
    color: var(--text);
    background: var(--surface-soft);
    border: 1px solid var(--line);
    border-radius: var(--r-sm);
    transition: border-color var(--speed) ease, background var(--speed) ease,
                box-shadow var(--speed) ease;
    -webkit-appearance: none;
    appearance: none;
}
input::placeholder, textarea::placeholder { color: var(--text-faint); }
input:hover, select:hover, textarea:hover { border-color: var(--line-strong); }
/* 聚焦态只改 background-**color**（v1.20.0）。
   以前这里写的是 `background` 简写：它会把 background-position / -size / -repeat
   一起重置成初始值，凡是在别处定过背景图（自绘下拉箭头）的控件都会走样 ——
   文件页的排序下拉一聚焦，那颗 12px 的小箭头就被放大成默认尺寸并平铺，
   压在「按名称」那几个字上（用户报的"按时间排序时 UI 渲染乱"就是它）。
   只改颜色，背景图的其余性质一律不动。 */
input:focus, select:focus, textarea:focus {
    outline: none;
    background-color: var(--surface);
    border-color: var(--primary);
    box-shadow: 0 0 0 3px var(--primary-soft);
}
textarea { resize: vertical; min-height: 74px; line-height: 1.6; }

/* 下拉框：自绘小箭头，避免各浏览器原生箭头风格不一 */
select {
    background-image: var(--chevron-down);
    background-repeat: no-repeat;
    background-position: right 11px center;
    background-size: 15px;
    padding-right: 34px;
    cursor: pointer;
}

/* 自定义勾选框：圆角方块 + 打勾 */
input[type=checkbox] {
    -webkit-appearance: none;
    appearance: none;
    flex: none;
    width: 19px;
    height: 19px;
    border: 1.6px solid var(--line-strong);
    border-radius: 6px;
    background: var(--surface);
    cursor: pointer;
    position: relative;
    transition: background var(--speed) ease, border-color var(--speed) ease;
    margin: 0;
}
input[type=checkbox]:checked { background: var(--honey); border-color: var(--honey); }
input[type=checkbox]:checked::after {
    content: "";
    position: absolute;
    left: 5.5px;
    top: 2px;
    width: 5px;
    height: 9.5px;
    border: solid #fff;
    border-width: 0 2.2px 2.2px 0;
    border-radius: 1px;
    transform: rotate(42deg);
}

.hint {
    font-size: 11.5px;
    color: var(--text-faint);
    line-height: 1.65;
    margin-top: 6px;
}
.hint code {
    background: var(--surface-soft);
    border: 1px solid var(--line);
    border-radius: 5px;
    padding: 1px 5px;
    font-size: 11px;
    color: var(--text-mid);
}

/* 备注里的行内链接：淡蓝底 + 下划线，一眼看出可以点 */
.inline-link {
    color: var(--primary-dark);
    font-weight: 600;
    padding: 1px 5px;
    margin: 0 1px;
    border-radius: 6px;
    background: var(--primary-soft);
    transition: background var(--speed) ease;
}
.inline-link:hover { background: #dbe8ff; }

/* ---------------------------------------------------------------- 7. 空状态 / 折叠区 */
.empty,
.empty-state {
    text-align: center;
    padding: 34px 20px;
    color: var(--text-soft);
    font-size: 12.5px;
    line-height: 1.7;
}
.empty-state { padding: 60px 20px; }

/* 空状态里的小插画泡泡 */
.empty-emoji {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 62px;
    height: 62px;
    margin: 0 auto 14px;
    border-radius: 50%;
    background: var(--primary-soft);
    color: var(--primary);
    font-size: 24.5px;
}

/* 首页空卡片：用虚线框表达"这里还是空的"，比实心卡片更轻 */
.list-item.is-empty {
    border: 1px dashed var(--line-strong);
    border-left: 4px dashed var(--line-strong);
    background: transparent;
    box-shadow: none;
    flex-direction: column;
    gap: 4px;
}
.list-item.is-empty .item-name { font-size: 14px; color: var(--text-mid); }

/* 「全部任务已完成🎉」（v1.14.0）：同样是虚线空卡片，但换成薄荷绿并给标题着色。
   它和「暂无通知」长一样的话，一眼分不出"事情都办完了"和"这里本来就空着"。
   复用 --mint 三件套，不新配颜色。 */
.list-item.is-empty.is-all-done {
    border-color: var(--mint-line);
    background: var(--mint-soft);
}
.list-item.is-empty.is-all-done .item-name { color: var(--mint-ink); font-weight: 700; }

/* 已完成折叠条：就地铺开的抽屉头 */
.fold-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 13px 16px;
    margin-bottom: 12px;
    background: var(--surface);
    border: 1px dashed var(--line-strong);
    border-radius: var(--r-md);
    font-size: 12px;
    font-weight: 600;
    color: var(--text-soft);
    cursor: pointer;
    user-select: none;
    transition: background var(--speed) ease, color var(--speed) ease,
                border-color var(--speed) ease;
}
.fold-bar > span { display: inline-flex; align-items: center; gap: 7px; }
.fold-bar .ic { font-size: 14px; }
.fold-bar:hover { background: var(--surface-soft); border-color: var(--primary-line); color: var(--primary-dark); }
.fold-bar:active { transform: scale(.99); }
/* 折叠条的箭头：现在是 SVG，靠 .chev 这个类旋转即可
   （旧的 <i class="chev"> 写法已随 Font Awesome 一起移除） */
.fold-bar .chev { transition: transform .25s var(--ease); }
.fold-bar.open {
    background: var(--mint-soft);
    border-style: solid;
    border-color: var(--mint-line);
    color: var(--mint-ink);
    margin-bottom: 8px;
}
.fold-bar.open .chev { transform: rotate(180deg); }

.fold-body { display: none; }
.fold-body.open { display: block; animation: foldExpand .3s var(--ease); }
@keyframes foldExpand {
    from { opacity: 0; transform: translateY(8px); }
    to   { opacity: 1; transform: translateY(0); }
}
.fold-body .fold-loading,
.fold-loading {
    text-align: center;
    padding: 18px 0;
    font-size: 12px;
    color: var(--text-faint);
}

/* 折叠区底部哨兵（v1.10.0）：分页加载时摆在最后一条卡片后面。
   它自己占一行：滚到它进入视口就再要一批，"加载中 / 已到底 / 加载失败"都写在这里。
   加载失败时可以点一下重试 —— 所以它是 button 而不是 div（键盘也能聚焦）。 */
.fold-sentinel {
    display: block;
    width: 100%;
    text-align: center;
    padding: 14px 0 6px;
    font-size: 12px;
    font-family: inherit;
    color: var(--text-faint);
    background: none;
    border: none;
}
.fold-sentinel.loading::after {
    /* 纯 CSS 三个点，省一个转圈图标 */
    content: '…';
    animation: foldDots 1.2s steps(4, end) infinite;
}
@keyframes foldDots {
    0% { content: ''; } 25% { content: '·'; } 50% { content: '··'; } 75% { content: '···'; }
}
.fold-sentinel.retry {
    color: var(--rose-ink);
    cursor: pointer;
    text-decoration: underline;
    text-underline-offset: 3px;
}
.fold-sentinel.end { color: var(--text-faint); }

/* 通用小徽标（管理页的数据库后端标记等） */
.badge {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 3px 10px;
    border-radius: var(--r-pill);
    font-size: 11px;
    font-weight: 700;
    line-height: 1.5;
    border: 1px solid transparent;
}
.badge.mysql { background: var(--primary-soft); color: var(--primary-dark); border-color: var(--primary-line); }
.badge.sqlite { background: var(--peach-soft); color: var(--peach-ink); border-color: var(--peach-line); }
/* 玫红徽标：用来提醒"这里的操作是不可逆的"（文件管理页的「删除只在本页」） */
.badge.danger { background: var(--rose-soft); color: var(--rose-ink); border-color: var(--rose-line); }
/* 原来还有一个 .badge.mint（接龙页那句「N 人已接龙」用的），
   v1.7.0 把那行冗余说明删掉后它就没人用了，一并移除 */

/* 提示条 */
.notice-bar {
    display: flex;
    gap: 9px;
    align-items: flex-start;
    font-size: 12px;
    line-height: 1.7;
    padding: 12px 14px;
    border-radius: var(--r-md);
    background: var(--honey-soft);
    border: 1px solid var(--honey-line);
    color: var(--honey-ink);
    margin-bottom: 18px;
}
.notice-bar .ic { font-size: 14px; margin-top: 2px; color: var(--honey); }
/* 成功态：接龙页的「接龙成功 / 你已接龙」用它 */
.notice-bar.ok {
    background: var(--mint-soft);
    border-color: var(--mint-line);
    color: var(--mint-ink);
}
.notice-bar.ok .ic { color: var(--mint); }

/* ---------------------------------------------------------------- 8. 自绘弹窗 */
/* 全站唯一的对话框（v1.10.0，见 static/ui.js）。代替 window.confirm / alert：
   奶油底、圆角卡片、胶囊按钮，与页面其它部分同一套令牌。
   结构：.ui-mask（全屏遮罩）> .ui-dialog（卡片）> 图标 + 标题 + 正文 + 双按钮 */
html.ui-lock, html.ui-lock body { overflow: hidden; }

.ui-mask {
    position: fixed;
    inset: 0;
    z-index: 200;                 /* 高于页眉(100)与底部导航，低于无 */
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 22px;
    background: rgba(74, 64, 72, .38);
    backdrop-filter: blur(3px);
    -webkit-backdrop-filter: blur(3px);
    animation: uiMaskIn .18s ease;
}
@keyframes uiMaskIn { from { opacity: 0; } to { opacity: 1; } }

.ui-dialog {
    width: 100%;
    max-width: 330px;
    padding: 22px 22px 18px;
    text-align: center;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 22px;
    box-shadow: var(--sh-md);
    animation: uiDialogIn .24s var(--ease);
}
@keyframes uiDialogIn {
    from { opacity: 0; transform: translateY(10px) scale(.96); }
    to   { opacity: 1; transform: translateY(0) scale(1); }
}

/* 图标泡泡：普通提示用雾蓝，危险操作用粉红 */
.ui-dialog-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 46px;
    height: 46px;
    margin: 0 auto 12px;
    border-radius: 50%;
    background: var(--primary-soft);
    color: var(--primary-dark);
    font-size: 21px;
}
.ui-dialog-icon.danger { background: var(--rose-soft); color: var(--rose-ink); }
.ui-dialog-icon .ic { font-size: 21px; }

.ui-dialog-title {
    font-size: 15px;
    font-weight: 700;
    color: var(--text);
    margin-bottom: 8px;
    word-break: break-word;
}
.ui-dialog-msg {
    font-size: 12.5px;
    line-height: 1.75;
    color: var(--text-mid);
    word-break: break-word;
}
.ui-dialog-msg p + p { margin-top: 6px; }
.ui-dialog-msg em {
    font-style: normal;
    font-weight: 700;
    color: var(--text);
}
/* 输入框弹窗（UI.prompt，v1.11.0）："新建文件夹"这类要你填一个名字的操作用它。
   弹窗整体是居中的，输入框这一块单独左对齐 —— 标签紧贴输入框更好读。 */
.ui-dialog-field {
    display: block;
    margin-top: 14px;
    text-align: left;
}
.ui-dialog-label {
    display: block;
    font-size: 11.5px;
    font-weight: 600;
    color: var(--text-mid);
    margin-bottom: 5px;
}
.ui-dialog-input { text-align: left; }
/* 必填没填时点确认：输入框转玫红，不关窗，焦点回到框里 */
.ui-dialog-input.invalid {
    border-color: var(--rose);
    background: var(--rose-soft);
    box-shadow: 0 0 0 3px rgba(255, 143, 163, .18);
}
.ui-dialog-actions {
    display: flex;
    gap: 10px;
    margin-top: 18px;
}
/* 两个按钮各占一半：手机上一只手就能点到，也不会因为文字长短一宽一窄 */
.ui-dialog-actions .btn { flex: 1; padding: 11px 12px; }
/* 纯提示（UI.alert）时只有确认按钮，取消那格隐藏 —— 隐藏由 [hidden] 那条全局规则兜住 */

/* ============================================================================
   页面：首页
   ========================================================================== */
/* ---- 页面：首页 ---- */
body.has-bottom-nav {
    /* 底部留白要连安全区一起算：导航栏自己会长高那么多（见下面的 .bottom-nav），
       不留出来的话最后一排卡片会被压在小白条下面 */
    padding-bottom: calc(var(--nav-height) + 28px + env(safe-area-inset-bottom, 0px));
}

/* ---- 页眉 ---- */
.header {
    position: sticky;
    top: 0;
    z-index: 100;
    padding: 20px 20px 18px;
    background: rgba(253, 249, 244, .88);
    backdrop-filter: saturate(1.4) blur(12px);
    -webkit-backdrop-filter: saturate(1.4) blur(12px);
    border-bottom: 1px solid transparent;
    transition: border-color .3s ease, box-shadow .3s ease, padding .3s ease;
    /* 毛玻璃背景铺满整行，内容居中收窄 */
    display: flex;
    flex-direction: column;
    align-items: center;
}
.header.scrolled {
    border-bottom-color: var(--line);
    box-shadow: 0 3px 14px rgba(120, 96, 72, .05);
}
/* 标题左边一个可爱的小圆点，替代 emoji，风格更干净统一 */
.header h1 {
    display: flex;
    align-items: center;
    gap: 9px;
    width: 100%;
    max-width: var(--content-max);
    font-size: 20.5px;
    font-weight: 700;
    letter-spacing: .3px;
    line-height: 1.35;
}
.header h1::before {
    content: "";
    flex: none;
    width: 11px;
    height: 11px;
    border-radius: 50%;
    background: var(--primary);
    box-shadow: 0 0 0 4px var(--primary-soft);
}
.header .sub-title {
    width: 100%;
    max-width: var(--content-max);
    font-size: 12px;
    color: var(--text-soft);
    margin-top: 5px;
    padding-left: 20px;
}

/* ---- 下拉刷新指示器（v1.17.0 起真的能用） ----
   位置与位移都由 static/app.js 的 PullToRefresh 用 transform 直接写：
   手势期间写 `translate(-50%, Npx)`（跟着手指走），刷新中写在页眉下面那个固定位置。
   这里只负责"它长什么样"与"回弹的动画"。 */
.ptr-element {
    position: fixed;
    top: 0;
    left: 50%;
    /* 初始位置在屏幕外面（-80px 足够把它整个藏起来），JS 拖动时才把它拉下来 */
    transform: translate(-50%, -80px);
    z-index: 9999;
    width: 42px;
    height: 42px;
    border-radius: 50%;
    background: var(--surface);
    border: 1px solid var(--line);
    box-shadow: var(--sh-md);
    display: flex;
    align-items: center;
    justify-content: center;
    /* 回弹动画。JS 在拖动期间会把 transition 临时设成 none，否则指示器跟不上手指 */
    transition: transform .3s var(--ease);
    /* 它只用来显示状态，不该抢走手势或挡到页眉上的按钮 */
    pointer-events: none;
}
.ptr-element .ic { color: var(--primary); font-size: 17px; transition: transform .3s ease; }
/* 拽够了（松手就会刷新）：箭头翻过来 + 换色，给一个"这就松手"的信号 */
.ptr-element.ready .ic { transform: rotate(180deg); color: var(--primary-dark); }
/* 正在刷新：转圈 */
.ptr-element.refreshing .ic { animation: spin 1s linear infinite; }
/* 刷新完成：打勾 + 薄荷绿（成功是报喜，用与「全部任务已完成」同一支绿色） */
.ptr-element.done .ic { color: var(--mint-ink); }
@keyframes spin { to { transform: rotate(360deg); } }

/* ---- 栏目容器与切换 ---- */
.page { display: none; padding: 18px 16px 8px; }
.page.active { display: block; animation: fadeIn .34s var(--ease); }
/* 宽屏下把栏目内容收成居中窄栏（与页眉对齐） */
#page-notice, #page-homework, #page-online, #page-mine {
    max-width: var(--content-max);
    margin: 0 auto;
}
@keyframes fadeIn {
    from { opacity: 0; transform: translateY(8px); }
    to   { opacity: 1; transform: translateY(0); }
}

/* ---- 底部导航 ---- */
.bottom-nav {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 1000;
    margin: 0 auto;
    max-width: var(--content-max);
    /* 安全区（iPhone 的小白条 / 微信底栏）只加在**高度**上，下面再用内边距留出来：
       内容区因此恒为 --nav-height，图标与文字在电脑端和手机端都居中。
       v1.20.0 之前是 height 固定 68px、底部内边距吃安全区 —— 内容区被压扁，
       整排图标文字被顶高，手机上看着明显偏上。 */
    height: calc(var(--nav-height) + env(safe-area-inset-bottom, 0px));
    padding: 0 6px env(safe-area-inset-bottom, 0px);
    display: flex;
    justify-content: space-around;
    align-items: center;
    background: rgba(255, 255, 255, .94);
    backdrop-filter: saturate(1.4) blur(14px);
    -webkit-backdrop-filter: saturate(1.4) blur(14px);
    border-top: 1px solid var(--line);
    border-radius: 22px 22px 0 0;
    box-shadow: 0 -4px 18px rgba(120, 96, 72, .06);
    transform: translateY(0);
    transition: transform .3s var(--ease);
}
.bottom-nav.hidden { transform: translateY(100%); }

.nav-item {
    position: relative;
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 3px;
    /* 上下内边距刻意不等（10 / 6）：图标与文字整体下移 2px。
       导航栏是"图标在上、文字在下"的两行，几何居中之后图标看上去仍偏上一点，
       所以按观感再压 2px 下来，电脑端和手机端都更稳。 */
    padding: 10px 0 6px;
    font-size: 10.5px;
    font-weight: 600;
    color: var(--text-faint);
    transition: color var(--speed) ease;
}
.nav-item .ic { font-size: 20.5px; transition: transform .3s var(--ease); }
/* 选中态：图标上浮 + 背后一块柔和小色块 */
.nav-item::after {
    content: "";
    position: absolute;
    /* 跟着上面那 2px 一起下移，色块才继续套在图标背后 */
    top: 6px;
    left: 50%;
    width: 44px;
    height: 30px;
    margin-left: -22px;
    border-radius: var(--r-pill);
    background: var(--primary-soft);
    opacity: 0;
    transform: scale(.6);
    transition: opacity var(--speed) ease, transform .3s var(--ease);
}
.nav-item > * { position: relative; z-index: 1; }
.nav-item.active { color: var(--primary-dark); }
.nav-item.active::after { opacity: 1; transform: scale(1); }
.nav-item.active .ic { transform: translateY(-1px) scale(1.06); }

/* ---- 页脚版本信息 ---- */
.update-info {
    text-align: center;
    font-size: 11.5px;
    color: var(--text-faint);
    margin-top: 18px;
    padding-bottom: 18px;
    letter-spacing: .2px;
}
/* 两行式页脚（v1.24.4）：说明页的页脚是「更新时间 / 版本号」+「备案」两行。
   两行都是 .update-info 里的正文，所以内层 <p> 自带的外边距一律清掉 ——
   行与行之间只留 .beian-line 自己那 5px，块的上留白 / 下留白仍由 .update-info 出。
   首页的 .update-info 里没有子元素，这条对它没有影响。 */
.update-info > * { margin: 0; }

/* ============================================================================
   页面：首页 —— 附件 / 文件收集（v1.17.0 起）

   这一节只管**首页卡片上的胶囊**（「有附件」+「文件收集」）。
   文件清单本身（事项页 /notice/{id}）用的是文件页那套 .file-list / .file-row，
   不在这里重复定义 —— 它是同一批文件，长得一样才对。
   ========================================================================== */
/* 「有附件」胶囊。用雾青（--teal-*，v1.10.0 为「定时未开始」引入的那一支）：
   它既不是"紧急"也不是"已完成"，是第三种"这条还有东西可拿"的中性提示。 */
.item-tag.tag-attach {
    background: var(--teal-soft);
    border-color: var(--teal-line);
    color: var(--teal-ink);
}
.item-tag.tag-attach .ic { font-size: 11px; }

/* 「文件收集」胶囊（v1.18.0）：**与「接龙」同一支淡紫**（v1.21.0 按用户要求改色）。
   理由：这两颗胶囊是一对"要同学动手"的提示（报名 / 交文件），
   在卡片上并排出现时用同一种颜色才像一组；蜜黄（--honey-*）留给置顶，
   免得一行的胶囊五彩斑斓。配上传图标，与「N 人接龙」照样一眼分得清。 */
.item-tag.tag-collect {
    background: var(--lilac-soft);
    border-color: var(--lilac-line);
    color: var(--lilac-ink);
}
.item-tag.tag-collect .ic { font-size: 11px; }

/* ============================================================================
   页面：事项页（/notice/{id}，v1.18.0）

   接龙 / 上传附件 / 查看附件三块合在一页上，骨架（.admin-topbar / .admin-card /
   .admin-sub / .file-list / .file-row / .rc-*）全部复用现成的，
   本节只加这一页独有的几处。
   ========================================================================== */
/* 「查看附件」板块标题下面的说明行：条数与"点哪儿下载"。与 .admin-sub 同一处位置。 */
.attach-sub { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.attach-sub .attach-tip { color: var(--text-faint); font-size: 11.5px; }
/* 「上传附件」板块标题下面的说明行：与 .attach-sub 同一处位置、更小一号 */
.collect-sub { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.collect-sub .collect-tip { color: var(--text-faint); font-size: 11.5px; }
/* 上传工具行：与「班级文件」页的 .file-toolbar 同一套，去掉多余的下边距 */
.collect-toolbar {
    margin-bottom: 12px;
    padding-bottom: 12px;
    border-bottom: 1px dashed var(--line);
}
.collect-list { margin-top: 2px; }
/* 附件的空态沿用 .empty（文件页同一套），这里不额外定义 */

/* ---- 详细通知板块（v1.22.0）----
   事项页最上面那一块。折叠的实现在这里（CSS 截断），"要不要显示那颗展开按钮"
   由 static/notice.js 量一次真实高度决定（服务端只能估，估不准）。 */
.detail-card { position: relative; }
/* 正文容器：折叠态下固定行数，超出部分藏起来 */
.detail-body {
    position: relative;
    margin-top: 2px;
    font-size: 13.5px;
    line-height: 1.75;
    color: var(--text);
    word-break: break-word;
}
.detail-body.collapsed .detail-content {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    /* 行数由模板注入的 --detail-lines 决定（默认 8 行，见 notice.html）；
       同时给标准属性，将来浏览器支持了就自动生效 */
    -webkit-line-clamp: var(--detail-lines, 8);
    line-clamp: var(--detail-lines, 8);
    overflow: hidden;
}
/* 折叠时底部一块**毛玻璃**（v1.24.2 按用户要求：原来是白→透明的渐变，
   最后一排字只是"变淡"，还不足以提示"下面没显示完"）。三层叠出来的效果：
     · backdrop-filter 把被截断的那半行字磨开 —— 这是"毛玻璃"的本体；
     · 一层往下越来越实的白，把截断的边收干净；
     · mask 让上半截的模糊淡入，不出现一条生硬的横边。
   v1.23.0 起只有**真的折起来了**（内容超出）才盖上 —— 以前是"折着就显示"，
   于是内容本来就没超出时，最后一排字会被它压得发虚（用户反馈的正是这个）。
   要不要盖由 static/notice.js 量完行数决定（.has-fade）。 */
.detail-fade {
    display: none;
    position: absolute;
    left: 0; right: 0; bottom: 0;
    /* v1.24.3 按用户要求"薄一点"：高度 46 → 38px、模糊 3 → 1.8px、
       白色那层也调淡（.55 → .42）。以前那一块压得有点像"贴了张白纸"，
       现在只是把最后半行字轻轻磨开，仍然看得出下面还有内容。 */
    height: 38px;
    background: linear-gradient(to bottom,
        rgba(255, 255, 255, 0) 0%,
        rgba(255, 255, 255, .42) 48%,
        var(--surface) 100%);
    backdrop-filter: blur(1.8px);
    -webkit-backdrop-filter: blur(1.8px);
    -webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 52%);
    mask-image: linear-gradient(to bottom, transparent 0%, #000 52%);
    pointer-events: none;
}
.detail-body.collapsed.has-fade .detail-fade { display: block; }
/* 正文里的元素：与全站同一套字号与间距，不用浏览器默认的（默认 h3 / ul 太大太散） */
.detail-content > *:first-child { margin-top: 0; }
.detail-content p { margin: 0 0 8px; }
.detail-content h1, .detail-content h2, .detail-content h3, .detail-content h4 {
    font-size: 14.5px;
    font-weight: 700;
    margin: 14px 0 6px;
    color: var(--text);
}
.detail-content ul, .detail-content ol { margin: 0 0 8px; padding-left: 22px; }
.detail-content li { margin-bottom: 3px; }
.detail-content blockquote {
    margin: 0 0 8px;
    padding: 6px 12px;
    border-left: 3px solid var(--primary-line);
    background: var(--surface-soft);
    border-radius: 0 var(--r-sm) var(--r-sm) 0;
    color: var(--text-mid);
}
/* 插图：等比缩放到栏宽之内，圆角与全站一致 */
.detail-content img {
    max-width: 100%;
    height: auto;
    border-radius: var(--r-sm);
    border: 1px solid var(--line);
    margin: 6px 0;
    cursor: zoom-in;
}
/* 展开 / 收起按钮：整行居中的一条淡底，**不要边框**（v1.24.2 按用户要求去掉）——
   它紧挨着上面那块毛玻璃，再套一圈虚线就成了两个框叠在一起。 */
.detail-toggle {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    width: 100%;
    margin-top: 10px;
    padding: 9px;
    border: none;
    border-radius: var(--r-sm);
    background: var(--surface-soft);
    color: var(--text-mid);
    font-size: 12px;
    font-weight: 600;
    transition: background var(--speed) ease, color var(--speed) ease;
}
.detail-toggle:hover { background: var(--primary-soft); color: var(--primary-dark); }
.detail-toggle .ic { font-size: 13px; transition: transform var(--speed) var(--ease); }
.detail-body.open + .detail-toggle .ic { transform: rotate(180deg); }
/* 量高度那一瞬用的类：不做过渡，免得用户看到内容"抖一下"（见 notice.js） */
.detail-body.measuring, .detail-body.measuring .detail-fade { transition: none !important; }

/* ============================================================================
   页面：管理后台 —— 附件（v1.17.0）

   两处：表单里的"上传暂存区"（.attach-field）与列表每行展开后的清单
   （.attach-line / .attach-body）。两处都用文件页那套 .file-row 来显示文件，
   只在外面包一层，保证"同一种文件在哪儿都长得一样"。
   ========================================================================== */
.attach-field { margin-top: 4px; }
.attach-actions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.attach-actions .btn { padding: 9px 15px; font-size: 12.5px; }
.attach-hint-extra { color: var(--text-faint); }

/* 暂存区清单：一行一个文件，与列表里那份同款 */
.attach-list { display: flex; flex-direction: column; gap: 8px; margin-top: 10px; }
.attach-list[hidden] { display: none; }
.attach-list .file-row { background: var(--surface); }
/* 上传中的那一行：进度条替掉大小，右边的类型徽标换成百分比 */
.attach-bar {
    display: block;
    height: 5px;
    margin-top: 4px;
    border-radius: var(--r-pill);
    background: var(--bg-lines);
    overflow: hidden;
}
.attach-bar > i {
    display: block;
    height: 100%;
    width: 0;
    border-radius: var(--r-pill);
    background: var(--primary);
    transition: width .15s linear;
}
.attach-state {
    flex: none;
    font-size: 11.5px;
    font-weight: 600;
    color: var(--primary-dark);
    font-variant-numeric: tabular-nums;
}
.file-row.is-uploading { opacity: .9; }
.attach-list .attach-del { flex: none; }
.attach-list .file-thumb .ic { font-size: 17px; }
/* 没有图标文字可用时（.file-thumb 里放的是 <svg> 而不是扩展名缩写），
   方块仍然要居中 —— 文件页那一行是文字，这里偏一点看得出来 */
.attach-list .file-thumb,
.attach-body .file-thumb { display: flex; align-items: center; justify-content: center; }

/* 列表里那一行「附件（N 个）· 点击查看」 */
.attach-line { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.attach-open {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 5px 11px;
    border-radius: var(--r-pill);
    background: var(--teal-soft);
    border: 1px solid var(--teal-line);
    color: var(--teal-ink);
    font-size: 12px;
    font-weight: 600;
}
.attach-open:hover { background: var(--teal-line); }
.attach-open .ic { font-size: 12px; transition: transform .25s var(--ease); }
.attach-line.open .attach-open .ic { transform: rotate(180deg); }

.attach-body {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-top: 8px;
    padding-left: 2px;
}
.attach-body[hidden] { display: none; }
.attach-body .file-row { background: var(--surface); }
.attach-body .file-open { cursor: pointer; }
.attach-body .attach-item-del { flex: none; }
.attach-loading { font-size: 11.5px; color: var(--text-faint); padding: 6px 2px; }

/* 管理页每行那句「附件（N 个）· 点击查看 / 事项详情页 / 已接龙 N 人 / 已上传 N 份文件 /
   打包下载」（v1.19.0 起合成一行）。四样说的是同一件事的入口与现状，所以并排：
   展开按钮与详情链接在最前、情况跟在后面、打包按钮靠右（margin-left:auto）——
   一排扫下来就知道这条有什么、有多少、能不能导。
   窄屏放不下时整行换行（flex-wrap），按钮自己落到第二行，不会把链接挤成两行。 */
.detail-line { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.detail-line .attach-open { margin: 0; }       /* 与详情链接同一排，不留额外外边距 */
.detail-line .zip-btn {
    /* 「打包下载」是这一排里唯一真正"导出文件"的动作，靠右放，
       与左边的文字链接 / 详细通知按钮拉开距离 */
    margin-left: auto;
    padding: 5px 10px;
    font-size: 11.5px;
    font-weight: 600;
    white-space: nowrap;
}
.detail-line .zip-btn .ic { font-size: 12px; }
/* 列表里那颗「详细通知」按钮（v1.22.0）：与「打包下载」同为这一排的动作按钮，
   用琥珀金（与卡片上那颗胶囊同色），一眼分得出"这个是写内容、那个是导出文件"。
   margin-left:auto 让它自己靠右；若「打包下载」也在（它也有 auto），
   两者按顺序排在右侧 —— 谁先出现谁吃剩余空间，视觉上仍是"动作在右、信息在左"。 */
.detail-line .detail-edit-btn {
    margin-left: auto;
    padding: 5px 10px;
    font-size: 11.5px;
    font-weight: 600;
    white-space: nowrap;
    background: var(--honey-soft);
    border: 1px solid var(--honey-line);
    color: var(--honey-ink);
}
.detail-line .detail-edit-btn:hover { background: var(--honey-line); }
.detail-line .detail-edit-btn .ic { font-size: 12px; }

/* ============================================================================
   页面：管理后台 —— 详细通知编辑框（v1.22.0，v1.23.0 "大而不满屏"，v1.24.2 "几乎满屏"）

   一个自绘弹窗（结构见 classroot/admin.html 的 #detailMask）。壳的手感与
   static/ui.js 那套 .ui-* 一致（圆角、阴影、遮罩、锁滚动），
   只是内容换成了一个"带工具栏的编辑区"，所以它不放进 ui.js 的通用弹窗里 ——
   那个弹窗是"回答一个问题的地方"（两个按钮 + 一段话），这里是"写东西的地方"。

   三条与通用弹窗**刻意不同**的约定：

   1. **尽量占满屏幕**（v1.24.2 按用户要求再放大）：桌面只在四周留 10px 遮罩，
      宽度上限 1200px；手机端连这圈边和圆角都去掉，就是整屏。写通知是
      "坐下来写东西"的场景，编辑区越大越少滚动。高度默认 100%，
      用户拖过右下角之后按拖过的像素值来（见 admin.html 的 savedDetailHeight），
      手机端不采纳那个值（窄屏就是整屏）。
   2. **键盘弹出时不把编辑框顶上去**：手机弹软键盘时浏览器默认会把整个文档往上推，
      弹窗跟着上移、露出后面的页面（顶部那条"标题 + 关闭"还可能被推出屏幕）。
      这里的做法是：遮罩直接钉在**视觉视口**上（top 与高度都由 admin.html 的
      syncViewportBox 按 visualViewport 实时写入），键盘一来弹窗只是**变矮**，
      位置一动不动。旧内核没有 visualViewport 时退回 CSS 默认值，
      此时靠 <meta name="interactive-widget"> 让可见区自己变矮（同样不位移）。
   3. **点编辑框外面不关闭**：以前点遮罩 = 取消，手机上误触一次就白写了。
      现在只有右上角的叉和「取消编辑」会关（回归"关窗必须是有意的"）。
   ========================================================================== */
.detail-mask {
    position: fixed;
    /* v1.24.2：整块遮罩直接对齐**视觉视口**（--vv-top / --vv-height 由 admin.html 的
       syncViewportBox 写进来）。以前是 top:0 + bottom:--kb-inset，靠"从底下收多少"
       避开软键盘；但手机上弹键盘时整页会被浏览器往上顶，遮罩钉在布局视口上就跟着跑，
       表现正是"编辑框被顶上去"。改成显式跟随视觉视口之后，键盘一来弹窗只是**变矮**，
       位置一动不动。 */
    top: var(--vv-top, 0px);
    left: 0;
    right: 0;
    height: var(--vv-height, 100%);
    z-index: 120;
    display: flex;
    align-items: stretch;
    justify-content: center;
    padding: 10px;
    background: rgba(74, 64, 72, .42);
    backdrop-filter: blur(2px);
    animation: uiMaskIn .16s ease both;
}
.detail-mask[hidden] { display: none; }
.detail-dialog {
    display: flex;
    flex-direction: column;
    width: 100%;
    /* v1.24.2：尽量占满屏幕 —— 宽度上限放到 1200（大屏写长文更舒服），
       高度默认就是遮罩的 100%（手机=整屏，桌面=上下各留 10px 遮罩边）；
       用户拖过右下角的话按记在 --detail-h 的那个高度（手机端不采纳，见下面媒体查询）。 */
    max-width: 1200px;
    height: var(--detail-h, 100%);
    max-height: 100%;
    padding: 14px 16px 12px;
    border-radius: var(--r-lg);
    background: var(--surface);
    box-shadow: var(--sh-md);
    animation: uiDialogIn .2s var(--ease) both;
}
/* 关掉动画的项目里（prefers-reduced-motion）不要那一下缩放 */
@media (prefers-reduced-motion: reduce) {
    .detail-mask, .detail-dialog { animation: none; }
}
.detail-head {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 8px;
    flex: none;                       /* 标题与叉永远在顶上，不参与滚动 */
}
.detail-title {
    display: flex;
    align-items: center;
    gap: 7px;
    font-size: 15px;
    font-weight: 700;
    color: var(--text);
}
.detail-title .ic { font-size: 16px; color: var(--honey-ink); }
.detail-who {
    flex: 1;
    min-width: 0;
    font-size: 11.5px;
    color: var(--text-faint);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* 右上角那颗关闭叉：与自绘弹窗同一处位置、同一套手感（点它才关窗） */
.ui-dialog-x {
    flex: none;
    width: 28px;
    height: 28px;
    border-radius: 50%;
    font-size: 17px;
    line-height: 1;
    color: var(--text-soft);
    background: var(--surface-soft);
}
.ui-dialog-x:hover { background: var(--bg-lines); color: var(--text); }

/* 工具栏：一行小胶囊按钮，窄屏横向滚动（不换行，免得编辑区被挤矮） */
.detail-toolbar {
    display: flex;
    align-items: center;
    gap: 6px;
    padding-bottom: 8px;
    margin-bottom: 8px;
    border-bottom: 1px dashed var(--line);
    overflow-x: auto;
    scrollbar-width: none;
    flex: none;
}
.detail-toolbar::-webkit-scrollbar { display: none; }
.detail-tool {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    flex: none;
    padding: 6px 10px;
    border-radius: var(--r-pill);
    border: 1px solid var(--line);
    background: var(--surface-soft);
    color: var(--text-mid);
    font-size: 12px;
    font-weight: 600;
    white-space: nowrap;
}
.detail-tool:hover { background: var(--primary-soft); border-color: var(--primary-line); color: var(--primary-dark); }
.detail-tool .ic { font-size: 13px; }
.detail-tool.danger { color: var(--rose-ink); border-color: var(--rose-line); background: var(--rose-soft); }
.detail-tool.danger:hover { background: var(--rose-line); color: var(--rose-ink); }
.detail-tool-sep { flex: none; width: 1px; height: 18px; background: var(--line); margin: 0 2px; }

/* 编辑区：白底、可滚动、把弹窗剩下的高度全吃掉（v1.23.0 起不再封 46vh，
   编辑框越大越好写；上限由弹窗自身的高度决定） */
.detail-editor {
    flex: 1;
    min-height: 160px;
    max-height: none;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    padding: 12px 13px;
    border-radius: var(--r-md);
    border: 1px solid var(--line);
    background: var(--surface-soft);
    font-size: 13.5px;
    line-height: 1.75;
    color: var(--text);
    word-break: break-word;
}
.detail-editor:focus { outline: none; border-color: var(--primary-line); background: var(--surface); }
.detail-editor:empty::before {
    content: attr(data-placeholder);
    color: var(--text-faint);
    pointer-events: none;
}
/* 编辑区里的元素：与最终页面（.detail-content）保持同一套排版，
   否则"编辑时看着挺好、保存后变了样" */
.detail-editor p { margin: 0 0 8px; }
.detail-editor h1, .detail-editor h2, .detail-editor h3, .detail-editor h4 {
    font-size: 14.5px;
    font-weight: 700;
    margin: 12px 0 6px;
}
.detail-editor ul, .detail-editor ol { margin: 0 0 8px; padding-left: 22px; }
.detail-editor li { margin-bottom: 3px; }
.detail-editor img {
    max-width: 100%;
    height: auto;
    border-radius: var(--r-sm);
    border: 1px solid var(--line);
    margin: 6px 0;
}
.detail-editor blockquote {
    margin: 0 0 8px;
    padding: 6px 12px;
    border-left: 3px solid var(--primary-line);
    background: var(--surface-soft);
    color: var(--text-mid);
}

.detail-foot {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    margin-top: 10px;
    flex: none;                       /* 动作按钮永远在底下，不参与滚动 */
}
.detail-tip { flex: 1; min-width: 140px; font-size: 11px; color: var(--text-faint); }
.detail-foot-btns { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.detail-foot-btns .btn { padding: 9px 15px; font-size: 12.5px; }
/* 「取消编辑」= 不保存地关窗，跟保存按钮拉开一点距离，免得手机上点错 */
#detailCancel { margin-left: auto; }
/* 右下角的拖拽纹：拖动改弹窗高度。
   用 repeating-linear-gradient 画三道斜纹，与全站"自绘、不引外部图标"一致。
   窄屏（手机）隐藏：弹窗本来就占了小半屏，手指也没地方拖；
   按**宽度**判而不是按指针类型 —— 窄屏一定是手机，而"指针细不细"
   在模拟器 / 触屏笔记本上并不总说得准。 */
.detail-resize {
    flex: none;
    align-self: center;
    width: 18px;
    height: 18px;
    border-radius: 4px;
    cursor: ns-resize;
    opacity: .55;
    background-image: repeating-linear-gradient(
        135deg,
        var(--text-faint) 0 2px,
        transparent 2px 5px
    );
}
.detail-resize:hover { opacity: .9; }
@media (max-width: 560px) {
    /* 手机：弹窗铺满整个可见区域（没有遮罩边、没有圆角）—— 编辑框越大越少滚动，
       键盘顶起来时也只剩"变矮"这一件事。拖拽纹在手机上本来就藏掉。 */
    .detail-resize { display: none; }
    .detail-mask { padding: 0; }
    .detail-dialog { border-radius: 0; height: 100%; padding: 12px 12px 10px; }
    /* 底部按钮别压到 iPhone 的 Home 指示条上 */
    .detail-foot { padding-bottom: env(safe-area-inset-bottom, 0px); }
}
/* 拖动改高度时：整页跟着显示"正在拖"的光标，别让指针划过别处变回箭头 */
html.ui-resizing, html.ui-resizing * { cursor: ns-resize !important; }

/* 表单里那一行「填写详细通知」+ 当前状态（v1.22.0） */
.detail-field { margin-top: 4px; }
.detail-actions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.detail-actions .btn { padding: 9px 15px; font-size: 12.5px; }
.detail-state { font-size: 11.5px; color: var(--text-faint); }

/* ============================================================================
   页面：管理后台 / 事项页
   事项页（body.notice）刻意复用管理页的整套骨架：顶栏、说明行、卡片、
   提示条、表单控件全部用下面这些类，页面之间不会各长一套样式。
   ========================================================================== */
/* v1.19.0：body.notice 补进这一条 —— 事项页是唯一一个**没有**内边距的页面
   （卡片 border 左右各顶到屏幕边缘，手机上看着"边框被切掉了"，
   而且横向的圆角、阴影全都贴边）。补上之后与其余页面同宽同轴。
   顶部内边距同时收成 14px（用户反馈"顶部 UI 太高"）：事项页进来第一眼要看的是
   事项内容，顶上那两行（顶栏 + 标题块）不该占掉小半屏。 */
body.admin, body.rollcall, body.notice {
    padding: 22px 16px 70px;
    background:
        radial-gradient(1100px 420px at 50% -160px, #eef4ff 0%, rgba(238, 244, 255, 0) 70%),
        var(--bg);
    background-attachment: fixed;
}
/* v1.21.0：事项页的「顶部更紧凑」那几条专属覆盖全部撤掉了 ——
   它们只管顶栏与标题块，于是**事项页的顶栏比文件库矮半档**（标题 17.5px vs 19.5px、
   内边距 14px vs 22px、下外边距 4px vs 6px），两页的顶栏不在同一条基线上，
   来回切一眼就看得出来。现在事项页与文件库 / 管理页共用同一份顶栏几何，
   元素排版完全一致（用户要求："事项页顶部的元素排版应与文件库的一致"）。 */
/* 事项页另换一层淡紫光晕，与管理页的雾蓝区分开（它上面既有接龙也有交作业）；
   接龙页（旧地址留下的 body.rollcall）是同一支颜色，不必分两套。 */
body.notice, body.rollcall {
    background:
        radial-gradient(1100px 420px at 50% -160px, #f2edff 0%, rgba(242, 237, 255, 0) 70%),
        var(--bg);
    background-attachment: fixed;
}

.admin-topbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    /* 右侧可能有两颗按钮（如文件页的「同学端 / 返回管理」），窄屏放不下时整行换行，
       而不是把标题挤成两行 */
    flex-wrap: wrap;
    margin-bottom: 6px;
}
.admin-topbar h1 {
    display: flex;
    align-items: center;
    gap: 9px;
    font-size: 19.5px;
    font-weight: 700;
    letter-spacing: .3px;
}
.admin-topbar h1 .ic { font-size: 19.5px; color: var(--primary); }
.admin-sub {
    font-size: 12px;
    color: var(--text-soft);
    margin-bottom: 20px;
    display: flex;
    align-items: center;
    gap: 7px;
    flex-wrap: wrap;
}

.admin-card {
    max-width: 860px;
    margin: 0 auto 18px;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    box-shadow: var(--sh-sm);
    padding: 20px;
}
/* 顶栏与后端提示条要跟卡片同宽、同轴对齐 */
.admin-topbar, .admin-sub, .notice-bar {
    max-width: 860px;
    margin-left: auto;
    margin-right: auto;
}
.admin-card > h2 {
    display: flex;
    align-items: center;
    /* 窄屏（320px）上标题与「复制接龙结果」按钮挤不进一行时，让按钮整块换行，
       而不是把「接龙名单（3 人）」折成两行 */
    flex-wrap: wrap;
    gap: 8px;
    font-size: 14.5px;
    font-weight: 700;
    margin-bottom: 16px;
    color: var(--text);
}
.admin-card > h2 .ic { font-size: 16px; color: var(--primary); }

.admin-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0 16px; }
@media (max-width: 560px) { .admin-grid { grid-template-columns: 1fr; } }
.row-full { grid-column: 1 / -1; }

/* 实时判定预览 */
.preview {
    margin-top: 4px;
    padding: 11px 14px;
    font-size: 12px;
    line-height: 1.65;
    border-radius: var(--r-md);
    background: var(--surface-soft);
    border: 1px solid var(--line);
    color: var(--text-mid);
    transition: background var(--speed) ease, color var(--speed) ease, border-color var(--speed) ease;
}
.preview b { font-size: 12px; }
.preview.urgent { background: var(--rose-soft); border-color: var(--rose-line); color: var(--rose-ink); }
.preview.normal { background: var(--primary-soft); border-color: var(--primary-line); color: var(--primary-dark); }
.preview.done { background: var(--mint-soft); border-color: var(--mint-line); color: var(--mint-ink); }
.preview.pinned { background: var(--honey-soft); border-color: var(--honey-line); color: var(--honey-ink); }

/* 判定规则说明与提交按钮的上下留白（原来写在模板的 style 属性里） */
.hint-rules { margin-top: 8px; }
/* 提交行：新增/修改的保存按钮 + 编辑模式下才出现的「取消编辑」 */
.submit-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.submit-btn { margin-top: 16px; }
.submit-row .btn-ghost { margin-top: 16px; }

/* 置顶勾选框：整块可点 */
.pin-toggle {
    display: flex;
    align-items: flex-start;
    gap: 9px;
    font-size: 12px;
    line-height: 1.6;
    color: var(--honey-ink);
    cursor: pointer;
    user-select: none;
    padding: 10px 12px;
    border: 1px solid var(--honey-line);
    border-radius: var(--r-sm);
    background: var(--honey-soft);
    transition: box-shadow var(--speed) ease;
}
.pin-toggle:hover { box-shadow: 0 0 0 3px rgba(247, 185, 85, .16); }
.pin-toggle input { margin-top: 1px; }
.pin-toggle strong { color: var(--honey-ink); }

/* 接龙勾选框：与置顶同一套结构，换成淡紫配色以示区分 */
.roll-toggle {
    display: flex;
    align-items: flex-start;
    gap: 9px;
    font-size: 12px;
    line-height: 1.6;
    color: var(--lilac-ink);
    cursor: pointer;
    user-select: none;
    padding: 10px 12px;
    border: 1px solid var(--lilac-line);
    border-radius: var(--r-sm);
    background: var(--lilac-soft);
    transition: box-shadow var(--speed) ease;
}
.roll-toggle:hover { box-shadow: 0 0 0 3px rgba(114, 87, 201, .14); }
.roll-toggle input { margin-top: 1px; }
.roll-toggle input[type=checkbox]:checked { background: var(--lilac-ink); border-color: var(--lilac-ink); }
.roll-toggle strong { color: var(--lilac-ink); }

/* 文件收集勾选框（v1.18.0）：与接龙同一套结构，换成蜜黄色 ——
   与首页卡片上那颗「文件收集」胶囊同一个色，勾上之后在主页也能一眼对上 */
.collect-toggle {
    display: flex;
    align-items: flex-start;
    gap: 9px;
    font-size: 12px;
    line-height: 1.6;
    color: var(--honey-ink);
    cursor: pointer;
    user-select: none;
    padding: 10px 12px;
    border: 1px solid var(--honey-line);
    border-radius: var(--r-sm);
    background: var(--honey-soft);
    transition: box-shadow var(--speed) ease;
}
.collect-toggle:hover { box-shadow: 0 0 0 3px rgba(247, 185, 85, .18); }
.collect-toggle input { margin-top: 1px; }
.collect-toggle input[type=checkbox]:checked { background: var(--honey-ink); border-color: var(--honey-ink); }
.collect-toggle strong { color: var(--honey-ink); }

/* 定时勾选框 / 定时面板 / 定时徽标（v1.10.0）与「到点前只在管理页可见」那套
   在 v1.20.0～v1.21.0 全部删掉了：开始时间先改成与截止时间并列的普通字段，
   后来连定时显示本身也按用户要求取消 —— 事项保存后立即出现在主页上。 */

/* 接龙「附加信息」配置面板（v1.8.0）：挂在「开启接龙」下面的一小块设置，
   虚线淡紫描边表明它属于接龙那一组；没勾接龙时整块隐藏（字段仍会提交）。 */
.roll-note-config {
    margin: 2px 0 12px;
    padding: 12px 14px 8px;
    border: 1px dashed var(--lilac-line);
    border-radius: var(--r-sm);
    background: var(--surface);
}
/* 面板里的标签用淡紫，和上面的接龙勾选框归为同一组 */
.roll-note-config > .field-label,
.roll-note-config .admin-grid .field-label { color: var(--lilac-ink); }
.roll-note-config .admin-grid { gap: 0 14px; }
.roll-note-config .roll-toggle { margin-bottom: 8px; background: var(--lilac-soft); }
.roll-note-config .hint { color: var(--text-soft); }

/* 分类页签 */
.tabs { display: flex; gap: 8px; margin-bottom: 16px; flex-wrap: wrap; }
.tab {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 8px 15px;
    border-radius: var(--r-pill);
    font-size: 12px;
    font-weight: 600;
    color: var(--text-mid);
    background: var(--surface-soft);
    border: 1px solid var(--line);
    cursor: pointer;
    user-select: none;
    transition: background var(--speed) ease, color var(--speed) ease,
                border-color var(--speed) ease, transform var(--speed) var(--ease);
}
.tab:hover { border-color: var(--primary-line); color: var(--primary-dark); }
.tab:active { transform: scale(.96); }
.tab.active {
    background: var(--primary-soft);
    color: var(--primary-dark);
    border-color: var(--primary);
    box-shadow: 0 2px 8px rgba(110, 168, 254, .18);
}
.tab .n {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    border-radius: var(--r-pill);
    background: var(--surface);
    font-size: 10.5px;
    font-weight: 700;
    color: var(--text-soft);
}
.tab.active .n { background: var(--primary); color: #fff; }

/* 管理页事项行 */
.item {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 12px;
    padding: 13px 0;
    border-bottom: 1px dashed var(--bg-lines);
}
.item:last-child { border-bottom: none; }
.item .info { flex: 1; min-width: 0; }
.item .t {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    font-size: 13.5px;
    font-weight: 600;
    margin-bottom: 4px;
    word-break: break-word;
}
.item .t .ic { color: var(--honey); font-size: 13px; }
.item .m { font-size: 11.5px; color: var(--text-soft); line-height: 1.7; word-break: break-all; }
/* 剩余时间 / 定时倒计时（v1.12.0）：管理页的计时每秒由 tickCountdowns() 重算，
   所以它必须是 .m 那句话里的**独立节点**，不能混在一串死文字里。
   基础样式复用首页那颗 .item-countdown 胶囊，这里只把尺寸压小一点、
   并按行上的 data-level 换色（与首页卡片同一套语义色）。 */
.item .m .item-countdown { padding: 3px 8px; vertical-align: 1px; }
.item[data-level="normal"] .item-countdown { background: var(--primary-soft); color: var(--primary-dark); border-color: var(--primary-line); }
.item[data-level="urgent"] .item-countdown { background: var(--rose-soft); color: var(--rose-ink); border-color: var(--rose-line); }
.item[data-level="done"] .item-countdown { background: var(--mint-soft); color: var(--mint-ink); border-color: var(--mint-line); }
.item[data-level="pinned"] .item-countdown { background: var(--honey-soft); color: var(--honey-ink); border-color: var(--honey-line); }
/* v1.21.0：`.item.sched-item`（定时未开始的雾青整行底）与 `.tag-sched`（那枚徽标）
   也一起删掉了 —— 定时显示整体取消，管理页与同学端看到的是同一份列表。
   剩下的两种"整行底色"只有置顶（琥珀）与下面这些通用行样式。 */
.item.pinned-item {
    background: linear-gradient(180deg, #fffaf0 0%, #fffdf9 100%);
    border: 1px solid var(--honey-line);
    border-radius: var(--r-md);
    padding: 13px 13px;
    margin-bottom: 8px;
}
.item-actions { display: flex; flex-direction: column; gap: 6px; flex: none; }
/* 纯图标按钮（编辑 / 删除）并排一行，宽度与上面的置顶按钮对齐成一个小方块，
   避免一行里出现两个空荡荡的长按钮 */
.icon-row { display: flex; gap: 6px; justify-content: flex-end; }
.edit-btn, .del { width: 38px; }

/* ============================================================================
   页面：接龙（/rollcall/{id}）
   骨架（body.rollcall / .admin-topbar / .admin-card / .notice-bar / 表单控件）
   全部复用管理页那一套，这里只写接龙页独有的：事项名称块（+ 剩余时间胶囊）、
   名单、报名表单、附加信息「进行选择」的选项胶囊。
   事项的其余字段（分类 / 级别 / 截止 / 地点 / 备注 / 外链）不在本页重复展示 ——
   同学点进来只为报名，那些信息在首页卡片上已经有了。
   ========================================================================== */
/* 事项名称块（v1.8.0）：事项页唯一保留的事项信息，左名称、右剩余时间胶囊。
   窄屏放不下时胶囊自动换到第二行。

   v1.21.0：**去掉整块的装饰**（浅紫渐变底 / 描边 / 投影 / 内边距）——
   它原本是页面上第二个"卡片"，与顶栏叠在一起就成了"顶部有两层 UI"，
   用户反馈"事项页顶部的元素太高了"。现在它退化成一行普通信息（标题 + 胶囊），
   与「班级文件」页那条 .admin-sub 说明行占同一个位置、同一条左边线，
   两页顶部的元素排版因此一致。字号仍与页面标题同级（19.5px），
   这样"页面标题 → 事项名"的层级一眼看得出来。 */
.rc-hero {
    max-width: 860px;
    margin: 0 auto 16px;
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px 12px;
}
.rc-title {
    flex: 1 1 160px;
    min-width: 0;
    font-size: 19.5px;
    font-weight: 700;
    letter-spacing: .3px;
    line-height: 1.4;
    color: var(--text);
    word-break: break-word;
}

/* 剩余时间胶囊：与首页的 .item-countdown 同一套语义 ——
   平时雾蓝，不足 10 分钟（.soon）与已过期（.over）转粉红。
   数字由页面脚本每秒重算，服务端先渲染一份，JS 没跑起来也能看。
   v1.19.0：整体瘦一档（内边距 6/12→4/10、字 12.5px→12px、图标 13px→12px），
   430px 的手机上它能与事项名挤在同一行；再窄（360/320）时它会自己落到第二行，
   那一行的展开量也小了一截。 */
.rc-countdown {
    flex: none;
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 12px;
    font-weight: 700;
    line-height: 1;
    padding: 4px 10px;
    border-radius: var(--r-pill);
    background: var(--surface);
    color: var(--primary-dark);
    border: 1px solid var(--primary-line);
    white-space: nowrap;
}
.rc-countdown .ic { font-size: 12px; }
/* 最后 10 分钟转粉红；已过期则填成实心粉红 —— 一眼分得出"快到了"和"已经过了" */
.rc-countdown.soon { background: var(--rose-soft); color: var(--rose-ink); border-color: var(--rose-line); }
.rc-countdown.over { background: var(--rose); color: #fff; border-color: var(--rose); }

/* 名单标题不折行（人数字样短，折了两行很难看），挤不下时让复制按钮换行 */
.rc-list-title { white-space: nowrap; }

/* 「复制接龙结果」：跟在名单标题后面靠右，做成小号次级按钮，
   不与主色大按钮（我要接龙 / 返回主页）抢注意力 */
.rc-copy {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    margin-left: auto;
    padding: 5px 11px;
    font-size: 11.5px;
    font-weight: 600;
    color: var(--text-mid);
    background: var(--surface-soft);
    border: 1px solid var(--line);
    border-radius: var(--r-pill);
    white-space: nowrap;
    transition: background var(--speed) ease, color var(--speed) ease,
                border-color var(--speed) ease, transform var(--speed) var(--ease);
}
.rc-copy .ic { font-size: 12.5px; }
.rc-copy:hover { background: var(--bg-lines); }
.rc-copy:active { transform: scale(.96); }
/* 复制成功的短暂反馈：按钮自己变成薄荷色并写「已复制」，1.6 秒后复原 */
.rc-copy.done {
    background: var(--mint-soft);
    color: var(--mint-ink);
    border-color: var(--mint-line);
}

/* 名单：一行一个人，序号是「接龙名次」，自己的那条用薄荷色标出来 */
.rc-list { display: flex; flex-direction: column; gap: 8px; margin-bottom: 16px; }
.rc-item {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 9px 12px;
    border: 1px solid var(--line);
    border-radius: var(--r-sm);
    background: var(--surface-soft);
    font-size: 12.5px;
    animation: fadeIn .3s var(--ease);
}
.rc-no {
    flex: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    border-radius: 50%;
    background: var(--primary-soft);
    color: var(--primary-dark);
    font-size: 11px;
    font-weight: 700;
}
.rc-name { font-weight: 600; word-break: break-word; }
.rc-note-inline {
    font-size: 11.5px;
    color: var(--text-soft);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r-pill);
    padding: 2px 8px;
    word-break: break-word;
}
.rc-time {
    flex: none;
    margin-left: auto;
    font-size: 11px;
    color: var(--text-faint);
    white-space: nowrap;
}
.rc-item.mine {
    border-color: var(--mint-line);
    background: var(--mint-soft);
}
.rc-item.mine .rc-no { background: var(--mint); color: #fff; }
.rc-item.mine .rc-name { color: var(--mint-ink); }
.rc-cancel {
    flex: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    background: var(--surface);
    border: 1px solid var(--rose-line);
    color: var(--rose-ink);
    font-size: 11px;
    transition: background var(--speed) ease, color var(--speed) ease, transform var(--speed) var(--ease);
}
.rc-cancel:hover { background: var(--rose); color: #fff; }
.rc-cancel:active { transform: scale(.9); }
.rc-cancel:disabled { opacity: .5; cursor: not-allowed; }
.rc-cancel .ic { font-size: 11px; }
.rc-empty {
    text-align: center;
    padding: 22px 10px;
    border: 1px dashed var(--line-strong);
    border-radius: var(--r-sm);
    font-size: 12.5px;
    color: var(--text-soft);
}

/* 附加信息选「进行选择」（v1.9.0）：候选项不再是下拉框，而是一排直接能点的胶囊。
   下拉框得先点开才知道有哪些选项，选项少的时候纯属多一步；现在点一下就选中。
   原生 radio 藏起来，选中态画在后面的 <span> 上 —— 原生小圆点在手机上又小又难点。
   flex-wrap 是这一版的重点：优先排成一行，一行放不下（选项多 / 屏幕窄）才自动换行；
   每颗的宽度由文字决定且**不压缩**（flex: 0 0 auto）——
   留 flex-shrink 的话，一行放不下时浏览器会把每颗都挤窄去迁就，永远也换不了行；
   单个超长选项（上限 20 字）由 max-width: 100% 兜住，让文字在胶囊里自己折行。 */
.rc-choices { display: flex; flex-wrap: wrap; gap: 8px; }
.rc-choice { position: relative; flex: 0 0 auto; max-width: 100%; }
.rc-choice input[type=radio] {
    position: absolute;
    width: 1px;
    height: 1px;
    opacity: 0;
    pointer-events: none;
}
.rc-choice span {
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 13px;
    font-weight: 600;
    line-height: 1.25;
    padding: 9px 14px;
    border-radius: var(--r-sm);
    background: var(--surface-soft);
    border: 1px solid var(--line);
    color: var(--text-mid);
    word-break: break-word;
    transition: background var(--speed) ease, border-color var(--speed) ease,
                color var(--speed) ease;
}
.rc-choice span:hover { border-color: var(--line-strong); }
/* 键盘流：radio 本身不可见，用 :focus-visible 把焦点环画在胶囊上 */
.rc-choice input[type=radio]:focus-visible + span { box-shadow: 0 0 0 3px var(--primary-soft); }
.rc-choice input[type=radio]:checked + span {
    background: var(--primary-soft);
    border-color: var(--primary);
    color: var(--primary-dark);
}

/* ============================================================================
   页面：文件浏览（/files） / 文件管理（/admin/files）
   两个页面共用 _filebrowser.html 与 static/files.js，只差一个 can_manage，
   所以样式也不写两套：.file-* / .crumb* 一次写好，两边都吃这一份。
   骨架（body.admin / .admin-card / .btn / .ui-* 弹窗）全部复用管理页那一套。
   ========================================================================== */
/* 浏览页换一层薄荷色光晕：一眼分辨"这是同学在看班级资料"而不是管理后台。
   （管理页是雾蓝、接龙页是淡紫、404 页是雾蓝，四个页面各有各的气味）
   v1.14.0 起浏览页**不再有底部导航栏**，所以 v1.13.0 那条
   `body.files.browse.has-bottom-nav` 的底部留白也一起删掉了 ——
   现在只剩 body.admin 给的 70px，最后一排文件不会被任何固定元素压住，
   浏览页与管理页在这件事上重新一致。 */
body.files.browse {
    background:
        radial-gradient(1100px 420px at 50% -160px, #e8f8f1 0%, rgba(232, 248, 241, 0) 70%),
        var(--bg);
    background-attachment: fixed;
}
/* 顶栏右侧可能有两颗按钮（同学端 / 返回管理），靠 .admin-topbar 的 flex-wrap 换行 */
.topbar-btns { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
body.files .admin-sub code {
    background: var(--surface-soft);
    border: 1px solid var(--line);
    border-radius: 5px;
    padding: 1px 6px;
    font-size: 11px;
    color: var(--text-mid);
    word-break: break-all;
}

.file-card { position: relative; }

/* ---- 工具行：上传 / 新建文件夹 / 刷新 + 一句说明 ---- */
.file-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 9px;
    margin-bottom: 14px;
}
.file-toolbar .btn { padding: 10px 16px; font-size: 12.5px; }
/* 原生文件选择框藏起来，由 <label class="btn"> 代它出场（样式才能与全站一致） */
.file-input { display: none; }
.upload-hint {
    flex: 1 1 150px;
    font-size: 11px;
    line-height: 1.55;
    color: var(--text-faint);
}
/* 只在手机上多说一句（v1.14.0）：微信 / "网站转 App" 那类内嵌网页给的文件选择器
   常常只有相册与相机（宿主环境写死的，网页侧改不了），同学会以为"传不了文件"。
   这么一句提示的代价是零，能省掉一次"为什么只能传图片"的追问。
   用媒体查询而不是 JS 判断：无 JS / 脚本报错时这句提示照样在。 */
.upload-hint .touch-only { display: none; }
@media (pointer: coarse) {
    .upload-hint .touch-only { display: inline; }
}

/* ---- 排序控件（v1.12.0）----
   一颗下拉（按名称 / 按修改时间）+ 一颗升降序按钮，做成一个整体的胶囊组，
   与「上传 / 新建文件夹 / 刷新」那几颗按钮排在同一行。 */
.file-sort {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    padding: 3px 5px 3px 11px;
    border: 1px solid var(--line);
    border-radius: var(--r-pill);
    background: var(--surface-soft);
}
.file-sort-label {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 11px;
    font-weight: 700;
    color: var(--text-soft);
    white-space: nowrap;
}
.file-sort-label .ic { font-size: 12px; }
/* 下拉：共用规则给的是"满宽 + 大内边距"的表单控件（select 默认 width:100%），
   这里要的是一颗跟文字一样宽的小胶囊，所以宽度 / 内边距 / 配色全部要覆盖回来。
   v1.20.0：聚焦态不必再补一次箭头了 —— 基线的 `select:focus` 改成只改
   background-color（见样式表开头），箭头的尺寸与位置不会再被简写重置。
   以前就是这里"补了图、没补尺寸"，一聚焦箭头被放大平铺，糊在文字上。 */
.file-sort-select {
    width: auto;
    max-width: 9em;
    padding: 5px 24px 5px 6px;
    font-size: 12px;
    font-weight: 600;
    color: var(--primary-dark);
    background-color: transparent;
    background-image: var(--chevron-down);
    background-position: right 7px center;
    background-size: 12px;
    background-repeat: no-repeat;
    border-color: transparent;
    border-radius: var(--r-pill);
}
.file-sort-select:hover { border-color: transparent; background-color: var(--surface); }
.file-sort-select:focus {
    background-color: var(--surface);
    border-color: var(--primary-line);
    box-shadow: 0 0 0 3px var(--primary-soft);
}
.file-sort-dir {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 6px 11px;
    border-radius: var(--r-pill);
    font-size: 11px;
    font-weight: 700;
    line-height: 1;
    color: var(--primary-dark);
    background: var(--surface);
    border: 1px solid var(--primary-line);
    /* 窄屏上"升序"会被挤成"升 / 序"两行，那颗胶囊立刻变得又高又怪 —— 不许换行 */
    white-space: nowrap;
    transition: background var(--speed) ease, transform var(--speed) var(--ease);
}
.file-sort-dir:hover { background: var(--primary-soft); }
.file-sort-dir:active { transform: scale(.95); }
.file-sort-dir .ic { font-size: 12px; }
.file-sort-dir-text { white-space: nowrap; }

/* ---- 面包屑 ---- */
.crumbs {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 3px;
    padding: 8px 11px;
    margin-bottom: 12px;
    background: var(--surface-soft);
    border: 1px solid var(--line);
    border-radius: var(--r-sm);
}
.crumb {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    max-width: 210px;
    padding: 4px 9px;
    border: 1px solid transparent;
    border-radius: var(--r-pill);
    font-size: 12px;
    font-weight: 600;
    line-height: 1.4;
    color: var(--primary-dark);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    transition: background var(--speed) ease, transform var(--speed) var(--ease);
}
.crumb .ic { font-size: 13px; }
button.crumb:hover { background: var(--primary-soft); }
button.crumb:active { transform: scale(.96); }
/* 当前这一级不可点：给它一块白底卡片的样子，一眼看出"你在这里" */
.crumb.current {
    color: var(--text);
    background: var(--surface);
    border-color: var(--line);
    cursor: default;
}
.crumb-sep { font-size: 11px; color: var(--text-faint); }

/* ---- 列表 ---- */
.file-list { display: flex; flex-direction: column; gap: 8px; }

.file-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 9px 11px;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    transition: border-color var(--speed) ease, box-shadow var(--speed) ease,
                background var(--speed) ease;
}
.file-row:hover { border-color: var(--primary-line); box-shadow: var(--sh-xs); }
/* 整行里"可以点的那一块"（文件夹 = 进去，可预览的图片 = 看大图）。
   用独立的按钮包住图标与名字，右侧的操作按钮才不会嵌在里面。
   v1.12.0 起多了一种"本来就不可点"的行（非图片文件）：它是个 <div>，
   光标与按压动效都不该出现 —— 否则用户会以为点一下有反应。 */
.file-open {
    flex: 1;
    min-width: 0;
    display: flex;
    align-items: center;
    gap: 11px;
    padding: 0;
    background: none;
    border: none;
    color: inherit;
    text-align: left;
    transition: transform var(--speed) var(--ease);
}
.file-open:active { transform: scale(.99); }
/* 不可点的行：不缩放、光标是普通箭头，但名字照样可以框选复制 */
.file-open.is-static { cursor: default; }
.file-open.is-static:active { transform: none; }
/* 可预览的文件（图片 / PDF / 纯文本）：鼠标悬停时名字换色，暗示"这一行点得动" */
.file-open.is-preview { cursor: zoom-in; }
/* v1.21.0：可预览的不再只有图片，悬停高亮就不能只认 .k-img ——
   改成命中任何一种可预览行的方块（图片 / pdf / txt） */
.file-row:hover .file-open.is-preview .file-thumb { box-shadow: 0 0 0 3px var(--primary-line); }
.file-row:hover .file-open.is-preview .file-name { color: var(--mint-ink); }

/* 左侧小方块：文件夹是琥珀色；文件按**真实类型**上色（v1.13.0）。
   配色与图标一一对应，判据只有 static/files.js 的 TYPES 一张表，这里只负责上色：
     图片=薄荷   视频 / 音频=淡紫（影音一家，靠图标分）
     Word=雾蓝   Excel=叶绿   PPT=蜜桃   PDF=玫红
     压缩包=琥珀 代码=雾青    文本=中性灰
   认不出的类型不加类名，就吃 .file-thumb 的默认雾蓝底、图标是 i-file。 */
.file-thumb {
    flex: none;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    border-radius: var(--r-sm);
    background: var(--primary-soft);
    color: var(--primary-dark);
    font-size: 19px;
}
.file-thumb.dir { background: var(--honey-soft); color: var(--honey-ink); }
.file-thumb.k-img { background: var(--mint-soft); color: var(--mint-ink); }
.file-thumb.k-video { background: var(--lilac-soft); color: var(--lilac-ink); }
.file-thumb.k-audio { background: var(--lilac-soft); color: var(--lilac-ink); }
.file-thumb.k-word { background: var(--primary-soft); color: var(--primary-dark); }
.file-thumb.k-excel { background: var(--leaf-soft); color: var(--leaf-ink); }
.file-thumb.k-ppt { background: var(--peach-soft); color: var(--peach-ink); }
.file-thumb.k-pdf { background: var(--rose-soft); color: var(--rose-ink); }
.file-thumb.k-zip { background: var(--honey-soft); color: var(--honey-ink); }
.file-thumb.k-code { background: var(--teal-soft); color: var(--teal-ink); }
.file-thumb.k-text { background: var(--surface-soft); color: var(--text-mid); }

/* 右下角那枚小眼睛角标（.file-zoom，v1.13.0～v1.21.0）在 v1.22.0 **按用户要求删掉了**：
   它挤在 38px 的小方块上，和"这个文件是什么类型"的图标叠在一起显得乱。
   "这一行点得动"现在只由悬停换色（.file-open.is-preview）与 zoom-in 光标表达 ——
   手机上看不到悬停，但点一下就有预览层弹出来，本来也不需要一颗角标来预告。 */

.file-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.file-name {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    font-size: 13.5px;
    font-weight: 600;
    color: var(--text);
    word-break: break-word;
}
.file-meta { font-size: 11px; color: var(--text-soft); }
/* 扩展名小徽标：文件名很长时也能一眼看出是什么类型。
   v1.13.0 起跟着类型上色（与左边的方块同色系），一行的类型信息前后呼应；
   认不出的类型保持默认的中性灰。 */
.file-tag {
    display: inline-flex;
    align-items: center;
    padding: 3px 6px;
    border-radius: 5px;
    border: 1px solid var(--line);
    background: var(--surface-soft);
    font-size: 9.5px;
    font-weight: 700;
    line-height: 1;
    color: var(--text-soft);
    white-space: nowrap;
}
.file-tag.k-img { background: var(--mint-soft); border-color: var(--mint-line); color: var(--mint-ink); }
.file-tag.k-video { background: var(--lilac-soft); border-color: var(--lilac-line); color: var(--lilac-ink); }
.file-tag.k-audio { background: var(--lilac-soft); border-color: var(--lilac-line); color: var(--lilac-ink); }
.file-tag.k-word { background: var(--primary-soft); border-color: var(--primary-line); color: var(--primary-dark); }
.file-tag.k-excel { background: var(--leaf-soft); border-color: var(--leaf-line); color: var(--leaf-ink); }
.file-tag.k-ppt { background: var(--peach-soft); border-color: var(--peach-line); color: var(--peach-ink); }
.file-tag.k-pdf { background: var(--rose-soft); border-color: var(--rose-line); color: var(--rose-ink); }
.file-tag.k-zip { background: var(--honey-soft); border-color: var(--honey-line); color: var(--honey-ink); }
.file-tag.k-code { background: var(--teal-soft); border-color: var(--teal-line); color: var(--teal-ink); }
.file-tag.k-text { background: var(--surface-soft); border-color: var(--line); color: var(--text-mid); }

.file-actions { flex: none; display: flex; align-items: center; gap: 6px; }
/* 文件夹右侧的小箭头：转到朝右 */
.file-chev { font-size: 15px; color: var(--text-faint); transform: rotate(-90deg); }
.file-row.is-dir:hover .file-chev { color: var(--honey-ink); }

/* 窄屏上把两颗操作按钮的文字收起来，只留图标 —— 手机上一行里要塞下
   文件名 + 扩展名徽标 + 两个按钮，文字留着会把文件名挤得没地方。
   title 属性还在，长按 / 悬停都能看到按钮是干什么的。 */
@media (max-width: 480px) {
    .file-actions .btn-label { display: none; }
    .file-actions .btn-sm { padding: 8px 9px; }
}

/* ---- 拖拽上传 ---- */
.file-card.dragging {
    border-color: var(--primary);
    box-shadow: 0 0 0 4px var(--primary-soft);
}
.file-card.dragging::after {
    content: "松手上传";
    position: absolute;
    left: 50%;
    bottom: 14px;
    transform: translateX(-50%);
    padding: 7px 15px;
    border-radius: var(--r-pill);
    background: var(--primary);
    color: #fff;
    font-size: 12px;
    font-weight: 600;
    white-space: nowrap;
    box-shadow: var(--sh-primary);
    pointer-events: none;
}

/* ---- 上传进度托盘 ---- */
.upload-tray {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 11px 12px;
    margin-bottom: 12px;
    background: var(--primary-soft);
    border: 1px dashed var(--primary-line);
    border-radius: var(--r-md);
    animation: fadeIn .3s var(--ease);
}
.up-item { display: flex; align-items: center; gap: 10px; font-size: 12px; }
.up-icon { flex: none; font-size: 15px; color: var(--primary-dark); }
.up-main { flex: 1; min-width: 0; }
.up-name {
    font-weight: 600;
    color: var(--text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.up-bar {
    height: 4px;
    margin-top: 5px;
    border-radius: var(--r-pill);
    background: rgba(110, 168, 254, .22);
    overflow: hidden;
}
.up-bar > i {
    display: block;
    width: 0;
    height: 100%;
    border-radius: var(--r-pill);
    background: var(--primary);
    transition: width .2s ease;
}
.up-state { flex: none; font-size: 11px; font-weight: 700; color: var(--text-mid); white-space: nowrap; }
.up-close {
    flex: none;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    font-size: 11px;
    color: var(--text-soft);
    transition: background var(--speed) ease, color var(--speed) ease;
}
.up-close:hover { background: var(--surface); color: var(--text-mid); }
.up-retry {
    flex: none;
    padding: 4px 10px;
    border-radius: var(--r-pill);
    border: 1px solid var(--rose-line);
    background: var(--surface);
    font-size: 11px;
    font-weight: 600;
    color: var(--rose-ink);
}
.up-retry:hover { background: var(--rose); border-color: var(--rose); color: #fff; }
/* 三种结局：跑完转薄荷，失败转玫红，进度条都不再需要 */
.up-item.done .up-bar, .up-item.failed .up-bar { display: none; }
.up-item.done .up-icon, .up-item.done .up-state { color: var(--mint-ink); }
.up-item.failed .up-icon, .up-item.failed .up-state { color: var(--rose-ink); }

/* 空文件夹 / 加载失败时的一句补充说明 */
.empty-sub { margin-top: 5px; font-size: 11.5px; color: var(--text-faint); }
/* 空状态里的那颗按钮（如"回到全部文件"） */
.empty-action { margin-top: 14px; }

/* ---- 图片预览层（v1.12.0）----
   只有图片能点开（见 static/files.js）。它不是"回答一个问题"的自绘弹窗，
   而是"看东西的地方"：顶栏一行放文件名 + 尺寸 + 下载 + 关闭，下面整块留给图片。
   遮罩、圆角、动效都沿用 .ui-* 那一套令牌，观感与全站一致。 */
.file-preview {
    position: fixed;
    inset: 0;
    z-index: 210;                 /* 比自绘弹窗（200）再高一档：预览上不该再叠弹窗 */
    display: flex;
    flex-direction: column;
    padding: 14px;
    background: rgba(48, 41, 47, .82);
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
    animation: uiMaskIn .18s ease;
}
.file-preview-bar {
    flex: none;
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 8px 9px 8px 14px;
    margin-bottom: 12px;
    background: var(--surface);
    border-radius: var(--r-pill);
    box-shadow: var(--sh-md);
}
.file-preview-name {
    flex: 1;
    min-width: 0;
    font-size: 12.5px;
    font-weight: 700;
    color: var(--text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.file-preview-meta {
    flex: none;
    font-size: 11px;
    font-weight: 600;
    color: var(--text-soft);
    white-space: nowrap;
}
.file-preview-close {
    flex: none;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    border-radius: 50%;
    font-size: 13px;
    color: var(--text-mid);
    background: var(--surface-soft);
    transition: background var(--speed) ease, color var(--speed) ease;
}
.file-preview-close:hover { background: var(--rose-soft); color: var(--rose-ink); }

/* 图片舞台：整块都是"关闭区"，图本身居中且不超出可用高度 */
.file-preview-stage {
    flex: 1;
    min-height: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: auto;
}
.file-preview-img {
    max-width: 100%;
    max-height: 100%;
    border-radius: var(--r-sm);
    background: var(--surface);
    box-shadow: var(--sh-md);
    animation: uiDialogIn .24s var(--ease);
}

/* ---- PDF（v1.21.0 起能预览，v1.24.2 起自己画）----
   以前是 <iframe> 直接指向站内的内联响应，交给浏览器自带的 PDF 阅读器。
   桌面端一直好好的，但手机上安卓 Chrome / 微信 / QQ 的内核**不支持内嵌 PDF**，
   会把那一份当下载处理 —— 用户报的"点预览直接下载 PDF"就是它。
   现在由 pdf.js 把每一页画成 <canvas>（见 static/ui.js 的 pvRenderPdf），
   所以这里的骨架是"一叠纸"：白底 + 圆角 + 与图片预览同一份浅投影。 */
.file-pdf {
    display: flex;
    flex-direction: column;
    gap: 10px;
    width: 100%;
    padding-bottom: 2px;
}
/* 一页一张纸。还没画出来时先用"第 N 页"的占位撑住高度；
   宽高比由 JS 拿到页面尺寸后写在行内 aspect-ratio 上，canvas 进来时不会再跳。 */
.file-pdf-page {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    min-height: 140px;
    font-size: 12px;
    color: var(--text-faint);
    background: var(--surface);
    border-radius: var(--r-sm);
    box-shadow: var(--sh-md);
    overflow: hidden;
}
.file-pdf-canvas { display: block; width: 100%; height: auto; }
/* 打不开时的一句话：铺在舞台中间，不占满整块高度 */
.file-preview-fail {
    max-width: 460px;
    margin: 0;
    padding: 22px 20px;
    font-size: 12.5px;
    line-height: 1.8;
    text-align: center;
    color: var(--text-mid);
    background: var(--surface);
    border-radius: var(--r-sm);
    box-shadow: var(--sh-md);
}
/* 兜底：pdf.js 没加载出来（静态文件缺失 / 断网）时仍然退回 iframe ——
   桌面浏览器自带阅读器能用，手机上会下载，但总比一块空白强。 */
.file-preview-frame {
    width: 100%;
    height: 100%;
    border: 0;
    border-radius: var(--r-sm);
    background: var(--surface);
    box-shadow: var(--sh-md);
    animation: uiDialogIn .24s var(--ease);
}
.file-preview-stage.is-pdf { display: block; }

/* ---- 纯文本 / 代码（v1.21.0）----
   文本按原样铺在一张米白纸片上：等宽字体 + 保留换行与空格 + 长行横向滚动
   （不折行是刻意的 —— 代码 / 表格文本一折行就对不齐了）。
   纸片**撑满整个舞台**：只包住内容的话，内容少时下面会空出一大块深色背景，
   看着像预览没加载完（PDF 那边阅读器自己就是深底铺满的，两者观感要一致）。 */
.file-preview-text {
    margin: 0;
    padding: 18px 20px;
    flex: 1;
    min-height: 0;
    width: 100%;
    overflow: auto;
    text-align: left;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Courier New", monospace;
    font-size: 12.5px;
    line-height: 1.75;
    color: var(--text);
    background: var(--surface);
    border-radius: var(--r-sm);
    box-shadow: var(--sh-md);
    white-space: pre;
    tab-size: 4;
    animation: uiDialogIn .24s var(--ease);
}
/* 文本舞台：纸片与舞台等高（纵向 flex + stretch），横向仍然铺满 */
.file-preview-stage.is-text { align-items: stretch; justify-content: stretch; }
/* 文本太长时的提示（服务端截断 / 编码兜底都用同一句） */
.file-preview-note {
    /* v1.24.3：这一段是追加到 #previewTip 里的，必须自己占一行 ——
       以前是行内元素，会和「点空白处或按 Esc 关闭」挤在同一行里粘连成一句 */
    display: block;
    flex: none;
    margin-top: 6px;
    text-align: center;
    font-size: 11px;
    color: rgba(255, 255, 255, .62);
}
.file-preview-tip {
    flex: none;
    margin-top: 12px;
    text-align: center;
    font-size: 11px;
    color: rgba(255, 255, 255, .62);
}
/* "手机长按可存进相册"这句只在触屏设备上有意义，桌面端不显示 */
.file-preview-tip-save { display: none; }
@media (hover: none) and (pointer: coarse) {
    .file-preview-tip-save { display: inline; }
    .file-preview-tip-save::before { content: " · "; }
}

@media (max-width: 480px) {
    .file-preview { padding: 9px; }
    .file-preview-bar { padding: 7px 7px 7px 11px; gap: 7px; }
    .file-preview-name { font-size: 12px; }
    /* 手机上一行塞不下文件名 + 尺寸 + 下载 + 关闭：尺寸让位（它在列表里已经看过了） */
    .file-preview-meta { display: none; }
}

/* ============================================================================
   页面：404
   ========================================================================== */
body.page-404 {
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 22px;
    background:
        radial-gradient(900px 420px at 50% -140px, #eef4ff 0%, rgba(238, 244, 255, 0) 72%),
        var(--bg);
}
.error-card {
    width: 100%;
    max-width: 420px;
    padding: 44px 32px 34px;
    text-align: center;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 26px;
    box-shadow: var(--sh-md);
}
.illustration {
    position: relative;
    width: 116px;
    height: 116px;
    margin: 0 auto 24px;
}
.circle-bg {
    position: absolute;
    inset: 0;
    border-radius: 50%;
    background: linear-gradient(135deg, var(--primary-soft) 0%, #dbe8ff 100%);
    animation: pulse 3.4s ease-in-out infinite;
}
.plane {
    position: absolute;
    top: 50%;
    left: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 58px;
    height: 58px;
    color: var(--primary);
    animation: float 3.4s ease-in-out infinite;
}
.plane .ic { width: 100%; height: 100%; }
@keyframes float {
    0%, 100% { transform: translate(-50%, -50%) rotate(-6deg); }
    50%      { transform: translate(-50%, -62%) rotate(6deg); }
}
@keyframes pulse {
    0%, 100% { transform: scale(1); opacity: .85; }
    50%      { transform: scale(1.06); opacity: 1; }
}
.error-code {
    font-size: 56.5px;
    font-weight: 800;
    line-height: 1;
    letter-spacing: -2px;
    color: var(--primary);
    margin-bottom: 10px;
}
.error-title { font-size: 18px; font-weight: 700; margin-bottom: 10px; }
.error-desc { font-size: 12.5px; color: var(--text-soft); line-height: 1.8; margin-bottom: 28px; }
.btn-group { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; }
.footer-hint { margin-top: 26px; font-size: 11px; color: var(--text-faint); }

@media (max-width: 480px) {
    .error-card { padding: 34px 22px 26px; border-radius: 22px; }
    .error-code { font-size: 47px; }
    .illustration { width: 100px; height: 100px; margin-bottom: 18px; }
}

/* ============================================================================
   页面：使用说明（/help，v1.16.0，v1.21.0 重写为简洁版）

   骨架（body.admin / .admin-topbar / .admin-card / .notice-bar / .badge /
   .bottom-nav）全部复用现成的那一套，本分区只补几个本页独有的类：

     .help-note     正文段落（说明是给人读的，字号比 .hint 大一档）
     .help-sub      卡内小标题（如「四种颜色，看一眼就懂」）
     .help-steps    操作步骤的有序列表（自己画序号泡泡，不用浏览器默认的 1. 2.）
     .help-list     要点清单
     .help-key      正文里指代某颗按钮的迷你胶囊（接龙页撤销用的 ✕）
     .help-demo     示例块：里面装的是**系统里真实的 UI 组件**
     .help-legend   四张迷你示例卡并排（四种级别的颜色对比）

    v1.21.0 重写后全页只留两处示例（一条事项卡 + 四种颜色），装的都是首页那套
   .list-item / .item-tag，不是另画的插图 —— 首页样式一改，说明里的"截图"
    跟着一起变，永远不会出现"说明里长得和实际不一样"。
    所以这里绝不覆盖 .list-item 那一套的任何属性，只负责给它们腾地方。
   ========================================================================== */
/* 暖橙光晕：与「我的」栏目那张入口小卡的暖色呼应，一眼看出"这是说明页"。
   页面上其余页面各有气味：管理页雾蓝、接龙页淡紫、文件浏览页薄荷、404 页雾蓝。 */
body.help {
    background:
        radial-gradient(1100px 420px at 50% -160px, #fff1e6 0%, rgba(255, 241, 230, 0) 70%),
        var(--bg);
    background-attachment: fixed;
}
/* 本页从「我的」点进来，底部导航栏照旧给着（见 help.html 的 include），
   所以底部留白要跟首页同一条算法，否则固定导航会压住最后一段话。
   注意 body.admin 那条 70px 的 padding 在本页不够用，这里按首页的
   `--nav-height + 28px` 覆盖（body.help 的选择器优先级压得住 body.admin）。
   安全区也要一起算：导航栏手机上会比 --nav-height 高出一个小白条。 */
body.help.has-bottom-nav {
    padding-bottom: calc(var(--nav-height) + 28px + env(safe-area-inset-bottom, 0px));
}
/* 说明是拿来读的：卡片比文件页窄一点，一行少放几个字更好扫 */
body.help .admin-card { max-width: 720px; }

.help-note {
    font-size: 13px;
    line-height: 1.85;
    color: var(--text-mid);
}
.help-note + .help-note { margin-top: 10px; }
.help-note b { color: var(--text); }

/* 卡内小标题：左侧一条短色带，与正文分开又不像新卡片那么重 */
.help-sub {
    display: flex;
    align-items: center;
    gap: 7px;
    font-size: 13px;
    font-weight: 700;
    color: var(--text);
    margin: 20px 0 10px;
    padding-left: 10px;
    border-left: 3px solid var(--primary-line);
}
.help-sub .ic { font-size: 14.5px; color: var(--primary); }

/* 要点清单：默认的圆点又小又灰，换成淡蓝小圆点 */
.help-list {
    list-style: none;
    margin-top: 10px;
}
.help-list > li {
    position: relative;
    padding-left: 18px;
    font-size: 13px;
    line-height: 1.8;
    color: var(--text-mid);
}
.help-list > li + li { margin-top: 7px; }
.help-list > li::before {
    content: "";
    position: absolute;
    left: 4px;
    top: 9px;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--primary-line);
}
.help-list b { color: var(--text); }

/* 操作步骤：序号自己画一枚淡蓝泡泡（浏览器默认的 "1." 与全站圆角风格不搭） */
.help-steps {
    list-style: none;
    counter-reset: helpstep;
    margin-top: 12px;
}
.help-steps > li {
    position: relative;
    counter-increment: helpstep;
    padding-left: 32px;
    font-size: 13px;
    line-height: 1.8;
    color: var(--text-mid);
}
.help-steps > li + li { margin-top: 9px; }
.help-steps > li::before {
    content: counter(helpstep);
    position: absolute;
    left: 0;
    top: 1px;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    background: var(--primary-soft);
    color: var(--primary-dark);
    border: 1px solid var(--primary-line);
    font-size: 11.5px;
    font-weight: 700;
}
.help-steps b { color: var(--text); }
/* 正文里提到的那颗小按钮（接龙页撤销用的 ✕）：画成一颗迷你胶囊，指代清楚 */
.help-key {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 18px;
    padding: 1px 6px;
    border-radius: var(--r-pill);
    background: var(--surface-soft);
    border: 1px solid var(--line-strong);
    font-size: 11px;
    font-weight: 700;
    line-height: 1.5;
    color: var(--text-mid);
}

/* ---- 示例块 ---- */
/* 虚线框 + 一块极淡的暖底：明确区分"这是给你看的样例"与"这是页面自己的内容" */
.help-demo {
    margin-top: 12px;
    padding: 14px 12px 12px;
    border: 1px dashed var(--line-strong);
    border-radius: var(--r-md);
    background: var(--surface-soft);
}
.help-demo-tag {
    display: inline-block;
    margin-bottom: 10px;
    padding: 3px 10px;
    border-radius: var(--r-pill);
    background: var(--honey-soft);
    border: 1px solid var(--honey-line);
    color: var(--honey-ink);
    font-size: 10.5px;
    font-weight: 700;
}
/* 示例里的卡片压在暖底上，白卡片才浮得起来；宽度也让它们填满虚框 */
.help-demo .list-item { background: var(--surface); }
.help-demo .list-item.normal {
    /* 首页的 .normal 自带一层渐变底（为的是在奶油底上浮起来），
       放进虚框里那一层会与白卡打架，这里退回纯白 */
    background: var(--surface);
}
.help-caption {
    margin-top: 10px;
    font-size: 11.5px;
    line-height: 1.7;
    color: var(--text-soft);
}

/* ---- 胶囊一览（v1.24.2）----
   使用说明要把卡片上可能出现的**每一颗**小块都摆出来（用户要求，含「地点」这种
   常被忽略的）。一行一颗 + 一句解释：宽屏两列、窄屏一列。
   胶囊本身用的就是首页那套 .item-tag / .item-countdown / .item-location，
   所以这张表跟着首页一起变，不会写走样。 */
.help-pills {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 9px 16px;
    margin-top: 10px;
}
.help-pill {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    font-size: 12px;
    line-height: 1.7;
    color: var(--text-mid);
}
/* 左边那颗胶囊不许被压扁、也不许跟着文字换行断开 */
.help-pill > .item-tag,
.help-pill > .item-countdown,
.help-pill > .item-location { flex: none; margin-top: 2px; }
/* 手机上一行放不下"胶囊 + 一句解释"：挤成两列的话每列只剩八十来像素，
   解释会被折成竖条（v1.24.3 顺手改成单列）。 */
@media (max-width: 560px) {
    .help-pills { grid-template-columns: 1fr; gap: 10px; }
}

/* ---- 四种颜色并排看 ---- */
/* 两列网格（手机上自动变一列）：四张迷你卡并排，颜色的差别一眼能比出来。
   卡片本身是首页的 .list-item，这里只管排列与间距。 */
.help-legend {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 10px;
}
.help-legend .list-item { margin: 0; }
/* 迷你卡内容压短了，右下角没有时间块，让标题与正文自己撑起高度 */
.help-legend .item-name { font-size: 13.5px; }
.help-legend .item-note { font-size: 11.5px; }

@media (max-width: 560px) {
    body.help .admin-card { padding: 18px 15px; }
    /* 窄屏上四张迷你卡改成一条竖排：两列时并列的两句话长度不一样，
       短的那张会留一大块空白，反而不好读；竖排后宽度也够放下整句说明。
       （桌面上两列仍然保留，四色并排一眼可比。） */
    .help-legend { grid-template-columns: 1fr; }
    /* 虚框左右不留内边距：卡片与说明正文一样宽，窄屏上多出来的这 20px 很值钱
       （320px 的屏上正好能让「高等数学作业（第 3 章习题）」少折一行）。
       上下留一点，虚实两层边框才不粘在一起。 */
    .help-demo { padding: 12px 0 10px; }
    .help-demo-tag { margin-left: 10px; }
    .help-caption { padding: 0 10px; }
}

/* ================================================================ 登录页（v1.24.0）
   页面骨架在 classroot/login.html，这里只管外观。
   复用全站的设计变量（--primary / --surface / --line / --sh-*），
   所以它跟首页、管理页、404 是同一套皮肤，不是另起一炉。 */

/* 登录页是整屏居中的一张卡：不复用 .page-404 的背景，那里是"错误页"的语义 */
body.page-login {
    min-height: 100vh;
    min-height: 100dvh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 22px 18px;
    background:
        radial-gradient(900px 420px at 50% -140px, #eef4ff 0%, rgba(238, 244, 255, 0) 72%),
        var(--bg);
}

.login-card {
    width: 100%;
    max-width: 420px;
    padding: 34px 30px 26px;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 26px;
    box-shadow: var(--sh-md);
}

.login-brand { text-align: center; margin-bottom: 22px; }

/* 站点 logo（v1.24.2）：以前这里是一颗通用的「用户」图标 + 蓝紫渐变底，
   现在换成 cover.png 里裁出来的矿车插画（static/logo.png，192×192）。
   插画自带米色纸底，所以这块做成"圆角小方牌"：纸底色与卡片拉开一点层次，
   比直接贴在白卡上更像一枚图标。 */
.login-logo {
    width: 78px;
    height: 78px;
    margin: 0 auto 14px;
    border-radius: 22px;
    overflow: hidden;
    background: var(--surface-soft);
    border: 1px solid var(--line);
    box-shadow: var(--sh-sm);
}
.login-logo img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;      /* 正方裁切，任何尺寸都不变形 */
}

.login-title { font-size: 19px; font-weight: 800; letter-spacing: .3px; }
.login-sub { margin-top: 6px; font-size: 12.5px; color: var(--text-soft); }

/* 失败提示：默认 hidden，只有服务端带回 ?err= 时才出现。
   颜色的取法与"紧急"标签同一口径（红系），但更淡，避免整页发红。 */
.login-alert {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    margin-bottom: 16px;
    padding: 11px 13px;
    font-size: 12.5px;
    line-height: 1.65;
    color: #b4453f;
    background: #fdf0ef;
    border: 1px solid #f6d5d3;
    border-radius: 14px;
}
.login-alert[hidden] { display: none; }
.login-alert .ic { flex: none; font-size: 15px; margin-top: 1px; }

.login-form { display: flex; flex-direction: column; gap: 14px; }

.login-field { display: flex; flex-direction: column; gap: 7px; }

.login-label {
    font-size: 12.5px;
    font-weight: 700;
    color: var(--text-mid);
    padding-left: 2px;
}

.login-field input[type=text],
.login-field input[type=password] {
    width: 100%;
    padding: 12px 14px;
    font-size: 15px;
    font-family: inherit;
    color: var(--text);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 14px;
    transition: border-color .18s ease, box-shadow .18s ease;
}
.login-field input:focus {
    outline: none;
    border-color: var(--primary);
    box-shadow: 0 0 0 3px var(--primary-soft);
}

/* 密码框右侧的"看一眼"按钮：绝对定位压在输入框里，
   输入框右侧留出 44px 内边距免得文字被按钮盖住。 */
.login-pass { position: relative; display: block; }
.login-pass input { padding-right: 44px !important; }
.login-eye {
    position: absolute;
    top: 50%;
    right: 6px;
    transform: translateY(-50%);
    width: 34px;
    height: 34px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--text-soft);
    background: transparent;
    border: none;
    border-radius: 10px;
    cursor: pointer;
    transition: background .18s ease, color .18s ease;
}
.login-eye:hover { background: var(--bg-lines); color: var(--text-mid); }
.login-eye .ic { font-size: 16px; }

.login-submit {
    margin-top: 4px;
    width: 100%;
    justify-content: center;
    padding: 12px 18px;
    font-size: 15px;
}
.login-submit[disabled] { opacity: .72; cursor: default; }

/* 两张说明小字：第一张是"用的是学校账号"，第二张是"密码怎么处理"。
   第二张刻意更淡更小 —— 它是隐私说明，不是操作指引，不该抢按钮的注意力。 */
.login-note {
    margin-top: 18px;
    font-size: 12px;
    line-height: 1.75;
    color: var(--text-mid);
    text-align: center;
}
.login-note strong { color: var(--text); font-weight: 700; }
.login-note-quiet { margin-top: 8px; font-size: 11px; color: var(--text-soft); }

.login-foot {
    margin-top: 20px;
    padding-top: 14px;
    border-top: 1px dashed var(--bg-lines);
    font-size: 11px;
    color: var(--text-faint);
    text-align: center;
}

/* 备案信息行（v1.24.4，登录页与说明页共用；标记抽在 classroot/_beian.html）。
   背景：这行以前是登录页手写的内联标记 —— 而 style.css 顶部有一条
   `img, svg { display: block }`（图标一律当块级，省掉每处再写 vertical-align），
   公安徽标因此独占一行、被顶到卡片左边缘，它下面那条文字行里两个备案号又首尾
   相接，整块看着像排版崩了。
   现在整行是 flex：徽标和它自己的备案号同属一个 .beian-item（内部写死不折行），
   两条之间 14px，一行放不下时**整条**备案换行 —— 绝不会从
   「11011402057569」这种数字中间劈开。
   注意顺序：这里的 margin-top 要写在 .login-update 之前，登录页那边用后者的 8px。 */
.beian-line {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    column-gap: 14px;
    row-gap: 4px;
    margin-top: 5px;
}
.beian-item {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    white-space: nowrap;
    color: inherit;          /* a 全局是 color: inherit，这里只是把意图写明 */
}
.beian-item:hover { color: var(--text-mid); }
/* 徽标按小字算：13×14 与 11.5px 的字身齐平（登录页那边是 10.5px，同样对齐）。
   原图 62×67，两倍屏缩到 26×28，仍然清晰；flex:none 防它在窄屏被压扁。 */
.beian-icon {
    width: 13px;
    height: 14px;
    flex: none;
    object-fit: contain;
}

/* 登录页页脚（v1.24.1）：更新时间 / 版本号 / 备案号。
   复用首页与说明页那份 .update-info（见上面「页脚版本信息」一节，文案口径一致），
   只是它排在登录卡内部 —— 卡自己已经有下内边距，所以把默认的上下留白收掉，
   压成薄薄一行小字。 */
.login-update {
    margin-top: 8px;
    padding-bottom: 0;
    font-size: 10.5px;
}
/* 三个字段各自不许内部换行：窄屏放不下时只会在两个 `|` 之间换行，
   否则中文可以逐字断行，会把"京ICP备2026069673号-1"从数字中间劈开。
   （v1.24.4：这条同时管说明页页脚的第一行 .update-line，不只是登录页。） */
.update-info span { white-space: nowrap; }

/* v1.24.1：原来这里的 .userbar（首页页眉右上角的身份条，见 classroot/_userbar.html）
   与 .usertag（事项页 / 文件页 / 管理页顶栏里的身份胶囊，见 classroot/_usertag.html）
   连同它们各自的窄屏规则一起删掉了 —— 按用户要求，账号信息只在「我的」页显示，
   页眉与顶栏不再重复"你是谁"；模板里那两处 include 也一并去掉了。 */

@media (max-width: 560px) {
    .login-card { padding: 26px 20px 20px; border-radius: 22px; }
    .login-logo { width: 66px; height: 66px; border-radius: 19px; }
    .login-title { font-size: 17.5px; }
}

/* ---------- 管理员名单（v1.24.0，管理页最后一张卡） ---------- */

.admin-add-row {
    display: flex;
    flex-wrap: wrap;
    gap: 9px;
    margin: 14px 0 4px;
}
.admin-add-row input[type=text] {
    flex: 1 1 200px;
    min-width: 0;
    padding: 10px 13px;
    font-size: 14px;
    font-family: inherit;
    color: var(--text);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 12px;
}
.admin-add-row input[type=text]:focus {
    outline: none;
    border-color: var(--primary);
    box-shadow: 0 0 0 3px var(--primary-soft);
}

/* 加人成功/失败的一句话回执：就在按钮下面，不弹窗 —— 加人是高频且低风险的动作，
   为一个"已加入"弹一次窗反而打断操作。 */
.admin-add-hint {
    margin: 4px 0 10px;
    font-size: 12px;
    line-height: 1.7;
    color: var(--text-mid);
}
.admin-add-hint[hidden] { display: none; }
.admin-add-hint.ok { color: #3f8a5b; }
.admin-add-hint.warn { color: #b4453f; }

.admin-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 11px 4px;
    border-top: 1px dashed var(--bg-lines);
}
.admin-row:first-child { border-top: none; }

.admin-row-main {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 7px;
}
.admin-row-name { font-size: 14px; font-weight: 700; color: var(--text); }
.admin-row-sid {
    font-size: 12px;
    color: var(--text-mid);
    font-variant-numeric: tabular-nums;
}
/* 院系 / 专业 / 行政班：占满自己那一行，长文本也不会把姓名挤走 */
.admin-row-sub {
    flex: 1 1 100%;
    font-size: 11.5px;
    color: var(--text-soft);
    line-height: 1.6;
    word-break: break-all;
}
.admin-row-me {
    padding: 1px 7px;
    font-size: 10.5px;
    font-weight: 700;
    color: var(--primary-dark);
    background: var(--primary-soft);
    border: 1px solid var(--primary-line);
    border-radius: 999px;
}
.admin-row-del { flex: none; padding: 7px 12px; font-size: 12px; }

@media (max-width: 560px) {
    .admin-add-row input[type=text] { flex: 1 1 100%; }
    .admin-add-row .btn { flex: 1 1 auto; justify-content: center; }
}

/* ---------- 「我的」页里的本校身份卡（v1.24.0） ---------- */

.me-card .item-content { width: 100%; }

.me-admin-tag {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    margin-left: 6px;
    padding: 1px 8px;
    font-size: 10.5px;
    font-weight: 700;
    color: var(--primary-dark);
    background: var(--primary-soft);
    border: 1px solid var(--primary-line);
    border-radius: 999px;
    vertical-align: middle;
}
.me-admin-tag .ic { font-size: 11px; }

/* 字段表：桌面上两列，窄屏一列。
   用 grid 而不是每行一个 div，是因为这几项长短差别很大
   （"学号 2610403111" 与 "院系 机械与电气工程学院"），
   两列并排能把这张卡的高度压住，不必滑很久才看到下面的「班级文件」。 */
.me-fields {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 6px 14px;
    margin: 10px 0 8px;
}
.me-field {
    display: flex;
    align-items: baseline;
    gap: 7px;
    min-width: 0;
    font-size: 12.5px;
    color: var(--text-mid);
    line-height: 1.7;
}
.me-field b {
    flex: none;
    font-size: 11px;
    font-weight: 700;
    color: var(--text-soft);
}
.me-field > :last-child { word-break: break-all; }

.me-logout { flex: none; padding: 8px 13px; font-size: 12.5px; white-space: nowrap; }

@media (max-width: 560px) {
    /* 窄屏一列：两列时"机械与电气工程学院"会被折成三行，
       反而比一列更高、更难读。 */
    .me-fields { grid-template-columns: 1fr; }
    .me-card .item-meta { align-self: stretch; }
    .me-logout { width: 100%; justify-content: center; }
}

/* ---------------------------------------------------------------- 无障碍：尊重"减少动效" */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: .001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .001ms !important;
    }
}
