/* ============================================================
   HEU ESTA 官网 · 第 2 层：全站组件
   ------------------------------------------------------------
   每个页面都会加载。按钮 / 导航 / 页脚 / 表单 / 通用排版 / 内页
   面板 / 列表 / 徽章 / 分页 / 通知 / 公告 / 活动 / 积分 / 招新报名
   / 项目 / 新生指南 / 错误页 / 注册链路，以及它们的响应式规则。

   依赖 tokens.css 的令牌。首页专属分镜样式在 home.css，不要放这里。
   ============================================================ */


.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0 22px; }
.form-span-2 { grid-column: 1 / -1; }
.consent-row { padding: 16px; border: 1px solid var(--line); border-radius: 10px; background: rgba(255,255,255,.025); }
.consent-row label { display: flex; align-items: flex-start; gap: 10px; font-weight: 500; line-height: var(--lh-relaxed); }
.consent-row input { width: 18px; height: 18px; margin-top: 4px; flex: none; accent-color: var(--brand-2); }
.qq-card { display: flex; align-items: center; gap: 18px; margin-top: 24px; padding: 16px; border: 1px solid var(--line); border-radius: 14px; background: rgba(255,255,255,.03); }
.qq-card img { width: 142px; aspect-ratio: 1; object-fit: cover; border-radius: 10px; }
.qq-card div { display: flex; flex-direction: column; gap: 5px; }
.qq-card span, .qq-card small { color: var(--muted); }
.qq-card strong { color: #fff; font-size: var(--fs-2xl); }
.qq-card-compact { justify-content: center; margin: 24px auto; text-align: left; }
.legal-page { max-width: 900px; }
.release-footer { color: var(--brand-2); }
.qq-join-layout { display: flex; justify-content: center; align-items: center; gap: 34px; }
.home-qq-qr { width: min(280px, 60vw); border: 1px solid rgba(255,255,255,.16); border-radius: 18px; box-shadow: 0 24px 60px rgba(0,0,0,.28); }

/* 窄屏：账号链路一律单栏，说明在上、表单在下 —— 说明很短，先读理由再动手
   是对的顺序。sticky 必须关掉：单栏下它会把说明钉在表单上方一直跟着滚。 */
/* 窄屏重排成「标题 → 表单 → 补充说明」。表单是这些页面的主要动作，不能被
   整个左栏推到首屏之外。 */
@media (max-width: 900px) {
    .auth-grid {
        grid-template-columns: 1fr;
        grid-template-areas: "head" "panel" "extra";
        grid-template-rows: auto auto auto;
        gap: 22px;
    }
    .auth-box, .auth-box-wide { max-width: none; }
    .reg-channels { grid-template-columns: 1fr; }
}

@media (max-width: 720px) {
    .form-grid { grid-template-columns: 1fr; }
    .auth-stage { padding: 30px 0 48px; }
    .auth-box { padding: 22px 18px; }
    .reg-channel { padding: 24px 20px 22px; }
    .reg-channel-no { font-size: var(--fs-7xl); right: 16px; }
    .reg-channel h2 { font-size: var(--fs-3xl); }
    .reg-review { grid-template-columns: 1fr; gap: 2px; padding: 14px 16px; }
    .reg-review dt { margin-top: 8px; }
    .qq-join-layout { flex-direction: column; align-items: stretch; }
    .qq-card { align-items: flex-start; }
    .qq-card img { width: 110px; }
    .home-qq-qr { margin: auto; }
}

/* ---------- 按钮 ---------- */
.btn {
    display: inline-flex; align-items: center; justify-content: center; gap: 8px;
    padding: 12px 26px; border-radius: 999px; border: 1px solid transparent;
    font-size: var(--fs-md); font-weight: 600; cursor: pointer; white-space: nowrap;
    transition: transform .35s var(--ease-out), box-shadow .35s, background .25s, color .25s, border-color .25s;
    text-decoration: none; will-change: transform;
}
.btn:active { transform: scale(.965); transition-duration: .08s; }
.btn-primary { background: var(--accent); color: #fff; box-shadow: 0 8px 30px rgba(13, 169, 205, .35); }
.btn-primary:hover { background: var(--accent-2); color: #fff; box-shadow: 0 12px 40px rgba(13, 169, 205, .5); }
.btn-outline { background: transparent; color: var(--text); border-color: var(--line-d-strong); }
.btn-outline:hover { border-color: #fff; color: #fff; }
.btn-ghost { background: rgba(255, 255, 255, .07); color: var(--text); }
.btn-ghost:hover { background: rgba(255, 255, 255, .14); color: #fff; }
.btn-sm { padding: 7px 16px; font-size: var(--fs-sm); }
.btn-lg { padding: 15px 38px; font-size: var(--fs-lg); }
.btn-block { width: 100%; border-radius: 12px; }
/* 磁性按钮的 transform 由 JS lerp 驱动，去掉 transform 过渡避免追赶打架 */
.btn-magnet { transition: box-shadow .35s, background .25s, color .25s, border-color .25s; }
.inline-form { display: inline; }

/* 「了解更多」链接：细线 + 箭头滑动 */
.nf-link-more {
    display: inline-flex; align-items: center; gap: 8px;
    font-size: var(--fs-md); font-weight: 600; letter-spacing: .5px;
    color: inherit; opacity: .85; position: relative; padding-bottom: 3px;
}
.nf-link-more::after {
    content: ""; position: absolute; left: 0; bottom: 0; height: 1px; width: 100%;
    background: currentColor; transform: scaleX(.35); transform-origin: left;
    transition: transform .45s var(--ease-out); opacity: .5;
}
.nf-link-more:hover { opacity: 1; color: inherit; }
.nf-link-more:hover::after { transform: scaleX(1); }
.nf-link-more span { display: inline-block; transition: transform .35s var(--ease-out); }
.nf-link-more:hover span { transform: translateX(6px); }

/* ---------- 导航栏（透明 → 滚动后实底） ---------- */
.site-nav {
    position: fixed; top: 0; left: 0; right: 0; z-index: 100;
    background: transparent; border-bottom: 1px solid transparent;
    transition: background .4s, border-color .4s, backdrop-filter .4s;
}
.site-nav.scrolled {
    background: rgba(5, 5, 6, 0.78);
    backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
    border-bottom-color: var(--line-d);
}
.nav-inner { display: flex; align-items: center; gap: 30px; height: 74px; }
.nav-brand { display: flex; align-items: center; gap: 12px; }
/* 内联 SVG 会标：不再有位图的圆角裁切，stroke 颜色见下方反色小节 */
.nav-logo { width: 40px; height: 40px; display: block; flex: none; }
.nav-title { display: flex; flex-direction: column; line-height: var(--lh-tight); }
.nav-title strong { color: #fff; font-size: var(--fs-lg); letter-spacing: 1.5px; }
.nav-title small { color: var(--muted); font-size: var(--fs-3xs); font-family: var(--font-mono); letter-spacing: 3px; }
.nav-links { display: flex; align-items: center; gap: 2px; margin-left: auto; position: relative; }
/* nowrap 是必须的：不写的话空间一紧张，链接就在自己内部换行（「新生指 南」
   裂成两行），而不是让外层去处理溢出 —— 看着像随机的排版故障。 */
.nav-links a, .nav-group > summary { white-space: nowrap; }
.nav-links a {
    color: rgba(255, 255, 255, .72); font-size: var(--fs-sm); font-weight: 500;
    padding: 9px 15px; border-radius: 999px; position: relative; z-index: 1;
}
.nav-links a:hover { color: #fff; }
/* 无药丸（无 JS / 触屏 / reduced-motion）时退回逐链接背景 */
.nav-links:not(.has-pill) a:hover { background: rgba(255, 255, 255, .08); }

/* 滑动药丸：hover/focus 时在链接之间平滑滑动 */
.nav-pill {
    position: absolute; top: 0; left: 0; z-index: 0;
    background: rgba(255, 255, 255, .09); border-radius: 999px;
    opacity: 0; pointer-events: none;
    transition: transform .38s var(--ease-out), width .38s var(--ease-out), opacity .18s;
    will-change: transform, width;
}
.nav-links.pill-visible .nav-pill { opacity: 1; }

/* ---------- 导航折叠组（了解协会 / 学习资源） ----------
   用原生 `<details>`：没有 JS 也能展开，键盘可达，读屏认得是个展开控件。
   site.js 只加「Esc 关」「点外面关」「开一个关掉其他」这三件增强。 */
.nav-group { position: relative; }
.nav-group > summary {
    list-style: none; cursor: pointer;
    display: inline-flex; align-items: center; gap: 6px;
    color: rgba(255, 255, 255, .72); font-size: var(--fs-sm); font-weight: 500;
    padding: 9px 15px; border-radius: 999px; position: relative; z-index: 1;
}
.nav-group > summary::-webkit-details-marker { display: none; }
.nav-group > summary::after {
    content: ""; width: 6px; height: 6px; margin-top: -3px;
    border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor;
    transform: rotate(45deg); opacity: .7;
    transition: transform var(--t-fast);
}
.nav-group[open] > summary::after { transform: rotate(-135deg); margin-top: 2px; }
.nav-group > summary:hover, .nav-group[open] > summary { color: #fff; }
.nav-links:not(.has-pill) .nav-group > summary:hover { background: rgba(255, 255, 255, .08); }

.nav-group-menu {
    position: absolute; top: calc(100% + 8px); left: 0; z-index: 5;
    min-width: 232px; padding: 8px;
    display: flex; flex-direction: column; gap: 2px;
    border: 1px solid var(--line-d-strong); border-radius: 14px;
    background: rgba(9, 10, 12, .97); backdrop-filter: blur(10px);
    box-shadow: 0 24px 60px rgba(0, 0, 0, .55);
    animation: nav-group-in .18s var(--ease-out) both;
}
@keyframes nav-group-in { from { opacity: 0; transform: translateY(-6px); } }
@media (prefers-reduced-motion: reduce) {
    .nav-group-menu { animation: none; }
    .nav-group > summary::after { transition: none; }
}
.nav-links .nav-group-menu a {
    display: flex; flex-direction: column; gap: 2px;
    padding: 9px 13px; border-radius: 10px; white-space: nowrap;
    color: var(--text); font-size: var(--fs-sm); font-weight: 600;
}
.nav-links .nav-group-menu a small {
    color: var(--muted); font-size: var(--fs-2xs); font-weight: 400; letter-spacing: 0;
}
.nav-links .nav-group-menu a:hover { background: rgba(13, 169, 205, .12); color: #fff; }

/* 白区反色（导航压在浅色分镜上时） */
.site-nav.nav-on-light .nav-group > summary { color: rgba(14, 17, 22, .66); }
.site-nav.nav-on-light .nav-group > summary:hover,
.site-nav.nav-on-light .nav-group[open] > summary { color: var(--ink); }

/* 汉堡菜单接手的断点。**从 720px 提到 1024px**：实测顶层平铺时
   721~1090px 这一段宽度里导航会挤到换行，而那正是平板与小笔记本的宽度。
   条目收进折叠组之后其实宽松了，但这条边界仍然按「最坏情况（登录 + 长用户名）」
   留，宁可早一点换成汉堡。 */
@media (max-width: 1024px) {
    .nav-pill { display: none; }
    .site-nav.nav-open .nav-links a:hover { background: rgba(255, 255, 255, .08); }
    /* 汉堡面板里折叠组就地展开成一段缩进清单，不再做浮层 —— 窄屏上浮层会盖住
       它自己的触发器，而且第二层弹出在触屏上几乎没法关 */
    .site-nav.nav-open .nav-group-menu {
        position: static; min-width: 0; margin: 2px 0 6px 12px; padding: 0 0 0 12px;
        border: 0; border-left: 1px solid var(--line-d-strong); border-radius: 0;
        background: none; box-shadow: none; backdrop-filter: none; animation: none;
    }
    .site-nav.nav-open .nav-links .nav-group-menu a { padding: 11px 10px; border-radius: 0; }
    .site-nav.nav-open .nav-group > summary { padding: 14px 10px; }
}
.nav-auth { display: flex; align-items: center; gap: 9px; }
.nav-user { display: inline-flex; align-items: center; gap: 7px; color: var(--text); font-size: var(--fs-sm); font-weight: 600; padding: 6px 12px; border-radius: 999px; }
.nav-user:hover { background: rgba(255, 255, 255, .08); color: #fff; }
.nav-user-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--ok); box-shadow: 0 0 8px var(--ok); }
.nav-burger { display: none; background: none; border: none; cursor: pointer; padding: 8px; }
.nav-burger span { display: block; width: 22px; height: 2px; background: #fff; margin: 5px 0; border-radius: 2px; transition: all .3s; }

.site-main { min-height: 100vh; }
body:not(.page-home) .site-main { padding-top: 74px; }
body:not(.page-home) .site-nav { background: rgba(5, 5, 6, 0.85); backdrop-filter: blur(16px); border-bottom-color: var(--line-d); }

/* ---------- 导航栏黑白区反色 ----------
   模板里每个大区块都标了 data-nav="dark|light"，motion-core.js 判断当前
   压在导航栏下方的区块并切换 .nav-on-light。白区上导航必须翻成深色，
   否则白底白字直接不可读。

   过渡时长用 --t-base：比区块滚过的速度略快，读起来是"导航适应了背景"
   而不是"导航自己在变色"。 */
.site-nav.nav-on-light {
    background: rgba(247, 248, 250, .82);
    border-bottom-color: var(--line-l);
}
.site-nav.nav-on-light,
.site-nav.nav-on-light .nav-title strong,
.site-nav.nav-on-light .nav-user,
.site-nav.nav-on-light .nav-bell { color: var(--ink); }
.site-nav.nav-on-light .nav-title small,
.site-nav.nav-on-light .nav-links a { color: rgba(14, 17, 22, .66); }
.site-nav.nav-on-light .nav-links a:hover,
.site-nav.nav-on-light .nav-user:hover,
.site-nav.nav-on-light .nav-bell:hover { color: var(--ink); }
.site-nav.nav-on-light .nav-links:not(.has-pill) a:hover,
.site-nav.nav-on-light .nav-user:hover,
.site-nav.nav-on-light .nav-bell:hover { background: rgba(14, 17, 22, .07); }
.site-nav.nav-on-light .nav-pill { background: rgba(14, 17, 22, .08); }
.site-nav.nav-on-light .nav-hot { color: var(--accent) !important; }
.site-nav.nav-on-light .btn-outline { color: var(--ink); border-color: rgba(14, 17, 22, .28); }
.site-nav.nav-on-light .btn-outline:hover { color: var(--ink); border-color: var(--ink); }
.site-nav.nav-on-light .btn-ghost { background: rgba(14, 17, 22, .06); color: var(--ink); }
.site-nav.nav-on-light .btn-ghost:hover { background: rgba(14, 17, 22, .12); color: var(--ink); }
.site-nav.nav-on-light .nav-burger span { background: var(--ink); }
/* 会标是内联 SVG，stroke 走 currentColor，所以反色只需给它一个 color。
   不用 filter: invert()——那会连带影响抗锯齿，也没法只翻其中一部分。 */
.site-nav .nav-logo { color: #fff; }
.site-nav.nav-on-light .nav-logo { color: var(--ink); }
.site-nav,
.site-nav .nav-title strong, .site-nav .nav-title small, .site-nav .nav-links a,
.site-nav .nav-user, .site-nav .nav-bell, .site-nav .nav-logo {
    transition: background var(--t-base), border-color var(--t-base),
                color var(--t-base);
}
@media (prefers-reduced-motion: reduce) {
    .site-nav, .site-nav * { transition: none; }
}

/* ---------- 消息提示 ---------- */
/* 提示区现在在 <main> 里面，内页由 `.site-main` 的 padding-top: 74px 让开固定导航。
   首页的 `.site-main` 没有那份 padding（Hero 要满幅顶到导航底下），所以单独补一档，
   否则首页上的提示会重新被导航压住 —— 这正是它原来在 <main> 外面时的老毛病。 */
.messages-wrap { margin-top: 18px; position: relative; z-index: 5; }
body.page-home .messages-wrap { margin-top: 92px; }
.alert { padding: 12px 18px; border-radius: 12px; margin-bottom: 10px; font-size: var(--fs-md); border: 1px solid; }
.alert-success { background: rgba(63, 214, 143, .1); border-color: rgba(63, 214, 143, .35); color: var(--ok); }
.alert-info, .alert-debug { background: rgba(65, 216, 232, .1); border-color: rgba(65, 216, 232, .35); color: var(--accent-2); }
.alert-warning { background: rgba(244, 182, 63, .1); border-color: rgba(244, 182, 63, .35); color: var(--warn); }
.alert-error { background: rgba(240, 113, 110, .1); border-color: rgba(240, 113, 110, .35); color: var(--danger); }

/* ---------- 页脚 ---------- */
/* 页脚挂裸基板纹 + 颗粒：它是全站每一页的收尾，一整块纯平的黑最显薄。
   底纹画在 ::before、颗粒在 ::after（见 tokens.css 的表面系统），所以这里
   `background` 简写是安全的 —— 但 .footer-wave 是 position:absolute 的子元素，
   要靠 `.grain > *` 那条把它提到颗粒之上。 */
.site-footer {
    position: relative;
    border-top: 1px solid var(--line-d);
    background: var(--black);
}
/* 页脚顶部的一条微弱信号波形：全站唯二的隐喻装饰之一（另一处是白区坐标纸） */
.footer-wave {
    position: absolute; top: -1px; left: 0; width: 100%; height: 26px;
    display: block; color: var(--accent); opacity: .28; pointer-events: none;
}
.footer-grid { display: grid; grid-template-columns: 1.4fr 1fr 1fr; gap: 40px; padding: 60px 0 44px; }
/* 是 h2 而不是 h4：见 base.html 里的说明（内页正文常常只到 h2，写 h4 就跨级了）。
   尺寸由这里定，和标签级别无关 —— 层级是给读屏软件的，字号是给眼睛的。 */
.footer-col h2 { color: #fff; font-size: var(--fs-sm); margin-bottom: 16px; letter-spacing: 2px; font-weight: 600; }
.footer-col a, .footer-col span { display: block; color: var(--muted); font-size: var(--fs-sm); margin-bottom: 10px; }
.footer-col a:hover { color: #fff; }
.footer-logo-row { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; }
.footer-logo { width: 44px; height: 44px; display: block; flex: none; color: #fff; }
.footer-logo-row strong { display: block; color: #fff; font-size: var(--fs-lg); }
.footer-logo-row small { color: var(--muted); font-size: var(--fs-2xs); font-family: var(--font-mono); letter-spacing: 1px; }
.footer-brand p { color: var(--muted); font-size: var(--fs-sm); max-width: 340px; }
.footer-bar { border-top: 1px solid var(--line-d); padding: 18px 0; }
.footer-bar-inner { display: flex; justify-content: space-between; flex-wrap: wrap; gap: 10px; font-size: var(--fs-xs); color: var(--muted-dim); }
.footer-beian { display: flex; gap: 18px; flex-wrap: wrap; }
.footer-beian a { color: var(--muted-dim); }
.footer-beian a:hover { color: var(--accent-2); }

/* ---------- 表单 ---------- */
.form-group { margin-bottom: 18px; }
.form-group label { display: block; font-size: var(--fs-sm); font-weight: 600; color: var(--text); margin-bottom: 7px; }
.form-group .req { color: var(--danger); }
.input, input[type="file"], textarea.input, select.input {
    width: 100%; padding: 12px 15px; border-radius: 12px;
    border: 1px solid var(--line-d-strong); background: rgba(255, 255, 255, .04);
    color: var(--text); font-size: var(--fs-md); outline: none;
    transition: border-color .2s, box-shadow .2s; font-family: inherit;
}
.input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(13, 169, 205, .18); }
select.input option { background: var(--bg-2); }
.form-help { display: block; color: var(--muted); font-size: var(--fs-xs); margin-top: 5px; }
.form-error { display: block; color: var(--danger); font-size: var(--fs-xs); margin-top: 5px; }
.has-error .input { border-color: var(--danger); }
.form-nonfield-errors { margin-bottom: 16px; }

/* 长表单分组（注册页等）：mono 编号沿用编号=铜色的全站语义 */
.form-section { border: none; margin: 0 0 10px; }
.form-section + .form-section { margin-top: 26px; }
.form-section-title {
    display: flex; align-items: center; gap: 10px; width: 100%;
    color: #fff; font-size: var(--fs-md); font-weight: 700;
    padding-bottom: 10px; margin-bottom: 18px;
    border-bottom: 1px solid var(--line-d);
}
.form-section-title span {
    font-family: var(--font-mono); font-size: var(--fs-2xs); font-weight: 700;
    color: var(--copper); letter-spacing: 1px;
}

/* ============================================================
   通用排版元素
   ============================================================ */
/* ---------- 丝印条（eyebrow 的器械化） ----------
   原来这里只有一行 mono 小字。现在把它做成**仪器面板上的丝印**：
   左边一段刻度尺（细刻度 + 每三格一根主刻度），右边一条带端标的尺寸线。

   为什么改 `.nf-eyebrow` 而不是新建组件：全站 14 处都在用它，改一次所有区块
   标题一起器械化，模板一个字都不用动，文字本身照旧、无障碍树不变。

   刻度尺用两层 repeating-linear-gradient 叠出来 —— 单层渐变没法让刻度高低交替。 */
.nf-eyebrow {
    font-family: var(--font-mono); font-size: var(--fs-xs); letter-spacing: 4px;
    color: var(--accent); margin-bottom: 18px; text-transform: uppercase;
}
/* 两个记号都是 **inline-block**，不做成 flex 项。
   原因是踩过：第一版把 `.nf-eyebrow` 设成 `display: flex`，而首屏那条 eyebrow
   还带着 `.line-reveal`（`display: block`，在 core.css 里排在后面、同特异性）——
   于是运行时不是 flex，两个伪元素退回 inline，**width/height 被直接忽略**，
   记号一个都没画出来，而页面照常渲染、控制台干净。
   这套 eyebrow 有 14 处调用、各自的父容器不同，与其去猜谁会覆盖 display，
   不如让记号本身与父级的 display 无关。 */
.nf-eyebrow::before {
    content: ""; display: inline-block; vertical-align: middle;
    width: 54px; height: 14px; margin-right: 13px;
    background-image:
        repeating-linear-gradient(90deg, currentColor 0 1px, transparent 1px 6px),
        repeating-linear-gradient(90deg, currentColor 0 1px, transparent 1px 18px);
    background-size: 100% 6px, 100% 14px;
    background-position: 0 100%, 0 100%;
    background-repeat: repeat-x;
    opacity: .6;
}
/* 尺寸线：一段定长细线 + 两端端标（工程图的标注线语法）。
   定长而不是撑满剩余宽度 —— 撑满会让同一个组件在宽窄容器里长得不一样。 */
.nf-eyebrow::after {
    content: ""; display: inline-block; vertical-align: middle;
    width: 76px; height: 7px; margin-left: 14px;
    border-left: 1px solid currentColor; border-right: 1px solid currentColor;
    background: linear-gradient(currentColor, currentColor) left center / 100% 1px no-repeat;
    opacity: .32;
}
@media (max-width: 720px) {
    /* 窄屏收掉尺寸线，只留刻度：再多一截会把 eyebrow 挤成两行 */
    .nf-eyebrow::after { display: none; }
    .nf-eyebrow::before { width: 30px; margin-right: 9px; }
}
/* 分镜与区块大标题。字距从 +1px 改成负值：换成得意黑之后正字距会散架 ——
   那款字形的侧边留白本来就小，正字距是给思源 Heavy 那种方正字形留的。 */
.nf-display {
    font-family: var(--font-display);
    font-size: var(--fs-dsp-4); font-weight: 900; line-height: var(--lh-tight);
    letter-spacing: -.018em; margin-bottom: 34px; color: inherit;
}

/* ---------- 标题的「工艺层」：试过，撤了 ----------
   这里曾经有两个类，都因为**被真实读者读成故障**而删除，别再加回来：

   `.plated`（横向阻焊开窗，用 mask 在字面上切一道细缝）
       意图是「铜面被阻焊层开了一次窗」。反馈是「有不明条纹」。
       在没有这套上下文的人眼里，横穿字面的一道缝就是渲染故障。
       （更早还试过 45° 密斜纹，那连可读性都掉，48px 上糊成一片。）

   `.pad-out`（句尾焊盘环 + 引线）
       意图是「信号从这里出去」，同会标的 pad-out。反馈是
       「句号和输入符号重合」—— 一个圆环紧跟句末会被读成标点，
       再加一道横线更像输入光标。

   留下来的规矩：**装饰记号不要放进句子的读取路径里。**
   同一套记号放在 eyebrow 那条丝印带上（刻度尺 + 尺寸线）没有人误读，
   因为那里本来就是放记号的地方。 */
.nf-display .accent { color: var(--accent); }
.nf-center { text-align: center; }
.nf-section { padding: clamp(90px, 12vw, 150px) 0; position: relative; overflow: hidden; }

/* 深/浅两种大区块 */
.nf-dark { background: var(--black); color: var(--text); }
/* 白区 = 示波器坐标纸：10px 细格 + 50px 粗格，极低对比度。
   黑区保持纯黑（暗室），黑白交替因此有了叙事：屏幕亮起/熄灭 */
.nf-light {
    background-color: var(--paper);
    background-image:
        linear-gradient(rgba(14, 17, 22, .052) 1px, transparent 1px),
        linear-gradient(90deg, rgba(14, 17, 22, .052) 1px, transparent 1px),
        linear-gradient(rgba(14, 17, 22, .028) 1px, transparent 1px),
        linear-gradient(90deg, rgba(14, 17, 22, .028) 1px, transparent 1px);
    background-size: 50px 50px, 50px 50px, 10px 10px, 10px 10px;
    color: var(--ink);
}
/* 白区上的强调色必须压暗：--accent 在 --paper 上只有 2.61:1。
   这两条覆盖的是「eyebrow 小字」与「大标题里的强调词」，白区里只有这两处用它。 */
.nf-light .nf-eyebrow { color: var(--accent-ink); }
.nf-light .nf-display .accent { color: var(--accent-ink); }
.nf-light h3 { color: var(--ink); }
.nf-light p { color: var(--ink-muted); }

/* ---------- 滚动渐入（CSS animation 实现，交错延迟走 --reveal-delay） ----------
   用 animation 而非 transition：入场动画与组件自身的 hover transition
   （方向卡/B站卡的 transform 过渡）互不抢占，延迟也不会残留到 hover 上。
   fill 只用 backwards：动画结束后元素回到自然样式，零残留。 */
.reveal { opacity: 0; }
.reveal:not(.is-visible) { will-change: opacity, transform; }
.reveal.is-visible {
    opacity: 1;
    animation: reveal-up 1s var(--ease-out) backwards;
    animation-delay: var(--reveal-delay, 0ms);
}
@keyframes reveal-up {
    from { opacity: 0; transform: translateY(56px); }
}

/* Hero 首屏文字逐行升起。
   ------------------------------------------------------------
   加载动画还盖着的时候必须把它按住。这些动画的延迟最迟 .8s、时长 1.1s，到
   1.9s 就全跑完了，而加载动画那会儿才刚到 85% —— 幕布揭开时文字早已就位，
   「揭幕接 Hero」这一拍完全是空的（截图确认过）。

   用 animation-play-state 而不是「默认隐藏、等 JS 加类才动」：
   `esta-pre-lock` 这个类本身是 preloader.js 加的，脚本没加载就根本没有这个类，
   于是动画照常立刻跑。不引入任何「必须有 JS 才能看见内容」的依赖。
   暂停也会把延迟一起冻住，所以解锁后节奏完整重现。 */
.line-reveal { display: block; opacity: 0; transform: translateY(46px); animation: line-up 1.1s var(--ease-out) forwards; }
html.esta-pre-lock .line-reveal { animation-play-state: paused; }
.line-reveal:nth-child(1), .nf-hero-text > .line-reveal:nth-of-type(1) { animation-delay: .15s; }
.nf-hero-title .line-reveal:nth-child(1) { animation-delay: .3s; }
.nf-hero-title .line-reveal:nth-child(2) { animation-delay: .45s; }
.nf-hero-sub.line-reveal { animation-delay: .65s; }
.nf-hero-cta.line-reveal { animation-delay: .8s; }
@keyframes line-up { to { opacity: 1; transform: none; } }

@media (prefers-reduced-motion: reduce) {
    .reveal, .line-reveal { opacity: 1 !important; transform: none !important; transition: none; animation: none; }
    html { scroll-behavior: auto; }
}

/* ---------- 招新截止倒计时 ----------
   首页 CTA 与招新落地页共用。渐进增强：静态截止日期是服务端渲染的、永远在
   （也是无障碍树里那份权威信息）；滴答的时钟初始 hidden，JS 上去才放出来。
   行为在 site.js（全站加载，两个页面都要用）。 */
.nf-countdown {
    position: relative; display: inline-flex; align-items: center; gap: 20px;
    flex-wrap: wrap; justify-content: center;
    margin: 0 auto 38px; padding: 13px 26px; border-radius: 999px;
    border: 1px solid rgba(65, 216, 232, .26);
    /* 阻焊绿窄条 —— 全站少数几处用第三色的地方 */
    background: rgba(10, 47, 42, .5);
}
.nf-countdown-static { color: var(--muted); font-size: var(--fs-sm); letter-spacing: .5px; }
.nf-countdown-static time { color: var(--text); font-family: var(--font-mono); }
.nf-countdown-clock {
    display: inline-flex; align-items: baseline; gap: 3px;
    font-family: var(--font-mono); color: var(--accent-2);
}
.nf-countdown-clock b, .nf-countdown-clock i { font-family: var(--font-num); }
.nf-countdown-clock b {
    font-size: var(--fs-4xl); font-weight: 800; line-height: var(--lh-flat);
    /* 倒计时用自己那套走线数字（--font-num，见 tokens.css）。它本身就是等宽的，
       下面那条 tabular-nums 仍然留着 —— 字体请求失败时会退到 mono，那时要靠它。 */
    /* 等宽数字：不加这条，秒位每次跳动都会把后面的字推一下 */
    font-variant-numeric: tabular-nums;
}
.nf-countdown-clock i {
    font-style: normal; font-size: var(--fs-2xs); color: var(--muted);
    margin-right: 7px; letter-spacing: 1px;
}
.nf-countdown-clock.is-over { color: var(--warn); font-size: var(--fs-sm); font-weight: 700; }

@media (max-width: 720px) {
    .nf-countdown { gap: 10px; padding: 12px 18px; }
    .nf-countdown-clock b { font-size: var(--fs-2xl); }
}

/* ---------- 示波器网格（暗场版工具类） ----------
   白区的坐标纸已经在 .nf-light 里内联，这是给黑区区块用的对应物，格距与白区
   一致以保持同一套度量。挂在需要「示波器屏幕」质感的区块上。
   两层网格：10px 细格 + 50px 粗格，对比度压到几乎看不见 —— 它是底纹不是内容。 */
/* 最后那一层 `--scope-art` 是**可选的氛围图**，默认 `none`：想给某个用了
   `.nf-scope` 的区块配一张暗调氛围图，只在模板的 `style` 属性上设这一个变量 ——
   值是一个指向那张图的 CSS 图片引用，和隔壁 `--hero-accent` 一样的用法，
   路径走 `{% static %}` 拿哈希名。**不要为了配图再复制一份 hero**，
   也不要在别处重写这四层网格（写第二份必然漂移）。

   **注释里不要把那个函数的字面写法抄出来。** `ManifestStaticFilesStorage`
   扫 CSS 里的图片引用时**连注释一起扫**，于是注释里那个占位路径会被当成真文件去
   找、`collectstatic` 直接抛 ValueError —— 而 collectstatic 在 entrypoint 里，
   容器就此起不来、线上 502。这条真踩过（一次生产事故，见下面那条守卫）。

   层序是刻意的：网格四层在前（画在上面），氛围图在最后（画在下面）。
   坐标纸压在材质上才是「仪器屏幕」，反过来是「网格上贴了张图」。

   **设了这个变量的页面，`check_a11y.py` 会跳过该元素的对比度计算**（图背景算不出
   唯一底色），所以那一页要么另外补几个实底角色进 `CONTRAST_TARGETS`、要么靠
   `check_artwork.py` 的「左 55% 区 99 分位亮度 ≤30」那条兜住。
   `/recruit/` 的章节头横幅就是这么处理的，别只加图不管这一步。
   `right center` + `cover`：桌面横幅扁到 5.3:1、窄屏只有 1.7:1，两边都靠
   `cover` 裁 —— 所以要求图的意象**放在右侧 40%、竖直居中**，
   窄屏裁掉的是左边的空处，桌面裁掉的是上下。 */
.nf-scope {
    position: relative;
    background-image:
        linear-gradient(var(--scope-line) 1px, transparent 1px),
        linear-gradient(90deg, var(--scope-line) 1px, transparent 1px),
        linear-gradient(var(--scope-line-strong) 1px, transparent 1px),
        linear-gradient(90deg, var(--scope-line-strong) 1px, transparent 1px),
        var(--scope-art, none);
    background-size:
        100% var(--scope-cell-fine), var(--scope-cell-fine) 100%,
        100% var(--scope-cell), var(--scope-cell) 100%,
        cover;
    background-position: 0 0, 0 0, 0 0, 0 0, right center;
    background-repeat: repeat, repeat, repeat, repeat, no-repeat;
}
/* 网格向上下边缘渐隐，避免出现一条生硬的截断线 */
.nf-scope::before {
    content: ""; position: absolute; inset: 0; pointer-events: none;
    background: linear-gradient(180deg, var(--black) 0%, transparent 18%,
                                transparent 82%, var(--black) 100%);
}
.nf-scope > * { position: relative; z-index: 1; }

/* ============================================================
   素材槽（{% slot %} 渲染，两种状态）
   ------------------------------------------------------------
   协会现在缺图，而且会缺一段时间。所以「缺图」不是异常分支，是一个正式的
   设计状态：空焊盘 —— 虚线板框 + 四角定位标 + 拍摄要求。它和填好图的状态
   占据完全相同的版面（比例由内联 aspect-ratio 给），补图前后不用重新排版。

   定位标用 --silk 系列而不是 --accent-2：占位是等待状态，不该抢交互色。
   ============================================================ */
.slot {
    position: relative; margin: 0; overflow: hidden;
    border-radius: 14px; background: var(--bg-2);
}
.slot > img,
.slot > video {
    display: block; width: 100%; height: 100%;
    object-fit: cover;   /* object-position 由内联 style 给，来自焦点字段 */
}

/* 视频槽的手动播放入口。只有「不自动播」时 JS 才把 hidden 摘掉：
   省流模式、prefers-reduced-motion，或浏览器直接拒绝了自动播放。 */
.slot-video-play {
    position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
    width: 62px; height: 62px; border-radius: 50%; cursor: pointer;
    display: grid; place-items: center; padding: 0;
    border: 1px solid rgba(65, 216, 232, .5);
    background: rgba(5, 5, 6, .55); color: var(--accent-2);
    backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
    transition: transform var(--t-fast) var(--ease-damp), background var(--t-fast);
}
.slot-video-play:hover { transform: translate(-50%, -50%) scale(1.08); background: rgba(13, 169, 205, .3); color: #fff; }

/* 图注：压在图片下沿的渐变里，不额外占高度（走廊里每一格高度必须一致） */
.slot-cap {
    position: absolute; inset: auto 0 0 0; padding: 26px 16px 13px;
    display: flex; align-items: baseline; gap: 10px;
    background: linear-gradient(180deg, transparent, rgba(5, 5, 6, .82));
}
.slot-cap-text { color: #fff; font-size: var(--fs-sm); font-weight: 600; letter-spacing: .5px; }
.slot-cap-credit {
    margin-left: auto; flex: none;
    font: 400 var(--fs-2xs)/var(--lh-flat) var(--font-mono); color: var(--silk-dim);
}

/* ---------- 空焊盘 ---------- */
.slot.is-empty {
    background:
        repeating-linear-gradient(90deg, var(--scope-line) 0 1px, transparent 1px var(--scope-cell-fine)),
        repeating-linear-gradient(0deg, var(--scope-line) 0 1px, transparent 1px var(--scope-cell-fine)),
        rgba(255, 255, 255, .015);
    border: 1.5px dashed var(--silk-line);
    transition: border-color var(--t-base) var(--ease-out);
}
.slot.is-empty:hover { border-color: rgba(65, 216, 232, .45); }

/* 四角定位标：照 PCB 的 fiducial 画成折角，和 3D 基板丝印上那四个同源。
   四个 <i> 靠 nth-child 分别贴到四角，只画两条边形成"L" */
.slot-fid {
    position: absolute; width: 13px; height: 13px;
    border: 1.5px solid var(--silk-line); pointer-events: none;
}
.slot-fid:nth-child(1) { top: 9px; left: 9px; border-right: 0; border-bottom: 0; }
.slot-fid:nth-child(2) { top: 9px; right: 9px; border-left: 0; border-bottom: 0; }
.slot-fid:nth-child(3) { bottom: 9px; left: 9px; border-right: 0; border-top: 0; }
.slot-fid:nth-child(4) { bottom: 9px; right: 9px; border-left: 0; border-top: 0; }

.slot-empty-body {
    position: absolute; inset: 0; padding: 22px;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 7px; text-align: center;
}
.slot-empty-mark {
    font: 700 var(--fs-3xl)/var(--lh-flat) var(--font-mono); letter-spacing: 2px;
    color: rgba(65, 216, 232, .5); margin-bottom: 2px;
}
.slot-empty-label {
    color: var(--silk); font-size: var(--fs-lg); font-weight: 700; letter-spacing: 1.5px;
}
.slot-empty-brief {
    color: var(--muted); font-size: var(--fs-xs); line-height: var(--lh-normal);
    max-width: 30ch;
}
/* 站务专属：直达上传。普通访客看不到，避免把内部流程暴露在公开页面上 */
.slot-empty-cta {
    margin-top: 6px; padding: 5px 13px; border-radius: 999px;
    border: 1px solid rgba(65, 216, 232, .4);
    font: 700 var(--fs-2xs)/var(--lh-normal) var(--font-mono); color: var(--accent-2);
    transition: background var(--t-fast), border-color var(--t-fast);
}
.slot-empty-cta:hover { background: rgba(65, 216, 232, .12); border-color: var(--accent-2); }
.slot-empty-key {
    margin-top: 4px; font: 400 var(--fs-3xs)/var(--lh-flat) var(--font-mono); color: var(--silk-dim);
}

/* 白区里的占位槽：网格与描边要换成深色，否则浅底上几乎看不见 */
.nf-light .slot.is-empty,
.panel .slot.is-empty {
    background: rgba(14, 17, 22, .035);
    border-color: rgba(14, 17, 22, .16);
}
.nf-light .slot-fid,
.panel .slot-fid { border-color: rgba(14, 17, 22, .2); }
.nf-light .slot-empty-label,
.panel .slot-empty-label { color: var(--ink); }
.nf-light .slot-empty-brief,
.panel .slot-empty-brief { color: var(--ink-muted); }

@media (max-width: 720px) {
    /* 窄屏格子小，拍摄要求会挤成一团 —— 只留名字，要求去驾驶舱看 */
    .slot-empty-brief { display: none; }
    .slot-empty-mark { font-size: var(--fs-xl); }
}

/* ============================================================
   内页通用（所有 Tier 2 内容页 + 作品墙 / 荣誉墙 / 团队页共用）
   ------------------------------------------------------------
   这一段是「一个内容页由什么组成」的唯一定义：页头横幅、卡片、筛选药丸、
   分页、空态。**新页面不要再各写一份** —— 作品墙 / 荣誉墙 / 团队页最初各自
   复制了一遍 hero 与空态（三份几乎逐字相同的 `xx-hero` / `xx-empty`），
   Task 18 把它们合并到这里，差异只留一个强调色变量。
   ============================================================ */
.page-section { padding: 56px 0 60px; }

/* ---------- 页头横幅 ----------
   暗场示波器网格 + 注释风 eyebrow + 展示体大标题 + 一句说明。
   叠 `.nf-scope` 用（`<section class="page-hero nf-scope">`），所以底色
   **只能写 background-color**：简写会把网格的 background-image 一起重置掉。

   每页只改一个强调色：`style="--hero-accent: var(--copper-2)"`。
   不要为了换个颜色再复制一份 hero。 */
.page-hero {
    position: relative;
    background-color: var(--black);
    padding: clamp(52px, 7vw, 88px) 0 clamp(26px, 4vw, 40px);
    border-bottom: 1px solid var(--line-d);
}
/* ---------- 每页一张氛围图 ----------
   走 `.nf-scope` 的 `--scope-art`（那一层默认 `none`，说明在它旁边）。
   收敛之后这七页的页头只差一个强调色，**静止时看起来是同一个黑矩形七遍** ——
   新生指南五章配了章节头横幅之后这个落差就很明显了。

   挂在 `body.page-*` 上而不是模板的 inline style，理由有三条：
   - 七个引用集中在这里，改名/换图时一眼看得全，不用翻七个模板；
   - 它们因此落在 `check_static_pipeline` 的「CSS 引用必须指向真实文件」那步里
     （inline style 不在 CSS 里，那一步扫不到）；
   - `body_class` 这七个类本来就都有了，不用为配图往模板里加东西。

   `page-works` 同时挂在作品墙与作品详情上，所以选择器要收窄到 `.page-hero`
   —— 详情页用的是 `.wk-detail-hero`，它顶上压着封面与标题，不该再加氛围。
   意象与安全区的要求见 `docs/美术资产清单.md` 的「第二批工单」。 */
body.page-news     .page-hero { --scope-art: url("../img/hero-news.e2458ba2d645.webp"); }
body.page-events   .page-hero { --scope-art: url("../img/hero-events.32e2c8ca4fe6.webp"); }
body.page-resources .page-hero { --scope-art: url("../img/hero-resources.1d5218387d29.webp"); }
body.page-works    .page-hero { --scope-art: url("../img/hero-works.c16c28d14773.webp"); }
body.page-honors   .page-hero { --scope-art: url("../img/hero-honors.737533954641.webp"); }
body.page-team     .page-hero { --scope-art: url("../img/hero-team.f8f67e0eb827.webp"); }
body.page-leaderboard .page-hero { --scope-art: url("../img/hero-leaderboard.f850dafee3a4.webp"); }
/* 高对比偏好下摘掉氛围图：它是气氛，而这里的诉求是让标题最清楚。
   和章节头横幅同一个处理。 */
@media (prefers-contrast: more) {
    .page-hero { --scope-art: none; }
}
/* 页头铺一层颗粒：它是每个内页最先看到的一块，一整片纯平的黑最显薄。
   噪声图复用 tokens.css 的 `--grain-img`（一份图、两处用，见那里的说明）。
   挂在组件自己的 `::after` 上，而不是让十来个模板各加一个 `.grain` 类；
   `::before` 已经被 `.nf-scope` 的上下渐隐占着，两者正好不冲突。 */
.page-hero::after {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    z-index: 0;
    opacity: var(--grain-strength);
    background-size: var(--grain-cell) var(--grain-cell);
    background-image: var(--grain-img);
}
.page-hero > * { position: relative; z-index: 1; }
.page-hero h1 {
    font-family: var(--font-display); color: #fff; font-weight: 900;
    font-size: var(--fs-dsp-3); line-height: var(--lh-tight); margin: 12px 0 14px;
}
.page-hero h1 .accent { color: var(--hero-accent, var(--accent)); }
/* 页头那句说明是**导语**，不是正文：它比正文大一档、行高更松、行宽掐在 720px。
   用流体的 --fs-lead 而不是固定档 —— 窄屏 15px（不挤），宽屏 18px（撑得住）。

   导语用宋体（第二声音）。这是全站唯一一处「每页都出现」的非黑体文字，
   目的是让页头有两个声音：Heavy 黑体喊一句，宋体接着说一句。
   文案都在模板里，符合宋体子集的使用条件（见 tokens.css）。
   `font-weight: 600` 要显式写：那个家族只有这一档。 */
.page-hero-sub {
    color: var(--muted); font-size: var(--fs-lead); line-height: var(--lh-prose); max-width: 720px;
    font-family: var(--font-serif); font-weight: 600;
}
.page-hero-sub strong {
    color: #fff; font-family: var(--font-mono); font-size: var(--fs-2xl);
    font-variant-numeric: tabular-nums;
}
.page-hero-sub a { color: var(--accent-2); }

/* 页头 mono 大数字：延续「数字是主角」的语言。用在横幅里或旧的 .page-head 里都行 */
.page-stats { display: flex; align-items: baseline; flex-wrap: wrap; gap: 14px; margin-top: 22px; }
.page-stat { color: var(--muted); font-size: var(--fs-xs); letter-spacing: 1px; }
.page-stat strong {
    font-family: var(--font-num); font-weight: 700; font-size: var(--fs-4xl);
    color: #fff; margin-right: 7px; letter-spacing: 0;
    font-variant-numeric: tabular-nums;
}
.page-stat-sep { width: 1px; height: 22px; background: var(--line-d-strong); align-self: center; }

/* ---------- 旧的扁平页头 ----------
   横幅取代它当列表页页头；这里保留给「详情页 / 表单页」那种不该压一条暗场
   横幅的场合。 */
.page-head { margin-bottom: 34px; }
.page-head h1 { font-family: var(--font-display); color: #fff; font-size: var(--fs-dsp-2); font-weight: 900; margin-bottom: 10px; }
.page-head p { color: var(--muted); font-size: var(--fs-md); max-width: 640px; }
/* 内页延续首页的注释风 eyebrow 语法 */
.page-head .nf-eyebrow { margin-bottom: 10px; font-size: var(--fs-2xs); }
.auth-card .nf-eyebrow { margin-bottom: 10px; font-size: var(--fs-2xs); letter-spacing: 3px; }

/* ---------- 通用卡片 ----------
   公告 / 活动 / 项目 / 作品 / 通知 / 排行榜的卡片原来各写一份几乎相同的
   「1px 描边 + 圆角 + hover 抬起」，圆角、底色、位移量三处都对不齐。
   现在共用 `.card`，各自只补自己的内部排版。
   `.card-link` 给整块可点的卡片（<a class="card card-link">）。 */
.card {
    position: relative;
    border: 1px solid var(--line-d); border-radius: 16px;
    background: rgba(255, 255, 255, .022);
    transition: transform var(--t-base) var(--ease-out),
                border-color var(--t-base), background var(--t-base);
}
a.card-link { display: flex; color: var(--text); }
.card-link:hover {
    transform: translateY(-3px); color: var(--text);
    border-color: rgba(65, 216, 232, .5); background: rgba(13, 169, 205, .05);
}
/* 白区（首页分镜里的浅色段）反色 */
.nf-light .card { border-color: var(--line-l); background: rgba(14, 17, 22, .025); }
.nf-light .card-link:hover { border-color: rgba(13, 169, 205, .4); background: rgba(13, 169, 205, .07); }

/* ---------- 筛选药丸 ----------
   `.pill-row > a.pill`。`.pill b` 是 mono 计数（作品墙那种「硬件部 3」）。
   状态类统一叫 `.is-current` —— 原来两套并存（`.filter-tabs a.active` 与
   `.wk-filter a.is-current`），同一个交互两个名字迟早只改一处。 */
.pill-row { display: flex; flex-wrap: wrap; gap: 8px; }
.pill {
    display: inline-flex; align-items: baseline; gap: 7px;
    padding: 7px 16px; border-radius: 999px;
    border: 1px solid var(--line-d); color: var(--muted); font-size: var(--fs-sm);
    transition: border-color var(--t-fast), color var(--t-fast), background var(--t-fast);
}
.pill:hover { color: var(--text); border-color: var(--line-d-strong); }
.pill.is-current {
    border-color: var(--accent); background: rgba(13, 169, 205, .1); color: var(--accent-2);
}
.pill b { font-family: var(--font-mono); font-size: var(--fs-2xs); font-weight: 700; opacity: .8; }

/* ---------- 空态 ----------
   「列表里没有内容」的唯一写法。原来有五种：`.empty-hint` 一行小字、
   驾驶舱的 `.dash-empty`、以及作品墙/荣誉墙/团队页各自的 `xx-empty` 三段式。
   合并成这一份，并且**必须给下一步**（`.empty-state-cta`）——
   「暂无内容」是死胡同，读者到这里就走了。

   注意和 `includes/empty_board.html` 是两件事：那份是**图片**缺位的占位框
   （虚线板框 + 四角定位标 + 拍摄要求），这份是「这个列表还没有条目」。 */
.empty-state {
    max-width: 660px; margin: 0 auto; text-align: center; padding: 44px 30px;
}
.empty-state .empty-icon {
    display: inline-flex; color: var(--muted); opacity: .55; margin-bottom: 6px;
}
.empty-state .empty-icon .icon { width: 40px; height: 40px; }
.empty-state h2 { color: #fff; font-size: var(--fs-3xl); margin: 8px 0 12px; }
.empty-state p { color: var(--muted); font-size: var(--fs-md); line-height: var(--lh-prose); }
.empty-state p + p { margin-top: 12px; }
.empty-state strong { color: #fff; font-family: var(--font-mono); }
.empty-state a { color: var(--accent-2); }
/* 只有站务看得到的那句操作提示，压一条分隔线以示不是给访客看的 */
.empty-state-hint {
    margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--line-d);
    font-size: var(--fs-sm) !important;
}

.auth-card {
    position: relative; overflow: hidden;
    max-width: 460px; margin: 48px auto 30px; padding: 40px 38px;
    border: 1px solid var(--line-d); border-radius: 22px; background: rgba(255, 255, 255, .025);
    box-shadow: 0 30px 80px rgba(0, 0, 0, .5);
}
/* 品牌顶边：像电路板上的一根信号走线 */
.auth-card::before {
    content: ""; position: absolute; top: 0; left: 0; right: 0; height: 2px;
    background: linear-gradient(90deg, var(--accent), rgba(13, 169, 205, 0) 70%);
}
.auth-card-wide { max-width: 560px; }
.auth-card h1 { font-family: var(--font-display); color: #fff; font-size: var(--fs-4xl); font-weight: 900; margin-bottom: 6px; }
.auth-card .auth-sub { color: var(--muted); font-size: var(--fs-sm); margin-bottom: 26px; }
.auth-alt { text-align: center; margin-top: 18px; font-size: var(--fs-sm); color: var(--muted); }
.auth-links { display: flex; justify-content: space-between; margin-top: 14px; font-size: var(--fs-sm); }
.auth-links a { color: var(--muted); }
.auth-links a:hover { color: var(--brand-2); }
.code-row { display: flex; gap: 8px; align-items: stretch; }
.code-row .input { flex: 1; }
.code-row .btn { white-space: nowrap; flex-shrink: 0; }
.code-msg-ok { color: var(--ok) !important; }
.code-msg-err { color: var(--danger) !important; }

.panel {
    border: 1px solid var(--line-d); border-radius: var(--radius);
    background: rgba(255, 255, 255, .02); padding: 26px 28px; margin-bottom: 20px;
}
.panel h2 { color: #fff; font-size: var(--fs-2xl); margin-bottom: 16px; }

.table-wrap { overflow-x: auto; }
/* 表格装在 panel 里时收掉左右内边距，让表头分隔线通到两边。
   原来是每处各写一遍 `style="padding:8px 16px"`。 */
.table-panel { padding: 8px 16px; }
.table { width: 100%; border-collapse: collapse; font-size: var(--fs-md); }
/* 单元格里的第二行小字（资料说明、用户名等） */
.table-sub { display: block; margin-top: 4px; color: var(--muted); font-size: var(--fs-xs); }
/* 行末操作列：下载 / 删除这类按钮排成一行，别竖着堆 */
.row-actions { white-space: nowrap; }
.row-actions > * { display: inline-block; vertical-align: middle; margin-right: 6px; }
.row-actions > *:last-child { margin-right: 0; }
/* 嵌在面板或表格里的轻量空态（「这个目录还是空的」「还没有勋章」）。
   和整页的 `.empty-state` 是两个量级：那份是「这一页没内容」，要带下一步；
   这份只是一个小区块空着，旁边还有别的内容。 */
.inset-empty {
    color: var(--muted); font-size: var(--fs-sm); line-height: var(--lh-relaxed);
    padding: 22px 4px; text-align: center;
}
.inset-empty .icon { width: 30px; height: 30px; opacity: .5; display: block; margin: 0 auto 8px; }
.inset-empty a { color: var(--accent-2); }
.table th { text-align: left; color: var(--accent-2); font-size: var(--fs-xs); letter-spacing: 1.5px; padding: 12px 14px; border-bottom: 1px solid var(--line-d-strong); white-space: nowrap; }
.table td { padding: 13px 14px; border-bottom: 1px solid var(--line-d); color: var(--text); }
.table tr:hover td { background: rgba(255, 255, 255, .025); }

.badge { display: inline-block; padding: 3px 11px; border-radius: 999px; font-size: var(--fs-2xs); font-weight: 600; border: 1px solid; }
.badge-blue { color: var(--accent-2); border-color: rgba(65, 216, 232, .4); background: rgba(65, 216, 232, .1); }
.badge-green { color: var(--ok); border-color: rgba(63, 214, 143, .4); background: rgba(63, 214, 143, .1); }
.badge-amber { color: var(--warn); border-color: rgba(244, 182, 63, .4); background: rgba(244, 182, 63, .1); }
.badge-gray { color: var(--muted); border-color: rgba(151, 161, 179, .35); background: rgba(151, 161, 179, .08); }

.pagination { display: flex; gap: 8px; justify-content: center; margin-top: 28px; }
.pagination a, .pagination span {
    min-width: 38px; height: 38px; display: inline-flex; align-items: center; justify-content: center;
    padding: 0 12px; border-radius: 999px; border: 1px solid var(--line-d); color: var(--muted); font-size: var(--fs-sm);
}
.pagination a:hover { border-color: var(--accent); color: var(--accent-2); }
.pagination .current {
    background: var(--accent); border-color: var(--accent); color: #fff;
    font-variant-numeric: tabular-nums;
}
/* 首页/末页时那一侧仍占位，否则「下一页」会从右边跳到左边 */
.pagination .is-disabled { opacity: .32; }

.filter-bar { display: flex; gap: 12px; flex-wrap: wrap; align-items: center; margin-bottom: 24px; }
.filter-bar .input { width: 260px; }
.filter-spacer { flex: 1; }
/* 筛选条里的搜索表单（原来是内联 style="display:flex;gap:8px"） */
.filter-search { display: flex; gap: 8px; }

.profile-grid { display: grid; grid-template-columns: 300px 1fr; gap: 22px; align-items: start; }
.profile-side { text-align: center; }

/* 实验室工牌：panel 变身门禁卡 */
.id-card { overflow: hidden; }
.id-card-band {
    display: flex; align-items: center; justify-content: center;
    height: 42px; margin: -26px -28px 20px;
}
.id-card-slot {
    width: 56px; height: 9px; border-radius: 999px;
    background: var(--black);
    box-shadow: inset 0 1px 4px rgba(0, 0, 0, .65);
}
.id-card-access {
    display: flex; align-items: center; justify-content: space-between;
    margin: 16px 0 6px; padding: 10px 14px;
    border: 1px dashed var(--line-d-strong); border-radius: 10px;
}
.id-card-access-label { font-family: var(--font-mono); font-size: var(--fs-3xs); letter-spacing: 2px; color: var(--muted); }
.id-card-access-value { font-weight: 800; font-size: var(--fs-md); }
.profile-avatar { width: 96px; height: 96px; border-radius: 50%; object-fit: cover; margin: 0 auto 14px; border: 2px solid var(--line-d-strong); }
.profile-avatar-fallback {
    width: 96px; height: 96px; border-radius: 50%; margin: 0 auto 14px;
    display: flex; align-items: center; justify-content: center;
    background: linear-gradient(135deg, #063c4d, var(--accent)); color: #fff; font-size: var(--fs-6xl); font-weight: 800;
}
.profile-side h2 { color: #fff; font-size: var(--fs-2xl); margin-bottom: 4px; }
.profile-side .profile-username { color: var(--muted); font-size: var(--fs-xs); font-family: var(--font-mono); margin-bottom: 12px; }
.profile-meta { list-style: none; text-align: left; margin-top: 18px; }
.profile-meta li { display: flex; justify-content: space-between; padding: 9px 0; border-bottom: 1px solid var(--line-d); font-size: var(--fs-sm); }
.profile-meta li span:first-child { color: var(--muted); }
.profile-actions { display: flex; flex-direction: column; gap: 10px; margin-top: 20px; }
.profile-badges { display: flex; flex-wrap: wrap; gap: 6px; justify-content: center; margin: 4px 0 2px; }
.medal-wall { display: grid; grid-template-columns: repeat(auto-fill, minmax(84px, 1fr)); gap: 14px; }
.medal-item { display: flex; flex-direction: column; align-items: center; gap: 6px; text-align: center; }
.medal-icon { width: 52px; height: 52px; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: #fff; font-size: var(--fs-3xl); box-shadow: 0 4px 12px rgba(2,8,20,.4); }
.medal-name { font-size: var(--fs-xs); color: var(--muted); }

/* 反馈弹窗 */
.fb-mask {
    position: fixed; inset: 0; z-index: 200;
    background: rgba(2, 8, 20, .72); backdrop-filter: blur(4px);
    display: flex; align-items: center; justify-content: center; padding: 18px;
    animation: fb-mask-in .24s ease both;
}

.fb-modal {
    width: 100%; max-width: 520px;
    background: var(--panel, #0d1830); border: 1px solid var(--line-d, rgba(148,163,184,.18));
    border-radius: 16px; padding: 22px 22px 18px;
    box-shadow: 0 30px 80px rgba(0, 0, 0, .5);
    animation: fb-in .28s var(--ease-out) both;
}
@keyframes fb-mask-in { from { opacity: 0; } }
@keyframes fb-in { from { opacity: 0; transform: translateY(18px) scale(.97); } to { opacity: 1; transform: none; } }
/* 关闭：反向缩退，动画结束后 JS 再置 hidden */
.fb-mask.closing { animation: fb-mask-out .2s ease both; }
.fb-mask.closing .fb-modal { animation: fb-out .2s ease-in both; }
@keyframes fb-mask-out { to { opacity: 0; } }
@keyframes fb-out { to { opacity: 0; transform: translateY(12px) scale(.97); } }
@media (prefers-reduced-motion: reduce) {
    .fb-mask, .fb-modal, .fb-mask.closing, .fb-mask.closing .fb-modal { animation: none; }
}
.fb-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 6px; }
.fb-head strong { font-size: var(--fs-xl); color: #fff; }
.fb-close {
    border: none; background: none; color: var(--muted); font-size: var(--fs-4xl); line-height: var(--lh-flat);
    cursor: pointer; padding: 2px 8px; border-radius: 8px;
}
.fb-close:hover { color: #fff; background: rgba(255,255,255,.08); }
.fb-tip { color: var(--muted); font-size: var(--fs-sm); margin-bottom: 14px; }
.fb-modal textarea.input { width: 100%; resize: vertical; min-height: 120px; }
.fb-contact { width: 100%; margin-top: 10px; }
.fb-foot { display: flex; align-items: center; justify-content: space-between; margin-top: 14px; }
.fb-foot a { color: var(--muted); font-size: var(--fs-sm); }
.fb-foot a:hover { color: var(--brand-2); }
.fb-msg { margin-top: 12px; font-size: var(--fs-sm); padding: 10px 12px; border-radius: 10px; }
.fb-msg-ok { color: var(--ok); background: rgba(63, 214, 143, .1); }
.fb-msg-err { color: var(--danger); background: rgba(240, 113, 110, .1); }

/* 反馈页“我的反馈”列表 */
.fb-mine { margin-top: 26px; border-top: 1px solid var(--line-d, rgba(148,163,184,.18)); padding-top: 18px; }
.fb-mine h2 { font-size: var(--fs-xl); margin-bottom: 12px; }
.fb-mine ul { list-style: none; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.fb-mine li { display: flex; align-items: center; gap: 10px; font-size: var(--fs-sm); }
.fb-mine-text { flex: 1; }
a.fb-mine-text:hover { color: var(--brand-2); }
.fb-mine small { color: var(--muted); }
.fb-mine-hint { margin-top: 10px; color: var(--muted); font-size: var(--fs-xs); }

/* 反馈对话页 */
.fbd-head { display: flex; align-items: center; gap: 12px; }
.fbd-thread { display: flex; flex-direction: column; gap: 12px; margin: 18px 0 22px; }
.fbd-msg {
    border: 1px solid var(--line-d, rgba(148,163,184,.18));
    border-radius: 12px; padding: 12px 14px; background: rgba(255,255,255,.02);
}
.fbd-msg-staff { border-color: rgba(13, 169, 205, .35); background: rgba(13, 169, 205, .06); }
.fbd-msg-meta { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
.fbd-msg-meta strong { color: #fff; font-size: var(--fs-sm); }
.fbd-msg-meta small { color: var(--muted); font-size: var(--fs-2xs); }
.fbd-msg-body { color: var(--text); font-size: var(--fs-md); line-height: var(--lh-relaxed); word-break: break-word; }
.fbd-reply-form textarea { resize: vertical; }

/* ============================================================
   错误页（404 信号丢失 / 500 系统过载）
   ============================================================ */
.error-hero {
    min-height: calc(100vh - 74px);
    display: flex; align-items: center; justify-content: center;
    padding: 80px 0;
    background:
        radial-gradient(ellipse 60% 50% at 50% 42%, rgba(13, 169, 205, .08), transparent 70%),
        var(--black);
}
.error-code {
    font-family: var(--font-num); font-weight: 800; line-height: var(--lh-flat);
    font-size: var(--fs-mega); letter-spacing: 2px;
    color: transparent;
    -webkit-text-stroke: 2px rgba(233, 237, 244, .35);
    text-stroke: 2px rgba(233, 237, 244, .35);
    margin: 6px 0 -10px;
    user-select: none;
}
.error-wave {
    display: block; width: min(560px, 86%); height: 84px; margin: 0 auto 8px;
    color: var(--accent-2);
    filter: drop-shadow(0 0 12px rgba(13, 169, 205, .55));
    animation: wave-flicker 4s steps(2, jump-none) infinite;
}
@keyframes wave-flicker {
    0%, 92% { opacity: 1; }
    94% { opacity: .55; }
    96% { opacity: .9; }
    98% { opacity: .4; }
}
@media (prefers-reduced-motion: reduce) {
    .error-wave { animation: none; }
}
.error-hero h1 { font-family: var(--font-display); color: #fff; font-size: var(--fs-dsp-2); font-weight: 900; margin-bottom: 12px; }
.error-sub { color: var(--muted); font-size: var(--fs-md); max-width: 460px; margin: 0 auto 32px; }
.error-actions { display: flex; align-items: center; justify-content: center; gap: 26px; flex-wrap: wrap; color: var(--text); }

/* ============================================================
   新生指南（recruit）
   ============================================================ */
.recruit-hero {
    position: relative; padding: 90px 0 66px; overflow: hidden;
    /* **只能写 background-color。** 这个元素上还挂着 .nf-scope（示波器网格），
       用 background 简写会把它的 background-image 一起重置掉 —— 网格凭空消失
       且不报错。原来那道青色辉光也是 background-image，会和网格抢同一个属性，
       所以搬到 ::after 上当一层独立的叠加。 */
    background-color: var(--black-2);
    border-bottom: 1px solid var(--line-d);
}
.recruit-hero::after {
    content: ""; position: absolute; inset: 0; pointer-events: none;
    background: radial-gradient(ellipse 70% 90% at 85% -10%, rgba(13, 169, 205, .16), transparent 60%);
}
.recruit-hero h1 { font-family: var(--font-display); color: #fff; font-size: var(--fs-dsp-3); font-weight: 900; margin-bottom: 14px; }
.recruit-hero p {
    color: var(--muted); max-width: 680px; font-size: var(--fs-lead);
    font-family: var(--font-serif); font-weight: 600;   /* 导语用宋体，见 tokens.css */
}

/* 「这一页有多长」：进来先给尺度，长页最怕的是不知道要读多久 */
.rg-meta {
    display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
    margin-top: 24px; color: var(--muted); font-size: var(--fs-xs);
}
.rg-meta-item b {
    font-family: var(--font-mono); font-weight: 700; font-size: var(--fs-2xl);
    color: #fff; margin-right: 5px;
}
.rg-meta-sep { width: 1px; height: 18px; background: var(--line-d-strong); }

/* ---------- 阅读进度线 ----------
   压在固定导航正下方。**纯增强**：宽度由 recruit-guide.js 写，没有 JS 时
   这条 <i> 保持 0 宽 —— 不留一条空槽，也不需要 hidden。 */
.rg-bar {
    position: fixed; top: 73px; left: 0; right: 0; height: 2px;
    z-index: 99;                     /* 导航是 100，进度线要压在它下面 */
    background: rgba(255, 255, 255, .05); pointer-events: none;
}
.rg-bar > i {
    display: block; height: 100%; width: 0;
    background: linear-gradient(90deg, var(--accent), var(--accent-2));
    box-shadow: 0 0 8px rgba(13, 169, 205, .6);
}

.recruit-layout { display: grid; grid-template-columns: 210px 1fr; gap: 44px; align-items: start; padding-top: 48px; }

/* ---------- 目录 = 一段竖着的 PCB 走线 ----------
   章节是方形 SMD 焊盘，读过哪一章那个焊盘就通电。和招新报名进度、会员等级
   阶梯、首页分镜 06 的路线图同一种语言 —— 同一件事（推进）在全站长一个样子。
   主干 .rg-trunk 由 recruit-guide.js 按「读到第几章」写高度，止于当前焊盘中心。 */
.recruit-toc { position: sticky; top: 100px; }
.recruit-toc a {
    position: relative; display: flex; align-items: center; gap: 11px;
    padding: 9px 0; color: var(--muted); font-size: var(--fs-sm);
    transition: color var(--t-base);
}
.recruit-toc a:hover { color: var(--accent-2); }
.recruit-toc a.is-read { color: #dce8f2; }
.recruit-toc a.is-current { color: var(--accent-2); font-weight: 700; }

/* 裸铜主干：焊盘之间的连线，未通电状态。上下各缩 22px 让它止于首末焊盘中心 */
.recruit-toc::before {
    content: ""; position: absolute; left: 12px; top: 22px; bottom: 22px;
    width: 2px; background: rgba(201, 138, 61, .28);
}
/* 通电段：脚本写 top 与 height（都按首末焊盘中心实测，不写死 22px —— 目录项
   高度会随字号和换行变）。没有 JS 时高度为 0，退化成一条裸铜线 + 静态编号，
   目录本身是锚点，功能完整。 */
.rg-trunk {
    position: absolute; left: 12px; top: 22px; width: 2px; height: 0;
    background: linear-gradient(180deg, var(--accent), var(--accent-2));
    box-shadow: 0 0 10px rgba(13, 169, 205, .5);
    transition: height var(--t-base) var(--ease-out);
}
.rg-pad {
    position: relative; z-index: 1; flex: none;
    width: 26px; height: 26px; border-radius: 7px;
    display: grid; place-items: center;
    border: 1px solid rgba(201, 138, 61, .45);
    /* 不透明底：主干从焊盘背后穿过，透明的话会看到线压在数字上 */
    background: var(--black);
    font: 700 var(--fs-3xs)/var(--lh-flat) var(--font-mono); color: var(--copper-2);
    transition: border-color var(--t-base), color var(--t-base),
                background var(--t-base), box-shadow var(--t-base);
}
.recruit-toc a.is-read .rg-pad {
    border-color: var(--accent-2); background: rgba(9, 30, 38, 1);
    color: var(--accent-2);
}
.recruit-toc a.is-current .rg-pad { box-shadow: 0 0 12px rgba(13, 169, 205, .45); }
@media (prefers-reduced-motion: reduce) {
    .rg-trunk, .rg-pad, .recruit-toc a { transition: none; }
}

/* 阅读行宽控制：正文不超过 ~40 字/行 */
.recruit-content { max-width: 780px; }
.recruit-content section { margin-bottom: 58px; scroll-margin-top: 100px; }
/* ---------- 章节头横幅 ----------
   五章各一张暗调氛围图（`img/banner-*.webp`，1920x810，7~25KB）。
   标题**压在图上**，所以生成时就要求「左侧 40% 留干净」—— 图右侧是意象，
   左侧是标题的地盘。`background-position: right center` 保证窄屏裁掉的是右边
   的意象而不是标题区。

   带一个 `background-color`：`check_a11y.py` 逐层向上找不透明背景来算对比度，
   只给 background-image 的话它会一路找到更外层去，量出来的不是这里的实际底色。
   （另外验收脚本已经量过左区 99 分位亮度 ≤80，白字压上去仍在 9:1 以上。）

   选择器必须是 `section > h2`（章节头），**不能是 `.recruit-content h2`** ——
   页末加入面板的标题也是 h2，套上这层壳之后它是一个「有描边、没有图」的黑盒子，
   看起来像一处加载失败的横幅。截图才看出来的。 */
.recruit-content section > h2 {
    padding: 30px 26px 26px; border-radius: 14px; border: 1px solid var(--line-d);
    background-color: #050506;
    background-repeat: no-repeat;
    background-position: right center;
    background-size: cover;
}
#intro > h2 { background-image: url("../img/banner-intro.ee04eb193408.webp"); }
#training > h2 { background-image: url("../img/banner-training.8fec13579b8d.webp"); }
#hardware > h2 { background-image: url("../img/banner-hardware.b074d4119bb3.webp"); }
#software > h2 { background-image: url("../img/banner-software.bdd8ad8ffc8e.webp"); }
#resources > h2 { background-image: url("../img/banner-contest.ff14d0ea10b6.webp"); }
/* 章节副标题贴着横幅下缘，读成一组 */
.recruit-content h2 + .sec-sub { margin-top: 10px; }
@media (prefers-contrast: more) {
    /* 高对比偏好下摘掉横幅：它是氛围，而这里的诉求是让标题最清楚 */
    .recruit-content h2 { background-image: none !important; }
}
.recruit-content h2 {
    display: flex; align-items: baseline; gap: 12px;
    font-family: var(--font-display); color: #fff; font-size: var(--fs-4xl); font-weight: 900; margin-bottom: 6px;
}
/* 章节编号用丝印语气：铜色 mono，和目录焊盘上的编号一一对应 */
.rg-no {
    font: 700 var(--fs-md)/var(--lh-flat) var(--font-mono); color: var(--copper);
    letter-spacing: 1px; flex: none;
}
/* ---------- 开篇导语 + 三联画 ----------
   导语是**第二声音**（宋体 SemiBold），和列表页页头那句 `.page-hero-sub` 同一个
   角色：比正文大一档、行高更松。这一页有一万多字，先给一句能读完的。 */
.rg-lead {
    font-family: var(--font-serif); font-weight: 600;
    font-size: var(--fs-lead); line-height: var(--lh-prose);
    color: #fff; margin: 0 0 22px;
}
/* 三联画：一年的三个节点。`<ol>` 而不是 `<div>` —— 它是有顺序的三步，
   序号在语义里就该存在（读屏软件会念「列表，3 项」）。 */
.rg-arc {
    display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px;
    list-style: none; padding: 0; margin: 0 0 26px;
}
.rg-arc li { margin: 0; }
/* **不要给 `<img>` 写 width/height 属性**：宽高都是确定值时 CSS 的 aspect-ratio
   会被忽略，占位就没了（见 tokens.css 那条教训）。所以比例写在这里。 */
.rg-arc img {
    display: block; width: 100%; aspect-ratio: 4 / 3; object-fit: cover;
    border-radius: 10px; border: 1px solid var(--line-d);
    background: var(--black-2);
}
.rg-arc p {
    display: flex; align-items: baseline; gap: 7px;
    margin: 8px 0 0; color: var(--muted); font-size: var(--fs-xs);
}
/* 序号沿用丝印语气（铜色 mono），和章节编号 `.rg-no`、目录焊盘上的编号同一套 */
.rg-arc b {
    font: 700 var(--fs-2xs)/var(--lh-flat) var(--font-mono);
    color: var(--copper); letter-spacing: 1px; flex: none;
}
@media (max-width: 720px) {
    /* 窄屏三栏各只有 110px 宽，插画整个糊掉 —— 换成一栏一张，图反而更大。
       「窄屏换构图，不是缩小桌面版」。
       同时把比例从 4:3 压到 16:10：三张全宽 4:3 图加图注要占掉 900px，
       而这是一页一万多字长文的**开篇**，铺垫不该比正文还长。三张画的主体都是
       横向居中的，`cover` 裁掉的是上下两条空白。 */
    .rg-arc { grid-template-columns: 1fr; gap: 18px; }
    .rg-arc img { aspect-ratio: 16 / 10; }
}

/* ---------- 四张最小电路（原理图片段） ----------
   `brand/schem/*.svg`，由 scripts/build_schematics.py 从符号库拼出来。
   画布统一 240x240，所以给一半宽度就能让四张的线宽一致。

   `<ol>` 而不是 `<div>`：这四个是有顺序的（从最简单往上走），
   序号在语义里就该存在。 */
.schem-grid {
    display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px;
    list-style: none; padding: 0; margin: 0 0 26px;
}
.schem-grid li { margin: 0; }
.schem-grid figure {
    margin: 0; height: 100%;
    border: 1px solid var(--line-d); border-radius: 12px;
    background: rgba(255, 255, 255, .03); padding: 12px 14px 14px;
}
/* 原理图本身是**内容**不是装饰，所以用正文色，不像元件符号那样用铜色 */
.schem { display: block; width: 100%; height: auto; color: var(--text); }
.schem-grid figcaption {
    margin-top: 8px; color: var(--muted);
    font-size: var(--fs-xs); line-height: var(--lh-prose);
}
.schem-grid figcaption b { display: block; color: #fff; font-weight: 700; }
@media (max-width: 720px) {
    /* 窄屏两栏各只有 165px，原理图上的 Vin/Vout 标注就糊了 —— 换成一栏一张 */
    .schem-grid { grid-template-columns: 1fr; }
}

/* 每章一张现场照（素材槽）：拍到之前是空焊盘占位 */
.rg-shot { margin: 18px 0 22px; }
.recruit-content .sec-sub { color: var(--accent); font-size: var(--fs-xs); margin-bottom: 24px; font-family: var(--font-mono); letter-spacing: 2px; }
.recruit-content h3 { color: var(--accent-2); font-size: var(--fs-xl); margin: 26px 0 12px; }
.recruit-content p { color: var(--text); font-size: var(--fs-md); margin-bottom: 12px; }
.recruit-content li { color: var(--text); font-size: var(--fs-md); margin-bottom: 7px; }
.recruit-content ul { padding-left: 22px; margin-bottom: 14px; }

details.fold {
    border: 1px solid var(--line-d); border-radius: 16px; background: rgba(255, 255, 255, .02);
    margin-bottom: 14px; overflow: hidden;
}
details.fold summary {
    cursor: pointer; list-style: none; padding: 17px 22px; color: #fff; font-weight: 700; font-size: var(--fs-xl);
    display: flex; justify-content: space-between; align-items: center; user-select: none;
    transition: background .2s;
}
details.fold summary:hover { background: rgba(255, 255, 255, .035); }
details.fold summary::-webkit-details-marker { display: none; }
/* 折叠标记用 V 形而不是「＋ 旋转 45° 变 ×」—— 后者展开后长得像关闭按钮，
   读成「点它会删掉这块内容」。V 形转 180° 的语义只有「收起 / 展开」一种。 */
details.fold summary::after {
    content: ""; flex: none; width: 8px; height: 8px; margin-right: 3px;
    border-right: 2px solid var(--accent-2); border-bottom: 2px solid var(--accent-2);
    transform: rotate(45deg) translateY(-2px);
    transition: transform .3s var(--ease-out);
}
details.fold[open] summary::after { transform: rotate(-135deg) translateY(-2px); }
details.fold .fold-body { padding: 6px 22px 22px; border-top: 1px solid var(--line-d); }

/* 折叠开合高度动画：现代 Chromium 渐进增强，其余浏览器保持瞬开 */
@supports (interpolate-size: allow-keywords) {
    details.fold {
        interpolate-size: allow-keywords;
    }
    details.fold::details-content {
        height: 0;
        overflow: clip;
        transition: height .45s var(--ease-out), content-visibility .45s allow-discrete;
    }
    details.fold[open]::details-content { height: auto; }
    @media (prefers-reduced-motion: reduce) {
        details.fold::details-content { transition: none; }
    }
}

.img-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 12px; margin: 14px 0; }
/* 元件图统一「标本板」模板：冷调浅灰底板 + multiply 融合，
   白底元件照不再像贴在黑底上的便利贴，且全部图卡色温一致 */
.img-grid figure {
    position: relative; border-radius: 12px; overflow: hidden; border: 1px solid var(--line-d);
    background: linear-gradient(180deg, #eef1f5 0%, #dbe1e9 100%);
    aspect-ratio: 4 / 3;
}
.img-grid img {
    width: 100%; height: 100%; object-fit: contain; padding: 10px 10px 32px;
    mix-blend-mode: multiply; filter: saturate(.96);
}
.img-grid figcaption {
    position: absolute; left: 0; right: 0; bottom: 0; text-align: center;
    font-family: var(--font-mono); font-size: var(--fs-2xs); letter-spacing: 1px;
    padding: 5px 6px; color: #dfe5ec; background: rgba(5, 5, 6, .82);
}
.img-grid.img-grid-lg { grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); }
.img-grid.img-grid-lg figure { aspect-ratio: 16 / 10; }
.img-grid figure.img-wide { grid-column: 1 / -1; aspect-ratio: auto; }
.img-grid figure.img-wide img { max-height: 420px; object-fit: contain; margin: 0 auto; }

.component-block { padding: 18px 0; border-bottom: 1px solid var(--line-d); }
.component-block:last-child { border-bottom: none; }
/* ---------- 元件名 + 原理图符号 ----------
   一行里：左边是元件名，右边是它在原理图里的画法（`brand/sym/*.svg`，
   由 scripts/build_symbols.py 生成）。下面那排是实物照片 —— 照片教
   「长什么样」，符号教「怎么画」，原来只有前一半。

   符号用铜色而不是青色：全站的语汇里青色是「信号」，铜色是「工艺记号 / 丝印」
   （章节编号 `.rg-no`、三联画的序号都是铜色）。原理图符号属于后者。 */
.cb-head { display: flex; align-items: center; gap: 16px; }
.cb-sym {
    /* **高度固定、宽度自动**：符号库里每个 SVG 的 viewBox 高度都是 48，
       所以固定高度就等于固定缩放比例 —— 一列符号的线宽才一样粗。

       高度取 40px 而不是 30：两端元件的本体只占画布中间 22/48，30px 时实际墨迹
       只有 14px、比 20px 的元件名还小，读起来像个附注；40px 之后墨迹约 20px，
       和元件名同高。三极管/运放占满画布所以会明显更大 —— 那是**对的**，
       真实符号库里三极管本来就比电阻大一圈，统一的是比例尺不是视觉大小。

       **`width` 必须显式给出来，不能只写 `auto`。** 内联 SVG 只有 viewBox、
       没有 width/height 属性时，`width: auto` 解析成 100%（撑满 flex 容器），
       于是它按容器宽度反推高度、把整行顶成几百像素高。上面那条 `height: 40px`
       会被这个宽度连带作废。所以宽度按各自的 viewBox 比例写死在下面。 */
    height: 40px; flex: none; margin-left: auto;
    color: var(--copper-2);
}
/* 按 viewBox 宽高比给宽度（高度统一 48）：56/48 → 46.7px，48/48 → 40px，
   32/48 → 26.7px。写成 aspect-ratio 更省事，但 Safari 对无内在尺寸的内联 SVG
   支持得不一致，直接给 width 最稳。 */
.cb-sym { width: 46.7px; }                       /* 两端元件 56x48 */
.cb-sym[viewBox="0 0 48 48"] { width: 40px; }    /* 三极管 / MOSFET */
.cb-sym[viewBox="0 0 32 48"] { width: 26.7px; }  /* 接地 / 电源 / 排针 */
@media (max-width: 720px) {
    /* 窄屏元件名可能换行，符号跟着挤成一条竖的。让它退到名字下面单独一行。 */
    .cb-head { flex-wrap: wrap; gap: 6px 16px; }
    .cb-sym { margin-left: 0; }
}
/* 是 h3 而不是 h4：见 recruit.html 里的说明（外层是 h2，中间的 summary 不是标题）。
   字号 16px 比 `.recruit-content h3` 小是刻意的 —— 层级给读屏，字号给眼睛。 */
.component-block h3 { color: #fff; font-size: var(--fs-xl); margin: 0 0 6px; }
.component-block > p { color: var(--muted); font-size: var(--fs-sm); }

/* recruit 页末尾的加入面板。
   底部留 130px（比顶部厚一倍）是给 `.join-bg` 的焊点材质腾地方，见下面那段。 */
.join-panel {
    position: relative; text-align: center; padding: 60px 30px 130px; border-radius: 24px; overflow: hidden;
    border: 1px solid var(--line-d-strong);
    background: radial-gradient(ellipse 90% 120% at 50% -20%, rgba(13, 169, 205, .18), transparent 60%), var(--black-2);
}
/* `.join-bg` 是模板里那个空 div，本来只是占位。给它铺上焊点微距（局部材质，
   1024px 一张、53KB）—— 这一段的语义是「来实验室焊你的第一块板」，焊点特写是
   全站唯一一处「有景深的真实材质」，放在这里正好。

   **混合模式必须是 `screen`，不能是 `soft-light`**：这张图很暗（中位亮度 14，
   99 分位才 170 —— 一片黑底上一个高光焊点），而 soft-light 在接近纯黑的底色上
   输出仍然接近纯黑，整张图等于没画。第一版就是这样，截图上一点痕迹都没有。
   screen 只加光不减光：黑场原样留黑，只有那个高光透上来，读起来是「面板上真的
   有一个反光的焊点」而不是贴了一张照片。

   **材质只画在面板底部那条 130px 的空带里**，文案一个字都不压上去。先试过整块
   铺满 `cover` 并把焦点放在高光上（68% / 45%），结果亮部升到 L≈120 正压在副标题
   那一行：`var(--muted)` 的灰字在那里几乎看不见 —— 而 `check_a11y.py` 量的是
   `.join-panel` 自己的 `background-color`（材质是兄弟节点，不在它那条链上），
   这种事它发现不了。挪进空带之后不再有文字压上来，反而可以把强度给足。

   取景与遮罩**全部用 px、不用百分比**：这个面板在窄屏会变成三倍高（内容竖排），
   百分比写的话同一条带子在手机上有 200px 深，直接吃掉整个 QQ 群号那一块。
   `bottom -180px / auto 450px` 把高光钉在「距底边约 67px」处、`right -30px` 把它
   钉在「距右边约 123px」处 —— 两个数都与面板尺寸无关，桌面和窄屏取景一致。

   遮罩是**一个以高光为心的椭圆**，不是自下往上的线性渐隐：线性渐隐只在竖直方向
   收边，图片自己的左边界就成了一条笔直的竖线切在面板中间（截图上很显眼）。椭圆
   的半径按 px 给死，两个方向同时收：竖直方向在文案下沿处正好归零，水平方向在图片
   左边界处归零 —— 四面都没有硬边。 */
.join-bg.join-bg {
    position: absolute; inset: 0;
    background: url("../img/tex-solder-joint.3460144b8739.webp") right -30px bottom -180px / auto 450px no-repeat;
    opacity: .9;
    mix-blend-mode: screen;
    -webkit-mask-image: radial-gradient(ellipse 320px 150px at calc(100% - 123px) calc(100% + 10px), #000 0 40%, transparent 92%);
    mask-image: radial-gradient(ellipse 320px 150px at calc(100% - 123px) calc(100% + 10px), #000 0 40%, transparent 92%);
    pointer-events: none;
}
.join-panel > * { position: relative; }
/* `.recruit-content h2` 把 h2 变成了 flex 容器，于是 `.join-panel` 的
   `text-align: center` 对它无效 —— 这条标题一直是贴左的，和面板里其它全部居中的
   东西不是一路。补 `justify-content` 才是居中 flex 项的办法。 */
.join-panel h2 { color: #fff; font-size: var(--fs-dsp-1); margin-bottom: 10px; justify-content: center; }
.join-panel > p { color: var(--muted); margin-bottom: 26px; }
.join-qq {
    display: inline-flex; align-items: center; gap: 18px; flex-wrap: wrap; justify-content: center;
    padding: 16px 30px; border-radius: 999px; border: 1px dashed var(--line-d-strong);
    background: rgba(5, 5, 6, .5);
}
.join-qq-label { color: var(--muted); font-size: var(--fs-sm); }
.join-qq strong { font-family: var(--font-mono); font-size: var(--fs-dsp-1); color: var(--accent-2); letter-spacing: 3px; }

/* ============================================================
   通知中心：导航铃铛 + 下拉 + 列表页
   ============================================================ */
.nav-bell-wrap { position: relative; display: flex; }
.nav-bell {
    position: relative; display: inline-flex; align-items: center; justify-content: center;
    width: 38px; height: 38px; border-radius: 50%; color: var(--text);
}
.nav-bell:hover { background: rgba(255, 255, 255, .08); color: #fff; }
.nav-bell .icon { width: 20px; height: 20px; }
.nav-bell-badge {
    position: absolute; top: 1px; right: -2px; min-width: 17px; height: 17px; padding: 0 4px;
    border-radius: 999px; background: var(--danger); color: #fff;
    font-size: var(--fs-3xs); font-weight: 700; font-style: normal; line-height: 17px; text-align: center;
    box-shadow: 0 0 0 2px rgba(5, 5, 6, .8);
}
.bell-menu {
    position: absolute; top: calc(100% + 12px); right: -6px; width: 350px; z-index: 60;
    background: rgba(13, 16, 22, .97); border: 1px solid var(--line-d-strong); border-radius: 14px;
    box-shadow: 0 18px 50px rgba(0, 0, 0, .55); overflow: hidden;
    backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
}
.bell-menu-head {
    display: flex; align-items: center; justify-content: space-between;
    padding: 12px 16px; border-bottom: 1px solid var(--line-d);
}
.bell-menu-head strong { color: #fff; font-size: var(--fs-sm); }
.bell-menu-head a { color: var(--accent-2); font-size: var(--fs-xs); }
.bell-menu-list { max-height: 380px; overflow-y: auto; }
.bell-menu-empty { padding: 30px 16px; text-align: center; color: var(--muted); font-size: var(--fs-sm); }
.bell-item { display: block; padding: 11px 16px 11px 26px; position: relative; border-bottom: 1px solid var(--line-d); }
.bell-item:last-child { border-bottom: none; }
.bell-item:hover { background: rgba(255, 255, 255, .05); }
.bell-item-unread::before {
    content: ""; position: absolute; left: 11px; top: 17px; width: 7px; height: 7px;
    border-radius: 50%; background: var(--accent); box-shadow: 0 0 6px var(--glow);
}
.bell-item-title { display: block; color: var(--text); font-size: var(--fs-sm); font-weight: 600; line-height: var(--lh-snug); }
.bell-item-unread .bell-item-title { color: #fff; }
.bell-item-body {
    display: block; color: var(--muted); font-size: var(--fs-xs); line-height: var(--lh-normal); margin-top: 2px;
    overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}
.bell-item-meta { display: block; color: var(--muted); font-size: var(--fs-2xs); margin-top: 4px; opacity: .8; }

.ntf-list { display: flex; flex-direction: column; gap: 10px; }
/* 盒子样式在 `.card` / `.card-link` 里。通知条**不做 hover 位移** —— 一列十几条
   同高的条目，任何一条抬起来都像列表在抖。 */
.ntf-item { align-items: flex-start; gap: 14px; padding: 16px 18px; }
.ntf-item.card-link:hover { transform: none; }
.ntf-dot { flex: none; width: 8px; height: 8px; border-radius: 50%; margin-top: 8px; background: transparent; }
.ntf-unread { border-color: rgba(13, 169, 205, .35); }
.ntf-unread .ntf-dot { background: var(--accent); box-shadow: 0 0 8px var(--glow); }
.ntf-main { flex: 1; min-width: 0; }
.ntf-title { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; color: var(--text); font-weight: 600; font-size: var(--fs-md); }
.ntf-unread .ntf-title { color: #fff; }
.ntf-body { display: block; color: var(--muted); font-size: var(--fs-sm); line-height: var(--lh-normal); margin-top: 5px; }
.ntf-time { flex: none; color: var(--muted); font-size: var(--fs-xs); margin-top: 3px; }

/* ============================================================
/* ============================================================
   公告：列表卡片 + 文章页 + Markdown 正文
   （首页速览条 .nf-news-* 在 home.css）
   ============================================================ */
.news-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; }
/* 描边、圆角、hover 抬起都在 `.card` / `.card-link` 里，这里只补内部排版 */
.news-card { flex-direction: column; overflow: hidden; }
.news-card-cover { display: block; aspect-ratio: 21 / 9; overflow: hidden; }
.news-card-cover img { width: 100%; height: 100%; object-fit: cover; }
.news-card-body { display: flex; flex-direction: column; gap: 9px; padding: 18px 20px; }
.news-card-meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.news-card-title { color: #fff; font-size: var(--fs-2xl); line-height: var(--lh-normal); }
.news-card-excerpt { color: var(--muted); font-size: var(--fs-sm); line-height: var(--lh-relaxed); }
.news-card-foot { color: var(--muted); font-size: var(--fs-xs); font-family: var(--font-mono); opacity: .85; }

.news-article { max-width: 780px; margin: 0 auto; }
.news-article-head h1 {
    font-family: var(--font-display); color: #fff; font-weight: 900;
    font-size: var(--fs-dsp-2); line-height: var(--lh-snug); margin: 8px 0 14px;
}
.news-article-meta { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; color: var(--muted); font-size: var(--fs-sm); }
.news-article-cover { margin: 26px 0 0; border-radius: var(--radius); overflow: hidden; border: 1px solid var(--line-d); }
.news-article-cover img { width: 100%; display: block; }
.news-article-body { margin-top: 30px; }
.news-article-foot { margin-top: 40px; padding-top: 22px; border-top: 1px solid var(--line-d); }
.news-related { max-width: 780px; margin: 46px auto 0; padding-top: 26px; border-top: 1px dashed var(--line-d); }
.news-related h2 { color: #fff; font-size: var(--fs-xl); margin-bottom: 14px; }
.news-related ul { list-style: none; display: flex; flex-direction: column; gap: 10px; }
.news-related li { display: flex; align-items: baseline; justify-content: space-between; gap: 14px; }
.news-related li a { color: var(--text); font-size: var(--fs-md); }
.news-related li a:hover { color: var(--accent-2); }
.news-related li small { color: var(--muted); font-family: var(--font-mono); font-size: var(--fs-2xs); flex: none; }

/* ============================================================
   Markdown 正文排版（公告 / 活动详情共用）
   ------------------------------------------------------------
   这一份是**读者真正花时间的地方**，而它原来只有 18 条平铺规则：一个字号、
   三级标题、一个引用块、一个代码块、一个表格。改版把首屏做得很足，长文却还是
   「一个标题 + 一片正文」。

   正文内容是站务写的 Markdown，`nh3` 会把 class 属性剥掉（见
   `news/markdown.py`），所以**这里的样式只能挂在元素选择器上** ——
   作者没有办法给某一段加类名，也就没法自己发明版式。这个限制是好的：
   版式由这一份决定，内容只管写。

   同理，这里**不能用宋体**（`--font-serif` 是按模板取字的子集，正文里的生僻字
   会掉回黑体，一段引文里混两种字形）。引用块的分量靠字号与记号做，不靠换字体。
   ============================================================ */
.md-body { color: var(--text); font-size: var(--fs-lg); line-height: var(--lh-prose); }
/* 开篇第一段当导语：比正文大一档。只在文章**真的以段落开头**时生效 ——
   用 `:first-child` 而不是 `:first-of-type`，后者在「小标题 + 段落」的开头
   也会命中，把一个普通段落放大成导语。 */
.md-body > p:first-child { font-size: var(--fs-lead); color: #fff; }
/* 避免孤字行（最后一行只剩一个字）。浏览器不支持时无副作用。 */
.md-body p, .md-body li { text-wrap: pretty; }
.md-body strong { color: #fff; font-weight: 700; }
.md-body h1, .md-body h2, .md-body h3, .md-body h4 { color: #fff; margin: 1.6em 0 .6em; line-height: var(--lh-snug); }
/* 长文标题拉开到 30 / 22 / 18，正文 16。原来是 26 / 20 / 18，h3 只比正文大 12%，
   三级标题挤在一起 —— 公告和活动详情是读者真正停留的地方，层级最不该含糊。 */
.md-body h1 { font-size: var(--fs-5xl); }
.md-body h2 { font-size: var(--fs-3xl); padding-bottom: 8px; border-bottom: 1px solid var(--line-d); }
.md-body h3 { font-size: var(--fs-xl); }
.md-body p { margin: .9em 0; }
.md-body a { color: var(--accent-2); text-decoration: underline; text-underline-offset: 3px; }
.md-body ul, .md-body ol { margin: .9em 0; padding-left: 1.6em; }
.md-body li { margin: .3em 0; }
/* 列表记号跟着品牌色。`::marker` 只吃颜色/字体这几个属性，够用了 */
.md-body li::marker { color: var(--accent); font-family: var(--font-mono); }
.md-body img { max-width: 100%; border-radius: 10px; border: 1px solid var(--line-d); margin: 10px 0; }

/* ---------- 图注 ----------
   `render_markdown` 把「整段只有一张图」的段落升级成 figure，说明文字取 alt。
   原来那句 alt **一个字都不显示**，而图注是长文里独立的一层信息。 */
.md-body figure { margin: 1.6em 0; }
.md-body figure img { margin: 0; display: block; width: 100%; }
.md-body figcaption {
    margin-top: 9px; padding-left: 15px; position: relative;
    font-family: var(--font-mono); font-size: var(--fs-xs); line-height: var(--lh-normal);
    color: var(--muted); letter-spacing: .3px;
}
/* 图注前面一道短竖线，和站上「丝印标注」的语法一致 */
.md-body figcaption::before {
    content: ""; position: absolute; left: 0; top: .45em; bottom: .3em;
    width: 2px; background: var(--copper);
}

/* ---------- 引用 ----------
   原来是一个青色底的圆角块，读起来像「提示框」。改成**拉引言**：大一号、
   亮一档、去掉底色，左侧换成走线 + 焊盘（同会标的语法）。
   底色去掉之后它不再抢正文的注意力，而分量反而更足。 */
.md-body blockquote {
    margin: 1.5em 0; padding: 2px 0 2px 26px; position: relative;
    font-size: var(--fs-xl); line-height: var(--lh-relaxed); color: #fff;
    border-radius: 0; background: none;
}
.md-body blockquote::before {
    content: ""; position: absolute; left: 3px; top: .55em; bottom: .55em;
    width: 2px; background: var(--accent);
}
/* 顶端一个焊盘，读作「这段话从这里接出来」 */
.md-body blockquote::after {
    content: ""; position: absolute; left: 0; top: calc(.55em - 4px);
    width: 8px; height: 8px; border-radius: 50%;
    border: 2px solid var(--accent-2); background: var(--black);
}
.md-body blockquote p { margin: .4em 0; }
.md-body blockquote p:first-child { margin-top: 0; }
.md-body blockquote p:last-child { margin-bottom: 0; }

/* ---------- 定义列表 = 参数表 ----------
   Markdown 的 `术语` / `:   定义` 语法。这是长文里唯一能写「元件参数」的结构，
   原来完全没有样式（dt 和 dd 长得一样，读不出哪个是名哪个是值）。 */
.md-body dl {
    margin: 1.4em 0; display: grid; grid-template-columns: minmax(96px, auto) 1fr;
    gap: 1px; border: 1px solid var(--line-d); border-radius: 10px; overflow: hidden;
}
.md-body dt {
    padding: 9px 14px; background: rgba(255, 255, 255, .045);
    font-family: var(--font-mono); font-size: var(--fs-sm); color: var(--copper-2);
    letter-spacing: .5px;
}
.md-body dd {
    padding: 9px 16px; margin: 0; font-size: var(--fs-sm);
    background: rgba(255, 255, 255, .015);
}
@media (max-width: 560px) {
    /* 窄屏两列会把术语挤成竖排单字，改成上下两行 */
    .md-body dl { grid-template-columns: 1fr; }
    .md-body dt { padding-bottom: 4px; }
    .md-body dd { padding-top: 4px; padding-left: 14px; }
}

/* ---------- 脚注 ----------
   `nh3` 原来把锚点 id 剥掉，所以脚注**点了跳不动**（见 news/markdown.py）。
   放开 id 之后这一套才真的能用，于是也值得给它排版。 */
.md-body sup { font-family: var(--font-mono); font-size: .72em; }
.md-body sup a { color: var(--accent-2); text-decoration: none; padding: 0 2px; }
.md-body sup a:hover { text-decoration: underline; }
.md-body .footnote { margin-top: 2.4em; font-size: var(--fs-sm); color: var(--muted); }
.md-body .footnote hr {
    border: none; height: 8px; margin: 0 0 14px;
    /* 丝印刻度当分隔线，比一条虚线更像仪器面板 */
    background-image: repeating-linear-gradient(90deg, var(--line-d-strong) 0 1px, transparent 1px 7px);
    background-size: 100% 8px; background-repeat: repeat-x;
}
.md-body .footnote ol { padding-left: 1.4em; }
.md-body .footnote li { margin: .5em 0; }
.md-body .footnote a[title] { color: var(--accent-2); text-decoration: none; }
.md-body code { font-family: var(--font-mono); font-size: .88em; background: rgba(255, 255, 255, .08); padding: 2px 6px; border-radius: 5px; }
.md-body pre { margin: 1em 0; padding: 14px 18px; background: rgba(0, 0, 0, .45); border: 1px solid var(--line-d); border-radius: 10px; overflow-x: auto; }
.md-body pre code { background: none; padding: 0; font-size: var(--fs-xs); line-height: var(--lh-relaxed); }
/* ---------- 表格 ----------
   表头改成 mono + 字距 + 大写，读作「参数表的列名」；数字列用等宽数字对齐，
   否则一列数字每行宽度不同，扫视时对不上。 */
.md-body table {
    width: 100%; border-collapse: collapse; margin: 1.4em 0; font-size: var(--fs-sm);
    font-variant-numeric: tabular-nums;
}
.md-body th, .md-body td { padding: 9px 13px; border: 1px solid var(--line-d); text-align: left; }
.md-body th {
    background: rgba(255, 255, 255, .05); color: #fff;
    font-family: var(--font-mono); font-size: var(--fs-xs);
    letter-spacing: 1px; text-transform: uppercase; font-weight: 700;
}
.md-body tbody tr:nth-child(even) { background: rgba(255, 255, 255, .018); }
/* ---------- 分隔线 ----------
   一排丝印刻度，取代一条虚线。同一套语言在 eyebrow、脚注分隔线上也在用。 */
.md-body hr {
    border: none; height: 10px; margin: 2em 0;
    background-image: repeating-linear-gradient(90deg, var(--line-d-strong) 0 1px, transparent 1px 8px);
    background-size: 100% 10px; background-repeat: repeat-x;
}

/* 公告/活动编辑（驾驶舱） */
.news-form-panel { max-width: 860px; }
.md-toolbar { display: flex; align-items: center; gap: 12px; margin-bottom: 8px; }
.md-toolbar-hint { color: var(--muted); font-size: var(--fs-xs); }
.md-img-btn { cursor: pointer; }
.news-form-actions { display: flex; gap: 10px; margin-top: 18px; }

/* ============================================================
   活动：列表卡片 + 详情信息面板 + 报名/签到操作区
   ============================================================ */
.event-grid { display: flex; flex-direction: column; gap: 14px; }
/* 盒子样式在 `.card` / `.card-link` 里 */
.event-card { align-items: stretch; gap: 20px; padding: 18px 22px; }
.event-card-date {
    flex: none; display: flex; flex-direction: column; align-items: center; justify-content: center;
    width: 74px; border-right: 1px dashed var(--line-d); padding-right: 20px;
}
.event-card-date strong { font-family: var(--font-num); font-size: var(--fs-5xl); color: var(--accent-2); line-height: var(--lh-tight); }
.event-card-date small { color: var(--muted); font-size: var(--fs-xs); margin-top: 2px; }
.event-card-main { display: flex; flex-direction: column; gap: 7px; min-width: 0; }
.event-card-meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.event-card-title { color: #fff; font-size: var(--fs-xl); line-height: var(--lh-normal); }
.event-card-info { color: var(--muted); font-size: var(--fs-xs); font-family: var(--font-mono); }
.event-points-hint { margin-top: 30px; text-align: center; color: var(--muted); font-size: var(--fs-sm); }
.event-points-hint a { color: var(--accent-2); }

.event-info-panel {
    margin-top: 26px; padding: 18px 22px; display: grid; gap: 10px;
    border: 1px solid var(--line-d); border-radius: var(--radius); background: var(--panel);
}
.event-info-item { display: flex; gap: 14px; font-size: var(--fs-md); color: var(--text); }
.event-info-label { flex: none; width: 72px; color: var(--muted); }

.event-action-box {
    margin-top: 16px; padding: 18px 22px; display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
    border: 1px dashed var(--line-d-strong); border-radius: var(--radius);
    background: rgba(13, 169, 205, .05);
}
.event-action-status { color: var(--text); font-size: var(--fs-md); }
.event-action-status.ok { color: var(--ok); font-weight: 600; }
.event-spots { color: var(--muted); font-size: var(--fs-xs); }
.event-checkin-form { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.event-checkin-input { width: 130px; font-family: var(--font-mono); letter-spacing: 3px; text-align: center; }
.event-checkin-hint { flex-basis: 100%; color: var(--muted); font-size: var(--fs-xs); }
.event-code {
    font-family: var(--font-mono); font-size: var(--fs-xl); letter-spacing: 4px; color: var(--accent-2);
    background: rgba(65, 216, 232, .08); border: 1px solid rgba(65, 216, 232, .35);
    border-radius: 8px; padding: 3px 10px;
}

/* ============================================================
   积分：明细 + 排行榜
   ============================================================ */
.points-pos { color: var(--ok); font-weight: 700; font-family: var(--font-mono); }
.points-neg { color: var(--danger); font-weight: 700; font-family: var(--font-mono); }
.lb-list { display: flex; flex-direction: column; gap: 10px; max-width: 720px; }
/* 盒子样式在 `.card` 里（排行榜整行不可点，所以不加 .card-link） */
.lb-row { display: flex; align-items: center; gap: 16px; padding: 13px 20px; }
.lb-top { border-color: rgba(201, 138, 61, .45); background: rgba(201, 138, 61, .06); }
.lb-rank { flex: none; width: 34px; text-align: center; font-family: var(--font-mono); font-size: var(--fs-xl); color: var(--muted); }
.lb-avatar { flex: none; width: 40px; height: 40px; border-radius: 50%; object-fit: cover; border: 1px solid var(--line-d-strong); }
.lb-avatar-fallback {
    display: inline-flex; align-items: center; justify-content: center;
    background: rgba(13, 169, 205, .18); color: var(--accent-2); font-weight: 700; font-size: var(--fs-lg);
}
.lb-name { flex: 1; min-width: 0; color: #fff; font-weight: 600; font-size: var(--fs-md); display: flex; flex-direction: column; }
.lb-name small { color: var(--muted); font-weight: 400; font-size: var(--fs-xs); margin-top: 2px; }
.lb-score { font-family: var(--font-mono); font-size: var(--fs-2xl); font-weight: 700; color: var(--accent-2); }
.lb-score small { font-size: var(--fs-2xs); color: var(--muted); font-weight: 400; }

/* ============================================================
   招新报名
   ============================================================ */
.nav-hot { color: var(--accent-2) !important; }

/* ---------- 招新落地页（/recruitment/） ----------
   注意与「新生指南」的 .recruit-* 区分：那是 core:recruit，这里是 recruitment
   app 的报名页。两者路径像、职责完全不同，所以这一套统一用 rec- 前缀。 */
.rec-hero {
    position: relative; padding: 96px 0 70px;
    /* 必须是 background-color 而不是 background 简写：这个区块同时挂了 .nf-scope，
       简写会把它的 background-image（示波器网格）整个重置掉，而两条规则同特异性、
       .rec-hero 在后面 —— 结果就是网格静默消失。截图确认过。 */
    background-color: var(--black); border-bottom: 1px solid var(--line-d);
}
/* 覆盖 .nf-scope 默认的上下渐隐，改成「左侧压暗、右侧露出」。
   .nf-scope 的默认强度是给整块深色区当底纹用的；这块 Hero 又矮、又没有粒子
   波场分散注意力，不压的话网格会和巨字抢视线（截图确认过）。
   左边是标题的地盘，网格往右让。 */
.rec-hero.nf-scope::before {
    background:
        linear-gradient(90deg, var(--black) 0%, rgba(5, 5, 6, .88) 32%,
                        rgba(5, 5, 6, .55) 66%, rgba(5, 5, 6, .38) 100%),
        linear-gradient(180deg, var(--black) 0%, transparent 28%,
                        transparent 72%, var(--black) 100%);
}
.rec-hero-title {
    font-family: var(--font-display); color: #fff; font-weight: 900;
    font-size: var(--fs-dsp-4); line-height: var(--lh-tight); letter-spacing: 1px;
    margin: 8px 0 20px;
}
.rec-hero-title .accent { color: var(--accent); }
.rec-hero-meta { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 22px; }
.rec-hero-campaign { color: var(--muted); font-family: var(--font-mono); font-size: var(--fs-sm); letter-spacing: 1px; }
.rec-hero-sub {
    color: var(--muted); font-size: var(--fs-lead); max-width: 620px; line-height: var(--lh-prose);
    font-family: var(--font-serif); font-weight: 600;   /* 导语用宋体，见 tokens.css */
}
.rec-hero-sub strong { color: var(--accent-2); font-family: var(--font-mono); letter-spacing: 1px; }

/* 实时报名数据。第一格是主角（已报名总数），后面三格是意向分布 */
.rec-stats { display: flex; align-items: flex-end; gap: 34px; flex-wrap: wrap; margin-top: 8px; }
.rec-stat { display: flex; flex-direction: column; gap: 2px; }
.rec-stat strong {
    font-family: var(--font-num); font-weight: 700; line-height: var(--lh-flat);
    font-size: var(--fs-4xl); color: var(--text); font-variant-numeric: tabular-nums;
}
.rec-stat span { color: var(--muted); font-size: var(--fs-xs); letter-spacing: 1px; }
.rec-stat-lead strong { font-size: var(--fs-7xl); color: var(--accent-2); font-family: var(--font-num); }
.rec-stats-empty { color: var(--muted); font-size: var(--fs-md); }

.rec-body {
    display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 400px);
    gap: 44px; align-items: start; padding: 54px 0 80px;
}
.rec-main { line-height: var(--lh-prose); color: var(--text); max-width: 720px; }
.rec-main h2 { color: #fff; font-size: var(--fs-3xl); font-weight: 800; margin: 30px 0 10px; }
.rec-main h2:first-child { margin-top: 0; }
.rec-main p { color: var(--text); font-size: var(--fs-md); margin-bottom: 12px; }
.rec-main strong { color: var(--accent-2); }
/* sticky：右栏跟着走，看说明时表单一直在视野里 */
.rec-side { position: sticky; top: 96px; }
.rec-box h2 { color: #fff; font-size: var(--fs-3xl); font-weight: 800; margin-bottom: 12px; }
.rec-hint { color: var(--muted); font-size: var(--fs-sm); line-height: var(--lh-relaxed); }
.rec-hint a, .rec-note a { color: var(--accent-2); }
.rec-hint strong { color: var(--accent-2); font-family: var(--font-mono); }
.rec-actions { display: flex; gap: 10px; margin-top: 16px; flex-wrap: wrap; }
.rec-note { font-size: var(--fs-sm); line-height: var(--lh-relaxed); padding: 12px 14px; border-radius: 12px; margin-top: 14px; }
.rec-note-warn { color: var(--warn); background: rgba(244, 182, 63, .08); border: 1px solid rgba(244, 182, 63, .28); }

/* ---------- 我的报名进度：一条竖向 PCB 走线 ----------
   焊盘串在一条裸铜主干上，走到哪通电到哪 —— 和首页分镜 06 是同一套语言。 */
.rec-status { display: flex; align-items: center; gap: 12px; margin-bottom: 20px; flex-wrap: wrap; }
.rec-status-badge { color: #fff; font-size: var(--fs-sm); font-weight: 700; border-radius: 999px; padding: 4px 14px; }
.rec-status-dept { color: var(--muted); font-size: var(--fs-sm); }

.rec-track { list-style: none; margin: 0; padding: 0; position: relative; }
/* 主干：未通电的裸铜，从第一个焊盘中心连到最后一个 */
.rec-track::before {
    content: ""; position: absolute; left: 17px; top: 18px; bottom: 20px;
    width: 3px; background: rgba(201, 138, 61, .3); border-radius: 2px;
}
.rec-track-item {
    position: relative; padding: 0 0 22px 52px; color: var(--muted);
}
.rec-track-item:last-child { padding-bottom: 0; }
/* 方形 SMD 焊盘 */
.rec-track-pad {
    position: absolute; left: 0; top: 0;
    width: 36px; height: 36px; border-radius: 6px;
    display: grid; place-items: center;
    font: 700 var(--fs-2xs)/var(--lh-flat) var(--font-mono); color: var(--copper);
    background: var(--black); border: 1.5px solid rgba(201, 138, 61, .55);
    transition: color var(--t-base), border-color var(--t-base), box-shadow var(--t-base);
}
.rec-track-item strong { display: block; color: var(--text); font-size: var(--fs-md); font-weight: 700; }
.rec-track-desc { display: block; font-size: var(--fs-xs); line-height: var(--lh-relaxed); margin-top: 2px; }
.rec-track-item time { display: block; margin-top: 4px; font: 400 var(--fs-2xs)/var(--lh-flat) var(--font-mono); color: var(--silk-dim); }
/* 通电：这一步已经走到 */
.rec-track-item.is-done { color: var(--text); }
.rec-track-item.is-done strong { color: #fff; }
.rec-track-item.is-done .rec-track-pad {
    color: var(--accent-2); border-color: var(--accent-2);
    box-shadow: 0 0 0 4px rgba(65, 216, 232, .12), 0 0 16px rgba(65, 216, 232, .3);
}
/* 已通电的那一段主干。画在「已完成且不是最后一项」上，于是通电部分自然停在
   最后一个已完成的焊盘处，不需要 JS 算高度 */
.rec-track-item.is-done:not(:last-child)::after {
    content: ""; position: absolute; left: 17px; top: 18px;
    width: 3px; height: calc(100% - 18px); border-radius: 2px;
    background: linear-gradient(180deg, var(--accent-2), var(--accent));
}

/* ---------- 分步报名表 ---------- */
.rec-form { display: flex; flex-direction: column; gap: 4px; }
/* 步骤指示：三个小焊盘串一条线 */
.rec-progress {
    list-style: none; display: flex; align-items: center; gap: 0;
    margin: 0 0 20px; padding: 0; position: relative;
}
.rec-progress li {
    flex: 1; display: flex; flex-direction: column; align-items: center; gap: 6px;
    position: relative; color: var(--muted); font-size: var(--fs-2xs); letter-spacing: 1px;
}
.rec-progress li span {
    width: 13px; height: 13px; border-radius: 3px;
    border: 1.5px solid rgba(201, 138, 61, .55); background: var(--black);
    transition: border-color var(--t-base), background var(--t-base), box-shadow var(--t-base);
}
/* 连线画在项与项之间。z-index -1 会跑到 .panel 背景后面，所以用 ::after 垫在
   焊盘下方的办法：给焊盘一个不透明背景压住线即可 */
.rec-progress li:not(:last-child)::after {
    content: ""; position: absolute; left: 50%; right: -50%; top: 6px;
    height: 2px; background: rgba(201, 138, 61, .3);
}
.rec-progress li span { position: relative; z-index: 1; }
.rec-progress li.is-done span, .rec-progress li.is-current span {
    border-color: var(--accent-2); background: var(--accent-2);
    box-shadow: 0 0 10px rgba(65, 216, 232, .55);
}
.rec-progress li.is-current { color: var(--accent-2); }
.rec-progress li.is-done { color: var(--text); }
/* 没有 JS 时三段全部展开，步骤指示就没有意义了 */
.rec-form:not(.is-stepped) .rec-progress { display: none; }

.rec-step { border: 0; padding: 0; margin: 0 0 6px; min-width: 0; }
.rec-step legend {
    padding: 0; color: #fff; font-size: var(--fs-md); font-weight: 700; margin-bottom: 4px;
}
/* 三段全展开时给每段之间一点呼吸，否则 legend 会贴住上一段的输入框 */
.rec-form:not(.is-stepped) .rec-step + .rec-step { margin-top: 20px; }
.rec-step-hint { color: var(--muted); font-size: var(--fs-xs); line-height: var(--lh-relaxed); margin-bottom: 12px; }

/* 方向单选：卡片而不是下拉 —— 三个选项，卡片更适合「选方向」这种决定，
   移动端也不用弹系统选择器 */
.rec-choice { display: flex; flex-direction: column; gap: 9px; }
.rec-choice-item {
    display: flex; align-items: center; gap: 11px; cursor: pointer;
    padding: 13px 15px; border-radius: 12px;
    border: 1px solid var(--line-d); background: rgba(255, 255, 255, .02);
    transition: border-color var(--t-fast), background var(--t-fast);
}
.rec-choice-item:hover { border-color: var(--line-d-strong); }
.rec-choice-item input { accent-color: var(--accent); flex: none; }
.rec-choice-body strong { color: var(--text); font-size: var(--fs-md); font-weight: 600; }
/* :has 给选中的卡片整体上色。不支持 :has 的浏览器只是少了这层高亮，原生单选
   圆点仍然表明选了哪个 —— 不是功能缺失 */
.rec-choice-item:has(input:checked) {
    border-color: var(--accent); background: rgba(13, 169, 205, .1);
}
.rec-choice-item:has(input:checked) .rec-choice-body strong { color: #fff; }

/* 多选组（感兴趣的方向 9 项、得知渠道 6 项）。
   比方向卡片轻一档：**九个和三个不是一回事** —— 九张同样厚的卡片会把这一段撑成
   一屏多，而方向那三张是「这一步就选一个」的主角。所以这里去掉描边、只留勾选框
   与文字，靠 gap 分隔。
   这一组的**名字由外层 fieldset 的 legend 承担**，所以模板里不走
   includes/field.html（理由写在那儿：CheckboxSelectMultiple 的 id_for_label
   指向一个不存在的 id，label 的 for 会落空）。 */
.rec-check { display: grid; gap: 2px; margin-bottom: 12px; }
.rec-check-item {
    display: flex; align-items: flex-start; gap: 10px; cursor: pointer;
    padding: 7px 9px; border-radius: 9px; margin: 0 -9px;
    color: var(--muted); font-size: var(--fs-sm); line-height: var(--lh-snug);
    transition: background var(--t-fast), color var(--t-fast);
}
.rec-check-item:hover { background: rgba(255, 255, 255, .04); color: var(--text); }
.rec-check-item input { accent-color: var(--accent); flex: none; margin-top: 2px; }
/* 勾上的那几项提亮 —— 一屏九项时「我选了哪几个」要能扫一眼看出来。
   不支持 :has 的浏览器只是少了这层提亮，原生勾选框照样表明状态。 */
.rec-check-item:has(input:checked) { color: var(--text); }

/* 身份回显：注册时填过的四项，只读。它是上下文不是输入，所以不套 .input 外观，
   也刻意不占分步的一段。 */
.rec-identity {
    margin: 0 0 8px; display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px 14px;
    padding: 13px 15px; border-radius: 12px;
    border: 1px solid var(--line-d); background: rgba(255, 255, 255, .02);
}
.rec-identity dt { color: var(--muted); font-size: var(--fs-3xs); letter-spacing: 1px; }
.rec-identity dd { margin: 2px 0 0; color: var(--text); font-size: var(--fs-sm); font-weight: 600; word-break: break-all; }
.rec-identity-note { color: var(--muted); font-size: var(--fs-xs); line-height: var(--lh-relaxed); margin: 0 0 18px; }

.rec-review { margin: 14px 0; display: grid; grid-template-columns: 64px 1fr; gap: 8px 14px; }
.rec-review dt { color: var(--muted); font-size: var(--fs-xs); }
.rec-review dd { margin: 0; color: var(--text); font-size: var(--fs-sm); line-height: var(--lh-relaxed); word-break: break-word; }

/* 「上一步」「下一步」「提交」三颗按钮的显隐由 form-enhance.js 用 hidden 属性
   控制；真的隐藏靠 tokens.css 那条全局 [hidden]，别在这里再写一遍。 */
.rec-nav { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; margin-top: 10px; }
.rec-nav .btn-block { flex: 1; margin-top: 0; }

@media (max-width: 1024px) {
    .rec-body { grid-template-columns: 1fr; gap: 30px; }
    .rec-side { position: static; }
}
@media (max-width: 720px) {
    .rec-hero { padding: 80px 0 54px; }
    .rec-stats { gap: 20px; }
    .rec-stat-lead strong { font-size: var(--fs-6xl); }
}

.recruit-apply-layout { display: grid; grid-template-columns: 1.15fr .85fr; gap: 20px; align-items: start; }
.recruit-intro { line-height: var(--lh-relaxed); }
.recruit-qq-line { margin-top: 16px; color: var(--muted); font-size: var(--fs-sm); }
.recruit-qq-line strong { color: var(--accent-2); font-family: var(--font-mono); letter-spacing: 1px; }
.recruit-apply-box h2 { margin-bottom: 14px; }
.recruit-hint { color: var(--muted); font-size: var(--fs-sm); line-height: var(--lh-relaxed); }
.recruit-hint a { color: var(--accent-2); }
.recruit-actions { display: flex; gap: 10px; margin-top: 14px; flex-wrap: wrap; }
.recruit-form { display: flex; flex-direction: column; gap: 4px; }
.btn-block { width: 100%; margin-top: 8px; }
.recruit-status { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; flex-wrap: wrap; }
.recruit-status-badge { color: #fff; font-size: var(--fs-sm); font-weight: 700; border-radius: 999px; padding: 4px 14px; }
.recruit-status-dept { color: var(--muted); font-size: var(--fs-sm); }
.recruit-status-timeline { list-style: none; padding: 0; margin: 0 0 14px; display: flex; flex-direction: column; gap: 10px; }
.recruit-status-timeline li {
    position: relative; padding-left: 26px; color: var(--muted); font-size: var(--fs-sm);
}
.recruit-status-timeline li::before {
    content: ""; position: absolute; left: 4px; top: 3px; width: 12px; height: 12px;
    border-radius: 50%; border: 2px solid var(--line-d-strong); background: var(--black);
}
.recruit-status-timeline li.done { color: var(--text); }
.recruit-status-timeline li.done::before { border-color: var(--accent); background: var(--accent); }

/* ============================================================
   项目档案库
   ============================================================ */
.proj-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
/* 描边、圆角、hover 抬起都在 `.card` 里，这里只补内部排版 */
.proj-card { flex-direction: column; gap: 8px; padding: 18px 20px; }
div.proj-card { display: flex; }
.proj-card-locked { opacity: .72; cursor: default; }
.proj-card-head { display: flex; gap: 8px; flex-wrap: wrap; }
.proj-card-title { color: #fff; font-size: var(--fs-xl); }
.proj-card-summary { color: var(--muted); font-size: var(--fs-sm); line-height: var(--lh-normal); flex: 1; }
.proj-card-foot { color: var(--accent-2); font-size: var(--fs-xs); font-family: var(--font-mono); }
.proj-card-locked .proj-card-foot { color: var(--muted); }

.proj-summary { color: var(--text); font-size: var(--fs-md); line-height: var(--lh-relaxed); max-width: 720px; }
.proj-back { margin-top: 20px; }
.proj-layout { display: grid; grid-template-columns: 1fr 280px; gap: 20px; align-items: start; }
.proj-breadcrumb { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 14px; font-size: var(--fs-sm); }
.proj-breadcrumb a { color: var(--accent-2); }
.proj-breadcrumb .sep { color: var(--muted); }
.proj-breadcrumb .cur { color: #fff; }
.proj-ops { display: flex; gap: 20px; flex-wrap: wrap; margin-bottom: 16px; }
.proj-op-form { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.proj-op-form .input { max-width: 220px; }
.proj-file-table td { vertical-align: middle; }
.proj-folder-link { color: var(--accent-2); font-weight: 600; }
.proj-file-ext {
    display: inline-block; min-width: 42px; text-align: center; font-size: var(--fs-3xs); font-family: var(--font-mono);
    color: var(--muted); border: 1px solid var(--line-d); border-radius: 6px; padding: 1px 6px; margin-right: 4px;
}
.proj-members { list-style: none; padding: 0; margin: 0 0 14px; display: flex; flex-direction: column; gap: 8px; }
.proj-members li { display: flex; align-items: center; gap: 8px; font-size: var(--fs-sm); color: var(--text); }
.proj-members-empty { color: var(--muted); font-size: var(--fs-sm); }
.proj-member-remove { background: none; border: none; color: var(--muted); cursor: pointer; font-size: var(--fs-lg); padding: 0 2px; }
.proj-member-remove:hover { color: var(--danger); }
.proj-add-member { display: flex; flex-direction: column; gap: 8px; margin-top: 10px; border-top: 1px solid var(--line-d); padding-top: 12px; }

/* ============================================================
   响应式（全站部分；首页分镜的响应式在 home.css）
   ============================================================ */
@media (max-width: 1024px) {
    .footer-grid { grid-template-columns: 1fr 1fr; }
    .profile-grid { grid-template-columns: 1fr; }
    .recruit-layout { grid-template-columns: 1fr; }
    .recruit-apply-layout { grid-template-columns: 1fr; }
    .proj-grid { grid-template-columns: repeat(2, 1fr); }
    .proj-layout { grid-template-columns: 1fr; }
    /* 窄屏换构图：竖走线没有位置，目录压成一行横向药丸。编号缩进药丸里当前缀，
       不再画焊盘 —— 26px 的方块在 13px 的药丸里会把它撑变形。 */
    .recruit-toc { position: static; display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 6px; }
    .recruit-toc::before, .rg-trunk { display: none; }
    .recruit-toc a { gap: 6px; border: 1px solid var(--line-d); border-radius: 999px; padding: 6px 13px; font-size: var(--fs-xs); }
    .recruit-toc a.is-current { border-color: var(--accent); background: rgba(13, 169, 205, .1); }
    .rg-pad {
        width: auto; height: auto; border: 0; border-radius: 0; padding: 0;
        background: none; font-size: var(--fs-3xs);
    }
    .recruit-toc a.is-read .rg-pad { background: none; }
    .rg-meta { gap: 12px; }
    .rg-meta-item b { font-size: var(--fs-lg); }
}

/* ---------- 汉堡菜单 ----------
   断点从 720px 提到 1024px（理由见上面 .nav-group 那一节：平板与小笔记本那一段
   宽度原来放不下平铺的导航）。**这里只放导航**；版面的窄屏改造仍然在下面的
   720px 块里 —— 一开始图省事把整块的断点一起提上来，结果 1024px 下公告变成单列、
   活动卡片竖排，白瞎了一半宽度。

   面板不用绝对定位，而是让 `.nav-inner` 换行：链接区和右侧账号区各占一整行。
   绝对定位那版要给账号区写死 `top`，而链接区的高度会随折叠组展开而变 —— 于是
   账号区只能改成 static，落回标题那一行，把「电子科技协会」挤成四行（截图才
   看得出来）。换行布局天然跟着内容高度走。 */
@media (max-width: 1024px) {
    .nav-links, .nav-auth { display: none; }
    .nav-burger { display: block; margin-left: auto; }
    .nav-inner { position: relative; flex-wrap: wrap; height: auto; min-height: 74px; }
    /* 标题行不许换行：这一行只有会标和汉堡，永远放得下 */
    .nav-title strong, .nav-title small { white-space: nowrap; }

    .site-nav.nav-open { background: rgba(5, 5, 6, .97); border-bottom: 1px solid var(--line-d); }
    /* `.nav-inner` 的 `gap: 30px` 一旦换行就变成行间距，在标题行/链接区/账号区之间
       各插 30px 死空白（实测把面板整体撑到 876px、超出 844px 的屏幕）。收掉。 */
    .site-nav.nav-open .nav-inner { row-gap: 0; }
    /* 显式排序，否则汉堡按钮（DOM 里在最后）会被挤到第四行去，跑到账号按钮下面。
       要的顺序是「会标 + 汉堡」一行，然后链接区、账号区各占一行。 */
    .nav-burger { order: 1; }
    .site-nav.nav-open .nav-links { order: 2; }
    .site-nav.nav-open .nav-auth { order: 3; }
    .site-nav.nav-open .nav-links,
    .site-nav.nav-open .nav-auth { display: flex; flex-basis: 100%; }
    .site-nav.nav-open .nav-links {
        flex-direction: column; align-items: stretch; margin-left: 0; gap: 0;
        /* 折叠组展开后条目会超过一屏，让链接区自己滚。**刻意留出一截页面**：
           面板占满整屏的话就没有「外面」可点了，而点外面关闭是触屏上唯一顺手的
           关法（Esc 在手机上不存在）。 */
        max-height: calc(100vh - 240px); overflow-y: auto;
    }
    .site-nav.nav-open .nav-links > a,
    .site-nav.nav-open .nav-group > summary {
        padding: 14px 10px; border-radius: 0;
        border-bottom: 1px solid var(--line-d);
    }
    /* 折叠组标题在窄屏是一整行（不是药丸）：`display: flex` 而不是 inline-flex，
       否则它只有文字那么宽，`justify-content` 也就无从生效、箭头贴在字后面。 */
    .site-nav.nav-open .nav-group,
    .site-nav.nav-open .nav-group > summary { display: block; }
    .site-nav.nav-open .nav-group > summary {
        display: flex; justify-content: space-between; align-items: center;
    }
    .site-nav.nav-open .nav-auth {
        flex-direction: row; align-items: center; gap: 9px;
        padding: 14px 10px 10px; border-top: 1px solid var(--line-d);
    }
    .site-nav.nav-open .nav-auth .btn { flex: 1; }
    .site-nav.nav-open .nav-auth .nav-bell-wrap { flex: none; }
}

@media (max-width: 720px) {
    .footer-grid { grid-template-columns: 1fr; gap: 26px; }
    .footer-bar-inner { flex-direction: column; align-items: flex-start; }
    .auth-card { padding: 28px 22px; }
    .filter-bar .input { width: 100%; }
    .filter-search { width: 100%; }
    .nf-section { padding: 70px 0; }
    .news-grid { grid-template-columns: 1fr; }
    .event-card { flex-direction: column; gap: 12px; }
    .event-card-date { flex-direction: row; gap: 8px; width: auto; border-right: none; padding-right: 0; align-items: baseline; }
    .event-card-date strong { font-size: var(--fs-3xl); }
    .event-info-label { width: 60px; }
    .proj-grid { grid-template-columns: 1fr; }
    .proj-ops { flex-direction: column; gap: 10px; }
    .page-hero { padding: 34px 0 22px; }
}


/* ============================================================
   作品墙（works）
   ------------------------------------------------------------
   前缀 `wk-`。和 `/projects/`（会员档案库，`proj-`）是两套页面：档案库是表格与
   文件树，作品墙是照片与一句话。共用的只有 Project 模型。

   卡片本体 includes/work_card.html 三处复用：作品墙、作品详情页底部、首页分镜 07。
   ============================================================ */
/* 页头横幅走通用的 `.page-hero`，筛选药丸走通用的 `.pill-row > .pill`
   —— 原来这里各有一份（`.wk-hero` / `.wk-filter`），而荣誉墙和团队页又各抄了
   一遍 hero。Task 18 把三份合并进「内页通用」那一段。 */
.wk-grid {
    display: grid; gap: 20px;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
}

/* ---------- 作品卡片 ---------- */
.wk-card {
    position: relative; overflow: hidden;
    display: flex; flex-direction: column; color: var(--text);
    border: 1px solid var(--line-d); border-radius: 16px;
    background: rgba(255, 255, 255, .022);
    transition: transform var(--t-base) var(--ease-out),
                border-color var(--t-base), background var(--t-base);
}
.wk-card:hover {
    transform: translateY(-4px); color: var(--text);
    border-color: rgba(65, 216, 232, .5); background: rgba(13, 169, 205, .05);
}
/* 顶边走线：hover 时通电铺满，和注册通道卡同一个手法 */
.wk-card::before {
    content: ""; position: absolute; top: 0; left: 0; right: 0; height: 2px; z-index: 2;
    background: linear-gradient(90deg, var(--accent), rgba(13, 169, 205, 0) 70%);
    transform: scaleX(.3); transform-origin: left;
    transition: transform var(--t-slow) var(--ease-out);
}
.wk-card:hover::before { transform: scaleX(1); }

.wk-cover { margin: 0; aspect-ratio: 4 / 3; overflow: hidden; background: var(--bg-2); }
.wk-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* 空焊盘占位当封面用时不要圆角内嵌（卡片自己已经裁了） */
.wk-cover.is-empty { border-radius: 0; border-left: 0; border-right: 0; border-top: 0; }

.wk-card-body { padding: 16px 18px 18px; display: flex; flex-direction: column; gap: 8px; flex: 1; }
.wk-card-meta { display: flex; align-items: center; gap: 8px; }
.wk-dept {
    font: 700 var(--fs-3xs)/var(--lh-flat) var(--font-mono); letter-spacing: 1.5px;
    color: var(--accent-2); text-transform: uppercase;
}
.wk-star {
    padding: 2px 8px; border-radius: 999px; font-size: var(--fs-3xs); font-weight: 700;
    color: var(--copper-2); border: 1px solid rgba(201, 138, 61, .45);
    background: rgba(201, 138, 61, .1);
}
.wk-card-title { color: #fff; font-size: var(--fs-2xl); font-weight: 700; line-height: var(--lh-snug); }
.wk-card-teaser { color: var(--muted); font-size: var(--fs-sm); line-height: var(--lh-relaxed); flex: 1; }

/* 标签：丝印语气的小方牌，不用圆角药丸 —— 圆角留给可点的东西 */
.wk-tags { display: flex; flex-wrap: wrap; gap: 6px; list-style: none; margin: 0; padding: 0; }
.wk-tags li {
    padding: 3px 8px; border-radius: 4px;
    border: 1px solid var(--silk-line);
    font: 400 var(--fs-2xs)/var(--lh-normal) var(--font-mono); color: var(--silk-dim);
}

/* 空态走通用的 `.empty-state` + `.empty-state-hint`（core.css 的「内页通用」段）。
   这里原来有一份 `.wk-empty`，和荣誉墙、团队页那两份逐字相同 —— Task 18 合并掉了。 */

/* ---------- 作品详情 ---------- */
.wk-detail-hero {
    background-color: var(--black);
    padding: clamp(44px, 6vw, 76px) 0 clamp(24px, 3vw, 36px);
}
.wk-detail-hero h1 {
    font-family: var(--font-display); color: #fff; font-weight: 900;
    font-size: var(--fs-dsp-2); line-height: var(--lh-tight); margin-bottom: 12px;
}
/* 作品导语是同一个「导语」角色，但**刻意不用宋体** —— 这段文字来自数据库
   （`Project.summary`，会员自己写的），而宋体子集只覆盖模板用字，一个生僻字
   就会在这段话里混进一个黑体字。别顺手把它统一过去。 */
.wk-detail-lede { color: var(--text); font-size: var(--fs-lead); line-height: var(--lh-prose); max-width: 680px; }
.wk-detail-meta {
    display: flex; align-items: center; gap: 16px; flex-wrap: wrap; margin-top: 18px;
}
.wk-detail-date { color: var(--muted); font: 400 var(--fs-xs)/var(--lh-flat) var(--font-mono); letter-spacing: 1px; }

.wk-detail-body {
    display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 320px);
    gap: clamp(24px, 4vw, 44px); align-items: start;
    padding: 40px 0 20px;
}
.wk-detail-main { min-width: 0; }
.wk-detail-side { min-width: 0; }
.wk-detail-text { margin: 24px 0 8px; }

.wk-shot {
    margin: 0 0 16px; overflow: hidden; border-radius: 14px;
    border: 1px solid var(--line-d); background: var(--bg-2);
}
.wk-shot img { width: 100%; height: auto; display: block; }
.wk-shot figcaption {
    padding: 9px 14px; color: var(--muted); font-size: var(--fs-xs);
    border-top: 1px solid var(--line-d);
}
.wk-shot-lead { margin-bottom: 8px; }
.wk-gallery { display: grid; gap: 16px; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); }

.wk-sub {
    color: #fff; font-family: var(--font-display); font-weight: 900;
    font-size: var(--fs-3xl); margin: 34px 0 18px;
}
.wk-team { list-style: none; margin: 0; padding: 0; }
.wk-team li {
    display: flex; justify-content: space-between; align-items: baseline; gap: 12px;
    padding: 9px 0; border-bottom: 1px solid var(--line-d);
}
.wk-team li:last-child { border-bottom: none; }
.wk-team strong { color: #fff; font-size: var(--fs-md); }
.wk-team span { color: var(--muted); font-size: var(--fs-xs); flex: none; }
.wk-side-text { color: var(--muted); font-size: var(--fs-sm); line-height: var(--lh-relaxed); margin-bottom: 16px; }

/* 首页分镜 07 的标题行：标题在左、「看全部」在右 */
.nf-works-head {
    display: flex; align-items: flex-end; justify-content: space-between;
    gap: 24px; flex-wrap: wrap; margin-bottom: 30px;
}
.nf-works-head .nf-display { margin-bottom: 0; }

@media (max-width: 900px) {
    .wk-detail-body { grid-template-columns: 1fr; padding-top: 26px; }
}
@media (max-width: 720px) {
    .wk-grid { grid-template-columns: 1fr; }
    .nf-works-head { margin-bottom: 22px; }
}


/* ============================================================
   荣誉墙（honors）
   ------------------------------------------------------------
   前缀 `hn-`。主体是**排版密实的清单**而不是证书照片墙 —— 二十张红底证书扫描件
   堆在一起像档案柜，而且协会现在一张都没有。这一页在零张照片时就该好看。

   一行 includes/honor_row.html 两处复用：荣誉墙的按年清单、首页分镜 08。
   ============================================================ */
/* 页头横幅与空态走通用的 `.page-hero` / `.empty-state`（强调色用
   `style="--hero-accent: var(--copper-2)"` 传）—— 这里原来各有一份。 */

/* 等级计数。数字是数出来的（Honor.summary()），不是手填的 */
.hn-stats { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 26px; }
.hn-stat {
    display: flex; align-items: baseline; gap: 8px;
    padding: 12px 20px; border-radius: 12px;
    border: 1px solid var(--line-d); background: rgba(255, 255, 255, .022);
}
.hn-stat strong {
    font-family: var(--font-num); font-weight: 700; font-size: var(--fs-5xl); line-height: var(--lh-flat);
    font-variant-numeric: tabular-nums;
}
.hn-stat span { color: var(--muted); font-size: var(--fs-xs); letter-spacing: 1px; }

/* 等级配色：国家级用沉金、省级焊锡铜、校级信号青、其他中性。
   金→铜→青 是含金量递减；和全站「铜=工艺记号 / 青=交互状态」不冲突，因为这里
   的颜色表达的是**等级**，属于内容语义。

   **只染徽章和统计数字，不染奖项名。** 写过一次 `.hn-lv-national strong`，结果
   它同时命中了 `.hn-row` 里的 `strong.hn-title`（特异度 0,2,0 压过
   `.hn-title` 的 0,1,0）—— 整份清单的标题按等级染成金/铜/青/灰四色，读起来像
   彩虹，扫视反而更慢。截图才看出来。 */
.hn-stat.hn-lv-national strong, .hn-lv-national > .hn-badge { color: var(--pad-gold); }
.hn-lv-national { border-color: rgba(232, 189, 109, .35); }
.hn-stat.hn-lv-provincial strong, .hn-lv-provincial > .hn-badge { color: var(--copper-2); }
.hn-lv-provincial { border-color: rgba(201, 138, 61, .3); }
.hn-stat.hn-lv-school strong, .hn-lv-school > .hn-badge { color: var(--accent-2); }
.hn-stat.hn-lv-other strong, .hn-lv-other > .hn-badge { color: var(--muted); }

.hn-sub {
    color: #fff; font-family: var(--font-display); font-weight: 900;
    font-size: var(--fs-3xl); margin: 8px 0 18px;
}

/* ---------- 证书条带（只有真的有照片时才渲染） ---------- */
.hn-cert-strip {
    display: grid; gap: 14px; margin-bottom: 40px;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
}
.hn-cert {
    margin: 0; overflow: hidden; border-radius: 12px;
    border: 1px solid var(--line-d); background: var(--bg-2);
}
/* `height: auto` 不能省。模板给 <img> 写了 width/height 属性（防止图片落地时把
   下面的内容顶一下），而那两个属性会变成实际的 used height —— 宽高**都确定**时
   `aspect-ratio` 直接被忽略。实测：不写 height:auto 时这张图渲染成 235×450
   （高是属性里那个 450），证书条带里一张图能占掉半屏。 */
.hn-cert img { width: 100%; height: auto; aspect-ratio: 4 / 3; object-fit: cover; display: block; }
.hn-cert figcaption {
    padding: 8px 12px; color: var(--muted); font-size: var(--fs-xs);
    border-top: 1px solid var(--line-d);
}

/* ---------- 按年分组的清单 ---------- */
.hn-years { display: flex; flex-direction: column; gap: 34px; }
.hn-year { display: grid; grid-template-columns: 96px minmax(0, 1fr); gap: 20px; align-items: start; }
/* 年份用丝印体大号数字，和首页分镜编号同一种语气 */
.hn-year-no {
    font: 900 var(--fs-5xl)/var(--lh-flat) var(--font-num); color: rgba(255, 255, 255, .14);
    letter-spacing: 1px; position: sticky; top: 100px;
}
.hn-year-list { display: flex; flex-direction: column; gap: 8px; min-width: 0; }

.hn-row {
    display: flex; align-items: baseline; gap: 14px;
    padding: 13px 16px; border-radius: 12px; color: var(--text);
    border: 1px solid var(--line-d); background: rgba(255, 255, 255, .018);
    transition: border-color var(--t-fast), background var(--t-fast);
}
.hn-row.is-link:hover {
    border-color: rgba(65, 216, 232, .45); background: rgba(13, 169, 205, .06);
    color: var(--text);
}
.hn-badge {
    flex: none; min-width: 52px;
    font: 700 var(--fs-2xs)/var(--lh-flat) var(--font-mono); letter-spacing: 1px;
}
.hn-year-tag {
    flex: none; font: 400 var(--fs-xs)/var(--lh-flat) var(--font-mono); color: var(--muted);
    font-variant-numeric: tabular-nums;
}
.hn-body { display: flex; flex-direction: column; gap: 4px; min-width: 0; flex: 1; }
/* 奖项名是这一行的主角，要和 13px 的赛事/获奖人小字分开一档。
   只升到 16 而不是 18：荣誉墙是一年十几行的密排清单，升两档整页会变高一屏。 */
.hn-title { color: #fff; font-size: var(--fs-lg); font-weight: 600; line-height: var(--lh-normal); }
.hn-meta { display: flex; flex-wrap: wrap; gap: 4px 14px; color: var(--muted); font-size: var(--fs-xs); }
.hn-contest::before { content: "赛事 "; opacity: .6; }
.hn-awardee::before { content: "获奖 "; opacity: .6; }
.hn-go { flex: none; color: var(--accent-2); font-size: var(--fs-xs); opacity: 0; transition: opacity var(--t-fast); }
.hn-row.is-link:hover .hn-go { opacity: 1; }

/* 白区（首页分镜 08）里的反色 */
.nf-light .hn-row { border-color: var(--line-l); background: rgba(14, 17, 22, .025); }
.nf-light .hn-row.is-link:hover { background: rgba(13, 169, 205, .07); border-color: rgba(13, 169, 205, .4); }
.nf-light .hn-title { color: var(--ink); }
.nf-light .hn-meta, .nf-light .hn-year-tag { color: var(--ink-muted); }
.nf-light .hn-lv-national > .hn-badge { color: #a97a1e; }
.nf-light .hn-lv-provincial > .hn-badge { color: #8d5f22; }
/* **不要用负 margin 往上贴。** 写过 `margin-top: -18px`，结果这行字被拉进 h2 的
   行盒里、和「拿过的奖」直接叠字（`.nf-display` 用的是 900 字重的展示字体，行高
   本来就大）。`.nf-works-head .nf-display` 已经把 margin-bottom 归零了，这里只要
   一点正的间距。截图才看得出来。 */
.nf-honors-sub { color: var(--ink-muted); font-size: var(--fs-md); margin-top: 8px; }

@media (max-width: 720px) {
    /* 窄屏换构图：年份从左侧一列变成清单上方的一行小标题，
       96px 的固定列在 390px 宽里会把奖项名挤成每行三四个字 */
    .hn-year { grid-template-columns: 1fr; gap: 10px; }
    .hn-year-no { position: static; font-size: var(--fs-3xl); }
    .hn-row { flex-wrap: wrap; gap: 6px 10px; padding: 12px 14px; }
    .hn-badge { min-width: 0; }
    .hn-go { display: none; }
    .hn-stat { padding: 10px 15px; }
    .hn-stat strong { font-size: var(--fs-3xl); }
}

/* ============================================================
   公开团队页（team）
   ------------------------------------------------------------
   前缀 `tm-`。一个人一张卡，按职位分组，分组标题带职位色（`--tm-accent`，
   从 Position.color 传进来，卡片和徽章共用同一个值）。

   **没有头像时不留空、也不放碎图**，渲染一枚丝印首字母牌（mono 字体 + 铜色描边
   + 方形焊盘的圆角）。缺头像在这个协会是常态，所以它得有一个设计过的样子；但它
   刻意**不是** `empty_board.html` 那个占位框 —— 那份是「一格版面」用的（虚线板框
   + 四角定位标 + 拍摄要求），套在 88px 的头像位上放不下、也不像一套设计。
   ============================================================ */
/* 页头横幅与空态走通用的 `.page-hero` / `.empty-state`。 */
.tm-grid {
    display: grid; gap: 16px;
    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
}

/* ---- 一个人一张卡 ----
   职位色（--tm-accent）只染左缘那条线和职位徽章。名字、介绍一律中性色 ——
   五张卡五种颜色会读成彩虹，而清单的可读性比色彩层次重要（同荣誉墙那条）。 */
.tm-card {
    display: flex; gap: 16px; align-items: flex-start;
    padding: 18px 18px 18px 16px; border-radius: 14px;
    border: 1px solid var(--line-d);
    border-left: 3px solid var(--tm-accent, var(--copper));
    background: rgba(255, 255, 255, .022);
    transition: border-color var(--t-fast), background var(--t-fast);
}
.tm-card:hover { background: rgba(255, 255, 255, .045); }
/* 职位徽章：**只有文字用职位色**，边框和底色一律中性。
   刻意不做成实心色块（驾驶舱那张表里是实心的，暗底上会太吵），也刻意不用
   color-mix() 按职位色算边框 —— 全站没有第二处用它，为一枚徽章引入一条新的
   浏览器依赖不值得，而且不支持时那两行会被整条丢掉、徽章变成裸文字。 */
.tm-pos {
    align-self: flex-start;
    padding: 3px 10px; border-radius: 999px;
    font: 700 var(--fs-2xs)/var(--lh-normal) var(--font-mono); letter-spacing: 1px;
    color: var(--tm-accent, var(--copper));
    border: 1px solid var(--line-d-strong);
    background: rgba(255, 255, 255, .04);
}
.tm-face { flex: none; width: 64px; height: 64px; }
.tm-avatar {
    /* 尺寸由 CSS 钉死 + object-fit，所以不写 width/height 属性也没有 CLS。
       顺带避开那条陷阱：`<img>` 上宽高都给了确定值时 CSS aspect-ratio 会失效。 */
    width: 64px; height: 64px; border-radius: 12px; object-fit: cover;
    border: 1px solid var(--line-d-strong); display: block;
}
.tm-initial {
    display: flex; align-items: center; justify-content: center;
    width: 64px; height: 64px; border-radius: 12px;
    font: 700 var(--fs-4xl)/var(--lh-flat) var(--font-mono); color: var(--copper-2);
    border: 1px dashed rgba(201, 138, 61, .55);
    background: rgba(201, 138, 61, .07);
}
.tm-body { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 5px; }
.tm-name { color: #fff; font-size: var(--fs-xl); font-weight: 600; line-height: var(--lh-snug); }
.tm-meta { display: flex; flex-wrap: wrap; gap: 4px 10px; color: var(--muted); font-size: var(--fs-xs); }
.tm-cohort { font-family: var(--font-mono); color: var(--accent-2); }
.tm-bio { color: var(--muted); font-size: var(--fs-sm); line-height: var(--lh-relaxed); margin: 3px 0 0; }
/* 本人没写介绍时退回职位的「这个职位做什么」。压暗一点，读者能看出这是职责说明
   而不是这个人自己的话。 */
.tm-bio-role { opacity: .8; }
.tm-medals {
    display: flex; flex-wrap: wrap; gap: 6px; list-style: none;
    margin: 6px 0 0; padding: 0;
}
.tm-medals li {
    display: inline-flex; align-items: center; gap: 4px;
    padding: 3px 9px; border-radius: 999px;
    border: 1px solid rgba(201, 138, 61, .35); background: rgba(201, 138, 61, .08);
    color: var(--copper-2); font-size: var(--fs-2xs);
}
.tm-medal-icon { font-size: var(--fs-xs); line-height: var(--lh-flat); }

.tm-foot {
    margin-top: 40px; padding-top: 20px; border-top: 1px solid var(--line-d);
    color: var(--muted); font-size: var(--fs-sm); line-height: var(--lh-prose);
}
.tm-foot strong { color: #fff; font-family: var(--font-mono); }

/* ---- 只有站务看得到的操作提示 ---- */
.tm-officer-note {
    margin-top: 34px; padding: 16px 20px; border-radius: 12px;
    border: 1px dashed var(--line-d-strong); background: rgba(201, 138, 61, .05);
}
.tm-officer-note > strong {
    display: block; font: 700 var(--fs-2xs)/var(--lh-flat) var(--font-mono); letter-spacing: 1.5px;
    color: var(--copper-2); margin-bottom: 8px;
}
.tm-officer-note p { color: var(--muted); font-size: var(--fs-sm); line-height: var(--lh-prose); margin: 0; }

@media (max-width: 720px) {
    /* 窄屏一栏。卡片保持横向（头像 + 文字），竖排会把每张卡拉到 200px 高，
       五个人就滚三屏 */
    .tm-grid { grid-template-columns: 1fr; }
    .tm-card { padding: 14px 14px 14px 12px; gap: 13px; }
    .tm-face, .tm-avatar, .tm-initial { width: 52px; height: 52px; }
    .tm-initial { font-size: var(--fs-3xl); }

}

/* 个人资料页里的「公开团队页」那一段。只有担任职位的人看得到（表单在 __init__
   里摘字段），所以它是一块**独立的、看得出来是另一件事**的区域 —— 公开自己的
   姓名和照片跟填学院不是一个量级的决定，不能混在同一串输入框里一路划过去。 */
.pf-team {
    margin: 26px 0 22px; padding: 18px 20px 6px; border-radius: 12px;
    border: 1px dashed var(--line-d-strong); background: rgba(13, 169, 205, .04);
}
.pf-team > legend {
    padding: 0 8px; font: 700 var(--fs-2xs)/var(--lh-flat) var(--font-mono); letter-spacing: 1.5px;
    color: var(--accent-2);
}
.pf-team-lede { color: var(--muted); font-size: var(--fs-sm); line-height: var(--lh-prose); margin: 0 0 14px; }
.pf-team-lede strong { color: #fff; }
