/* ============================================================
   队伍管理网站 · 基础样式
   规范见 docs/03-页面结构与信息架构.md §4：
     纯白底 / 1px 发丝分隔 / 无阴影 / 12px 圆角 / 绿色仅作点缀 / tabular-nums
   ============================================================ */

:root {
  --t1: #111111;
  --t2: #666666;
  --t3: #999999;
  /* ---------- iOS 材质（2026-10-17，ADR-0003）----------
     这一组取代了 2026-09 的「纯白底 + 无阴影」（ADR-0001，已 Superseded）。
     为什么改：iOS 的层次不是靠白底 + 细线，而是靠**分组背景 + 材质 + 一层极淡阴影**。
     ⚠️ 颜色只在这里出现一次。组件里写死 #fff / rgba(0,0,0,.1) 等于制造第二个真源。 */
  --bg: #F2F2F7;                      /* iOS systemGroupedBackground */
  --surface: #FFFFFF;                 /* 卡片 / 面板 / 表格容器 */
  /* 分隔线换成 iOS 的半透明黑：白底上是 #E8E8EA，灰底上会自动压深一点，
     比固定 #F0F0F0 更「跟着背景走」。 */
  --line: rgba(60, 60, 67, .10);
  --line-strong: rgba(60, 60, 67, .18);
  /* 两层阴影：1 是卡片的「贴地」，2 是浮层（弹层 / 抽屉）的「离地」。
     ⚠️ 只有「一整块内容」用，内部的每一行不许再加 —— 加满等于没有层次。 */
  --shadow-1: 0 1px 2px rgba(16, 24, 40, .04), 0 1px 1px rgba(16, 24, 40, .03);
  --shadow-2: 0 12px 32px rgba(16, 24, 40, .14), 0 2px 8px rgba(16, 24, 40, .06);
  --blur: saturate(180%) blur(20px);  /* iOS 顶栏那层玻璃 */
  --accent: #2EBE6E;
  --accent-text: #17A45B;
  --accent-soft: #E9FBF0;
  --fill: #F5F5F5;
  /* 比 --fill 深一档的灰，目前只给「当前页」用。
     深浅梯度：--fill(常态) < --line-strong(悬停) < --fill-strong(当前)。
     单独抽成令牌是因为阿凯可能还会说「再深一点 / 再浅一点」——
     那时只改这一行，不用去 CSS 里翻。 */
  --fill-strong: #D6D6D6;
  --danger: #D94A4A;
  --danger-soft: #FDF0F0;
  /* 战术板的草坪色。单独抽成令牌的理由：它是全站**唯一**一处大色块
     （2026-10-15 阿凯给的那张参考图就是一片通绿），
     将来他说「深一点 / 浅一点」时只改这一行，不用去 .tac-turf 里翻。
     ⚠️ 别拿 --accent 顶替：那是「点缀绿」（#2EBE6E，更亮更黄），
        铺满整块之后会刺眼；场地要的是压得住的那种绿。 */
  --field: #2E9E5B;
  --radius: 12px;
  --radius-sm: 8px;

  /* ---------- 动效令牌（2026-10-17，阿凯选「克制」档）----------
     时长三档，不许多写第四档：写多了一定会有人用 500ms 做 hover。
       dur-1 120ms —— 反馈（按下 / 悬停 / 展开箭头）
       dur-2 180ms —— 状态变化（tab 切换 / 底色 / 边框）
       dur-3 240ms —— 入场（页头 / 列表项 / 弹层）
     曲线三条：
       ease-out    —— 默认。cubic-bezier(.23,1,.32,1)：起手就快、收尾利落。
                      内置的 ease-out 太软，1px 位移看不出"回应"。
       ease-inout  —— 屏幕内移动（不是进出）。起止都不突然。
       ease-drawer —— 手机上贴底弹层专用（iOS 抽屉曲线）。
      WARNING 2026-10-17 第二版：原来那条带过冲的 --ease-spring 已删。
      依据 apple-design：「过冲只给带惯量的手势」，普通弹层淡入还弹一下是错的。 */
  --dur-1: 120ms;
  --dur-2: 180ms;
  --dur-3: 240ms;
  --ease-out: cubic-bezier(.23, 1, .32, 1);
  --ease-inout: cubic-bezier(.77, 0, .175, 1);
  /* iOS 抽屉曲线（Ionic 出处）。手机上贴底弹层用它 ——
     起手就是快、收尾极慢，和 iOS 原生 sheet 一个手感。 */
  --ease-drawer: cubic-bezier(.32, .72, 0, 1);
}

* { margin: 0; padding: 0; box-sizing: border-box; }
html { -webkit-font-smoothing: antialiased; }
body {
  font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Microsoft YaHei", sans-serif;
  background: #fff;
  color: var(--t1);
  font-size: 15px;
  line-height: 1.7;
}
a { color: inherit; }
.num { font-variant-numeric: tabular-nums; }

/* ---------- 平台层：让网页在手机上像 App（2026-10-17）----------
   依据 emilkowalski/skills 的 mobile-native：一个网页和「装好的 App」之间，
   差的往往不是设计，而是这几行平台层的声明。四条，每条一句话理由：

   ① 点一下那层灰蓝闪 —— iOS / Android 给所有可点元素画的，是"这是网页"
      最响的信号，而且会盖掉我们自己做的按下反馈。
   ② 触屏没有 hover，浏览器会伪造一个：第一次点击后 hover 状态**粘住不放**。
      所以全站 :hover 现在都包在 @media (hover: hover) and (pointer: fine) 里
      （见文件里 64 处 @media，2026-10-17 统一加的）。
   ③ 双击缩放会让每次点击等 300ms —— touch-action: manipulation 告诉浏览器
      "这块不会有双击缩放"，click 立刻触发。
   ④ 长按按钮会选中文字并弹"拷贝/分享"。文字是**控件**的不该可选，
      文字是**内容**的必须可选 —— 所以只点这几类，绝不给 body。 */
html {
  -webkit-tap-highlight-color: transparent;
  -webkit-text-size-adjust: 100%;   /* 横屏不许自作主张放大字号 */
}
button,
[role="button"],
.btn-sm,
.nav-btn,
.lp-btn,
.adm-nav a,
.adm-tabs a,
.adm-sub-bar a,
.modal-btn,
.modal-x,
.user-menu-btn,
.tsk {
  touch-action: manipulation;
  -webkit-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;   /* 长按不弹"拷贝/分享" */
}

/* 输入框在触屏上必须 ≥16px：iOS 聚焦到小于 16px 的输入框会**把整页放大**，
   而且失焦后不缩回 —— 用户从此在一个被裁掉一截的版面里填表。
   正确的修法是放大字号，不是 maximum-scale=1（那是无障碍失败）。 */
@media (pointer: coarse) {
  input, textarea, select { font-size: 16px; }
}
.wrap { max-width: 1080px; margin: 0 auto; padding: 0 24px; }

/* ---------- 导航 ---------- */
nav { border-bottom: 1px solid var(--line); background: #fff; }
.nav-inner {
  max-width: 1080px; margin: 0 auto; padding: 0 24px;
  height: 64px; display: flex; align-items: center; gap: 32px;
}
.brand { display: flex; align-items: center; gap: 10px; min-width: 0; text-decoration: none; }
.brand-logo {
  width: 28px; height: 28px; border-radius: 8px; background: var(--fill);
  display: flex; align-items: center; justify-content: center;
  font-size: 13px; font-weight: 600; color: var(--t2); overflow: hidden;
  flex-shrink: 0;
}
.brand-logo img { width: 100%; height: 100%; object-fit: cover; }
/* 队名可以很长（「湖南农业大学飞盘校队」），顶栏挤的时候让它先截断，
   而不是把右边的按钮顶出屏幕。overflow:hidden 顺带把 min-width 变成 0，
   否则 flex 项的默认 min-width:auto 会拒绝收缩。 */
.brand-name {
  font-size: 15px; font-weight: 600; letter-spacing: .2px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.nav-links { display: flex; gap: 28px; margin-left: auto; flex-shrink: 0; }
/* nowrap 是必要的：不加的话，放不下时 flex 会把每个链接压窄，
   中文没有空格可断，就变成一个字一行（实测 753px 就是这样）。 */
.nav-links a {
  font-size: 14px; color: var(--t2); text-decoration: none;
  padding: 4px 0; white-space: nowrap;
}
.nav-links a.active { color: var(--t1); font-weight: 500; }
/* 注意：靠右由 .nav-links 的 margin-left:auto 负责。这里不能再写 auto，
   否则两个 auto 会平分剩余空间，导航项被推到中间去。 */
.nav-right { display: flex; align-items: center; gap: 12px; flex-shrink: 0; margin-left: 28px; }
/* 这一页没有队伍公开导航时要换个「靠右」机制：
   原本是靠 .nav-links 的 margin-left:auto 把 .nav-right 顶到最右；
   管理台页不渲染 .nav-links 之后，那排按钮会紧贴品牌名 —— 改由它自己顶。
   （窄屏那一档本来就写了 .nav-right{margin-left:auto}，所以这里只管宽屏。） */
.nav-nolinks .nav-right { margin-left: auto; }
.nav-right .who { font-size: 13px; color: var(--t2); }
.nav-btn {
  font-size: 13px; color: var(--t1); text-decoration: none;
  border: 1px solid var(--line-strong); border-radius: 999px;
  padding: 6px 16px; background: none; cursor: pointer; font-family: inherit;
}
@media (hover: hover) and (pointer: fine) {

.nav-btn:hover { border-color: var(--t3); }
}
/* ★ 当前所在的那个台（2026-10-11 第四轮）。
   阿凯看完侧栏的灰底标记，转头看顶栏说「这里也没改啊」——
   因为「我在哪」这件事顶栏从来没表达过，这一排读起来就是个老样子。
   这里刻意**复用侧栏那一套灰**（--fill-strong），不另发明一种「选中色」：
   「我在哪」在全站只该有一个视觉语言，用户不用学第二遍。
   ⚠️ 这条必须写在 `.nav-btn:hover` **后面** —— 两者特异度相同（都 0,2,0），
     靠源序胜出；挪到前面，鼠标一悬停就会把灰底冲掉。
     （`—hover` 只改 border-color，所以这里不必再写 `.nav-btn.on:hover`。） */
.nav-btn.on { background: var(--fill-strong); border-color: var(--fill-strong); }
/* 顶栏右侧两组之间的发丝竖线（2026-10-11）：
   左＝「去别的台」（我的 / 队务 / 站长），右＝「我是谁」（头像菜单）。
   四个一模一样的胶囊排在一起表达不了「前三个是一类」，靠这条线分组。
   ⚠️ .nav-right 的 gap 是 12px，所以它两侧各有 12px 的呼吸，不用再写 margin。 */
.nav-div { width: 1px; height: 18px; background: var(--line-strong); flex: 0 0 auto; }
.nav-link-btn {
  font-size: 13px; color: var(--t3); background: none; border: 0;
  cursor: pointer; font-family: inherit; padding: 4px 0;
}

/* ---------- 队伍页：导航项移到左侧，做成悬浮竖排 ----------
   起因：队伍页有 8 个导航项（主页/队员/荣誉/队史/精神分/背号/赛季/加入我们），
   全挤在顶栏，左边是品牌名右边是用户菜单 —— 最挤的偏偏是中间这一排。
   而宽屏下内容区（1080px 居中）左侧本来就空着一大片，把它挪进去，顶栏立刻松开。

   排列：项与项从上往下堆，每项文字本身仍是横排（左→右）。
   位置：fixed 悬浮 —— 滚动时一直跟在手边，且**不贴屏幕边**，留出呼吸。

   为什么有 1360px 门槛：侧栏要贴在内容区左边（间隔 44px）且不压到内容，
   需要屏幕 ≥ 1080 + 2×(96 侧栏 + 44 间隔 + 20 边距) = 1360。
   更窄的屏没有这块空白可用，硬塞会把内容挤扁 —— 所以退回顶部横排。 */
@media (min-width: 1360px) {
  .nav-side .nav-links {
    position: fixed;
    top: 92px;
    /* 贴着内容区左边缘再往左 140px（= 96 宽度 + 44 间隔）；屏幕再窄也别小于 20px */
    left: max(20px, calc((100vw - 1080px) / 2 - 140px));
    width: 96px;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    margin-left: 0;
    padding: 10px;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: #fff;
    z-index: 20;
  }
  .nav-side .nav-links a {
    font-size: 13px; padding: 8px 10px; border-radius: var(--radius-sm);
  }
  .nav-side .nav-links a:hover { background: var(--fill); color: var(--t1); }
  .nav-side .nav-links a.active { background: var(--fill); color: var(--t1); font-weight: 500; }
  /* 最后一项是「加入我们」—— 它是「行动」，前面几项是「浏览」，用一条发丝线分开 */
  .nav-side .nav-links a:last-child {
    margin-top: 9px; border-top: 1px solid var(--line);
    padding-top: 13px; border-radius: var(--radius-sm);
  }
  /* 导航项脱离文档流后，顶栏靠右改由 nav-right 自己顶
     （原来是靠 .nav-links 的 margin-left:auto 把它推到右边的） */
  .nav-side .nav-right { margin-left: auto; }
}

/* ---------- 顶栏放不下时的折行（1200px 以下） ----------
   一行装得下 8 项需要约 1030px，而内容区到 1080px 就封顶了。
   所以「侧栏用不上、一行又装不下」的区间是真实存在的：
   753px 这种平板竖屏宽度，顶栏一样塞不进 8 项。
   这里的处理不是藏起来，而是折到第二行 —— 放不下就横向滑动。

   ⚠️ 为什么断点是 1200 而不是 720：
      原来只在 720 以下才处理，于是 720~1200 之间既没有侧栏、
      也没折行，导航被 flex 压窄，中文逐字换行成竖排。 */
@media (max-width: 1200px) {
  .nav-inner { height: auto; flex-wrap: wrap; gap: 0; }
  .brand, .nav-right { height: 52px; }
  .nav-right { margin-left: auto; }
  .nav-links {
    order: 9;                     /* 排到第一行（品牌 + 按钮）之后 */
    width: 100%;
    /* ★ 这两条缺一不可，否则下面的 overflow-x 形同虚设 ——
       .nav-links 是 flex item，默认 min-width:auto（不肯收缩到内容以下），
       而基础样式又写了 flex-shrink:0。两条叠加的结果：它被 8 个不换行的
       链接撑到 449px，自己顶出 390px 的视口（页面级横向溢出），
       而它**内部**永远不溢出 → overflow-x:auto 一次都没产生过滚动。
       实测（390px）：sw=449 cw=449 maxScroll=0。
       改成可收缩 + 允许窄于内容之后，它收缩回父容器宽（342px），
       内容这才真正溢出，横滑与右边缘渐隐才有意义。 */
    flex-shrink: 1;
    min-width: 0;
    margin-left: 0;
    gap: 24px;
    padding: 9px 0 11px;
    border-top: 1px solid var(--line);
    overflow-x: auto;             /* 放不下就横着滑，而不是折成两行 */
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .nav-links::-webkit-scrollbar { display: none; }
  /* ★ 右边缘渐隐：8 项在 390px 下只露到「赛季」，而滚动条是隐藏的 ——
     「能滑」不等于「知道能滑」，最后一项「加入我们」被切在屏外却毫无提示。
     加一条渐隐，被切断的那一项看起来就是「渐出」而不是「被裁掉」。
     管理台侧栏 (.adm-side::after) 早就这么做了，这里补上、两边一致。

     ⚠️ 为什么用 mask 而不是照抄 .adm-side 的 ::after 伪元素：
       .adm-side 的伪元素挂在**不滚动**的父元素上，所以固定不动；
       而这里的滚动容器就是 .nav-links 自己 —— 伪元素会跟着内容一起滚，
       滑到右边时渐隐也跟着跑到右边去了，等于白做。
       mask 是元素自身坐标系上的遮罩，不随内容滚动，正好绕开这个死结。
     区间取 24px：窄到不干扰刚好放得下 8 项的那档宽度（约 1030px+），
     又足够让「被切掉」这件事看得出来。 */
  .nav-links {
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent);
    mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent);
  }
}

/* ---------- Hero ---------- */
.hero { padding: 72px 0 56px; border-bottom: 1px solid var(--line); }
.hero-top { display: flex; align-items: center; gap: 24px; }
.hero-logo {
  width: 88px; height: 88px; border-radius: 20px; background: var(--fill);
  display: flex; align-items: center; justify-content: center;
  font-size: 34px; font-weight: 600; color: var(--t2); flex-shrink: 0; overflow: hidden;
}
.hero-logo img { width: 100%; height: 100%; object-fit: cover; }
.hero-name { font-size: 32px; font-weight: 600; letter-spacing: .5px; line-height: 1.25; }
.hero-slogan { font-size: 15px; color: var(--t2); margin-top: 10px; }
.hero-founding { font-size: 13px; color: var(--t3); margin-top: 8px; }
.hero-founding b { color: var(--accent-text); font-weight: 500; }

.stats { display: flex; margin-top: 40px; border-top: 1px solid var(--line); padding-top: 28px; }
.stat { flex: 1; }
.stat + .stat { border-left: 1px solid var(--line); padding-left: 28px; }
.stat-num { font-size: 24px; font-weight: 600; letter-spacing: -.3px; }
.stat-num em { font-style: normal; font-size: 13px; font-weight: 400; color: var(--t3); margin-left: 3px; }
.stat-label { font-size: 12px; color: var(--t3); margin-top: 2px; }

/* ---------- 区块 ---------- */
section { padding: 56px 0; border-bottom: 1px solid var(--line); }
section.last { border-bottom: none; }
.sec-head { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 28px; }
.sec-title { font-size: 17px; font-weight: 600; }
.sec-more { font-size: 13px; color: var(--t3); text-decoration: none; }
@media (hover: hover) and (pointer: fine) {

.sec-more:hover { color: var(--t2); }
}

/* ---------- 队长卡 ---------- */
.captains { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.cap-card {
  border: 1px solid var(--line); border-radius: var(--radius); padding: 20px;
  background: var(--surface);   /* 灰底上必须给表面，否则队长卡是一块空地 + 一条细线 */
  display: flex; align-items: center; gap: 14px;
}
.cap-avatar {
  width: 44px; height: 44px; border-radius: 10px; background: var(--fill);
  display: flex; align-items: center; justify-content: center;
  font-size: 17px; font-weight: 600; color: var(--t2); flex-shrink: 0; overflow: hidden;
}
.cap-avatar img { width: 100%; height: 100%; object-fit: cover; }
.cap-role { font-size: 12px; color: var(--t3); }
.cap-role.spirit { color: var(--accent-text); }
.cap-name { font-size: 15px; font-weight: 600; margin-top: 1px; }
.cap-term { font-size: 12px; color: var(--t3); margin-top: 3px; }

/* ---------- 列表 ---------- */
.list { border-top: 1px solid var(--line); }
.row { display: flex; align-items: center; gap: 16px; padding: 18px 0; border-bottom: 1px solid var(--line); }
.row-main { flex: 1; min-width: 0; }
.row-title { font-size: 15px; font-weight: 500; letter-spacing: .1px; }
.row-meta { font-size: 13px; color: var(--t3); margin-top: 3px; }
.row-right { font-size: 13px; color: var(--t3); flex-shrink: 0; }
.tag {
  display: inline-block; font-size: 12px; line-height: 1; padding: 4px 8px;
  border-radius: 6px; background: var(--accent-soft); color: var(--accent-text);
  margin-left: 8px; vertical-align: 1px;
}
.tag.gray { background: var(--fill); color: var(--t2); }

/* ---------- 时间轴 ---------- */
.timeline { border-top: 1px solid var(--line); }
.tl-item { display: flex; gap: 28px; padding: 20px 0; border-bottom: 1px solid var(--line); }
.tl-date { width: 92px; flex-shrink: 0; font-size: 13px; color: var(--t3); padding-top: 2px; }
.tl-body { flex: 1; min-width: 0; }
.tl-title { font-size: 15px; font-weight: 500; }
.tl-desc { font-size: 13px; color: var(--t2); margin-top: 4px; }
.tl-people { font-size: 12px; color: var(--t3); margin-top: 5px; }

/* ---------- 相册 ---------- */
.gal { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; }
.gal-item {
  aspect-ratio: 4 / 3; border-radius: var(--radius-sm); background: var(--fill);
  display: flex; align-items: center; justify-content: center;
  font-size: 12px; color: var(--t3); overflow: hidden; text-align: center; padding: 8px;
}
.gal-item img { width: 100%; height: 100%; object-fit: cover; }

/* ---------- 比赛 ---------- */
.match-row { display: flex; align-items: center; gap: 16px; padding: 18px 0; border-bottom: 1px solid var(--line); }
.match-name { font-size: 15px; font-weight: 500; }
.match-sub { font-size: 13px; color: var(--t3); margin-top: 3px; }
.match-score { font-size: 16px; font-weight: 600; letter-spacing: .5px; flex-shrink: 0; }
.match-res { font-size: 12px; flex-shrink: 0; width: 28px; text-align: right; }
.match-res.win { color: var(--accent-text); }
.match-res.lose { color: var(--t3); }
.match-res.draw { color: var(--t3); }
.spirit { font-size: 12px; color: var(--t3); margin-top: 3px; }

/* ---------- 队员名册 ---------- */
/* 只加这两个：其余全复用 .list / .row / .cap-avatar / .tag，
   避免为一个页面堆一套平行样式（那会让 app.css 慢慢长出两套列表） */
.mem-no { font-size: 15px; font-weight: 600; letter-spacing: .5px; flex-shrink: 0; }
.mem-off .cap-avatar { opacity: .55; }

/* ---------- 表单（登录 / 注册） ---------- */
.auth { max-width: 380px; margin: 0 auto; padding: 72px 24px 96px; }
.auth h1 { font-size: 24px; font-weight: 600; letter-spacing: .3px; }
.auth .sub { font-size: 14px; color: var(--t3); margin-top: 8px; margin-bottom: 36px; }
.field { margin-bottom: 20px; }
.field label { display: block; font-size: 13px; color: var(--t2); margin-bottom: 8px; }
.field input {
  width: 100%; font-family: inherit; font-size: 15px; color: var(--t1);
  padding: 11px 14px; border: 1px solid var(--line-strong); border-radius: 10px;
  background: #fff; outline: none; transition: border-color .15s;
}
.field input:focus { border-color: var(--t2); }
.field .hint { font-size: 12px; color: var(--t3); margin-top: 6px; }
.btn-primary {
  width: 100%; font-family: inherit; font-size: 15px; font-weight: 500;
  color: #fff; background: var(--t1); border: 0; border-radius: 10px;
  padding: 13px 0; cursor: pointer; margin-top: 8px;
}
@media (hover: hover) and (pointer: fine) {

.btn-primary:hover { background: #000; }
}
.auth-foot { font-size: 13px; color: var(--t3); text-align: center; margin-top: 24px; }
.auth-foot a { color: var(--t1); text-decoration: none; border-bottom: 1px solid var(--line-strong); }
.alert {
  font-size: 13px; padding: 11px 14px; border-radius: 10px; margin-bottom: 20px;
  background: var(--danger-soft); color: var(--danger);
}
/* 成功/提示态：同一套 alert，只换色。分两个类名而不是内联样式，
   是为了让「红=出错、绿=成功」在全站统一。 */
.alert.ok { background: var(--accent-soft); color: var(--accent-text); }

/* ---------- 找回密码：下一步指引 ---------- */
.fg-steps { margin: 8px 0 4px; border-top: 1px solid var(--line); }
.fg-step { display: flex; gap: 14px; padding: 15px 0; border-bottom: 1px solid var(--line); }
.fg-step-n {
  width: 22px; height: 22px; flex-shrink: 0; margin-top: 2px;
  border-radius: 50%; background: var(--fill); color: var(--t2);
  display: flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: 600;
}
.fg-step-b { display: block; flex: 1; min-width: 0; }
.fg-step-t { display: block; font-size: 14px; font-weight: 500; }
.fg-step-d { display: block; font-size: 13px; color: var(--t3); margin-top: 3px; line-height: 1.6; }

/* ---------- 账号设置 ----------
   这一页整页都窄（一列字段 + 两个按钮），如果沿用 1080px 的 .wrap，
   内容会全贴在左边、右边空一大片 —— 阿凯的原话是「整个的操作页面偏左了」。
   所以给它一个自己的窄容器：1080 → 560（仍是 margin:0 auto，真居中）。 */
.set-wrap { max-width: 560px; }
.set-hero { padding: 48px 0 28px; border-bottom: 1px solid var(--line); }
/* 容器本身已经收窄，字段再收一次就变成「窄框里更窄的框」，反而摇晃 */
.set-form { max-width: none; }

/* ---------- 二级页面（荣誉墙 / 队史全量） ---------- */
.page-head { padding: 44px 0 26px; border-bottom: 1px solid var(--line); }
.page-title { font-size: 24px; font-weight: 600; letter-spacing: -.2px; }
.page-sub { font-size: 14px; color: var(--t3); margin-top: 10px; }
.page-sub b { color: var(--t2); font-weight: 600; }
/* 年份分组：年份做小标题，条目仍复用 .list / .row 的发丝分隔体系 */
.year-group { margin-top: 40px; }
.year-head {
  font-size: 13px; font-weight: 600; color: var(--t3);
  letter-spacing: 1.2px; margin-bottom: 10px;
}
.page-foot { margin-top: 48px; padding-bottom: 72px; }
.back-link { font-size: 13px; color: var(--t3); text-decoration: none; }
@media (hover: hover) and (pointer: fine) {

.back-link:hover { color: var(--t2); }
}

/* ---------- 空态 / 错误页 ---------- */
.empty { padding: 120px 24px; text-align: center; }
.empty h1 { font-size: 20px; font-weight: 600; }
.empty p { font-size: 14px; color: var(--t3); margin-top: 10px; }

/* ---------- 页脚 ---------- */
footer { padding: 40px 0 56px; }
footer p { font-size: 12px; color: var(--t3); }

/* ---------- 页脚第二行：转化出口 + 转发入口 ----------
   只在「队伍公开页 + 看客不是本队成员」时出现（判据见 views/layout/foot.ejs）。
   刻意做得很轻：这一页的主角是那支队伍，我们是顺手指个路。
   ⚠️ 用 flex-start 而不是 center —— 上面那行队名是左对齐的，居中会对不齐。 */
.foot-act { margin-top: 10px; display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
.foot-act a { color: var(--t3); border-bottom: 1px solid transparent; }
@media (hover: hover) and (pointer: fine) {

.foot-act a:hover { color: var(--t2); border-bottom-color: var(--line-strong); }
}
.foot-sep { color: var(--line-strong); }

/* 「复制本页链接」是个 <button>（点了不该跳转，所以不能用 <a>）。
   要把它压成和旁边链接一模一样的样子：浏览器默认给 button 的
   字体、内边距、边框、背景全都得显式清掉，否则会凸出来一块。 */
.foot-share {
  font: inherit; font-size: 12px; line-height: inherit;
  color: var(--t3); background: none; border: 0; padding: 0;
  cursor: pointer; border-bottom: 1px solid transparent;
}
@media (hover: hover) and (pointer: fine) {

.foot-share:hover { color: var(--t2); border-bottom-color: var(--line-strong); }
}
.foot-share.is-ok { color: var(--accent-text); border-bottom-color: transparent; }

/* ============================================================
   队务管理台
   布局：左侧固定导航 + 右侧内容，桌面优先（队长在电脑上批量录入）
   视觉：沿用公开层的白底/发丝线/无阴影，只是密度更高
   ============================================================ */
/* 100vh 在手机上是**最大**视口（地址栏收起时的高度），首屏一定会被地址栏顶出去。
   先给 vh 兜底，再给 dvh —— 支持的浏览器跟着可见区域走。 */
.adm { display: flex; gap: 0; max-width: 1200px; margin: 0 auto; min-height: calc(100vh - 64px); }
@supports (height: 100dvh) {
  .adm { min-height: calc(100dvh - 64px); }
}

/* ---------- 侧栏 ---------- */
.adm-side {
  width: 168px; flex-shrink: 0; padding: 28px 0 40px;
  border-right: 1px solid var(--line);
}
.adm-side-head { padding: 0 20px 20px; }
.adm-back { font-size: 12px; color: var(--t3); text-decoration: none; }
@media (hover: hover) and (pointer: fine) {

.adm-back:hover { color: var(--t2); }
}
.adm-nav { display: flex; flex-direction: column; gap: 3px; padding: 0 10px; }

/* ---------- 侧栏：一级行 + 折叠模块（2026-10-11 重做）----------
   结构：成员（直连）· 赛训 · 队籍 · 过往 · 队伍资料（直连）

   折叠用原生 <details>/<summary>，不依赖 JS —— 无 JS 也照样能开合
   （与队史页「按年份折叠」同一条路子，见 _nav.ejs 注释）。

   ★ 2026-10-11 第二轮（阿凯原话：「太不明显了，字太小、没有背景」、
     「凭什么队伍资料的字体可以这么大、粗一点，其他的字却这么小」）——
     旧规则把**层级和字号写反了**：模块标题是 12px / 最淡的灰（--t3），
     二级项反而是 14px / 较深的灰（--t2）。于是「队伍资料」（二级）
     比它上面三个模块名还显眼。
     现在：**一级行（模块标题 + 直连项）共用同一套排版**，
     「看得出来是个框」靠淡底 + 圆角，不再靠字号差。
     ⚠️ 一级行和二级项的区别只剩「缩进 + 深浅」，别再往一级行上加字号。 */
.adm-1, .adm-grp-h {
  display: flex; align-items: center; gap: 6px;
  font-size: 13px; font-weight: 500; color: var(--t1);
  padding: 8px 10px; border-radius: var(--radius-sm);
  background: var(--fill);
  text-decoration: none; cursor: pointer; user-select: none;
}
@media (hover: hover) and (pointer: fine) {

.adm-1:hover, .adm-grp-h:hover { background: var(--line-strong); }
}
/* 当前页：**淡灰底 + 深色加粗字**，不用实心反白。侧栏只有 5 个一级项，
   一眼要能看出「我在哪」。
   ★ 2026-10-11 第三轮：原来是 `background: var(--t1)`（近黑）+ 白字。
     阿凯看到线上截图问「为什么『成员』单独这个按钮是黑色的？就是我鼠标点
     哪里，哪里才会显现深色吧？黑色太黑了，搞成灰色好不好」——
     近黑实心会被读成「按下去 / 鼠标正按着」，而不是「你正停在这一页」。
     侧栏其余行本来就是 #F5F5F5 的淡底，所以当前页**比它们深一档**就够，
     不必反白。层级仍靠 --fill-strong 与淡底区分。
   ⚠️ 保持「单选择器」写法：`.adm-1.on` 与上面 `.adm-1:hover` 的特异度相同
     （都是 0,2,0），靠**源序在后**胜出。所以这两条必须留在 hover 那条下面，
     且中间不要再插 .adm-1 的选择器。 */
.adm-1.on { background: var(--fill-strong); color: var(--t1); font-weight: 600; }
@media (hover: hover) and (pointer: fine) {

/* 悬停到当前页上时保持选中态，别被上面 :hover 那条冲淡 */
.adm-1.on:hover { background: var(--fill-strong); }
}

/* 折叠标志：**已去掉**（2026-10-12 阿凯点名）。
   ★ 他的原话：「『赛训』『队级』和『过往』这三个地方有个小箭头，把它们都去掉。
     不然搞得像区别对待一样，不好看，而且上面还顶了一个『成员』」——
   他说得对：那个 `›` 只挂在**可折叠模块**上，而「成员」「队伍资料」两个直连项没有，
   于是五个一级行排在一起，三个带头、两个不带头，左边缘参差不齐
   （前两个字被箭头整体右推），看起来就是「这三个跟别人不一样」。
   现在五个一级行完全一致：同一套排版、同一个左起点、都没有箭头。

   ⚠️ 代价要知道（上一版写这条注释时的顾虑仍然成立，只是被这次判断推翻了）：
     模块名与直连项现在**长得一样**，用户不再能一眼看出「赛训」点开是展开、
     「成员」点开是跳转。**有意识地接受它**，因为：
       · 侧栏只有 5 行，点一下就知道，试错成本极低；
       · 而「特别对待」带来的观感损失是**每一次打开侧栏都在付**的。
     若将来真出现「用户点了模块名、以为会跳转」的反馈，正确的补法是把
     **全部五个一级行**都给一个统一的行尾标记（而不是只给三个加回箭头）。 */
/* 原生 marker 在不同浏览器里形状与位置都不一样，自己画/自己藏才站得住 */
.adm-grp-h::-webkit-details-marker { display: none; }
.adm-grp-h::marker { content: ''; }

/* 待办红点：折叠状态下也必须看得见「有事要办」 */
.adm-dot {
  width: 5px; height: 5px; border-radius: 50%;
  background: #E24B4A; display: inline-block; flex: 0 0 auto;
}

/* 二级项：缩进 + 更淡，跟一级行的「块」区分开 */
.adm-grp-b { display: flex; flex-direction: column; gap: 1px; padding: 3px 0 4px 14px; }
.adm-grp-b a {
  font-size: 13px; color: var(--t2); text-decoration: none;
  padding: 6px 9px; border-radius: var(--radius-sm);
}
@media (hover: hover) and (pointer: fine) {

.adm-grp-b a:hover { background: var(--fill); color: var(--t1); }
}
.adm-grp-b a.on { background: var(--line-strong); color: var(--t1); font-weight: 500; }

/* 一级标签里的链接（2026-10-12 第三轮）。
   ★ 桌面：它只是那个「块」里的文字 —— 指针事件关掉，所以点上去仍然是
     <summary> 在开合。加了它之后**桌面行为与从前完全一致**，一个字没变。
   ★ 窄屏：由「管理台移动端」那个媒体查询把它打开 —— 那时它才是真正的入口。 */
.adm-grp-t { color: inherit; text-decoration: none; pointer-events: none; }

/* 窄屏专用的「当前模块子项条」（结构在 views/team/_nav.ejs 里）。
   桌面一律不显示。
   ⚠️ 它虽然 display:none，但**仍在 DOM 里** —— 用脚本「数页面上有几个子项链接」
      时会把它一起数进去，判「侧栏里有没有某项」时要记得这一点。 */
.adm-sub-bar { display: none; }

/* 设置类一级项（队伍资料）与上面三个内容模块之间的发丝线。
   它的性质是「设置」，不该跟内容模块平起平坐。
   手机上 .adm-nav 变横排，这条竖线没有意义 —— 由下面的媒体查询藏掉。 */
.adm-sep { height: 1px; background: var(--line); margin: 9px 0 6px; }

/* ---------- 主体 ---------- */
.adm-main { flex: 1; min-width: 0; padding: 28px 32px 80px; }

/* ---------- 队员视角的训练详情：单栏轻量壳（2026-10-12）----------
   这一页是唯一一条会被发进微信群的链接，点进来的人只做两件事：
   看清这次练什么、练完写一段。所以侧栏不渲染（模板里条件 include），
   布局也从「侧栏 + 主体」的两栏收成单栏。

   【为什么在这里改 display，而不是删掉 flex 的容器】
     .adm 是 display:flex。少了侧栏之后，主体仍是唯一的 flex item、
     会照旧撑满 —— 单看没错，但 max-width 就起不到「收窄成阅读宽度」的作用：
     flex item 的 width 由 flex-basis 决定，max-width 管的是它自己的上限，
     而这里没有别的 item 跟它抢，所以两栏的骨架会原样留着。
     改成 block 之后，「单栏、居中、780px」这三件事才真的成立。

   【为什么 780 而不是 860 / 660】
     正文里有战术板缩略图（110:40 的扁长条）和复盘文字。太宽了文字行会拉长到
     难读（一行 60+ 字），太窄了战术板会被压得看不清站位。780 是两者都还行的那档。

   ⚠️ 这里**不覆盖 .adm-main 的 padding** —— 让它继续走上面那条，
     以及窄屏媒体查询里那条 22px/18px。两边各写一份 padding，
     改一处忘一处就是手机上多出几像素的错位（本项目为此栽过，见 AGENTS.md）。 */
.adm-solo { display: block; max-width: 780px; }

/* 回训练列表。从微信点进来的人没有浏览器历史，「返回」键对他不起作用，
   所以这个出口是必要的，不是装饰。
   ⚠️ 不复用 .adm-back（那是侧栏头部的返回，语义与位置都不同）——
      本项目记过「借来的类名会跟着别人的改版一起消失」。 */
.adm-solo-bar { margin-bottom: 20px; }
.adm-solo-bar a { font-size: 13px; color: var(--t3); text-decoration: none; }
@media (hover: hover) and (pointer: fine) {

.adm-solo-bar a:hover { color: var(--t1); }
}

.adm-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 20px; margin-bottom: 26px;
}
.adm-title { font-size: 21px; font-weight: 600; letter-spacing: -.2px; }
.adm-sub { font-size: 13px; color: var(--t3); margin-top: 6px; }

/* ---------- 管理台上的「队伍」页（2026-10-12 新增） ----------
   这一页现在 = **队伍内容**（与公开队伍页共用 views/public/_team-show.ejs 那一份标记）
   + **管理块**（只有队长/教练看得到）。
   `.t-show.in-adm` 只做「在管理台外壳里」的那几处收紧，公开队伍页保持原样。

   为什么必须收紧：公开页的 section 自带 56px 上下留白，而 .adm-main 还有
   28px 内边距，两者叠加会在首屏堆出一大块空白 —— 而这一页第一屏要让「待办」
   和队伍门面同时在线。 */
.t-show.in-adm .hero { padding: 0 0 24px; }
.t-show.in-adm .hero .wrap { max-width: none; padding: 0; }
.t-show.in-adm .hero-logo { width: 64px; height: 64px; border-radius: 16px; font-size: 26px; }
.t-show.in-adm .hero-name { font-size: 26px; }
.t-show.in-adm .hero-slogan { margin-top: 8px; }
.t-show.in-adm .stats { margin-top: 24px; padding-top: 20px; }
.t-show.in-adm section { padding: 28px 0; }
.t-show.in-adm section .wrap { max-width: none; padding: 0; }
.t-show.in-adm .sec-head { margin-bottom: 18px; }
/* 管理台内容区比公开页窄（旁边有 168px 侧栏），四列相册会挤成邮票 */
.t-show.in-adm .gal { grid-template-columns: repeat(auto-fill, minmax(124px, 1fr)); }

/* 「管理」与队伍内容之间的分界标题。有它，队长才知道下面几块是**他专属**的，
   而不是所有人都能看到的队伍内容。 */
.adm-sec-h {
  display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap;
  margin: 32px 0 16px; padding-top: 22px; border-top: 1px solid var(--line);
}
.adm-sec-h-t { font-size: 17px; font-weight: 600; }
.adm-sec-h-n { font-size: 12px; color: var(--t3); }

/* 非管理层（普通队员 / 校友）在页面底部的一句话说明。
   ★ 不给这句话，队员会以为系统对他缺功能了 —— 而其实这正是他该看到的样子。 */
.adm-readonly {
  margin-top: 28px; padding-top: 20px; border-top: 1px solid var(--line);
  font-size: 13px; color: var(--t3); line-height: 1.8;
}

/* ---------- 站长跨队提示条（2026-10-17）----------
   只在 isPlatformAdmin 身上渲染，见 views/team/dashboard.ejs。

   【为什么是白卡而不是绿色警示条】
     它不是待办、不是警告，是**身份说明** —— 站长本来就知道自己在看别人的队。
     用 .adm-todo 那种绿底会把它读成「有事要办」，而这里没有任何事要办。
     白卡 + 发丝边和上面的队伍内容同一个材质档位，视线会先落在队名上。 */
.adm-ctx {
  border: 1px solid var(--line); background: var(--surface);
  border-radius: var(--radius); padding: 14px 18px; margin-bottom: 24px;
}
.adm-ctx-t { font-size: 14px; font-weight: 500; color: var(--t1); }
.adm-ctx-t em { font-style: normal; font-weight: 600; }
.adm-ctx-d { font-size: 12px; color: var(--t3); line-height: 1.7; margin-top: 4px; }
.adm-ctx-d b { font-weight: 500; color: var(--t2); }
.adm-ctx-act { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 16px; margin-top: 10px; }
/* 换队 = 一枚可展开的胶囊。原生 summary 的小三角已藏掉，箭头自己画，
   否则不同浏览器给的三角大小/位置都不一样（项目里 .adm-grp 踩过同款）。 */
.adm-ctx-sw > summary {
  font-size: 12px; color: var(--t2); cursor: pointer;
  list-style: none; padding: 5px 12px;
  border: 1px solid var(--line-strong); border-radius: 999px;
  display: inline-block;
}
.adm-ctx-sw > summary::-webkit-details-marker { display: none; }
.adm-ctx-sw > summary::after { content: ' ▾'; color: var(--t3); }
.adm-ctx-sw[open] > summary::after { content: ' ▴'; }
@media (hover: hover) and (pointer: fine) {

.adm-ctx-sw > summary:hover { border-color: var(--t3); }
}
.adm-ctx-list { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
.adm-ctx-list a {
  font-size: 12px; color: var(--t2); text-decoration: none;
  padding: 5px 12px; border: 1px solid var(--line); border-radius: 999px;
}
.adm-ctx-list a.on { color: var(--t1); border-color: var(--t1); }
@media (hover: hover) and (pointer: fine) {

.adm-ctx-list a:hover { border-color: var(--t3); color: var(--t1); }
}
.adm-ctx-back { font-size: 12px; color: var(--accent-text); text-decoration: none; }

/* ---------- 按钮 ---------- */
.btn-sm {
  font-size: 13px; color: #fff; background: var(--t1); border: 0;
  border-radius: 999px; padding: 8px 18px; cursor: pointer;
  font-family: inherit; text-decoration: none; display: inline-block; white-space: nowrap;
}
@media (hover: hover) and (pointer: fine) {

.btn-sm:hover { background: #000; }
}
.btn-sm.ghost { background: none; color: var(--t1); border: 1px solid var(--line-strong); }
@media (hover: hover) and (pointer: fine) {

.btn-sm.ghost:hover { border-color: var(--t3); background: none; }
}
.btn-sm.danger { background: none; color: var(--danger); border: 1px solid var(--line-strong); }
@media (hover: hover) and (pointer: fine) {

.btn-sm.danger:hover { border-color: var(--danger); background: var(--danger-soft); }
}
.btn-row { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }

/* ---------- 概览的状态格 ---------- */
.adm-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1px; background: var(--line); border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; }
.adm-cell { background: #fff; padding: 20px; }
.adm-cell-num { font-size: 26px; font-weight: 600; letter-spacing: -.5px; font-variant-numeric: tabular-nums; }
.adm-cell-num.hi { color: var(--accent-text); }
.adm-cell-num em { font-style: normal; font-size: 13px; font-weight: 400; color: var(--t3); margin-left: 3px; }
.adm-cell-label { font-size: 12px; color: var(--t3); margin-top: 3px; }

/* ---------- 概览的统计卡（2026-10-03 新） ----------
   原来概览的 4 个数字用的是 .adm-grid：gap:1px + 背景色当分隔线。
   4 格并排时像一张表，还行；一旦窄屏折成 2×2，正中间就交叉出一个十字，
   被阿凯形容成「米字旗」—— 那些线是布局的副产品，不该被看见。
   改成各自一张卡：有边框、有圆角、有真实间隙，怎么折行都不会交叉。
   ★ 为什么不直接改 .adm-grid：它还被站长台和「我的」两页当无边框数字行用着
     （那些地方靠内联 border:none + padding:0 把线抹掉），动它会连着破三处。 */
/* ---------- 队伍动态流（2026-10-11，队务首页）----------

   参照 aihot 的「打开即内容」：不做 hero、不做功能介绍，
   第一屏就是队伍最近发生了什么。

   ★ 为什么一条记录是一整行而不是一张卡
     动态流的每条信息量都很小（类型 + 标题 + 一句补充），
     给它套卡片会变成「一堆小盒子」，视觉上比平铺行更碎、也更长。
     卡片的成本要留给真正有内容的东西（训练详情、统计格）。
   ★ 为什么标签用**灰底 + 深字**而不是彩色底
     全站只有首页有氛围色（见 docs/03 §2.1），后台一律黑白灰 + 一点绿。
     五种类型靠文字区分而不是靠颜色 —— 颜色多了就成了「彩虹标签」，
     而这份页面是队长每天要看的，不该每天被五种颜色晃。 */
.feed { margin-top: 4px; }
/* 间隔靠 .feed-day + .feed-day 的 margin，不靠每个组自己加 padding ——
   否则最后一个组下面会多出一截空白（截图里 9月12日那行下方就空了一块）。 */
.feed-day + .feed-day { margin-top: 18px; }
.feed-day-h {
  font-size: 12px; color: var(--t3); letter-spacing: .04em;
  padding: 0 0 5px 14px; border-bottom: 1px solid var(--line);
  margin-bottom: 1px;
}
.feed-row {
  display: flex; align-items: baseline; gap: 10px;
  padding: 11px 0 11px 14px; text-decoration: none;
  border-bottom: 1px solid var(--line);
  border-left: 2px solid transparent; margin-left: -2px;
  transition: border-color .12s;
}
@media (hover: hover) and (pointer: fine) {

.feed-row:hover { border-left-color: var(--t3); }
}
@media (hover: hover) and (pointer: fine) {

.feed-row:hover .feed-t { color: var(--t1); }
}

/* 类型标签：固定宽度，标题才能纵向对齐成一条直线 */
.fd-tag {
  flex: 0 0 auto; font-size: 12px; line-height: 1.6; color: var(--t2);
  background: #F5F5F5; border-radius: 4px; padding: 1px 7px;
  min-width: 42px; text-align: center;
}
.feed-main { flex: 1; min-width: 0; display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.feed-t { color: var(--t1); font-size: 14px; }
/* 补充信息用 · 与标题断开：让人一眼分清「这是标题」和「这是旁注」 */
.feed-m { color: var(--t3); font-size: 13px; }
.feed-m::before { content: '· '; }
.feed-go { flex: 0 0 auto; color: var(--t3); font-size: 13px; opacity: 0; transition: opacity .12s; }
@media (hover: hover) and (pointer: fine) {

.feed-row:hover .feed-go { opacity: 1; }
}

.feed-empty { padding: 28px 0; }

/* 手机上：标签不占固定宽度，标题独占一行
   —— 390px 下 42px 标签 + 长标题会把标题挤成两行，一行一条反而读不下去 */
@media (max-width: 860px) {
  .feed-row { flex-wrap: wrap; gap: 4px 8px; padding: 10px 0 10px 12px; }
  .feed-day-h { padding-left: 12px; }
  .fd-tag { min-width: 0; }
  .feed-main { flex: 0 0 100%; order: 3; }
  .feed-go { order: 2; margin-left: auto; }
  .feed-row:hover .feed-go { opacity: 1; }
}

.stat-cards { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; }
.stat-card {
  display: block; text-decoration: none;
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: 18px 20px; transition: border-color .12s;
}
@media (hover: hover) and (pointer: fine) {

a.stat-card:hover { border-color: var(--t3); }
}
.stat-card-num {
  display: block; font-size: 26px; font-weight: 600; letter-spacing: -.5px;
  font-variant-numeric: tabular-nums;
}
.stat-card-num em { font-style: normal; font-size: 13px; font-weight: 400; color: var(--t3); margin-left: 3px; }
.stat-card-label { display: block; font-size: 12px; color: var(--t3); margin-top: 3px; }

/* ---------- 「往队里添内容」引导卡（2026-10-03 新） ----------
   阿凯的原话：「点了『相册』，我就知道该怎么传图片；点了『队史』，
   就知道该怎么去写队史……第一次用就能直接上手填」。
   所以这里每一张卡不是「一个入口」，而是「一个入口 + 一句怎么做」。
   入口列表式的「荣誉 · 管理」那种写法要靠人自己猜点进去是什么，作废。 */
.ent-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
.ent-card {
  display: flex; flex-direction: column; gap: 4px;
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: 16px 18px; text-decoration: none;
  transition: border-color .12s, background .12s;
}
@media (hover: hover) and (pointer: fine) {

.ent-card:hover { border-color: var(--t3); background: #FCFCFC; }
}
.ent-card-t {
  display: flex; align-items: baseline; gap: 6px;
  font-size: 14px; font-weight: 600;
}
.ent-card-n { font-size: 12px; font-weight: 400; color: var(--t3); font-variant-numeric: tabular-nums; }
.ent-card-d { flex: 1; font-size: 12.5px; color: var(--t3); line-height: 1.6; }
.ent-card-go { margin-top: 6px; font-size: 12px; color: var(--accent-text); }

@media (max-width: 860px) {
  .stat-cards { grid-template-columns: repeat(2, 1fr); }
  .ent-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 560px) {
  .ent-grid { grid-template-columns: 1fr; }
}

/* ---------- 待办提示条 ---------- */
.adm-todo {
  border: 1px solid var(--accent-soft); background: var(--accent-soft);
  border-radius: var(--radius); padding: 16px 20px; margin-bottom: 24px;
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
}
.adm-todo p { font-size: 14px; color: var(--accent-text); }
.adm-todo a { font-size: 13px; color: var(--accent-text); text-decoration: none; white-space: nowrap; border-bottom: 1px solid currentColor; }

/* ---------- 面板 ---------- */
.panel { border: 1px solid var(--line); border-radius: var(--radius); padding: 22px 24px; margin-bottom: 20px; }
.panel-head { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; margin-bottom: 18px; }
.panel-title { font-size: 15px; font-weight: 600; }
.panel-note { font-size: 12px; color: var(--t3); }
/* 面板内部的「这一格还没填」（2026-10-13）—— 训练详情页的
   「这次练什么」与「战术板」两处共用。就是**一行**：左对齐、14px、三级灰。
   ⚠️ 这两处原来借的是 .blk-empty：它自带 24px 上下留白 + text-align:center，
      「这次练什么」那块里面还挂了颗胶囊按钮 ⇒ 整个 panel 被撑到近 1/6 屏高，
      却给不出任何信息。阿凯的原话是「这个是干吗的，我看着很碍眼」。
      更要紧的是它出现在**每一条**训练上（线上 6 条训练的 plan 全是空的），
      于是管理台翻哪一页都顶着它。
   ⚠️ 别把 .blk-empty 借回来：那是**列表页**的长空态，语义是「这个列表是空的」；
      这里要说的是「这一格还没填」，一句话就够。两者不是一回事。 */
.panel-empty { font-size: 14px; color: var(--t3); }
.sec-h { font-size: 13px; font-weight: 600; color: var(--t3); letter-spacing: .8px; margin: 28px 0 12px; }

/* ---------- 表格 ---------- */
.tbl { width: 100%; border-collapse: collapse; }
.tbl th {
  text-align: left; font-size: 12px; font-weight: 500; color: var(--t3);
  padding: 0 12px 10px; border-bottom: 1px solid var(--line);
}
.tbl td { padding: 13px 12px; border-bottom: 1px solid var(--line); font-size: 14px; vertical-align: middle; }
.tbl tr:last-child td { border-bottom: none; }
/* .num 是「不该被断行的内容」—— 日期、背号、分数、届数。
   加 nowrap 是配合下面窄屏的 .tbl{min-width}：min-width 只是下限，
   表格遇到不换行的内容会自己继续变宽，于是日期能撑开、
   而备注这类真正需要折行的长文本仍然折行。 */
.tbl .num { font-variant-numeric: tabular-nums; white-space: nowrap; }
.tbl .muted { color: var(--t3); font-size: 13px; }
.tbl .tight { width: 1%; white-space: nowrap; }
/* 横向滚动容器。overscroll-behavior-x: contain 是给手机用的 ——
   否则在表格左边缘往右滑，会触发浏览器的「后退手势」，看表看着就退出页面了。 */
.tbl-scroll { overflow-x: auto; overscroll-behavior-x: contain; }

/* ---------- 表单（管理台） ----------
   ★ 单列，从上往下 —— 2026-10-03 从「左右两列」改掉。
   【为什么】原来的 1fr 1fr 让队长填表时眼睛在左右两栏之间跳：
         第一栏（标题）横跨、第二行（日期｜类型）分左右、第三行（描述）又横跨。
         阿凯的原话：「左边一个、右边一个……填完左边再填右边，看起来很乱」。
         管理台的使用者（队长 / 指导老师）不是天天填表的熟手，一列到底、
         填完一栏下一栏，比省掉几屏高度重要得多。
   ★ .full 保留（现在等于空规则）：十几个模板都在用它标「这一栏本来就要整行」，
     删掉标签只会制造一次大范围的无意义改动，留着当语义注释。
   ★ 单列之后行距是唯一的节奏，所以 row-gap 从 16 → 18，别再挤。 */
.frm-grid { display: grid; grid-template-columns: 1fr; gap: 18px; }
.frm-grid .full { grid-column: 1 / -1; }
.fld label { display: block; font-size: 13px; color: var(--t2); margin-bottom: 7px; }
.fld input, .fld select, .fld textarea {
  width: 100%; font-family: inherit; font-size: 14px; color: var(--t1);
  padding: 9px 12px; border: 1px solid var(--line-strong); border-radius: 9px;
  background: #fff; outline: none; transition: border-color .15s;
}
.fld input:focus, .fld select:focus, .fld textarea:focus { border-color: var(--t2); }
.fld textarea { resize: vertical; min-height: 76px; line-height: 1.6; }
/* ★ 下拉必须看得出是下拉。
   原来这里只有 `appearance:none` + `background-image:none` —— 只是把系统自带的
   箭头去掉了，却没补自己的，结果 select 和普通 input 长得一模一样：
   阿凯要的「第一次用就能直接上手填」，前提是人家得先知道这一栏能点开。
   补一个 12px 的灰箭头，并留出 34px 内边距免得文字压到箭头上。
   （data URI 里的 # 必须写成 %23，否则整条声明会被判为无效值、静默丢弃。） */
.fld select {
  appearance: none; -webkit-appearance: none; cursor: pointer;
  padding-right: 34px;
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 4.5L6 8L9.5 4.5' fill='none' stroke='%23999' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  background-size: 12px 12px;
}
.fld .hint { font-size: 12px; color: var(--t3); margin-top: 5px; }

/* ---------- 「照最近这次再排」引导条 + 常用环节（2026-10-13）----------
   新建训练页上两个替教练省手的东西。都往「不抢眼但一眼看得见」上做：
   它们不是主流程，漏看也不会出错（表单照样能填），但看到了就少点好几下。
   ⚠️ 2026-10-15：这条也跟着表单一起去掉了描边（原来是 1px + #FAFAFA）——
      同一屏上留一个描边矩形，前面那些「无框」就白做了。 */
.prefill-bar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap;
  border-radius: var(--radius-sm);
  background: #F7F7F7; padding: 12px 14px; margin-bottom: 16px;
}
.prefill-txt { font-size: 13px; color: var(--t2); }
.prefill-txt b { color: var(--t1); font-weight: 600; font-variant-numeric: tabular-nums; }
.prefill-bar .btn-sm { flex: none; }

.plan-quick { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 10px; }
/* 浅底胶囊（2026-10-15）：原来是**虚线描边**的胶囊。虚线也是"框"，
   在一张已经全无框的表里会显得脏。⇒ 改成无描边的灰底圆片 ——
   形状还在（看得出能点），但不再给页面添线条。 */
.plan-chip {
  font-family: inherit; font-size: 13px; line-height: 1;
  color: var(--t2); background: var(--fill);
  border: 0; border-radius: 999px;
  padding: 7px 14px; cursor: pointer; transition: background .15s, color .15s;
}
@media (hover: hover) and (pointer: fine) {

.plan-chip:hover { background: #ECECEC; color: var(--t1); }
}
.plan-chip:active { background: #E4E4E4; color: var(--t1); }

/* 上一次的内容参考。折叠着 —— 默认不占地方，想看再展开。
   （2026-10-15：底色块无描边，与同一屏的训练内容框同一套语言。） */
.plan-ref { margin-top: 12px; border-top: 1px solid var(--line); padding-top: 10px; }
.plan-ref summary { font-size: 13px; color: var(--t2); cursor: pointer; }
.plan-ref-body {
  margin: 10px 0 0; padding: 12px 14px;
  background: #FAFAFA; border-radius: var(--radius-sm);
  font-family: inherit; font-size: 13px; line-height: 1.7; color: var(--t2);
  white-space: pre-wrap; overflow-wrap: break-word;
}
.plan-ref-use { margin: 10px 0 0; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

/* ★★ 行式表单（2026-10-13）。
   阿凯给的那张参考图就是这样：一行一个字段、标签在左、值在右。
   原来是 .frm-grid 小格子（日期一格、地点一格、开始一格、结束一格……），
   八格摊开之后教练要先在格子里找"哪个是哪个"才能开始填 ——
   而他的时间应该花在"练什么"上，不是花在认字段上。
   ⚠️ 行与行之间用发丝线分隔（不是卡片、不是分组标题）——
     这是全站表单一直在用的那一套，别在这里另起一种。 */
.row-form { display: flex; flex-direction: column; }
.row-fld {
  display: flex; align-items: center; gap: 12px;
  min-height: 50px; padding: 6px 0;
  border-bottom: 1px solid var(--line);
}
.row-fld:last-child { border-bottom: 0; }
.row-fld > label { flex: none; width: 68px; font-size: 14px; color: var(--t2); }
.row-val { flex: 1; min-width: 0; display: flex; align-items: center; gap: 6px; row-gap: 9px; flex-wrap: wrap; }
/* ★★ 行内控件 = **无框**（2026-10-15）。
   阿凯给的那张参考图里，**一个输入框的边框都没有** —— 整张卡只有白底、
   一根根发丝线、左标签右值。他原话：「全是这种长方形的框，多难看」。
   ⇒ 这套表里的 input / select / textarea 全部去掉描边和底色，
     值直接落在行上；反馈改由「行」承担（见下面 :focus-within 两条）。

   ⚠️ 上一版（2026-10-13）只给了 `flex: 1; min-width: 0`，
     **根本没给这些控件写过任何外观** —— 所以它们渲染的是浏览器默认外观
     （描边方框 + 内阴影），这才是"全是长方形"的真正来源。
     ⇒ 别再只写布局、把外观漏给浏览器默认值。 */
.row-val > input,
.row-val > select,
.row-val > textarea {
  flex: 1; min-width: 0;
  font-family: inherit; font-size: 14px; color: var(--t1);
  background: none; border: 0; padding: 0; outline: none;
}
/* 下拉仍要看得出是下拉（AGENTS.md 管理台交互语言第 4 条）：
   描边去掉了，自绘箭头一个都不能少 —— 不然它跟旁边的自由文本长得一模一样，
   用户根本不知道这一栏能点开。 */
.row-val > select {
  appearance: none; -webkit-appearance: none; cursor: pointer;
  padding-right: 22px;
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 4.5L6 8L9.5 4.5' fill='none' stroke='%23999' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 4px center;
  background-size: 12px 12px;
}
/* 训练内容 / 备注：多行文本框不能用"行"的形态（它是这一页最该有存在感的地方，
   没有它用户不知道该往哪儿下笔）。⇒ 给一块**浅底色**当"能写"的提示，
   但**不描边** —— 是块，不是框。聚焦时底色轻压一档，跟行的 :focus-within 呼应。 */
.row-val > textarea {
  background: #FAFAFA; border-radius: var(--radius-sm);
  padding: 10px 12px; line-height: 1.7; resize: vertical;
}
.row-val > textarea:focus { background: var(--fill); }
/* 原生日期 / 时钟图标调淡：它们在无框文本旁边全彩的话，比文字还抢眼。
   只降透明度、**不删**（`appearance:none` 会连"点图标弹出日历"一起干掉，
   桌面上就没法点选了）。 */
.row-val input[type="date"]::-webkit-calendar-picker-indicator,
.row-val input[type="time"]::-webkit-calendar-picker-indicator {
  opacity: .45; cursor: pointer;
}
/* 焦点反馈：不新增任何元素，只让**这一行**说话 ——
   发丝线变深 + 标签从次级灰提到正文黑。 */
.row-fld:focus-within { border-bottom-color: var(--t2); }
.row-fld:focus-within > label { color: var(--t1); }
/* 日期比时间宽一点：它要多显示 4 个数字和两个分隔符。
   ★ 但它们都**不撑开**（2026-10-15）：撑开的话每个控件各占行的三分之一，
     值是散在三个位置上的（`--:--` 在这头、时钟图标在那头），中间一大片空 ——
     而「2026/10/15 20:57 – 22:00」本来就是**一个时刻**，要连着读。
     窄屏那两条媒体查询在后面，日期独占一行、两个时间各占一半，会覆盖这里。 */
.row-val > input[type="date"],
.row-val > input[type="time"] { flex: none; }
.row-dash { flex: none; font-size: 13px; color: var(--t3); }
/* ★★ 数字输入（比赛的比分 / 精神评分，2026-10-15）。
   【为什么必须单写这一条】上面那条「控件无框」只去掉了 border 与 background，
   而数字输入还带一个**上下箭头**（::-webkit-inner-spin-button）——
   它不属于边框，照样留在行尾。一排纯文字中间挂一个小方框箭头，
   正是阿凯说的「全是这种长方形的框」在最后剩下的那一处。
   ⚠️ 它**只在悬停 / 聚焦时才明显**，静态截图容易看不出来 ——
     所以这条别靠肉眼验收。
   ⚠️ 只去掉箭头，**不碰 type="number" 本身**：换掉 type 会连
     `min`/`max` 的校验与手机数字键盘一起丢（inputmode 是给 iOS 用的补充，
     不是替代品）。 */
.row-val > input[type="number"] {
  appearance: textfield;
  -moz-appearance: textfield;
}
.row-val > input[type="number"]::-webkit-outer-spin-button,
.row-val > input[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none;
  appearance: none;
  margin: 0;
}
/* 多行的那两行（训练内容 / 备注）：标签改成顶部对齐，
   不然「训练内容」四个字会贴在 6 行文本框的正中间。
   padding-top 12 = textarea 自己的上内边距(10) + 让 14px 标签文字与
   1.7 行高的正文第一行**基线**对齐的补偿。 */
.row-fld-top { align-items: flex-start; }
.row-fld-top > label { padding-top: 12px; }
/* 「常用环节」那一排和「上次参考」要独占自己那一行（它们本来就不是控件） */
.row-val .plan-quick,
.row-val .plan-ref { flex: 1 0 100%; }
.row-val .plan-ref { margin-top: 8px; }

/* 「更多：主题 / 带队人 / 备注」折叠区（2026-10-13）。
   这三样一周一周基本不变或干脆不用填；摊在主表单里，每次新建都要先扫过
   它们才按得到提交。⚠️ 已经填过的（编辑时）默认展开，收起来像"字段丢了"。 */
.more-box { margin-top: 16px; border-top: 1px solid var(--line); }
.more-box > summary {
  padding: 14px 0 4px;
  font-size: 13px; color: var(--t2); cursor: pointer;
}
.more-box .panel-note { margin: 10px 0 0; }

/* ★★ 手机上所有输入控件都用 16px（2026-10-13）。
   这不是审美选择：iOS Safari 在 **font-size < 16px** 的输入框获得焦点时
   会**自动把整个页面放大**，放大之后布局跟着跑、还得手动缩回去 ——
   教练在手机上写训练内容，每点一次那个框就来一次。
   全站表单原来是 14px（桌面看着更紧凑），所以这条只在窄屏生效，
   桌面版不动。 */
@media (max-width: 640px) {
  .fld input, .fld select, .fld textarea,
  .field input, .field select, .field textarea,
  .row-val input, .row-val select, .row-val textarea,
  .rv-write textarea,
  .rv-rep-form input, .hist-search input, .ex-mini input { font-size: 16px; }

  /* ★ 手机上「时间」那一行必须折成两行（2026-10-13 实拍发现）。
     日期 + 开始 + 结束三个**原生**控件并排至少要 320px，而 390 的屏在
     减去 68px 标签、内边距和两个间隔之后只剩 278px ——
     硬挤的话日期显示成「2026/10/」（后面被裁掉），两个时间被压到只剩
     `--:--` 的几个像素，等于没法填。
     ⚠️ 参考图里那行「2026/10/14 01:00 - 03:00」在 iOS 上是**一个** picker
        入口，不是三个控件 —— 那个形状用原生 input 做不出来，所以这里
        优先保证"填得进去"：日期独占一行，两个时间在第二行各占一半。
     （漏了这条的话，页面/截图/离线测试都不会报错，只有真去手机上填的人
       会发现时间框小到点不中。） */
  .row-val > input[type="date"] { flex: 1 0 100%; }
  .row-val > input[type="time"] { flex: 1 1 0; min-width: 0; }
}

/* ---------- 说明行分两级（.hint / .hint.quiet）----------
   这是阿凯 2026-10-03 那个问题的答案。他要的是：
     「默认隐藏 / 悬停显示 / 点击清除」。
   采纳「平时干净、要用时才出现」，但把触发条件从「悬停」扩成「悬停 或 聚焦」：
   手机没有 hover，而队务概览、点名页主要就是队长在手机上看的 ——
   只写悬停 = 手机用户永远看不到。
   （「点击清除」不用做：浏览器原生 placeholder 输入第一个字符就消失。）

   ★ 用 opacity 而不是 display:none，说明行**始终占着高度**。
     折叠高度的话，鼠标一点进输入框整页会往下跳一截，比看见字更难受。

   ★ 只给「补充/举例型」用 .quiet。规则型说明（「至少 8 位」「会显示在成员名单里」）
     必须常驻 —— 那是填之前就得知道的事，藏起来会直接填错。 */
.hint.quiet { opacity: .4; transition: opacity .18s ease; }
@media (hover: hover) and (pointer: fine) {
  .fld:hover .hint.quiet,
  .field:hover .hint.quiet { opacity: 1; }
}
/* focus-within 不包：键盘用户和触屏都得看见提示，这是功能不是装饰 */
.fld:focus-within .hint.quiet,
.field:focus-within .hint.quiet { opacity: 1; }
.frm-foot { display: flex; gap: 10px; align-items: center; margin-top: 22px; }

/* ---------- 徽标 ---------- */
.badge {
  display: inline-block; font-size: 12px; line-height: 1; padding: 4px 7px;
  border-radius: 5px; background: var(--fill); color: var(--t2); vertical-align: 1px;
}
.badge.ok { background: var(--accent-soft); color: var(--accent-text); }
.badge.warn { background: #FFF6E5; color: #A8761A; }
.badge.off { background: var(--fill); color: var(--t3); }
/* 缺席用的变体。原先只能借用 .off（灰），但请假和缺席视觉一样会分不清 ——
   而这两件事的性质完全不同，队员看到时必须一眼分辨。 */
.badge.bad { background: var(--danger-soft); color: var(--danger); }

/* ---------- 点名（手机优先） ---------- */
.att-list { border-top: 1px solid var(--line); }
.att-row { display: flex; align-items: center; gap: 14px; padding: 12px 0; border-bottom: 1px solid var(--line); }
.att-who { flex: 1; min-width: 0; }
.att-name { font-size: 15px; font-weight: 500; }
.att-meta { font-size: 12px; color: var(--t3); margin-top: 2px; }
.att-num {
  width: 34px; flex-shrink: 0; text-align: center; font-size: 13px; color: var(--t3);
  font-variant-numeric: tabular-nums;
}
/* 四态选择：用 radio + label 做成分段控件，点一下就行，不必先点开下拉 */
.seg { display: flex; flex-shrink: 0; border: 1px solid var(--line-strong); border-radius: 8px; overflow: hidden; }
.seg input { position: absolute; opacity: 0; pointer-events: none; }
.seg label {
  font-size: 12px; padding: 7px 10px; cursor: pointer; color: var(--t2);
  border-right: 1px solid var(--line-strong); user-select: none;
}
.seg label:last-of-type { border-right: none; }
.seg input:checked + label { background: var(--t1); color: #fff; }
.seg input[value="leave"]:checked + label { background: #A8761A; }
.seg input[value="absent"]:checked + label { background: var(--t3); }
.seg input[value="late"]:checked + label { background: var(--accent-text); }

/* ---------- 相册网格（管理台，带删除按钮） ---------- */
.ph-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 10px; }
.ph-item { position: relative; aspect-ratio: 1; border-radius: var(--radius-sm); overflow: hidden; background: var(--fill); }
.ph-item img { width: 100%; height: 100%; object-fit: cover; display: block; cursor: zoom-in; }
.ph-del {
  position: absolute; top: 6px; right: 6px; width: 22px; height: 22px; border-radius: 6px;
  background: rgba(0,0,0,.55); color: #fff; border: 0; cursor: pointer;
  font-size: 13px; line-height: 1; display: flex; align-items: center; justify-content: center;
  /* 平时半隐，鼠标进来才明显 —— 免得删除按钮在相册封面里抢戏 */
  opacity: 0; transition: opacity .15s ease;
  z-index: 2;
}
@media (hover: hover) and (pointer: fine) {
  .ph-item:hover .ph-del { opacity: 1; }
}
/* 焦点态不包：触屏上没有 hover，但键盘/读屏切到它时必须看得见 */
.ph-del:focus-visible { opacity: 1; }
@media (hover: hover) and (pointer: fine) {

.ph-del:hover { background: rgba(0,0,0,.78); }
}
/* 触屏设备没有 hover，删除按钮必须常显，否则永远点不到 */
@media (hover: none) { .ph-del { opacity: 1; } }

/* ---------- 照片灯箱（点开看大图） ----------
   【为什么必须做这个】
   飞盘队长传照片的动机是「让大家看到这场比赛」。如果点开只能看 1:1 的小方格，
   这个动机就废了 —— 人要在图里找自己、找队友、看比分牌。
   而且手机端必须有：不然后台相册就是一堆看不清的缩略图。 */
.lb {
  position: fixed; inset: 0; z-index: 200;
  background: rgba(12, 12, 12, .94);
  display: none; align-items: center; justify-content: center;
}
.lb.open { display: flex; }
.lb img {
  max-width: 94vw; max-height: 88vh; object-fit: contain;
  border-radius: 4px; display: block;
}
.lb-close, .lb-prev, .lb-next {
  position: absolute; background: rgba(255,255,255,.14); color: #fff;
  border: 0; cursor: pointer; border-radius: 999px;
  width: 44px; height: 44px; font-size: 20px; line-height: 1;
  display: flex; align-items: center; justify-content: center;
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  transition: background .15s ease;
}
@media (hover: hover) and (pointer: fine) {

.lb-close:hover, .lb-prev:hover, .lb-next:hover { background: rgba(255,255,255,.26); }
}
.lb-close { top: 18px; right: 18px; }
.lb-prev { left: 18px; top: 50%; transform: translateY(-50%); }
.lb-next { right: 18px; top: 50%; transform: translateY(-50%); }
.lb-count {
  position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%);
  color: rgba(255,255,255,.7); font-size: 13px;
  font-variant-numeric: tabular-nums;
}
@media (max-width: 640px) {
  .lb-prev, .lb-next { width: 38px; height: 38px; }
  .lb-prev { left: 8px; } .lb-next { right: 8px; }
  .lb-close { top: 10px; right: 10px; }
}

/* ---------- 照片上传区 ---------- */
.up-drop {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 6px; padding: 38px 20px; cursor: pointer;
  border: 1px dashed var(--line-strong); border-radius: var(--radius);
  background: #FCFCFC;
  transition: border-color .15s ease, background .15s ease;
}
@media (hover: hover) and (pointer: fine) {

.up-drop:hover { border-color: var(--t3); background: var(--fill); }
}
/* 拖拽进入时高亮：拖拽是「盲操作」，必须给明确的可放置反馈 */
.up-drop.over { border-color: var(--accent); background: var(--accent-soft); }
.up-icon { color: var(--t3); }
.up-icon svg { width: 26px; height: 26px; }
@media (hover: hover) and (pointer: fine) {

.up-drop:hover .up-icon { color: var(--t2); }
}
.up-main { font-size: 15px; font-weight: 500; color: var(--t1); }
.up-sub { font-size: 13px; color: var(--t3); }

.up-list {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  gap: 8px; margin-top: 14px;
}
.up-list:empty { margin-top: 0; }
.up-item {
  display: flex; align-items: center; gap: 8px;
  padding: 6px 8px; border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: #fff; min-width: 0;
}
.up-item.bad { border-color: var(--danger); background: var(--danger-soft); }
.up-thumb {
  width: 34px; height: 34px; flex-shrink: 0; border-radius: 6px; overflow: hidden;
  background: var(--fill); display: block;
}
.up-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.up-meta {
  font-size: 12px; color: var(--t2); line-height: 1.4;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}
.up-item.bad .up-meta { color: var(--danger); }

.up-alt { margin-top: 18px; border-top: 1px solid var(--line); padding-top: 14px; }
.up-alt summary {
  font-size: 13px; color: var(--t2); cursor: pointer;
  list-style: none; padding: 4px 0;
}
.up-alt summary::-webkit-details-marker { display: none; }
.up-alt summary::before { content: '+ '; color: var(--t3); }
.up-alt[open] summary::before { content: '− '; }
@media (hover: hover) and (pointer: fine) {

.up-alt summary:hover { color: var(--t1); }
}
.up-alt form { margin-top: 12px; }

/* ---------- 空态 ----------
   ★ 2026-10-03 去掉虚线框。阿凯的原话：「只要是那种有框的，框里面的文字
   是固定的……我觉得这样有点不美观」。
   他说得对，而且比他以为的更过头：空的列表**本来就已经在 .panel 的边框里**，
   再套一层虚线框 + 40px 内边距，等于给「什么都没有」画了整页最重的一个块 ——
   越空越显眼，正好反了。现在只有一行浅灰字（必要时跟一个轻链接）。

   ⚠️ 别再给这里加回边框。public/join.ejs 那两处借用了 .blk-empty 的排版，
   但它们要的是「状态盒」不是空态，所以那两处改成显式写 border，
   不指望这里还留着。 */
.blk-empty { padding: 24px 8px; text-align: center; }
.blk-empty p { font-size: 14px; color: var(--t3); }
.blk-empty p + p { margin-top: 6px; font-size: 13px; }

/* ---------- 提示条 ----------
   三态：成功（绿）/ 警告（黄，用于「部分成功」）/ 失败（红）。
   【为什么需要「警告」这一态】
   批量上传 20 张、坏了 1 张，既不是成功也不是失败。
   报成功会让用户以为全好了；报失败会让他以为全白传了 —— 都不对。 */
.toast {
  font-size: 13px; padding: 11px 14px; border-radius: 10px; margin-bottom: 20px;
  background: var(--accent-soft); color: var(--accent-text);
}
.toast.warn { background: #FFF8E8; color: #8A6410; }
.toast.bad { background: var(--danger-soft); color: var(--danger); }

/* ---------- 训练详情：复制到微信群（2026-10-12，2026-10-15 改版）----------
   ★ 2026-10-15 之前这里是个 `<details>` 浮层：点「发到微信群」弹出一个
     absolutely 定位的白框，里面有 textarea + 链接框 + 两颗按钮。
     阿凯：「我希望点一下那个按钮之后，直接就能**粘贴文字**，而不是复制链接」。
     ⇒ 主按钮改成"点一下直接复制"，**没有浮层了**；
       剩下的只是一块**兜底区**，只在自动复制失败时由 JS 显示（默认 hidden）。
   ⚠️ 所以定位属性（position:absolute / z-index / right / top）全部撤掉 ——
      它现在是文档流里的一块，不是浮层。留着那几条会让它飞出去。
   ⚠️ 别把 .user-menu-list 的类名借过来：那套带着自己的定位与最小宽度，
      它一改版这里就塌（借类名的代价本项目已经付过一次）。 */
.share-fallback {
  margin-top: 14px; padding: 14px;
  background: #FAFAFA; border-radius: var(--radius);
}
.share-pop-h { font-size: 13px; color: var(--t2); margin: 0 0 10px; }
.share-txt {
  width: 100%; box-sizing: border-box; display: block;
  font-family: inherit; font-size: 13px; line-height: 1.7; color: var(--t1);
  background: #fff; border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: 12px; resize: vertical;
}
.share-txt:focus { outline: none; border-color: var(--t3); }
/* 「只要链接」：一行 = 只读输入框 + 复制按钮（2026-10-12）。
   ⚠️ `min-width: 0` 不能省：flex 子项的默认 `min-width: auto` 会让长 URL
   把容器撑开 —— 在 390px 手机上会被一条 URL 顶出屏幕
   （本项目栽过一次横向溢出）。 */
.share-link { display: flex; align-items: center; gap: 8px; margin-top: 8px; }
.share-link-in {
  flex: 1 1 auto; min-width: 0; box-sizing: border-box;
  font-family: inherit; font-size: 12.5px; color: var(--t2);
  background: #fff; border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: 8px 10px;
}
.share-link-in:focus { outline: none; border-color: var(--t3); }
.share-link .btn-sm { flex: none; }

/* ---------- 管理台移动端 ----------
   ★ 2026-10-12 第三轮：**拆成两个媒体查询**。「侧栏放在哪」和「内容收紧多少」
     是两件事，原来挤在同一个 `max-width: 860px` 里，于是横屏也被迫顶栏化。

   阿凯的原话：「你要不把它放侧面？我发现如果手机和平板横过来，它可以放侧面；
   如果竖着的话，它就在最顶上。」
   ⇒ 下面**第一个**块只管「侧栏顶栏化」，条件是竖屏；
      横屏时侧栏回左侧、沿用桌面那一套排版。
   ⇒ **第二个**块（在所有窄屏上都生效）管内容区的内边距、表格、点名行 ——
      这些跟竖横无关，横屏同样需要，不能跟着侧栏一起被 orientation 挡掉。

   ⚠️ 为什么还留一个无方向的 `max-width: 560px`：
     560px 以下横竖都放不下侧栏（168px 侧栏只剩不到 400px 给内容），
     无条件顶栏。顺带兜住一个边界情况 —— 竖屏输入时软键盘弹出若导致
     orientation 翻转，窄屏也仍然走顶栏，不会突然跳成桌面双栏。 */
@media (max-width: 860px) and (orientation: portrait), (max-width: 560px) {
  .adm { flex-direction: column; }
  .adm-side {
    width: 100%; border-right: none; border-bottom: 1px solid var(--line);
    padding: 0; position: sticky; top: 0; background: #fff; z-index: 5;
  }
  .adm-side-head { display: none; }
  /* ★ 右边缘渐隐**保留**（2026-10-11 侧栏改版时差点删掉它）。
     改版后一级行只有 5 个短词（成员/赛训/队籍/过往/队伍资料），
     实测 390px 下不横向溢出 —— 但那是**当前**这几个词的结果，
     不是这条规则可以撤掉的理由：
     ① 以后再加一个模块，或某个词翻译变长，立刻恢复成「滑得动但看不出能滑」；
     ② 这条渐隐在没溢出时是**完全不可见**的（白底上白到透明），
        留着不花钱，缺了要出事。
     原注释仍然有效：.adm-nav 是横向滚动容器，用户不会去猜右边还有东西。 */
  .adm-side::after {
    content: ''; position: absolute; top: 0; right: 0; width: 28px;
    height: 100%; pointer-events: none;
    background: linear-gradient(to right, rgba(255,255,255,0), #fff);
  }

  /* ★ 手机上侧栏**横向排 + 允许换行**（2026-10-11 第二轮定稿）。
     ⚠️ 为什么必须 flex-wrap：
       模块是「一列」—— 标题在上、子项排在标题下面那一行，
       于是**这一列的宽度由子项那一行的宽度决定**（「过往」有 4 个子项，
       实测 216px）。若只写 flex-direction:row 不许换行，整排会涨到 437px，
       390px 下「队伍资料」被推出屏幕，要点它得先横向滑一下。
       而这一轮阿凯说的恰恰是「队伍资料凭什么显得跟别人不一样」，
       不能让它掉出屏幕。有 wrap 后，一级行自己排一行、子项另起一行，
       与某个模块展开几个子项无关，一级行永远完整可见。

     ⚠️⚠️ 曾经想用 `.adm-grp { display: contents }` + `order` 把子项
       挤成独立一行，**在 Chrome 上不成立，别再试**：
       Chrome 会把 <details> 的内容包进一个隐式的 ::details-content 盒子，
       于是
         ① 折叠状态下的子项**依然生成盒子**（只剩 padding 撑成的 4px 宽条），
            等于没折叠；
         ② `order` 挂在 .adm-grp-b 上不起作用（它不是 flex item，
            那个隐式盒子才是），子项反而被排到一级项前面。

     ⚠️ 更早还试过「子项 position:absolute 下拉」：`.adm-nav` 有
       overflow-x:auto，那同时是**纵向的裁剪边界**，下拉会被竖着切掉。

     overflow-x 保留 —— fold 之后几乎不触发，但万一某个标题长到一行放不下
     还有横滑兜底，且 .adm-side::after 的右缘渐隐提示依赖它。 */
  .adm-nav {
    flex-direction: row; flex-wrap: wrap;
    overflow-x: auto; padding: 6px 16px 8px; gap: 6px; align-items: flex-start;
  }
  .adm-nav { scrollbar-width: none; -ms-overflow-style: none; }
  .adm-nav::-webkit-scrollbar { display: none; }
  /* ★ 手机/平板上侧栏是**吸顶的第一行**，它就是阿凯说的「最顶上的字很小」。
     2026-10-12 整体放大一档：字号 13 → 14，内边距 9/12 → 10/14，
     触控高度从 ~40px 提到 ~44px（Apple HIG 的最小点击目标）。
     实测 390px 下五个一级行仍然一行放得下（约 340px < 358px 可用宽），
     展开子项时才折行 —— 与改版前一致。 */
  .adm-1, .adm-grp-h { flex: 0 0 auto; padding: 10px 14px; white-space: nowrap; font-size: 14px; }
  .adm-grp { flex: 0 0 auto; display: flex; flex-direction: column; }

  /* ★★ 第三轮的核心：子项搬出模块、独占下面一行（长注释在 _nav.ejs 里）。
     下面三条各司其职，缺一条这个方案就不成立：
       ① .adm-grp-b 藏掉 —— 子项已经不在模块内部了；
       ② .adm-grp-h / .adm-grp-t —— 一级标签在窄屏只跳转、不开合，
          所以它的尺寸永远不会因为「展开」而变化；
       ③ .adm-sub-bar 顶上来，只显示「当前模块」的子项。 */
  .adm-grp-b { display: none; }
  .adm-grp-h { pointer-events: none; }
  .adm-grp-t { pointer-events: auto; }
  .adm-sub-bar {
    display: flex; flex-wrap: wrap; gap: 2px; align-items: flex-start;
    /* ⚠️ flex: 1 0 100% 是**必须的**：.adm-nav 是 flex-wrap 容器，
        100% 基宽才能强制它另起一行；少了它，子项条会跟一级行挤在同一行。 */
    flex: 1 0 100%;
    /* 左缩进对齐上面一级标签的**文字起点**（一级行 padding 左右各 14px） */
    padding: 0 0 2px 14px;
  }
  .adm-sub-bar a {
    padding: 8px 12px; white-space: nowrap; font-size: 14px;
    color: var(--t2); text-decoration: none; border-radius: var(--radius-sm);
  }
  .adm-sub-bar a:hover { background: var(--fill); color: var(--t1); }
  .adm-sub-bar a.on { background: var(--fill); color: var(--t1); font-weight: 500; }

  /* 横排时这条发丝线没有意义（它会在行内挤出一个 1px 空档） */
  .adm-sep { display: none; }
}

/* ---------- 窄横屏：侧栏回左侧 ----------
   顶栏化只发生在竖屏；横屏沿用桌面排版（阿凯要的「横过来就放侧面」）。
   但窄横屏的**高度**很矮 —— 手机横过来常常只有 390px 高，而侧栏内容
   加内边距比它还高，于是侧栏被裁掉一半、「队伍资料」根本够不到。
   ⇒ 让它自己滚，并贴住顶部。
   ⚠️ 这里只改「高度相关」的东西；宽度、边框、内边距沿桌面那一套，
      别在这个块里把侧栏重写一遍（两份规则迟早对不上）。 */
@media (max-width: 860px) and (orientation: landscape) {
  .adm-side {
    padding: 16px 0 20px;
    align-self: flex-start; position: sticky; top: 0;
    /* ⚠️ 必须是 `100vh - 64px`，不是 `100vh`。
       顶栏占着上面 64px（全站约定，见 .adm 的 min-height），
       写 100vh 的话侧栏会从 y=64 往下铺满 390px，**最后 64px 落到视口外面**
       —— 而 `position: sticky; top: 0` 只在**页面滚动之后**才把它顶到 y=0，
       于是刚打开这一页时最底下那项（队伍资料）够不到。
       实测踩过：844×390 下 check-nav-fold 报「队伍资料点不到」。 */
    max-height: calc(100vh - 64px); overflow-y: auto;
  }
}

/* ---------- 所有窄屏：内容区收紧（竖横都要，与上面的 orientation 无关）---------- */
@media (max-width: 860px) {
  .adm-main { padding: 22px 18px 72px; }
  .adm-title { font-size: 21px; }
  .adm-sub { font-size: 13.5px; }
  .adm-grid { grid-template-columns: repeat(2, 1fr); }
  .adm-head { flex-direction: column; gap: 12px; }
  /* ★ 窄屏下 .adm-head 变成纵向堆叠，动作区贴在**左边** ——
     而 .share-pop 在桌面是按 right:0 向左生长的。不翻过来的话，
     浮层会从「发到微信群」按钮的右缘往左长出 440px，**溢出到屏幕外**。
     改成 left:0 就变成向右生长，和内容区左对齐。 */
  .share-pop { left: 0; right: auto; }
  /* （.frm-grid 的窄屏降列规则已删：它本来就是单列了） */
  .ph-grid { grid-template-columns: repeat(3, 1fr); }
  /* 点名行改两行：姓名在上，四态在下 —— 拇指够得着 */
  .att-row { flex-wrap: wrap; gap: 8px 12px; }
  .att-who { flex: 1 0 calc(100% - 50px); }
  .seg { width: 100%; }
  .seg label { flex: 1; text-align: center; padding: 9px 0; }
  /* 内容块在手机上收一点内边距，把宽度还给内容（390px 下正文可用宽 +8px） */
  .panel { padding: 18px 16px; }
  .adm-todo { padding: 14px 16px; }
  .stat-card { padding: 16px 16px; }
  /* ⚠️ 这个负边距是**跟着 .adm-main 的内边距走的**（表格要顶到屏幕两侧、
     再靠 padding 把内容推回来）。上面把 .adm-main 的 20px 改成 18px，
     这里必须同步改 —— 不改的话表格左边缘会比内容区多出 2px 的错位，
     而且只在手机上、只在带表格的页面上看得出来。 */
  .tbl-scroll { margin: 0 -18px; padding: 0 18px; }
  /* ★ 手机上必须给表格一个最小宽度，否则「横向滚动」是假的。
     原因：.tbl 只有 width:100%，浏览器会把表压进容器 —— 中文可以在任意字之间
     断行，所以压得再窄也不会溢出，.tbl-scroll 的 overflow-x 永远不触发。
     代价是列被挤碎：390px 下「张伟」被拆成两行、表头「飞盘名」拆两行。
     600px 是量出来的：管理台内容区最宽的表是 6 列（名册 / 任期 / 训练 / 比赛），
     600 / 6 ≈ 100px 一列，够放「2025-09-01」和两三个汉字；备注这类长文本照常折行。
     超过 600px 视口时容器本来就比它宽，这条规则不产生任何影响。 */
  .tbl { min-width: 600px; }
}

/* ============================================================
   申请入队页（公开）
   ============================================================ */
.join-hero { padding: 56px 0 40px; border-bottom: 1px solid var(--line); }
.join-steps { counter-reset: s; margin: 32px 0 8px; }
.join-step { display: flex; gap: 16px; padding: 16px 0; border-bottom: 1px solid var(--line); }
.join-step-n {
  width: 24px; height: 24px; flex-shrink: 0; border-radius: 50%; background: var(--fill);
  display: flex; align-items: center; justify-content: center;
  font-size: 12px; font-weight: 600; color: var(--t2);
}
.join-step-b { flex: 1; }
.join-step-t { font-size: 15px; font-weight: 500; }
.join-step-d { font-size: 13px; color: var(--t3); margin-top: 3px; }

@media (max-width: 720px) {
  .join-hero { padding: 36px 0 28px; }
  .ph-grid { grid-template-columns: repeat(3, 1fr); }
}

/* ---------- 移动端 ---------- */
@media (max-width: 720px) {
  .wrap, .nav-inner { padding: 0 20px; }
  /* 折行规则在 1200px 那个块里（手机也走它），这里只做手机专属的收紧 */
  /* ★ 手机上顶栏那一排按钮必须收紧，否则根本装不下（2026-10-11 量出来的）。
     390px 下「队伍主页 + 我的 + 队务 + 站长 + 头像」实测 380px 宽，
     而可用宽度只有 370px —— 头像被切掉一截，还带出 10px 的整页横向溢出
     （分组线再加上 13px 就是 23px）。
     为什么一直没人发现：**只有同时是队长 + 平台站长**的账号才会看到「站长」那个按钮，
     而预览集里恰好长期没有这一档（阿凯正好是这个身份，所以是他先碰上的）。
     收紧内边距 16→11、间隙 12→8，省下约 60px。 */
  .nav-right { gap: 8px; }
  .nav-btn { padding: 6px 11px; }
  .nav-right .who { display: none; }
  .nav-links a { font-size: 13px; padding: 2px 0; }
  .hero { padding: 40px 0 36px; }
  .hero-top { flex-direction: column; align-items: flex-start; gap: 16px; }
  .hero-logo { width: 64px; height: 64px; border-radius: 16px; font-size: 26px; }
  .hero-name { font-size: 24px; }
  .hero-slogan { font-size: 14px; }
  .stats { margin-top: 28px; padding-top: 20px; flex-wrap: wrap; gap: 20px 0; }
  .stat { flex: 0 0 50%; }
  .stat + .stat { border-left: none; padding-left: 0; }
  section { padding: 36px 0; }
  .sec-head { margin-bottom: 20px; }
  .captains { grid-template-columns: 1fr; }
  .row { padding: 15px 0; }
  .row-title { font-size: 14px; }
  .tl-item { gap: 16px; }
  .tl-date { width: 72px; font-size: 12px; }
  .gal { grid-template-columns: repeat(2, 1fr); }
  .match-row { padding: 15px 0; }
  .match-name { font-size: 14px; }
  .auth { padding: 44px 20px 72px; }
}

/* ============================================================
   队伍广场（/explore）· 未进队伍前的总浏览页
   结构：hero → 搜索+筛选 → 有队伍的学校 → 我的队伍 → 队伍卡片 → 建队 CTA
   ============================================================ */
.ex-hero { padding: 52px 0 32px; border-bottom: 1px solid var(--line); }
.ex-hero h1 { font-size: 26px; font-weight: 600; letter-spacing: -.2px; }
.ex-hero p { font-size: 14px; color: var(--t2); margin-top: 8px; max-width: 560px; }

/* 区块节奏 —— 2026-10-03 新增。
   广场页每一块的内容都很短（一行搜索框、一个校名），却仍走全局 section 的
   56px 上下留白，于是页面被切成一截内容 + 一大片空白 + 一截内容……
   阿凯的原话：「给人一种看起来很乱的感觉」。收紧到 32px，内容才连得起来。
   类名压在 section 上（.ex-sec 特异性高于元素选择器 section），不用 !important。 */
.ex-sec { padding: 32px 0; }

/* ---------- 广场标题行 + 搜索胶囊（2026-10-12 重做） ----------
   阿凯的原话：「『找队伍』的搜索框太大了，占据了屏幕中间绝大部分位置。
   把它缩小到小拇指那么长，或者浓缩成一个放大镜图标，做成椭圆形、长一点的样式，
   只要给用户留出搜索的地方就可以了」+「把搜索框放在右边，和『队伍广场』字体对齐」。

   旧版是「独占一张 .ex-panel 卡、横跨整个内容区的一个大输入框 + 一颗搜索按钮」——
   它把首屏中间整块吃掉了，而下面才是真正要看的队伍卡片。现在它是标题右侧的
   一颗 260px 胶囊（≈ 一根小拇指的宽度），图标本身就是提交键。

   ⚠️ 260px 是量出来的：390px 视口下「队伍广场」(22px×4=88px) + 胶囊放不下一行，
     所以 ≤720px 时它落到标题下面占满一行（见下面的媒体查询）——手机上
     全宽搜索反而更好点。 */
.ex-hero-top {
  display: flex; align-items: center; justify-content: space-between;
  gap: 20px; flex-wrap: wrap;
}
.ex-mini {
  display: flex; align-items: center; gap: 4px;
  width: 260px; max-width: 100%; flex-shrink: 0;
  border: 1px solid var(--line-strong); border-radius: 999px;
  padding: 5px 10px 5px 6px; background: #fff;
  transition: border-color .12s;
}
.ex-mini:focus-within { border-color: var(--t3); }
/* ★ 这颗放大镜**就是**提交按钮（type="submit"）——「浓缩成一个放大镜图标」
   指的是它要替掉右下角那颗「搜索」文字按钮，不然胶囊又被撑成原来那个大框。 */
.ex-mini-ic {
  flex: 0 0 auto; width: 28px; height: 28px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  border: 0; background: none; cursor: pointer; color: var(--t3);
  font-family: inherit;
}
@media (hover: hover) and (pointer: fine) {

.ex-mini-ic:hover { color: var(--t1); }
}
.ex-mini-ic svg {
  width: 15px; height: 15px; fill: none;
  stroke: currentColor; stroke-width: 1.6; stroke-linecap: round;
}
.ex-mini input {
  flex: 1; min-width: 0; border: 0; background: none; outline: none;
  font-family: inherit; font-size: 14px; color: var(--t1); padding: 3px 0;
}
.ex-mini input::placeholder { color: var(--t3); }

/* 标题 + 类型筛选，同一条线。它是「下面这块是什么」的说明，
   所以左边是文字标题（默认就是「队伍」），右边是筛选。
   —— 旧版的「找队伍」标题随那张大卡一起撤掉（阿凯：把「找队伍」改成「队伍」）。 */
.ex-bar {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 24px 0 0;
}
.ex-bar-t { font-size: 16px; font-weight: 600; letter-spacing: -.2px; }
.ex-bar-more { margin-left: auto; }
.ex-filters { display: flex; gap: 8px; flex-wrap: wrap; }
.ex-chip {
  font-size: 13px; color: var(--t2); text-decoration: none;
  border: 1px solid var(--line-strong); border-radius: 999px;
  padding: 6px 14px; background: none; white-space: nowrap;
}
.ex-chip.on { border-color: var(--t1); color: var(--t1); font-weight: 500; }

/* 有队伍的学校 —— 卡片网格，和下面的队伍卡同一套边框/圆角 */
.ex-schools { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 12px; }
.ex-school {
  display: flex; flex-direction: column; gap: 3px; text-decoration: none;
  border: 1px solid var(--line-strong); border-radius: var(--radius);
  padding: 15px 16px; transition: border-color .12s;
}
@media (hover: hover) and (pointer: fine) {

.ex-school:hover { border-color: var(--t3); }
}
.ex-school.on { border-color: var(--t1); }
.ex-school-name { font-size: 14px; font-weight: 500; color: var(--t1); }
.ex-school-meta { font-size: 12px; color: var(--t3); font-variant-numeric: tabular-nums; }
@media (max-width: 560px) { .ex-schools { grid-template-columns: 1fr; } }

/* 我的队伍 —— 已登录时置顶 */
.ex-mine { padding: 28px 0 4px; }
.ex-list { border-top: 1px solid var(--line); }
.ex-row {
  display: flex; align-items: center; gap: 16px;
  padding: 16px 0; border-bottom: 1px solid var(--line); text-decoration: none;
}
@media (hover: hover) and (pointer: fine) {

.ex-row:hover .ex-row-name { color: var(--accent-text); }
}
.ex-row-logo {
  width: 40px; height: 40px; flex-shrink: 0; border-radius: 10px; background: var(--fill);
  display: flex; align-items: center; justify-content: center;
  font-size: 15px; font-weight: 600; color: var(--t2); overflow: hidden;
}
.ex-row-logo img { width: 100%; height: 100%; object-fit: cover; }
.ex-row-body { flex: 1; min-width: 0; }
.ex-row-name { font-size: 15px; font-weight: 500; transition: color .12s; }
.ex-row-meta { font-size: 13px; color: var(--t3); margin-top: 2px; }
.ex-on { font-size: 13px; color: var(--t3); flex-shrink: 0; }
.ex-row-go { font-size: 13px; color: var(--t3); flex-shrink: 0; }

/* 队伍卡片网格 */
.ex-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
.ex-card {
  border: 1px solid var(--line-strong); border-radius: var(--radius);
  padding: 20px; text-decoration: none; display: flex; flex-direction: column;
  transition: border-color .12s; min-height: 148px;
}
@media (hover: hover) and (pointer: fine) {

.ex-card:hover { border-color: var(--t3); }
}
.ex-card-top { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; }
.ex-logo {
  width: 44px; height: 44px; flex-shrink: 0; border-radius: 12px; background: var(--fill);
  display: flex; align-items: center; justify-content: center;
  font-size: 16px; font-weight: 600; color: var(--t2); overflow: hidden;
}
.ex-logo img { width: 100%; height: 100%; object-fit: cover; }
.ex-card-hd { min-width: 0; flex: 1; }
.ex-name {
  font-size: 15px; font-weight: 600; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
.ex-school-label { font-size: 12px; color: var(--t3); margin-top: 2px; }
.ex-slogan {
  font-size: 13px; color: var(--t2); line-height: 1.6; flex: 1;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.ex-stats {
  display: flex; gap: 20px; margin-top: 14px; padding-top: 12px;
  border-top: 1px solid var(--line);
}
.ex-stat { display: flex; align-items: baseline; gap: 5px; }
.ex-stat b { font-size: 15px; font-weight: 600; font-variant-numeric: tabular-nums; }
.ex-stat span { font-size: 12px; color: var(--t3); }

/* 空状态 */
.ex-empty { padding: 64px 0; text-align: center; border-bottom: 1px solid var(--line); }
.ex-empty-t { font-size: 15px; color: var(--t2); }
.ex-empty-d { font-size: 13px; color: var(--t3); margin-top: 6px; }
.ex-empty-act { display: inline-block; margin-top: 20px; }

/* 建队 CTA —— 同样收进一张卡，和上面几块同一套语言。
   原来是 40px 上下的裸排版，夹在两条发丝线之间显得很空。 */
.ex-cta {
  border: 1px solid var(--line-strong); border-radius: var(--radius);
  padding: 22px 24px; display: flex; align-items: center; gap: 24px; flex-wrap: wrap;
}
.ex-cta-b { flex: 1; min-width: 260px; }
.ex-cta-t { font-size: 16px; font-weight: 600; }
.ex-cta-d { font-size: 13px; color: var(--t2); margin-top: 5px; }
.ex-cta-act { flex-shrink: 0; }

.btn-dark {
  display: inline-block; font-family: inherit; font-size: 14px; text-decoration: none;
  color: #fff; background: var(--t1); border: 1px solid var(--t1);
  border-radius: var(--radius-sm); padding: 10px 22px; cursor: pointer;
}
.btn-ghost {
  display: inline-block; font-family: inherit; font-size: 14px; text-decoration: none;
  color: var(--t1); background: #fff; border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm); padding: 10px 22px; cursor: pointer;
}
@media (hover: hover) and (pointer: fine) {

.btn-ghost:hover { border-color: var(--t3); }
}

@media (max-width: 900px) {
  .ex-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 720px) {
  .ex-hero { padding: 36px 0 24px; }
  .ex-hero h1 { font-size: 22px; }
  .ex-grid { grid-template-columns: 1fr; }
  /* 「队伍广场」+ 260px 胶囊在一行放不下（390px 实测 88 + 20 + 260 = 368 > 可用 350）
     ⇒ 让胶囊落到标题下面占满一行。手机上全宽的搜索比挤在标题右边好点。 */
  .ex-hero-top { gap: 14px; }
  .ex-mini { width: 100%; }
  .ex-bar { padding-top: 18px; }
  .ex-cta { gap: 16px; }
  .ex-cta-act { width: 100%; }
  .ex-cta-act .btn-dark, .ex-cta-act .btn-ghost { width: 100%; text-align: center; }
}

/* ============================================================
   创建队伍（/team/new）+ 提交成功页
   ============================================================ */
.cn-hero { padding: 48px 0 28px; border-bottom: 1px solid var(--line); }
.cn-form { max-width: 520px; }
.cn-form .field label .req {
  font-size: 11px; color: var(--accent-text); background: var(--accent-soft);
  border-radius: 4px; padding: 2px 6px; margin-left: 6px; font-weight: 500;
}
.cn-form select {
  width: 100%; font-family: inherit; font-size: 15px; color: var(--t1);
  border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
  padding: 11px 34px 11px 12px; background: #fff; outline: none;
  appearance: none; -webkit-appearance: none; cursor: pointer;
  /* 同上：必须自带箭头，否则它看起来就是个普通输入框 */
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 4.5L6 8L9.5 4.5' fill='none' stroke='%23999' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  background-size: 12px 12px;
}
.cn-form select:focus { border-color: var(--t2); }
.cn-form textarea {
  width: 100%; font-family: inherit; font-size: 15px; color: var(--t1);
  line-height: 1.6; border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
  padding: 11px 12px; background: #fff; outline: none; resize: vertical;
}
.cn-form textarea:focus { border-color: var(--t2); }
.cn-form .seg { width: max-content; }
.cn-form .seg label { padding: 9px 22px; font-size: 13px; color: var(--t2); cursor: pointer; }
.cn-note {
  font-size: 12px; color: var(--t3); line-height: 1.7;
  margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--line);
}

/* 补充资料折叠区：可选字段不占主视线，需要时才展开 */
.cn-more { margin-top: 26px; border-top: 1px solid var(--line); padding-top: 16px; }
.cn-more > summary {
  cursor: pointer; list-style: none; font-size: 13px; color: var(--t2);
  display: flex; align-items: center; gap: 8px;
}
.cn-more > summary::-webkit-details-marker { display: none; }
.cn-more > summary::before {
  content: '+'; font-size: 15px; line-height: 1; color: var(--t3);
  width: 12px; text-align: center;
}
.cn-more[open] > summary::before { content: '–'; }
@media (hover: hover) and (pointer: fine) {

.cn-more > summary:hover { color: var(--t1); }
}
.cn-more-b { display: flex; flex-direction: column; gap: 18px; margin-top: 20px; }

/* 提交成功 */
.sent-hero { padding: 56px 0 28px; border-bottom: 1px solid var(--line); }
.sent-hero .page-sub { max-width: 520px; }

@media (max-width: 720px) {
  .cn-hero { padding: 36px 0 22px; }
  .cn-form .seg { width: 100%; }
  .cn-form .seg label { flex: 1; text-align: center; }
  .sent-hero { padding: 40px 0 24px; }
}

/* ============================================================
   导航栏：队伍切换器 + 用户菜单
   ------------------------------------------------------------
   展开由**点击**控制（.is-open），开关写在 views/layout/head.ejs 底部的内联
   脚本里 —— 见那一节的长注释。

   ★ 为什么不继续用纯 CSS（:hover / :focus-within）：2026-10-02 阿凯报的 bug。
     菜单与按钮之间隔着 8px（见下面 .user-menu-list 的 top: calc(100% + 8px)）。
     指针经过那 8px 时既不在按钮上、也不在菜单上 → :hover 当场失效
     → display:none → 菜单在鼠标碰到它之前就消失了，永远点不到菜单项。
     :focus-within 补丁也不行：它救不了纯悬停，而且焦点留在按钮上时
     菜单永远关不掉（再点一次按钮也关不掉）。

   ★ 所以：展开条件绝不能依赖「指针停在某个元素上」或「焦点落在里面」。
     下面两条 html:not(.has-js) 是**无 JS 时的兜底**（脚本没跑起来就退回
     :focus-within，菜单至少点得开）；有 JS 时脚本会先给 html 打 .has-js，
     这两条自动退场，避免「关不掉」。
   ============================================================ */
.team-switch, .user-menu { position: relative; }

.team-switch-btn, .user-menu-btn {
  display: flex; align-items: center; gap: 8px;
  font-family: inherit; font-size: 13px; color: var(--t2);
  background: none; border: 1px solid var(--line-strong);
  border-radius: 999px; padding: 6px 14px; cursor: pointer; white-space: nowrap;
}
@media (hover: hover) and (pointer: fine) {

.team-switch-btn:hover, .user-menu-btn:hover { border-color: var(--t3); color: var(--t1); }
}
/* ★ 头像按钮的「当前所在」（2026-10-11 第五轮）。
   「我的」搬进头像菜单之后，/my 这一片子树里「我此刻在哪」就是头像本身。
   复用的还是同一套灰（--fill-strong）—— 全站「我在哪」只该有一个视觉语言。
   ⚠️ 必须写在 `.user-menu-btn:hover` **后面**：两者特异度相同（都 0,2,0），
     靠源序胜出。而 :hover 还会把 color 改深，所以这里把 color 一起写死，
     否则悬停在当前页上会出现「灰底是深一档、字却更黑」的怪组合。 */
.user-menu-btn.on {
  background: var(--fill-strong); border-color: var(--fill-strong); color: var(--t1);
}
.team-switch-caret, .user-caret { font-size: 9px; color: var(--t3); }

.user-avatar {
  width: 20px; height: 20px; flex-shrink: 0; border-radius: 50%; background: var(--fill);
  display: flex; align-items: center; justify-content: center;
  font-size: 10px; font-weight: 600; color: var(--t2); overflow: hidden;
}
.user-avatar img { width: 100%; height: 100%; object-fit: cover; }

.team-switch-menu, .user-menu-list {
  position: absolute; right: 0; top: calc(100% + 8px); z-index: 50;
  min-width: 220px; background: #fff;
  border: 1px solid var(--line-strong); border-radius: var(--radius);
  padding: 6px; display: none;
}
.team-switch.is-open .team-switch-menu,
.user-menu.is-open .user-menu-list { display: block; }

/* ★ 无 JS 兜底：脚本必须先把 .has-js 打到 html 上，这两条才会让位。
     有 JS 时若还留着，点按钮会让焦点停在按钮里 → 菜单永远关不掉。 */
html:not(.has-js) .team-switch:focus-within .team-switch-menu,
html:not(.has-js) .user-menu:focus-within .user-menu-list { display: block; }

.team-switch-menu a, .user-menu-list a {
  display: block; text-decoration: none; padding: 9px 12px; border-radius: var(--radius-sm);
}
@media (hover: hover) and (pointer: fine) {

.team-switch-menu a:hover, .user-menu-list a:hover { background: var(--fill); }
}
.team-switch-menu a.on, .user-menu-list a.on { background: var(--fill); }
.ts-name { display: block; font-size: 14px; color: var(--t1); font-weight: 500; }
.ts-meta { display: block; font-size: 12px; color: var(--t3); margin-top: 1px; }
.user-menu-list a { font-size: 14px; color: var(--t1); }
.ts-sep { height: 1px; background: var(--line); margin: 6px 4px; }
.ts-all { font-size: 13px !important; color: var(--t2) !important; }
.um-logout {
  display: block; width: 100%; text-align: left; font-family: inherit;
  font-size: 14px; color: var(--danger); background: none; border: none;
  padding: 9px 12px; border-radius: var(--radius-sm); cursor: pointer;
}
@media (hover: hover) and (pointer: fine) {

.um-logout:hover { background: var(--danger-soft); }
}
.nav-btn.solid { background: var(--t1); color: #fff; border-color: var(--t1); }

/* 站长后台内页 */
.adm-page { padding: 40px 0 80px; }
.adm-sec { padding: 32px 0; border-bottom: 1px solid var(--line); }
.adm-sec.last { border-bottom: none; }
.adm-tabs { display: flex; gap: 24px; border-bottom: 1px solid var(--line); }
.adm-tabs a {
  font-size: 14px; color: var(--t2); text-decoration: none;
  padding: 12px 0; border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.adm-tabs a.on { color: var(--t1); font-weight: 500; border-bottom-color: var(--t1); }
/* tab 上的待办数：没有它，队长看不出「另一边还有人在等」 */
.tab-n {
  display: inline-block; min-width: 16px; padding: 1px 5px; margin-left: 6px;
  border-radius: 9px; background: var(--accent-soft); color: var(--accent-text);
  font-size: 11px; font-weight: 600; line-height: 15px; text-align: center;
  font-variant-numeric: tabular-nums;
}
/* 审批表单：备注输入 + 两个动作按钮 */
.req-form { display: flex; flex-direction: column; gap: 6px; min-width: 190px; }
.req-form input[type="text"] {
  width: 100%; box-sizing: border-box;
  border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
  padding: 6px 9px; font-family: inherit; font-size: 12px; color: var(--t1);
}
.req-form input[type="text"]:focus { outline: none; border-color: var(--accent); }
.adm-kpis { display: flex; gap: 40px; flex-wrap: wrap; }
.adm-kpi-b { font-size: 26px; font-weight: 600; }
.adm-kpi-l { font-size: 13px; color: var(--t3); margin-top: 2px; }
.adm-card {
  border: 1px solid var(--line-strong); border-radius: var(--radius);
  padding: 18px 20px; margin-bottom: 14px;
}
.adm-card-hd { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.adm-card-t { font-size: 15px; font-weight: 600; }
.adm-card-d { font-size: 13px; color: var(--t2); margin-top: 6px; line-height: 1.7; }
.adm-card-act { display: flex; gap: 8px; margin-top: 14px; flex-wrap: wrap; }
.adm-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 14px; }
.adm-reason {
  font-size: 13px; color: var(--t2); background: var(--fill);
  border-radius: var(--radius-sm); padding: 10px 12px; margin-top: 10px; line-height: 1.7;
}
.adm-empty { font-size: 14px; color: var(--t3); padding: 28px 0; }
/* 站长台内页的搜索条（/admin/users）。
   ★ 它原来借的是广场页的 `.ex-search` —— 2026-10-12 广场重做搜索框时那条规则被删，
     这一页立刻塌成「输入框一行、按钮一行」，而且不报任何错。
     ⇒ 有自己的规则，别人的改版就不会把它带走。（守卫盯着这条） */
.adm-search {
  display: flex; align-items: center; gap: 8px;
  padding: 20px 0; border-bottom: 1px solid var(--line);
}
.adm-search input[type="search"] {
  flex: 1; min-width: 0; font-family: inherit; font-size: 14px; color: var(--t1);
  border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
  padding: 9px 14px; background: #fff; outline: none;
}
.adm-search input[type="search"]::placeholder { color: var(--t3); }
.adm-search input[type="search"]:focus { border-color: var(--t3); }

/* ===== 站长台「最近 7 天」的日访问条（2026-10-02 新增）=====
   刻意不做成图表：这一个块要回答的问题只有「哪天有人来、看了哪几页」，
   画成带坐标轴的折线反而看不清。条形的宽度是**行内 style**、
   按这 N 天里的最大值归一（见 views/admin/index.ejs）——
   分母不能用固定值，个位数访问量时所有条都会短得看不见，等于没画。
   视觉沿用站长台既有口径：发丝分隔、无阴影、绿点缀。 */
.adm-days { margin-top: 22px; display: flex; flex-direction: column; gap: 8px; max-width: 520px; }
.adm-day { display: flex; align-items: center; gap: 12px; font-size: 13px; }
.adm-day-d { color: var(--t3); flex-shrink: 0; width: 42px; }
.adm-day-bar { flex: 1; height: 6px; background: var(--fill); border-radius: 3px; overflow: hidden; }
.adm-day-bar i { display: block; height: 100%; background: var(--accent); border-radius: 3px; }
.adm-day-n { width: 30px; text-align: right; color: var(--t1); flex-shrink: 0; }
.adm-day-u { width: 46px; text-align: right; color: var(--t3); flex-shrink: 0; }
.adm-top { margin-top: 22px; display: flex; flex-wrap: wrap; gap: 8px 18px; align-items: baseline; font-size: 13px; }
.adm-top-h { color: var(--t3); }
.adm-top-i { color: var(--t2); }
.adm-top-i code { font-size: 12px; color: var(--t2); }
.adm-top-i b { margin-left: 6px; color: var(--t1); font-weight: 600; }

.btn-sm.danger { color: var(--danger); border-color: var(--line-strong); }
@media (hover: hover) and (pointer: fine) {

.btn-sm.danger:hover { border-color: var(--danger); }
}

@media (max-width: 720px) {
  /* 导航项在移动端已折到顶栏第二行（见 .nav-links 的移动端规则）——
     这里不要再写 display:none，CSS 顺序上它会覆盖掉那个折行规则。 */
  .adm-grid { grid-template-columns: 1fr; }
  .adm-kpis { gap: 24px; }
  .adm-kpi-b { font-size: 22px; }
  /* 窄屏下菜单必须**右对齐**（right:0），朝左展开。
     ★ 这条曾经写成 left:0 —— 而这两个按钮本来就在顶栏右侧
       （390 视口下头像按钮右边缘 ≈370），220px 宽的菜单从按钮左边往右伸，
       右边缘到 524，**超出屏幕 134px**：「队务管理台」「退出登录」被直接切掉。
       2026-10-02 用真实渲染量出来的（scripts/verify-user-menu.js）。
     ★ max-width 兜底：更窄的屏（320）也不至于顶出视口。 */
  .team-switch-menu, .user-menu-list {
    right: 0; left: auto;
    max-width: calc(100vw - 24px);
  }
}

/* ============================================================
   平台首页（产品落地页）· .lp-*
   ------------------------------------------------------------
   设计约束沿用全站规范：纯白底 / 发丝分隔 / 无阴影 / 绿色仅点缀。
   落地页比管理台「敢用一点空间」：section 之间留白到 88px，
   因为它的唯一任务是让人愿意往下滚。

   动效原则：只做「让人感觉产品是活的」的反馈（悬停抬升、箭头位移、
   数字入场），不做「先播片头再出内容」——那会吃掉 2-3 秒注意力。
   所有动效都包在 prefers-reduced-motion 里。
   ============================================================ */
.lp { overflow-x: hidden; }

/* ---------- Hero ---------- */
/* 【背景设计依据：Stripe 式「上三分之一氛围色，下方回白」】
   纯白板的问题不是不好看，是「没有纵深」——用户眼睛找不到落点，
   页面读起来像一张 A4 纸而不是一个产品。

   Stripe 的解法（那份 DESIGN.md 我读过了）是：用一大团柔和的多彩光晕占据
   页面上三分之一，文字和内容浮在上面，往下渐隐回纯白。

   【为什么用「模糊色块」而不是整块彩色背景】
   整块彩色会跟内容抢注意力，也扛不住长文案。模糊色块是「氛围」不是「底色」，
   文字压上去依然清晰——这才是大厂的实际做法。

   【为什么是绿/青/紫而不是 Stripe 的橙/紫/红】
   复用本项目已有的强调色系（绿 #2EBE6E），青、紫只做邻居色过渡，
   不乱入新色。Stripe 那份 Do/Don't 里明确写了「不要引入规范之外的强调色」，
   我们对自己的品牌同理。
*/
.lp-hero {
  position: relative;
  padding: 104px 0 88px;
  border-bottom: 1px solid var(--line);
  background: #fff;
  /* 让光晕只在这一层内可见，不外溢到下面的段落 */
  overflow: hidden;
  isolation: isolate;
}
/* 光晕层：三层径向渐变叠在一起，各自定位 + 自身模糊，
   形成「有光从侧上方打下来」的自然过渡（单层径向渐变会显得像一块脏斑）。
   z-index: -1 让它待在内容下方；isolation 保证不穿透到别的 section。

   【色相为什么只用绿→青，不要紫】
   第一版加了紫（#9676F0）。桌面宽屏下那团紫跑到右上角，和左边的绿形成
   一冷一暖对撞，看起来「脏」——手机端因为窄所以看不出来。
   去掉紫之后整片光晕是同一个色相家族的渐变（绿→青），干净得多。
   这也更符合「只有绿色点缀」这个既定调性：氛围色只能加深它，不能另开一个色系。
*/
.lp-hero::before {
  content: '';
  position: absolute;
  inset: -30% -10% auto -10%;
  height: 150%;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(42% 50% at 16% 22%, rgba(46, 190, 110, .34) 0%, rgba(46, 190, 110, 0) 70%),
    radial-gradient(38% 46% at 68% 8%, rgba(78, 176, 168, .24) 0%, rgba(78, 176, 168, 0) 72%),
    radial-gradient(34% 42% at 96% 40%, rgba(120, 190, 140, .18) 0%, rgba(120, 190, 140, 0) 74%);
  filter: blur(30px) saturate(108%);
  /* 往下渐隐回白：这是 Stripe「下方回白」的关键，没有这层就是一块死彩色 */
  -webkit-mask-image: linear-gradient(180deg, #000 0%, #000 42%, rgba(0, 0, 0, .5) 70%, transparent 100%);
  mask-image: linear-gradient(180deg, #000 0%, #000 42%, rgba(0, 0, 0, .5) 70%, transparent 100%);
}
/* 极细网格：给光晕一点「技术感」骨架，避免大面积模糊显得糊。
   【透明度压到 .045 还是太明显 —— 桌面宽屏上像方格纸】
   真正的 Stripe 式做法是「几乎看不见，只留下潜意识里的秩序感」。
   降到 .022 并把格子放大到 72px：单格更大 → 线更少 → 不形成格子观感。
   另外用两层 mask 做成「只在光晕附近隐约可见，往下完全消失」，
   而不是整屏铺满——铺满就会变成背景纹理，抢内容。 */
.lp-hero::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image:
    linear-gradient(to right, rgba(17, 17, 17, .022) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(17, 17, 17, .022) 1px, transparent 1px);
  background-size: 72px 72px;
  -webkit-mask-image: radial-gradient(64% 56% at 46% 0%, #000 0%, transparent 76%);
  mask-image: radial-gradient(64% 56% at 46% 0%, #000 0%, transparent 76%);
}
/* Hero 内容需要压过光晕，所以给内容层一个明确的 stacking context */
.lp-hero > .wrap { position: relative; z-index: 1; }

/* ⚠️ 这里曾经有一套 .lp-hero-grid（双栏）+ .lp-live（首屏实物卡）的规则，
   2026-10-02 第十三轮删掉了。删的理由不是样式问题，是**产品决定**：
   平台首屏不许出现某一支具体的队（推荐位 vs 名录，见 landing.ejs 顶部注释）。
   ★ 别把 .lp-live / .lp-hero-grid 加回来 —— **那两个类名名下装的就是「某一支队的
     私有内容」**，重建它们等于把那次的错误原样复制一遍。
   ★ 2026-10-02 第十四轮又把首屏做回双栏（`.lp-hero-cols`），但右栏换了内容：
     放的是**产品界面示意**（`.lp-hero-ui`），队名是「你的队名」占位、
     没有任何真实数据、且不可点 —— 这就是第十三轮那条区分里允许的那一半。
     换句话说：**首屏双栏不是错，右栏放「某支队」才是错。** */

/* ---------- 首屏双栏（2026-10-02 第十四轮） ----------
   左栏放主张，右栏放「这东西长什么样」。
   ⚠️ 右栏宽 380px 是按左栏 H1 的**最长一行**反推出来的，不是审美取值：
      wrap 1080 − 左右 padding 48 = 1032；1032 − 380 − 48(gap) = 604px 给左栏；
      而最长一行「荣耀、队史、训练」8 全角字 × 52px ≈ 406px。
      改右栏宽度 / 改 H1 字号，都要重新验一遍「最长那行还放不放得下」——
      H1 的换行是**显式 <br>**，超宽不会自己换行，是直接横向溢出。 */
.lp-hero-cols {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 380px;
  gap: 48px;
  align-items: center;
}
.lp-hero-lead { min-width: 0; }
/* ⚠️ 说明句 + 卡必须包在**同一个容器**里（`.lp-hero-side`）。
   它们要是直接挂在 .lp-hero-cols 下，grid 会把它们当成两栏的**两个格子** ——
   于是 lead 和说明句占第一行、卡被挤到第二行掉到左栏下面去。
   2026-10-02 第十六轮踩过，`npm test` 全绿也照样踩（断言只看 HTML 字符串），
   是预览截图里一眼看出来的。 */
.lp-hero-side { min-width: 0; }

/* ---------- 队伍主页示意卡（首屏右栏） ----------
   ★ 三条硬红线，缺一就退回了第十三轮被否掉的那张卡：
     ① 不许有真实队名 —— 队名位置是「你的队名」+ 虚线底，表示「这格等你的队名」；
     ② 不许有真实数字 —— 三行是**类目**（相当于目录），不是数据；
     ③ **不许是 <a>、不可点** —— 可点就跳走，一跳就从「产品界面」变成「平台钦点」。
   观感上它要像「产品截图」，但不能真用截图：截图会过时，还会把读者可能刚翻过来的
   那个页面降级成 PNG（这两条第十二轮已经否掉了）。所以用真 HTML 搭一个骨架。 */
/* 卡上方这句是这块的**身份说明**，不是装饰（2026-10-02 第十六轮补）。
   ★ 阿凯第一次看到这张卡时问的原话：「这个放这里是干嘛的，什么意思」。
     那一问本身就是判据：**一块内容如果需要靠嘴解释才懂，它就是不成立的。**
     病根不是卡做得不好，是它没说清自己和左边 H1 的关系 —— H1 讲「荣耀、队史、训练
     一直留在队里」，卡里又列了一遍同样三个词，看起来像把 H1 抄了第二遍。
     这句把分工点开：左栏是**承诺**（本来就该归你的队），右栏是**兑现的样子**。
   ⚠️ 删掉它，卡就退回「一张写着『你的队名』的空卡」—— 守卫 ⑤ 会红。 */
.lp-ui-cap { font-size: 13px; color: var(--t3); margin-bottom: 12px; }

.lp-hero-ui {
  background: #fff;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  padding: 20px 22px 18px;
}
.lp-ui-head {
  display: flex; align-items: center; gap: 12px;
  padding-bottom: 16px; border-bottom: 1px solid var(--line);
}
.lp-ui-logo {
  width: 40px; height: 40px; border-radius: 50%; flex: none;
  background: var(--fill); color: var(--t3);
  display: flex; align-items: center; justify-content: center;
}
.lp-ui-logo svg { width: 21px; height: 21px; }
.lp-ui-id { min-width: 0; }
/* 虚线底是全页唯一一处「刻意留空」的地方：它得让人一眼看出这格是空的。
   实线会读成「已经有了」，点线/虚线才读成「待填」。 */
.lp-ui-name {
  display: inline-block; font-size: 15px; font-weight: 500; color: var(--t2);
  border-bottom: 1px dashed var(--line-strong); padding-bottom: 1px;
}
.lp-ui-tag { display: block; font-size: 12px; color: var(--t3); margin-top: 5px; }
.lp-ui-list { list-style: none; }
.lp-ui-list li { display: flex; align-items: baseline; gap: 12px; padding: 12px 0; }
/* 发丝分隔用在行之间（不是行外围），读起来才像「一页里的几个区块」 */
.lp-ui-list li + li { border-top: 1px solid var(--line); }
.lp-ui-t {
  flex: none; width: 36px; letter-spacing: .5px;
  font-size: 14px; font-weight: 600; color: var(--t1);
}
.lp-ui-d { font-size: 13px; color: var(--t3); }
.lp-ui-foot {
  display: flex; align-items: center; gap: 7px;
  margin-top: 2px; padding-top: 14px; border-top: 1px solid var(--line);
  font-size: 12.5px; color: var(--t3);
}
.lp-ui-foot svg { width: 14px; height: 14px; flex: none; }

/* 单栏化：不等到 900px 才收 —— 1020px 以下左栏会窄到装不下 H1 最长那行。
   收起时把卡限宽，否则它会在整行里拉成一条横片，看着像横幅。 */
@media (max-width: 1020px) {
  .lp-hero-cols { grid-template-columns: minmax(0, 1fr); gap: 36px; }
  .lp-hero-ui { max-width: 440px; }
}

.lp-hero-eyebrow {
  display: inline-block;
  font-size: 13px; color: var(--accent-text);
  background: var(--accent-soft);
  border-radius: 999px; padding: 5px 14px;
  margin-bottom: 26px; letter-spacing: .2px;
}
.lp-hero-title {
  /* 46px → 52px：第十二轮为了迁就双栏（左栏约 590px）压到 46px，
     第十三轮首屏恢复单栏后放得下了，改回 52px。
     ⚠️ 第十四轮又做回双栏（左栏 604px），52px 仍然放得下最长那行（≈406px），
        所以字号没动 —— 但这条余量是按 604px 算的，右栏一变就要重算。
     另：max-width 760 现在没有实际作用（左栏只有 604px），留着是为了单栏化那一档
        （≤1020px 时左栏是整行宽，这行限制才生效）。 */
  font-size: 52px; line-height: 1.22; font-weight: 600;
  letter-spacing: -1.2px; color: var(--t1);
  margin-bottom: 24px; max-width: 760px;
}
/* 标题固定三行：把队伍的 / 荣耀、队史、训练 / 一直留在队里（4 / 8 / 6 字）。
   换行由显式 <br class="lp-hero-br"> 控制，任何宽度都是整齐的三行。
   ⚠️ 这里**不能**给 .lp-hero-br 设 display:none —— 一设就退回
      「靠容器宽度隐式断行」，窄容器下会断在「荣耀、队 / 史、训练」中间。 */

.lp-em {
  /* 用绿色「马克笔」式底衬而不是整段变色：点缀，不是铺色。
     ⚠️ 必须配 box-decoration-break: clone —— 否则换行时背景会被截断成
     两块互不相连的色带（这个标题在窄屏上一定会换行）。 */
  background: linear-gradient(180deg, transparent 62%, var(--accent-soft) 62%);
  padding: 0 3px;
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
}
.lp-hero-sub {
  font-size: 17px; line-height: 1.8; color: var(--t2);
  max-width: 600px; margin-bottom: 40px;
}
.lp-hero-act { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
.lp-btn {
  display: inline-flex; align-items: center; justify-content: center;
  height: 48px; padding: 0 28px;
  font-size: 15px; font-weight: 500; text-decoration: none;
  border-radius: 999px; border: 1px solid transparent;
  transition: transform .16s ease, background .16s ease, border-color .16s ease;
}
.lp-btn-primary { background: var(--t1); color: #fff; }
@media (hover: hover) and (pointer: fine) {

.lp-btn-primary:hover { transform: translateY(-1px); background: #000; }
}
.lp-btn-ghost { border-color: var(--line-strong); color: var(--t1); background: #fff; }
@media (hover: hover) and (pointer: fine) {

.lp-btn-ghost:hover { border-color: var(--t3); }
}
.lp-hero-note { font-size: 13px; color: var(--t3); margin-top: 18px; }

/* ⚠️ 这里曾经是「信任条 / 证据条」（.lp-trust*），2026-10-02 第十三轮整块删掉。
   它前后换过两版机制，两版都不成立：
     · 第一版「社会认同」—— 1 支队伍 / 24 项荣誉 / 1 所学校 / 9 条队史，
       四个数字三个是 1，等于在最显眼的位置自曝「只有一支队」。
     · 第二版「证据」—— 改成讲「这一支队有多少年、多少东西」，
       数字好看了，但**它讲的仍然是一支具体的队**，跟首屏那张卡同一个毛病。
   ★ 结论：单队阶段，**平台上任何拿数字自证的位置都是自曝**，别再加回来。
     真要重建这一格，前提是「数字属于平台（复数）而不是某一支队」，
     而那要等真的有多支队在用 —— 不是靠改文案。 */

/* ---------- 通用 section ---------- */
.lp-sec { padding: 88px 0; border-bottom: 1px solid var(--line); }
.lp-sec-alt { background: #FCFCFC; }
.lp-sec-head { margin-bottom: 48px; }
.lp-sec-head-row {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 24px; flex-wrap: wrap;
}
.lp-sec-title {
  font-size: 30px; font-weight: 600; letter-spacing: -.5px;
  color: var(--t1); margin-bottom: 10px;
}
.lp-sec-sub { font-size: 15px; color: var(--t2); }
.lp-more { font-size: 14px; color: var(--t2); text-decoration: none; white-space: nowrap; }
@media (hover: hover) and (pointer: fine) {

.lp-more:hover { color: var(--t1); }
}

/* ---------- 功能三列 ---------- */
.lp-grid3 {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 1px;
  background: var(--line); border: 1px solid var(--line);
  border-radius: var(--radius); overflow: hidden;
}
.lp-feat {
  background: #fff; padding: 32px 28px;
  display: flex; flex-direction: column;
  transition: background .18s ease;
}
@media (hover: hover) and (pointer: fine) {

.lp-feat:hover { background: #FCFCFC; }
}
.lp-feat-icon {
  width: 38px; height: 38px; border-radius: 10px;
  background: var(--fill); color: var(--t1);
  display: flex; align-items: center; justify-content: center;
  margin-bottom: 20px;
}
.lp-feat-icon svg { width: 20px; height: 20px; }
/* 引文层 —— 古籍原文。唯一的一行标题（第七轮起，出处行与 4+4 释义层都砍掉了） */
.lp-feat-t { font-size: 16px; font-weight: 600; color: var(--t1); margin-bottom: 10px; line-height: 1.5; }
.lp-feat-d { font-size: 14px; line-height: 1.75; color: var(--t2); flex: 1; }
.lp-feat-tag {
  margin-top: 18px; font-size: 12px; color: var(--accent-text);
  letter-spacing: .3px;
}

/* ---------- 对照：散在各处 / 归在一处（2026-10-02 新增，替掉原「三步」） ----------
   队长的决策是「换不换」，而**「换」只能靠并排看**做出来 —— 单向陈述给不出
   「不换」的代价。所以这里必须是两栏对照，不能退化成一栏的「我们的优点清单」。
   ⚠️ 左栏是「现状」，不是「你的错」：措辞不许有任何指责口吻。
   ★ 2026-10-11 第十七轮起，左栏只讲「东西在哪儿」（中性清点），一条抱怨都不留 ——
     理由见 landing.ejs 顶部第十七轮。判据：读起来像「你现在很惨」就是写错了。 */
.lp-cmp { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; }
.lp-cmp-col {
  background: #fff; border: 1px solid var(--line); border-radius: var(--radius);
  padding: 28px 26px;
}
/* 右栏（归在一处）加一道点缀色边 —— 只用来指示「这是答案」，不铺色 */
.lp-cmp-col-on { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent-soft); }
.lp-cmp-head {
  font-size: 13px; font-weight: 600; letter-spacing: .2px;
  color: var(--t3); margin-bottom: 18px;
}
.lp-cmp-col-on .lp-cmp-head { color: var(--accent-text); }
.lp-cmp-list { list-style: none; margin: 0; padding: 0; }
.lp-cmp-list li {
  font-size: 14px; line-height: 1.7; color: var(--t2);
  padding: 9px 0 9px 18px; position: relative;
}
.lp-cmp-list li::before {
  content: ''; position: absolute; left: 0; top: 17px;
  width: 5px; height: 5px; border-radius: 999px;
  /* 左栏刻意弱于右栏，但 #E5E5E5 太淡了 —— 淡到像没渲染出来，
     读者会以为那栏坏了。用 #D0D0D0：仍然明显弱于绿点，但看得见。 */
  background: #D0D0D0;
}
.lp-cmp-col-on .lp-cmp-list li::before { background: var(--accent); }

/* ---------- 已入驻队伍 ---------- */
.lp-teams { display: flex; flex-direction: column; gap: 1px; background: var(--line); border-radius: var(--radius); overflow: hidden; }
.lp-team {
  display: flex; align-items: center; gap: 16px;
  padding: 20px 24px; background: #fff; text-decoration: none;
  transition: background .16s ease, padding-left .16s ease;
}
@media (hover: hover) and (pointer: fine) {

.lp-team:hover { background: #FCFCFC; padding-left: 30px; }
}
.lp-team-logo {
  width: 46px; height: 46px; flex-shrink: 0; border-radius: 12px;
  background: var(--fill); color: var(--t2);
  display: flex; align-items: center; justify-content: center;
  font-size: 18px; font-weight: 600; overflow: hidden;
}
.lp-team-logo img { width: 100%; height: 100%; object-fit: cover; }
.lp-team-body { flex: 1; min-width: 0; }
.lp-team-name { display: block; font-size: 16px; font-weight: 500; color: var(--t1); margin-bottom: 3px; }
.lp-team-meta { display: block; font-size: 13px; color: var(--t3); }
.lp-team-stats { font-size: 13px; color: var(--t2); white-space: nowrap; }
.lp-team-stats b { font-weight: 600; color: var(--t1); }

/* ---------- 滚动淡入 ----------
   【为什么用 CSS 类 + IntersectionObserver 而不是纯 CSS animation】
   纯 CSS animation 在页面加载时就开始跑，用户还没滚到那一段，动画已经播完了——
   滚下去看到的是静止的，等于白做。必须由「是否进入视口」来触发。

   【为什么初始状态用 opacity:0 是安全的】
   有个经典坑：JS 挂了就永远不显示（内容白屏）。所以：
     ① 初始隐藏只在 html.js-ready 下生效（见下方 .js-ready 规则），
        没 JS 时内容正常显示；
     ② HTML 里给动效元素挂的是 class，脚本进不来也不影响可读性。
   这是必须做的兜底，不是可选项。
*/
.reveal { transition: opacity .62s var(--ease-out), transform .62s var(--ease-out); }
.js-ready .reveal { opacity: 0; transform: translateY(16px); }
.js-ready .reveal.is-in { opacity: 1; transform: none; }

/* 同组元素依次入场：给网格里的卡片错开 60ms，产生「一波推上来」的节奏。
   用 class 而不是 JS 里写 setTimeout，省一次遍历也少一个失败点。 */
.js-ready .reveal-d1 { transition-delay: .06s; }
.js-ready .reveal-d2 { transition-delay: .12s; }
.js-ready .reveal-d3 { transition-delay: .18s; }
.js-ready .reveal-d4 { transition-delay: .24s; }
.js-ready .reveal-d5 { transition-delay: .30s; }
.js-ready .reveal-d6 { transition-delay: .36s; }

/* ---------- 最终 CTA ---------- */
/* 收尾段不是纯灰板：给一层极淡的绿色光晕，和首屏呼应。
   透明度压到首屏的三分之一左右——收尾要「安静」，不能跟首屏抢戏。 */
.lp-final {
  position: relative; padding: 96px 0; text-align: center;
  background: #FCFCFC; overflow: hidden; isolation: isolate;
}
.lp-final::before {
  content: ''; position: absolute; inset: -40% -10% auto -10%; height: 160%;
  z-index: -1; pointer-events: none;
  background: radial-gradient(42% 50% at 50% 8%, rgba(46, 190, 110, .14) 0%, rgba(46, 190, 110, 0) 70%);
  filter: blur(26px);
  -webkit-mask-image: linear-gradient(180deg, #000 0%, transparent 82%);
  mask-image: linear-gradient(180deg, #000 0%, transparent 82%);
}
.lp-final-t { font-size: 32px; font-weight: 600; letter-spacing: -.6px; color: var(--t1); margin-bottom: 14px; }
.lp-final-d { font-size: 16px; color: var(--t2); margin-bottom: 36px; }
.lp-final .lp-hero-act { justify-content: center; }

/* ---------- 页脚 ---------- */
.lp-foot {
  border-top: 1px solid var(--line); padding: 32px 0;
  font-size: 13px; color: var(--t3);
}
.lp-foot .wrap { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.lp-foot-links { display: flex; gap: 22px; }
.lp-foot-links a { color: var(--t3); text-decoration: none; }
@media (hover: hover) and (pointer: fine) {

.lp-foot-links a:hover { color: var(--t1); }
}

/* ---------- 响应式 ---------- */
/* 桌面宽视口下同样一团光晕被摊薄了（面积大 → 看着更淡），
   所以宽屏要单独提一档强度，而不是让它「越宽越没颜色」。 */
@media (min-width: 1100px) {
  .lp-hero::before {
    background:
      radial-gradient(40% 54% at 14% 20%, rgba(46, 190, 110, .60) 0%, rgba(46, 190, 110, 0) 70%),
      radial-gradient(36% 50% at 64% 6%, rgba(78, 176, 168, .42) 0%, rgba(78, 176, 168, 0) 72%),
      radial-gradient(32% 46% at 94% 38%, rgba(120, 190, 140, .32) 0%, rgba(120, 190, 140, 0) 74%);
  }
}

@media (max-width: 900px) {
  .lp-hero { padding: 72px 0 64px; }
  .lp-hero-title { font-size: 38px; letter-spacing: -.8px; }
  .lp-hero-sub { font-size: 16px; }
  .lp-sec { padding: 64px 0; }
  .lp-sec-title { font-size: 25px; }
  .lp-grid3 { grid-template-columns: repeat(2, 1fr); }
  .lp-cmp { grid-template-columns: 1fr; gap: 16px; }
  .lp-final { padding: 72px 0; }
  .lp-final-t { font-size: 26px; }
}

@media (max-width: 640px) {
  .lp-hero-title { font-size: 31px; }
  .lp-hero-act { flex-direction: column; align-items: stretch; }
  .lp-btn { width: 100%; }
  .lp-grid3 { grid-template-columns: 1fr; }
  .lp-team { padding: 16px 18px; gap: 12px; }
  .lp-team:hover { padding-left: 18px; }
  .lp-team-stats { display: none; }
  .lp-sec-head-row { align-items: flex-start; }
}

/* 尊重系统「减少动态效果」设置
   ⚠️ 这里必须把 .js-ready 下的隐藏状态也一并解除——
   只写 transition: none 是不够的：元素仍停在 opacity:0 + translateY(16px)，
   等于内容彻底看不见。这是「无障碍兜底反而造成内容不可见」的典型坑。 */
@media (prefers-reduced-motion: reduce) {
  .lp-btn, .lp-feat, .lp-team { transition: none; }
  .lp-btn-primary:hover { transform: none; }
  .lp-team:hover { padding-left: 24px; }
  .reveal, .js-ready .reveal { opacity: 1 !important; transform: none !important; transition: none !important; }
}

/* ============================================================
   队员视角（/my）
   与管理台刻意区分：没有侧栏、没有管理按钮，只讲「跟我有关」的事。
   复用 .panel / .row / .adm-grid / .adm-tabs，只新增三组必要样式：
   球员卡、通知条目、申请进度时间线。
   ============================================================ */

/* 未读小点（用在「通知」tab 上） */
.udot {
  display: inline-block; width: 6px; height: 6px; border-radius: 50%;
  background: var(--accent); margin-left: 5px; vertical-align: 2px;
}

/* ---------- 球员卡 ----------
   这是「出勤率」的答案：把考核数据换成队内身份。
   编号 + 飞盘名是队里最有身份感的两样东西，而且都已全量录入。 */
.pc {
  display: flex; align-items: center; gap: 22px;
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: 24px; margin-bottom: 20px;
}
.pc-no {
  font-size: 42px; font-weight: 600; line-height: 1; letter-spacing: -1.5px;
  color: var(--accent-text); min-width: 76px; flex-shrink: 0;
}
.pc-no.dim { color: var(--t3); }
.pc-main { flex: 1; min-width: 0; }
.pc-name { font-size: 20px; font-weight: 600; letter-spacing: .2px; }
.pc-real { font-size: 13px; color: var(--t3); margin-top: 4px; }
.pc-meta { font-size: 12px; color: var(--t3); margin-top: 7px; }
.pc-stats { display: flex; gap: 26px; flex-shrink: 0; text-align: right; }
.pc-stats b {
  display: block; font-size: 20px; font-weight: 600;
  font-variant-numeric: tabular-nums; letter-spacing: -.3px;
}
.pc-stats b em { font-style: normal; font-size: 12px; font-weight: 400; color: var(--t3); margin-left: 2px; }
.pc-stats span { font-size: 11px; color: var(--t3); }

/* ---------- 通知条目 ---------- */
.nt { display: flex; gap: 14px; padding: 16px 0; border-bottom: 1px solid var(--line); }
.nt-dot {
  width: 7px; height: 7px; border-radius: 50%; background: var(--accent);
  margin-top: 8px; flex-shrink: 0;
}
.nt-dot.off { background: var(--line-strong); }
.nt-b { flex: 1; min-width: 0; }
.nt-t { font-size: 15px; font-weight: 500; text-decoration: none; display: block; }
@media (hover: hover) and (pointer: fine) {

.nt-t:hover { color: var(--accent-text); }
}
.nt-t.read { font-weight: 400; color: var(--t2); }
.nt-m { font-size: 13px; color: var(--t2); margin-top: 3px; }
.nt-d { font-size: 12px; color: var(--t3); margin-top: 6px; }

/* ---------- 申请进度（三态时间线） ---------- */
.st { padding-top: 2px; }
.st-row { display: flex; gap: 12px; }
.st-rail { width: 10px; flex-shrink: 0; display: flex; flex-direction: column; align-items: center; }
.st-dot {
  width: 9px; height: 9px; border-radius: 50%; flex-shrink: 0;
  border: 1.5px solid var(--line-strong); background: #fff; margin-top: 6px;
}
.st-dot.on { border-color: var(--accent); background: var(--accent); }
.st-dot.bad { border-color: var(--danger); background: var(--danger); }
.st-line { width: 1px; flex: 1; background: var(--line-strong); margin: 3px 0 1px; }
.st-b { flex: 1; min-width: 0; padding-bottom: 16px; }
.st-t { font-size: 14px; font-weight: 500; }
.st-t.off { color: var(--t3); font-weight: 400; }
.st-t.bad { color: var(--danger); }
.st-d { font-size: 12px; color: var(--t3); margin-top: 2px; }

/* ---------- 空状态 ---------- */
.my-empty { padding: 40px 0; text-align: center; }
.my-empty-t { font-size: 15px; color: var(--t2); }
.my-empty-d { font-size: 13px; color: var(--t3); margin-top: 6px; }

/* ============================================================
   训练点评（.cmt）
   刻意不复用通知的 .nt —— 两者长得像但语义不同：
   .nt-t 是「标题」且可点，.cmt-body 是「正文」且不跳转。
   混用会让「改通知的间距」意外改掉点评的排版。
   ============================================================ */
.cmt-list { border-top: 1px solid var(--line); }
.cmt { padding: 16px 0; border-bottom: 1px solid var(--line); }
.cmt-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.cmt-who { font-size: 14px; font-weight: 600; }
.cmt-time { font-size: 12px; color: var(--t3); }
.cmt-del {
  margin-left: auto; border: 0; background: none; cursor: pointer;
  font-size: 12px; color: var(--t3); padding: 2px 4px; font-family: inherit;
}
@media (hover: hover) and (pointer: fine) {

.cmt-del:hover { color: var(--danger); }
}
.cmt-body {
  font-size: 14px; line-height: 1.7; color: var(--t2);
  margin-top: 7px; white-space: pre-wrap; word-break: break-word;
}
.cmt-form { margin-top: 18px; }
.cmt-form textarea {
  width: 100%; box-sizing: border-box; resize: vertical;
  border: 1px solid var(--line-strong); border-radius: var(--radius);
  padding: 12px 14px; font-family: inherit; font-size: 14px; line-height: 1.6;
  color: var(--t1); background: #fff;
}
.cmt-form textarea:focus { outline: none; border-color: var(--accent); }

/* ============================================================
   队务概览：掉队预警 + 发公告
   ============================================================ */

/* 掉队名单。刻意不做成表格 —— 它不是考核记录，是「该去问一句」的清单。 */
.risk-list { display: flex; flex-direction: column; }
.risk {
  display: flex; align-items: baseline; gap: 12px;
  padding: 11px 0; border-bottom: 1px solid var(--line);
}
.risk:last-child { border-bottom: none; }
.risk-no {
  min-width: 46px; font-size: 15px; font-weight: 600; color: var(--t3);
  font-variant-numeric: tabular-nums;
}
.risk-name { font-size: 14px; font-weight: 500; flex: 1; min-width: 0; }
.risk-n { font-size: 13px; color: var(--t2); }
.risk-n b { color: var(--danger); font-weight: 600; }

/* 发公告表单 */
.ann-form { display: flex; flex-direction: column; gap: 10px; }
.ann-form input[type="text"],
.ann-form textarea {
  width: 100%; box-sizing: border-box;
  border: 1px solid var(--line-strong); border-radius: var(--radius);
  padding: 10px 14px; font-family: inherit; font-size: 14px; line-height: 1.6;
  color: var(--t1); background: #fff;
}
.ann-form textarea { resize: vertical; }
.ann-form input[type="text"]:focus,
.ann-form textarea:focus { outline: none; border-color: var(--accent); }

/* ============================================================
   公开层 · 飞盘独有页（精神分 / 背号墙 / 赛季回顾）
   规范不变：纯白底、发丝分隔、无阴影、绿色只作点缀。
   这几页的说服力来自「数字本身」，所以排版让数字站前面，修饰语往后放。
   ============================================================ */

/* ---------- 共同：一段「先讲清楚这是什么」的引言 ----------
   精神分和背号墙都有一件必须先说的事 —— 不然访客（尤其新生）
   看着一堆数字不知道在说什么。所以这两页都从一段解释开始。 */
.sp-hero {
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  padding: 26px 0 24px;
  margin-bottom: 38px;
}
.sp-hero-t { font-size: 19px; font-weight: 600; letter-spacing: .2px; margin-bottom: 12px; }
.sp-hero-d { font-size: 15px; color: var(--t2); line-height: 1.85; margin-bottom: 10px; max-width: 640px; }
.sp-hero-d b { color: var(--t1); font-weight: 600; }
.sp-hero-quiet { color: var(--t3); font-size: 14px; margin-top: 14px; }

/* ---------- 精神分：四个关键数 ---------- */
.sp-kpis {
  display: flex; gap: 44px; flex-wrap: wrap;
  padding: 0 0 30px;
}
.sp-kpi { min-width: 128px; }
.sp-kpi-n {
  font-size: 32px; font-weight: 600; line-height: 1.15; letter-spacing: -.8px;
  font-variant-numeric: tabular-nums;
}
.sp-kpi-n em { font-style: normal; font-size: 13px; font-weight: 400; color: var(--t3); margin-left: 3px; }
.sp-kpi-l { font-size: 12px; color: var(--t3); margin-top: 5px; }

.sp-note {
  font-size: 13px; color: var(--t2);
  background: var(--fill); border-radius: var(--radius-sm);
  padding: 12px 16px; margin-bottom: 30px;
}

/* ---------- 精神分：逐场 ---------- */
.sp-row { padding: 18px 0; border-bottom: 1px solid var(--line); }
.sp-row:last-child { border-bottom: none; }
.sp-row-main { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; }
.sp-row-title { font-size: 15px; font-weight: 500; }
.sp-row-meta { font-size: 13px; color: var(--t3); }
.sp-row-bars { margin-top: 12px; display: flex; flex-direction: column; gap: 7px; }

.sp-bar { display: flex; align-items: center; gap: 10px; }
.sp-bar-l { font-size: 12px; color: var(--t3); width: 76px; flex-shrink: 0; }
.sp-bar-track {
  position: relative; flex: 1; height: 6px; min-width: 60px;
  background: var(--fill); border-radius: 3px; overflow: hidden;
}
.sp-bar-fill { position: absolute; inset: 0 auto 0 0; border-radius: 3px; }
.sp-bar-fill.them { background: var(--accent); }
.sp-bar-fill.us { background: var(--line-strong); }
/* 10 分基准线：把「惯例基准」画出来，比写一句注释直观 */
.sp-bar-base {
  position: absolute; top: -2px; bottom: -2px; left: 50%;
  width: 1px; background: var(--t3); opacity: .55;
}
.sp-bar-v {
  width: 30px; text-align: right; flex-shrink: 0;
  font-size: 13px; font-weight: 600; font-variant-numeric: tabular-nums;
}
.sp-row-note {
  margin-top: 10px; font-size: 13px; color: var(--t2);
  padding-left: 12px; border-left: 2px solid var(--line-strong);
}
.sp-legend { font-size: 13px; color: var(--t3); margin-top: 18px; line-height: 1.7; }

/* ---------- 背号墙 ---------- */
.nw-hint { font-size: 13px; color: var(--t3); margin: -4px 0 16px; max-width: 620px; line-height: 1.7; }
.nw-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(196px, 1fr));
  gap: 1px; background: var(--line); border: 1px solid var(--line);
  border-radius: var(--radius); overflow: hidden;
}
.nw-cell {
  display: flex; align-items: center; gap: 14px;
  background: #fff; padding: 16px 18px; min-height: 84px;
}
.nw-cell.dim .nw-no { color: var(--t3); }
.nw-no {
  font-size: 30px; font-weight: 600; line-height: 1; letter-spacing: -1px;
  color: var(--accent-text); min-width: 46px; flex-shrink: 0;
  font-variant-numeric: tabular-nums;
}
.nw-main { min-width: 0; flex: 1; }
.nw-name { font-size: 14px; font-weight: 500; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.nw-meta { font-size: 12px; color: var(--t3); margin-top: 4px; font-variant-numeric: tabular-nums; }
.nw-cell.dim .nw-name { font-weight: 400; color: var(--t2); }

/* ---------- 赛季回顾 ---------- */
.ss-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 1px; background: var(--line); border: 1px solid var(--line);
  border-radius: var(--radius); overflow: hidden; margin-bottom: 34px;
}
.ss-cell { background: #fff; padding: 20px 22px; }
.ss-n {
  font-size: 30px; font-weight: 600; line-height: 1.15; letter-spacing: -.8px;
  font-variant-numeric: tabular-nums;
}
.ss-n em { font-style: normal; font-size: 13px; font-weight: 400; color: var(--t3); margin-left: 3px; }
.ss-l { font-size: 12px; color: var(--t3); margin-top: 5px; }
.ss-d { font-size: 12px; color: var(--t3); margin-top: 3px; font-variant-numeric: tabular-nums; }

.ss-highlight {
  border: 1px solid var(--accent-soft); background: var(--accent-soft);
  border-radius: var(--radius); padding: 20px 24px; margin-bottom: 34px;
}
.ss-hl-label { font-size: 12px; color: var(--accent-text); letter-spacing: .3px; }
.ss-hl-main {
  font-size: 19px; font-weight: 500; margin-top: 8px;
  display: flex; align-items: baseline; justify-content: space-between; gap: 16px; flex-wrap: wrap;
}
.ss-hl-main b { font-weight: 600; }
.ss-hl-score {
  font-size: 24px; font-weight: 600; color: var(--accent-text);
  font-variant-numeric: tabular-nums;
}
.ss-hl-score em { font-style: normal; font-size: 12px; font-weight: 400; margin-left: 2px; }
.ss-hl-meta { font-size: 13px; color: var(--accent-text); opacity: .85; margin-top: 6px; }

.ss-foot-note {
  font-size: 14px; color: var(--t2); line-height: 1.8;
  border-top: 1px solid var(--line); padding-top: 22px; margin-top: 34px;
}

/* ============================================================
   队徽 / 队史列表 / 队史批量导入（2026-10-03）
   ============================================================ */

/* ---------- 队徽（队伍资料页） ----------
   左边一张大预览，右边选文件 —— 这样点「保存队徽」之前就能看见会变成什么样。
   （原来只有一个地址输入框，用户填完不知道对不对，只能存了再回去刷新看。） */
.logo-row { display: flex; gap: 20px; align-items: flex-start; }
.logo-preview {
  width: 88px; height: 88px; flex-shrink: 0;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--fill); overflow: hidden;
  display: flex; align-items: center; justify-content: center;
  font-size: 30px; font-weight: 600; color: var(--t2);
}
.logo-preview img { width: 100%; height: 100%; object-fit: cover; }
.logo-col { flex: 1; min-width: 0; }
/* 原生 file input 的默认样式在各浏览器里差别很大，且和本站的输入框完全不像一套。
   这里只做最小规整：宽度撑满、字号一致，不重造一个假按钮（假按钮要靠 JS 同步文件名）。 */
.logo-file input[type="file"] {
  width: 100%; font-family: inherit; font-size: 13px; color: var(--t2);
}
.logo-file input[type="file"]::file-selector-button {
  font-family: inherit; font-size: 13px; color: var(--t1);
  background: #fff; border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
  padding: 7px 12px; margin-right: 10px; cursor: pointer;
}
@media (hover: hover) and (pointer: fine) {

.logo-file input[type="file"]::file-selector-button:hover { border-color: var(--t3); }
}

/* ---------- 队史：定位工具条 ---------- */
.hist-search { display: flex; gap: 10px; align-items: center; }
.hist-search input {
  flex: 1; min-width: 0; font-family: inherit; font-size: 14px; color: var(--t1);
  padding: 9px 12px; border: 1px solid var(--line-strong); border-radius: 9px;
}
.hist-search input:focus { outline: none; border-color: var(--t2); }
.hist-search .btn-sm { text-decoration: none; }

/* ---------- 队史：批量导入入口 ----------
   ⚠️ 2026-10-03 删掉了原来那组 .hist-add*（<details> 折叠的新增表单）。
      阿凯要求「点新增时其他界面虚化」，折叠做不到虚化，改成了模态
      （见上面的 .modal-*）。这里只留 .hist-alt —— 弹层里的那条旁路还用着。 */
/* 队史的「批量导入」旁路。现在只出现在弹层内部（.modal-box-bd 里），
   所以去掉了原来给独立卡片准备的上边距与分隔线。 */
.hist-alt {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 10px;
  margin-top: 18px;
  font-size: 13px; color: var(--t2);
}
.hist-alt a { color: var(--accent-text); }

/* ---------- 队史：按年份分组折叠 ----------
   【为什么用 <details> 而不是 JS 折叠】
   ① 无 JS 也能用（管理台在手机上打开的场景很常见）；
   ② 100 条摊开成一屏到底正是要解决的问题，而浏览器的原生折叠零成本。 */
.hist-groups { margin-top: 4px; }
.hist-year { border: 1px solid var(--line); border-radius: var(--radius); margin-bottom: 12px; }
.hist-year-sum {
  display: flex; align-items: baseline; gap: 10px;
  padding: 14px 20px; cursor: pointer; list-style: none;
}
.hist-year-sum::-webkit-details-marker { display: none; }
.hist-year-t { font-size: 15px; font-weight: 600; font-variant-numeric: tabular-nums; }
.hist-year-n { font-size: 12px; color: var(--t3); }
/* 折叠指示：年份右边一个小箭头，展开时转 90° */
.hist-year-t::before {
  content: "›"; display: inline-block; margin-right: 8px; color: var(--t3);
  transition: transform .15s;
}
.hist-year[open] .hist-year-t::before { transform: rotate(90deg); }

.hist-list { border-top: 1px solid var(--line); }
/* 一行 = 日期 · 标题 · 类型 · 编辑。
   网格而不是 flex：日期列宽度固定（tabular-nums 对齐），标题占满剩余，
   类型与编辑靠右 —— 100 行扫下来，眼睛只需要沿一条竖线往下走。 */
.hist-row {
  display: grid;
  grid-template-columns: 92px 1fr auto 44px;
  align-items: center; gap: 14px;
  padding: 11px 20px; border-bottom: 1px solid var(--line);
}
.hist-list .hist-row:last-child { border-bottom: none; }
.hist-date { font-size: 13px; color: var(--t3); font-variant-numeric: tabular-nums; }
.hist-title { font-size: 14px; color: var(--t1); min-width: 0; }
.hist-tag {
  font-size: 12px; line-height: 1; padding: 4px 8px; border-radius: 6px;
  background: var(--fill); color: var(--t2); white-space: nowrap;
}
.hist-edit { font-size: 13px; color: var(--t3); text-align: right; }
@media (hover: hover) and (pointer: fine) {

.hist-row:hover { background: #FAFAFA; }
}
@media (hover: hover) and (pointer: fine) {

.hist-row:hover .hist-edit { color: var(--accent-text); }
}
/* 搜索命中「描述」而不是标题时，把描述显示出来，否则用户看不出为什么这条会中 */
.hist-desc {
  padding: 0 20px 11px 126px; margin-top: -5px;
  font-size: 13px; color: var(--t3);
}

/* ---------- 通用模态弹层（2026-10-03）----------
   【为什么需要它】新增表单原来常驻在列表上方，六七个输入框从进页面起
   就占着半屏 —— 而它一年只用几次。阿凯的原话是
   「你不要上来就给它展开嘛……点展开的时候，其他界面虚化」。
   ⇒ 折叠（<details>）做不到虚化背景，所以改成模态。

   ★ **纯 CSS 实现，一行 JS 都不用**：
     隐藏 checkbox + label 触发 + :checked 兄弟选择器。
     为什么不用 <dialog> 或写 JS：这一页在手机上也要能用，
     而项目里大部分交互（年份折叠、四态点名）都刻意做成了不依赖 JS。
     checkbox 方案无 JS 时依然能开合，:checked 失效也只是"退化成永远关不掉"，
     不影响任何数据。

   ★ **结构顺序不能改**：input 必须紧邻 label 之前（input:checked + label）
     且弹层与 input 同级（后面的 ~ 选择器），改顺序弹层就不受控了。 */
.modal-tgl { position: absolute; opacity: 0; pointer-events: none; }

/* 工具按钮那一行：搜索卡与列表之间，右侧放「+ 新增」。 */
.adm-act-row {
  display: flex; justify-content: flex-end; align-items: center;
  gap: 10px; margin: 14px 0 10px;
}
/* 触发按钮：长得跟别的 .btn-sm 一样，只是多一个「+」 */
.modal-btn { cursor: pointer; user-select: none; }

/* 遮罩层：默认 display:none，勾上才出现。半透明 + 背景虚化，
   虚化是关键 —— 让队长一眼看出「现在我在填东西，别的先不管」 */
.modal-mask {
  position: fixed; inset: 0; z-index: 90;
  display: none;
  background: rgba(28, 28, 26, 0.32);
  -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px);
}
.modal-tgl:checked ~ .modal-mask { display: block; }

/* 弹出的那个框 */
.modal-box {
  position: fixed; z-index: 91;
  left: 50%; top: 50%; transform: translate(-50%, -50%);
  display: none;
  width: min(560px, calc(100vw - 40px));
  max-height: min(86vh, 760px);
  overflow-y: auto;
  background: #fff;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  box-shadow: 0 18px 48px rgba(0, 0, 0, 0.18);
}
.modal-tgl:checked ~ .modal-box { display: block; }

.modal-box-hd {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 16px 20px; border-bottom: 1px solid var(--line);
  position: sticky; top: 0; background: #fff; z-index: 1;
}
.modal-box-t { font-size: 15px; font-weight: 600; }
.modal-box-note { font-size: 12px; color: var(--t3); margin-top: 3px; }
/* 关闭：右上角一个 ×，以及点遮罩也能关（见模板里遮罩是<label>） */
.modal-x {
  font-size: 20px; line-height: 1; color: var(--t3); cursor: pointer;
  padding: 2px 8px; border-radius: 6px; user-select: none; flex-shrink: 0;
}
@media (hover: hover) and (pointer: fine) {

.modal-x:hover { color: var(--t1); background: var(--fill); }
}

.modal-box-bd { padding: 20px; }
.modal-box-bd .frm-foot { margin-bottom: 0; }

/* 窄屏：居中框在390px 下两头只剩 20px，键盘弹出时更挤。
   改成贴底抽屉 —— 输入框离拇指近，表单也不会被顶出屏幕。 */
@media (max-width: 640px) {
  .modal-box {
    left: 0; right: 0; top: auto; bottom: 0;
    transform: none;
    width: 100%;
    max-height: 92vh;
    border-radius: var(--radius) var(--radius) 0 0;
    border-bottom: none;
  }
  .modal-box-bd { padding: 16px; }
}

/* ---------- 荣誉列表（2026-10-03，与队史同一套约定） ----------
   一行 = 序号 · 名称 · 名次 · 级别 · 编辑。
   与 .hist-row 的差别只有两处，都是刻意的：
     ① 有**序号**列 —— 荣誉墙本质是奖杯清单，「1、2、3……N」是队长
        心里那个次序，显示出来他才不必数行。
     ② 有**名次**列 —— 荣誉最常被扫的就是「拿了个什么」，
        和名称（赛事名）不是同一条信息，必须分开占一列。 */
.hon-list { border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; }
.hon-row {
  display: grid;
  grid-template-columns: 34px 1fr auto auto 44px;
  align-items: center; gap: 14px;
  padding: 11px 20px; border-bottom: 1px solid var(--line);
}
.hon-list .hon-row:last-child { border-bottom: none; }
.hon-num { font-size: 12px; color: var(--t3); text-align: right; font-variant-numeric: tabular-nums; }
.hon-name { font-size: 14px; color: var(--t1); min-width: 0; }
/* 名次允许换行，不设 nowrap：它常是「双冠（极限飞盘、掷准飞盘团队赛）」
   这种长串，写死 nowrap 会把后面的级别和「编辑」一起顶出容器
   （实测第 2 行就是这样破相的）。名次换行只多占一行，不影响扫读。 */
.hon-result { font-size: 13px; color: var(--t2); }
.hon-tag {
  font-size: 12px; line-height: 1; padding: 4px 8px; border-radius: 6px;
  background: var(--fill); color: var(--t2); white-space: nowrap;
}
.hon-edit { font-size: 13px; color: var(--t3); text-align: right; }
@media (hover: hover) and (pointer: fine) {

.hon-row:hover { background: #FAFAFA; }
}
@media (hover: hover) and (pointer: fine) {

.hon-row:hover .hon-edit { color: var(--accent-text); }
}
/* 搜索命中「摘要」而不是赛事名时，把摘要显示出来，否则用户看不出为什么这条会中 */
.hon-desc {
  padding: 0 20px 11px 68px; margin-top: -5px;
  font-size: 13px; color: var(--t3);
}

/* ---------- 队史批量导入 ---------- */
.imp-text { min-height: 200px; font-size: 14px; line-height: 1.8; }
.imp-guide { font-size: 13px; color: var(--t2); line-height: 1.9; padding-left: 20px; }
.imp-guide li + li { margin-top: 4px; }
.imp-guide code {
  font-family: inherit; font-size: 12px; background: var(--fill);
  padding: 1px 5px; border-radius: 4px; color: var(--t1);
}
.imp-summary {
  font-size: 13px; color: var(--t2);
  padding: 12px 14px; border-radius: var(--radius-sm);
  background: var(--fill); margin-bottom: 16px;
}
.imp-warn { color: #8A6410; }

.imp-list { border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; }
.imp-row { display: flex; gap: 12px; align-items: flex-start; padding: 10px 14px; border-bottom: 1px solid var(--line); }
.imp-list .imp-row:last-child { border-bottom: none; }
/* 「要用户核对」的行给一层淡黄底：解析不准的地方应该是**看得见**的，
   而不是让用户逐条读一遍去发现 */
.imp-row.is-warn { background: #FFFBF2; }
.imp-keep { padding-top: 9px; flex-shrink: 0; }
.imp-keep input { width: 16px; height: 16px; cursor: pointer; }
/* 四个字段一行；窄屏折成两行（见文末媒体查询） */
.imp-fields {
  flex: 1; min-width: 0;
  display: grid; grid-template-columns: 120px 1fr 110px 1.4fr; gap: 8px;
}
.imp-fields input, .imp-fields select {
  width: 100%; font-family: inherit; font-size: 13px; color: var(--t1);
  padding: 8px 10px; border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
  background: #fff;
}
.imp-fields input:focus, .imp-fields select:focus { outline: none; border-color: var(--t2); }
.imp-fields select {
  appearance: none; -webkit-appearance: none; cursor: pointer; padding-right: 26px;
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.5 4.5L6 8L9.5 4.5' fill='none' stroke='%23999' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 8px center; background-size: 12px 12px;
}

/* ---------- 窄屏 ---------- */
@media (max-width: 720px) {
  .sp-kpis { gap: 28px; }
  .sp-kpi-n { font-size: 26px; }
  .sp-row-main { flex-direction: column; gap: 4px; }
  .sp-bar-l { width: 62px; font-size: 11px; }
  .nw-grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
  .nw-no { font-size: 24px; min-width: 36px; }
  .ss-n { font-size: 24px; }
}

@media (max-width: 720px) {
  .pc { flex-wrap: wrap; gap: 16px; }
  .pc-no { min-width: 58px; font-size: 34px; }
  .pc-stats { gap: 18px; }
}

/* ---------- 队徽 / 队史 / 导入：窄屏 ---------- */
@media (max-width: 720px) {
  /* 面板标题与右侧说明在窄屏会互相挤压：标题被折成两行、说明挤成三行，
     两个都读不顺。允许换行之后说明整条落到下一行，标题保持一行。 */
  .panel-head { flex-wrap: wrap; row-gap: 2px; }

  /* 预览图小一号，别把选文件挤到屏外 */
  .logo-row { flex-direction: column; gap: 14px; }
  .logo-preview { width: 72px; height: 72px; font-size: 24px; }

  /* 一行四项在 390px 下必然互相挤压。改成两行：
     第一行 日期 + 类型，第二行 标题（独占整行），编辑链接靠右。
     标题独占一行是刻意的 —— 队史标题最长，被压成两三个字就只能靠猜。 */
  .hist-row { grid-template-columns: 80px 1fr auto; gap: 10px; padding: 11px 14px; }
  .hist-title { grid-column: 1 / -1; grid-row: 2; }
  .hist-date { grid-column: 1; grid-row: 1; }
  .hist-tag { grid-column: 2; grid-row: 1; justify-self: start; }
  .hist-edit { grid-column: 3; grid-row: 1; }
  .hist-desc { padding: 0 14px 11px; margin-top: -3px; }

  /* 荣誉比队史多一列（名次），390px 下五列必然压扁。
     三行：
       第 1 行 序号 + 级别 + 编辑（都是短的东西，宽度可预测）
       第 2 行 赛事名独占整行（最长的一列，必须给整行）
       第 3 行 名次独占整行
     ⚠️ 三处都要显式 grid-row：DOM 顺序是 序号/名称/名次/级别/编辑，
     只写 grid-template-columns 的话名称不会落到第二行，后面全被挤走。
     ⚠️ 名次放第 3 行而不是并进第 1 行：名次常常很长
     （「双冠（极限飞盘、掷准飞盘团队赛）」这种），
     并进第 1 行会把「编辑」顶出容器、压成两行竖排。 */
  .hon-row { grid-template-columns: 28px 1fr auto; gap: 8px 10px; padding: 11px 14px; }
  .hon-num { grid-column: 1; grid-row: 1; }
  .hon-tag { grid-column: 2; grid-row: 1; justify-self: start; }
  .hon-edit { grid-column: 3; grid-row: 1; }
  .hon-name { grid-column: 1 / -1; grid-row: 2; }
  .hon-result { grid-column: 1 / -1; grid-row: 3; white-space: normal; }
  .hon-desc { padding: 0 14px 11px; margin-top: -3px; }

  /* 导入预览：日期 + 类型同一行，标题、描述各占整行（三行一条）。
     ⚠️ 必须显式指定 grid-row：DOM 顺序是 日期/标题/类型/描述，
     只写 `grid-template-columns:1fr 1fr` 的话，标题要独占整行会被挤到第二行，
     于是类型掉到第三行、描述第四行 —— 白白多出一行高度。 */
  .imp-row { gap: 8px; padding: 10px; }
  .imp-fields { grid-template-columns: 1fr 1fr; }
  .imp-date { grid-column: 1; grid-row: 1; }
  .imp-type { grid-column: 2; grid-row: 1; }
  .imp-title { grid-column: 1 / -1; grid-row: 2; }
  .imp-desc { grid-column: 1 / -1; grid-row: 3; }
}

/* ---------- 相册网格（列表页，2026-10-03） ----------
   ★ 这一块**不是**文字列表，所以不套队史/荣誉那套紧凑行。
     判据（写在 AGENTS.md）：内容的主体是**图片**时，
     列表的唯一职责就是「让人一眼看到图」—— 表格会把图挤成没有大小的
     一行文字（原来相册就是表格，四列里根本没有图片的位置）。
     同类还有：相册、未来的头像墙 / 装备墙。

   卡内结构照微信群相册：方形封面 + 右下角张数 + 图下标题。
   张数压在图上而不是放进标题行，是因为「有多少张」要在扫图时看到，
   放到标题下面就得逐张点开才知道。

   ⚠️ 命名与相册详情页的 .ph-grid（照片网格）刻意分开：
     .ph-* 是「一个相册里的照片」，.alb-* 是「相册本身」。
     两层网格长得像但作用不同，合并会让详情页的删除按钮逻辑被牵连。 */
.alb-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(168px, 1fr));
  gap: 16px 14px;
}
.alb-card { text-decoration: none; display: block; min-width: 0; }
.alb-thumb {
  position: relative;
  display: block;
  aspect-ratio: 1 / 1;          /* 正方：九宫格排布的关键 */
  border-radius: var(--radius-sm);
  background: var(--fill);
  overflow: hidden;
}
.alb-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* 0 张的相册不是错误状态，是「还没传」。所以给浅色底 + 灰字，不给红字。
   ⚠️ 这里不能用 display:flex 做垂直居中：两行截断要靠 -webkit-box，
     而 -webkit-box 与 flex 互斥（设了 -webkit-box 后 align-items:center
     会被忽略，文字贴到顶部）。实测表现：0 张的那一块标题明显比有封面的高一截。
     正解是把 padding 调大 + line-height 手动居中，两行截断保留。 */
.alb-thumb-tx {
  position: absolute; inset: 0;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
  padding: 0 12px;
  /* 单行时靠 line-height 撑到与两行等高；两行截断时整体仍在中间 */
  line-height: 22px;
  height: 44px;
  top: 50%; margin-top: -22px;
  text-align: center;
  font-size: 12px; color: var(--t3);
}
.alb-count {
  position: absolute; right: 6px; bottom: 6px;
  padding: 2px 7px; border-radius: 5px;
  background: rgba(0, 0, 0, 0.5); color: #fff;
  font-size: 11px; line-height: 1.5;
  font-variant-numeric: tabular-nums;
}
.alb-cap { display: block; padding: 8px 2px 0; }
.alb-title {
  display: block; font-size: 14px; color: var(--t1);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.alb-meta {
  display: block; font-size: 12px; color: var(--t3); margin-top: 2px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
@media (hover: hover) and (pointer: fine) {

.alb-card:hover .alb-title { color: var(--accent-text); }
}

/* 新建格：虚线框，与微信群相册左上角那一格同一个位置。
   它是 <label>（用来触发模态），不是 <a>，所以要自己补 cursor。 */
.alb-new {
  display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 6px;
  aspect-ratio: 1 / 1;
  border: 1px dashed var(--line-strong);
  border-radius: var(--radius-sm);
  background: transparent;
  cursor: pointer; user-select: none;
  color: var(--t3);
}
@media (hover: hover) and (pointer: fine) {

.alb-new:hover { border-color: var(--accent); background: #FAFAFA; }
}
.alb-new-ic { font-size: 26px; line-height: 1; font-weight: 300; }
.alb-new-t { font-size: 13px; }

/* ---------- 资料库紧凑行（2026-10-03） ----------
   与 .hist-row / .hon-row 同一套骨架，只多一个「排序」数字列。
   为什么把 sort 显示出来：它是这个列表唯一的隐藏排序依据，
   队长看不见就只会觉得「顺序怎么跟我想的不一样」，改不了也猜不到。 */
.lib-list { border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; }
.lib-row {
  display: grid;
  grid-template-columns: 1fr auto 44px;
  align-items: center; gap: 14px;
  padding: 11px 20px; border-bottom: 1px solid var(--line);
}
.lib-list .lib-row:last-child { border-bottom: none; }
@media (hover: hover) and (pointer: fine) {

.lib-row:hover { background: #FAFAFA; }
}
@media (hover: hover) and (pointer: fine) {

.lib-row:hover .lib-edit { color: var(--accent-text); }
}
.lib-main { min-width: 0; }
.lib-title {
  display: block; font-size: 14px; color: var(--t1); text-decoration: none;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
@media (hover: hover) and (pointer: fine) {

.lib-title:hover { color: var(--accent-text); }
}
.lib-meta {
  display: block; font-size: 12px; color: var(--t3); margin-top: 3px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.lib-sort { font-size: 12px; color: var(--t3); font-variant-numeric: tabular-nums; }
.lib-edit { font-size: 13px; color: var(--t3); text-align: right; }

/* ---------- 复选框组 ----------
   原来资料库/名册/导入预览各写一遍 inline style（display:flex;gap:18px…），
   三份各改各的。这里收成一个类，换行行为也一起定下来 ——
   三个档位在 390px 下必须能折行，否则第三个会被挤出容器。 */
.chk-row { display: flex; gap: 8px 18px; flex-wrap: wrap; padding-top: 2px; }
.chk {
  display: flex; align-items: center; gap: 6px;
  font-weight: 400; white-space: nowrap; cursor: pointer;
}

@media (max-width: 640px) {
  .lib-row { grid-template-columns: 1fr auto; gap: 8px 10px; padding: 11px 14px; }
  .lib-main { grid-column: 1 / -1; grid-row: 1; }
  .lib-sort { grid-column: 1; grid-row: 2; }
  .lib-edit { grid-column: 2; grid-row: 2; }
}


/* ============================================================
   队内身份声明（账号设置页 / 006）
   ------------------------------------------------------------
   「我在队里的身份」这块整体是低调的：默认折叠成一行灰字，
   点开才看到表单。理由是阿凯要的「隐晦一点」—— 绝大多数用户
   只是队员，不该被一个「你是什么身份」的入口打扰。
   ============================================================ */

/* 折叠开关本身 */
.id-fold {
  border-top: 1px solid var(--line);
  padding-top: 14px;
  margin-top: 4px;
}
.id-fold-sum {
  list-style: none;
  cursor: pointer;
  font-size: 14px;
  color: var(--t3);
  display: flex;
  align-items: center;
  gap: 6px;
  user-select: none;
}
.id-fold-sum::-webkit-details-marker { display: none; }
/* 自绘小三角：默认收起朝右，展开朝下 */
.id-fold-sum::before {
  content: '';
  width: 0; height: 0;
  border-left: 5px solid currentColor;
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
  transition: transform .15s;
  flex: none;
}
.id-fold[open] .id-fold-sum::before { transform: rotate(90deg); }
@media (hover: hover) and (pointer: fine) {

.id-fold-sum:hover { color: var(--t1); }
}
.id-fold[open] .id-fold-sum { color: var(--t1); margin-bottom: 6px; }

.id-form { margin-top: 10px; }

/* 已提交的身份列表 —— 与队史/荣誉的紧凑行同一套语言：一行一条，能扫 */
.id-list {
  list-style: none;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  overflow: hidden;
}
.id-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 14px;
  border-bottom: 1px solid var(--line);
}
.id-row:last-child { border-bottom: 0; }
.id-main { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
.id-name {
  font-size: 14px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.id-meta { font-size: 12px; color: var(--t3); flex: none; }
.id-badge {
  flex: none;
  font-size: 12px;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--fill);
  color: var(--t2);
}
.id-badge.ok { background: var(--accent-soft); color: var(--accent-text); }
.id-badge.no { background: var(--danger-soft); color: var(--danger); }
.id-badge.warn { background: #FFF7E6; color: #B8860B; }

/* ============================================================
   教练专属区块（名册页 · 队长交接 / 006）
   ------------------------------------------------------------
   它是名册页上「教练才有」的一块。刻意与上面的名册表格拉开距离：
   上面是「看名单」，这里是「做决定」，混在一起会误点。
   ============================================================ */
.coach-block {
  margin-top: 28px;
  padding-top: 18px;
  border-top: 1px solid var(--line);
}
.coach-note {
  font-size: 14px;
  color: var(--t2);
  margin: 8px 0 12px;
}
.coach-note b { color: var(--t1); font-weight: 600; }


/* ============================================================
   训练内容 / 战术板 / 训练复盘（2026-10-04 第四轮）
   ------------------------------------------------------------
   这三块都长在「训练」这条线上，是教练提的两个需求：
     ① 训练内容（这次练什么）—— 训练**前**看，帮队员提前了解
     ② 战术板             —— 训练**前**看，站位与跑位摆出来
     ③ 训练复盘           —— 训练**后**写，人人一份、可互评
   命名前缀：.plan-* / .tac-* / .rv-*

   ⚠️ 本轮起 .rv-* 专指「训练复盘」。申请页那个审批表单原来也占着这个前缀
      （review form，与「复盘」同名不同物）—— 本轮改名 .req-form。
      以后在 .rv-* 里找审批表单只会白找，这是刻意留的记录。
      ⇒ routes-smoke 里有一条守卫禁止这个旧名字回到 CSS 里，所以本条注释
        也不写出带点号的旧类名（守卫扫的是源码文本，注释一样会被扫到 ——
        这类「守卫被自己的注释绊红」在本项目已经踩过好几次了）。
   ============================================================ */

/* ---------- 训练内容（这次练什么） ---------- */
/* 内容是 <textarea> 的纯文本，展示时必须保住换行。
   少了 pre-wrap，队长分行写的「一、热身 / 二、竖排起手」会挤成一大段。 */
.plan-body {
  font-size: 14px;
  line-height: 1.85;
  color: var(--t1);
  white-space: pre-wrap;
  overflow-wrap: break-word;
  max-width: 68ch;        /* 一行别超过 68 字：再长眼睛就跳行了 */
}
/* 「要带的」那一行。拉一条发丝分隔 —— 它是注意事项，不是内容本身的续行 */
.plan-note {
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
  font-size: 13px;
  color: var(--t2);
}
.plan-note .tag { margin-left: 0; margin-right: 2px; }

/* 「训练详情」这一节的头（2026-10-14 阿凯定形）。
   与下面的「复盘」头同形同级 —— 这一页从此只有两件事：
   上面的正文、下面的评论区。两条 section 头用同一套字号字重，
   人滚到这里时"这里换话题了"是靠形状认出来的，不是靠颜色。
   ⚠️ 它不在 .rv-* 命名空间里（那个前缀专指复盘），用 .plan-* ——
      plan-body / plan-note 本来就在这一节里。 */
.plan-sec { margin-top: 20px; }
.plan-sec-t { font-size: 16px; font-weight: 600; color: var(--t1); margin-bottom: 10px; }

/* ============================================================
   战术板（2026-10-15 整块重画，照阿凯给的那张参考图）
   ============================================================
   【这一块只有一个形状】一块竖着的绿场地 + 底下一排工具。
     旧的「列表网格 / 缩略图卡片 / 编辑工具条 / 属性条」全部撤掉了 ——
     一个队一块板，阵型由底部的弹层切换（阿凯：「我要的效果是教练点击
     战术版就可以看到这个」）。

   ★★ .tac-field 的 aspect-ratio 是**交互正确性的前提**，不是版式偏好。
      tactics.ejs 的 fieldPos() 用 getBoundingClientRect() 把屏幕坐标换算成
      场地坐标（x∈[0,40]、y∈[0,68]）。只要盒子的宽高比与 viewBox 一致，
      preserveAspectRatio 就不会留信箱边，换算成立；比例一旦不一致，
      r.width 里混进留白，**整套拖拽会整体偏移**（而且只在拖动时才看得出来）。
      ⇒ 锁死 40 / 68，且 height 必须 auto —— 写死高度会立刻破比例。
      ⚠️ max-width 只用来「别在宽屏上摊成一张巨幅」；它不会破比例，
         因为宽度先被定死、height:auto 跟着算。
   ★★ 宽度上限 400px 是**量出来的**，不是随手写的：
      400 × 68/40 = 680px 高，加上页头与工具栏 ≈ 860px ——
      正好是笔记本一屏的高度。再宽就要滚，而滚着拖棋子很难受。
   ⚠️ 这个上限挂在**外层容器**（表单本身）上，不挂在 .tac-field 上 ——
      底部那排工具栏必须是同一个宽度：画布居中 400px、工具栏铺满整页的话，
      宽屏下「保存」会跑到离画布右缘老远的地方，一眼就看得出对不齐。 */
.tac-wrap { max-width: 400px; margin: 0 auto; }
.tac-field {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 40 / 68;
  background: var(--field);
  border-radius: var(--radius);
  user-select: none;
  -webkit-user-select: none;
  -webkit-tap-highlight-color: transparent;
}
/* 手势开关。★ touch-action: none 只在可编辑时给 ——
   不给的话，手指拖棋子会被浏览器当成「滚动页面」，pointercancel 一来
   拖拽当场断掉；而只读那一片要是也禁掉，用户在手机上就滑不动页面了。 */
.tac-field[data-editable="1"] { touch-action: none; cursor: grab; }
.tac-field[data-editable="1"] .tac-p { cursor: grab; }
.tac-field[data-editable="1"]:active { cursor: grabbing; }

/* ---------- 场地（SVG 内部） ----------
   ★ vector-effect: non-scaling-stroke 在这块**必须有**，不是随手加的：
     同一张图上的「1 个用户单位」在不同屏上是 8.75px（手机）到 10px（桌面），
     描边按用户单位走的话，一条线会有 9 像素粗 —— 一片白杠子。
     non-scaling-stroke 让描边永远是 1 个设备像素，两种屏同时成立。
     （旧版靠「编辑器整屏 / 列表缩略图」两种尺寸差 5 倍来论证这件事，
       现在只剩一种尺寸，但结论没变：手机和桌面的每码像素数仍然差着。） */
.tac-turf { fill: var(--field); }

/* 外框：比得分线淡一档。它只是「场地的四边」，不该比中间的线还抢眼 */
.tac-edge {
  fill: none;
  stroke: rgba(255, 255, 255, .5);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}
/* 两条得分线：纯白，是这片绿上唯一「实」的线 */
.tac-goal {
  stroke: #fff;
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}
/* 场地中央那两个小 ✕（示意标记，见 _tactic-svg.ejs 里的说明） */
.tac-mark line {
  stroke: rgba(255, 255, 255, .5);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

/* END ZONE：半透明白，压得很淡 —— 它是背景文字，不该和棋子抢注意力。
   ⚠️ 字号单位是**场地单位**（SVG 里 1px = 1 用户单位），不是屏幕像素：
      2.4 单位 ≈ 场宽的 6%，换算到手机上约 21px。
      改字号要同时重算 _tactic-svg.ejs 里两行 text 的基线 y。 */
.tac-ez-tx {
  fill: rgba(255, 255, 255, .32);
  font-size: 2.4px;
  font-weight: 600;
  letter-spacing: .3px;
}

/* ---------- 棋子 ----------
   ★ 两队只差「实心白 / 实心深」，不引入第三种颜色：
     这片绿上任何彩色都会和草坪打架，而白 / 深这一对在极小尺寸下也分得开，
     同时落在全站的极简调子里。
     ⚠️ 我方是**深色**那一队，不是白色 —— 这是跟参考图对齐的结果：
        参考图里我方在下排，而选中光晕是**半透明白**，
        套在深色棋子上才看得见（套在白棋子上等于没有）。
     如果哪天想把两队颜色换过来，光晕那条也要一起改。 */

/* ★★ 换阵型时那段「丝滑」就靠这一条 transition。
   位置写在 style.transform 里（不是 SVG 的 transform 属性），
   只有 CSS transform 是浏览器认的可过渡属性 —— 细节见 tactics.ejs 的 paint()。
   ⚠️ 时长与曲线全站只有这一处，JS 里不写第二份（rAF 补间会和它对打）。
   .32,.72,0,1 是 iOS 那套「先快后缓」的减速曲线：起步就跟手、尾巴收得慢，
   看起来像有人自己走过去，而不是整块图平移。 */
.tac-p { transition: transform .44s cubic-bezier(.32, .72, 0, 1); }

/* 正在被手指拖的那一颗：过渡必须**当场关掉**。
   留着的话棋子永远慢半拍地去追手指，像拖着一根皮筋，拖得越远越明显。
   ⚠️ is-drag 由 JS 在 pointerdown 挂上、pointerup / pointercancel 摘掉。
      只挂不摘的后果不是「下次拖不动」，而是那颗子留在盘上带着 is-drag ——
      下次换阵型时全场都在滑，只有它硬跳。 */
.tac-p.is-drag { transition: none; }

.tac-pb { stroke: none; }
.tac-us .tac-pb { fill: var(--t1); }
.tac-us .tac-pt { fill: #fff; }
.tac-them .tac-pb { fill: #fff; }
.tac-them .tac-pt { fill: var(--t1); }

/* 盘。**不是第三个队**，只是「盘在哪」。
   半透明白 + 白描边：在同一片绿地上，它必须一眼不同于球员（球员是不透明实心圆）。
   ⚠️ 半径 1.2 写在 _tactic-svg.ejs 与 tactics.ejs 两处。守卫比对的是 .tac-pb
      那条（1.7），**不包括这一条** —— 改半径要手动同步两个文件。 */
.tac-db {
  fill: rgba(255, 255, 255, .62);
  stroke: #fff;
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

/* 背号。font-size 的单位同样是**场地单位**：
   2 ÷ 棋子直径 3.4 ≈ 59%，与参考图里那个比例一致，单字双字都放得下。
   ⚠️ 这个 2 与 tactics.ejs 注释里的「半径 1.7 / dy 0.7」是同一组数，
      改一处必须改两处（routes-smoke 有守卫比对）。 */
.tac-pt {
  font-size: 2px;
  font-weight: 600;
  pointer-events: none;   /* 文字不参与指针事件，省掉「谁收指针」的心智负担 */
}

/* 选中光晕：每一颗子都渲染着这个圆，默认全透明，选中才点亮。
   半径 2.9 比棋子（1.7）大 1.2 个单位 —— 露在棋子外面的那一圈约 10px，
   所以它套在**白棋子上也看得见**（白圈落在绿地上，不是白压白）。 */
.tac-halo { fill: transparent; }
.tac-p.is-sel .tac-halo { fill: rgba(255, 255, 255, .34); }

/* ---------- 底部工具栏（照参考图那一排） ---------- */
.tac-stage { margin: 0 0 16px; }
.tac-bar { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }

/* 阵型名 + 展开标记。它是一整颗的点击区（它是个 <label>，点它开弹层） */
.tac-form-pick {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 14px; color: var(--t1); cursor: pointer;
  padding: 8px 8px 8px 2px; border-radius: var(--radius-sm);
  user-select: none; -webkit-user-select: none;
}
@media (hover: hover) and (pointer: fine) {

.tac-form-pick:hover { background: var(--fill); }
}
/* 队员（只读）那一份：同样一句话，但没有任何可点的东西 */
.tac-form-pick.is-static { cursor: default; color: var(--t2); }
@media (hover: hover) and (pointer: fine) {

.tac-form-pick.is-static:hover { background: none; }
}
.tac-caret { font-size: 11px; color: var(--t3); }

/* 保存推到最右：它是这一排里唯一「会写库」的动作，不该和别的挤在一起。
   ⚠️⚠️ 这里**必须**明确给出尺寸来源（`flex-basis` 或 `width`）——
      2026-10-15 实测踩过：`.btn-sm` 是 `display:inline-block`，进了 flex 容器会被
      blockify 成 `block`，于是它的 flex base size 被算成**整行宽**：
      结果是「保存」**独占一行**、还撑成一条通栏黑条，而 `.tac-bar` 是 wrap，
      看上去就像版式乱了（页面不报错、截图也能看出不对，但看源码完全找不到原因）。
      ⚠️ 改 `flex: none`（= `0 0 auto`）**没用** —— basis 还是 auto，还是回到 width:auto。
      必须像下面这样写死，`flex-basis: content` 与 `width: fit-content` 二选一即可。 */
.tac-save { margin-left: auto; flex: 0 0 auto; width: fit-content; }
.tac-bar-sep { width: 1px; height: 20px; background: var(--line-strong); margin: 0 4px; }

/* 撤销 / 重做：纯图标。
   ⚠️ 触摸目标不能小于 ~34px（AGENTS.md 的按钮宽度标准）——
     图标本身只有 17px，靠 padding 把它撑到 34 以上。 */
.tac-ico {
  font-family: inherit; font-size: 17px; line-height: 1;
  color: var(--t2); background: none; border: 0;
  padding: 8px 9px; border-radius: var(--radius-sm); cursor: pointer;
}
@media (hover: hover) and (pointer: fine) {

.tac-ico:hover:not(:disabled) { background: var(--fill); color: var(--t1); }
}
.tac-ico:disabled { color: var(--line-strong); cursor: default; }

/* ---------- 阵型弹层的选项列表 ----------
   一行一项：✓ · 名称 · （下一行）说明。
   用 grid 显式定位，是因为要让 ✓ 只占第一行却不占第二行的位置 ——
   用 flex 的话第二行的说明会被 ✓ 挤到右边去。 */
.tac-forms { border-top: 1px solid var(--line); }
.tac-form-opt {
  display: grid;
  grid-template-columns: 16px 1fr;
  align-items: center;
  column-gap: 10px; row-gap: 2px;
  padding: 12px 20px;
  border-bottom: 1px solid var(--line);
  cursor: pointer;
  user-select: none; -webkit-user-select: none;
}
.tac-form-opt:last-child { border-bottom: 0; }
@media (hover: hover) and (pointer: fine) {

.tac-form-opt:hover { background: #FAFAFA; }
}
/* ✓ 平时**占位但不出现**（visibility 而不是 display）——
   用 display:none 的话，没被选中的那几行会整体左移 16px，
   勾一换、整列文字就跟着跳一下。 */
.tac-check {
  grid-column: 1; grid-row: 1;
  font-size: 14px; color: var(--accent-text);
  visibility: hidden;
}
.tac-form-opt.on .tac-check { visibility: visible; }
.tac-form-lab { grid-column: 2; grid-row: 1; font-size: 15px; color: var(--t1); }
.tac-form-opt.on .tac-form-lab { color: var(--accent-text); font-weight: 500; }
.tac-form-note { grid-column: 2; grid-row: 2; font-size: 12px; color: var(--t3); }

/* ⚠️ 这一段的旧规则（.rv-mine / .rv-todo / .rv-withdraw / .rv-toast）
   已于 2026-10-14 整块搬到下面「训练复盘：评论区形态」那一节 ——
   形态从「一块 panel 里塞表单 + 卡片」变成「section 头 + 绿色入口条 + 卡片列表」，
   旧的选择器没有使用者了。
   （.rv-todo 搬到了新区，别在这里再写一遍 —— 两处各一份，改一处漏一处。） */
/* ============================================================
   训练：历史训练列表（2026-10-13 阿凯定义）
   ============================================================
   原话：「填好之后生成一个条状状态在训练模块可以看到，相当于历史上有哪些训练。
          然后可以点这个条状可以查看训练的详情。」
   ⇒ 一条 = 一次训练，**整条就是一个链接**。
   【为什么从表格改过来】表格 6 列在手机上要横滚，而每行真正能一眼认出的
     只有「哪天 + 练什么」；更要紧的是整行不可点 —— 只有最右边那颗小按钮
     能进去，而那一列还挤着两颗（查看 / 出勤），点错是常事。
     现在从日期到地点、包括行末的空白，点哪儿都进得去。
   【为什么不用 .panel 那种一页一张卡】一屏里可能有十几条，
     每条都做成厚卡片的话，"历史清单"就变成"一屏两条"了。
     这里要的是**能扫**：两行文字 + 一条发丝线，密而清楚。
   ============================================================ */
.tsk-list { display: flex; flex-direction: column; }
.tsk {
  display: flex; align-items: center; gap: 12px;
  padding: 14px 4px;
  border-bottom: 1px solid var(--line);
  color: inherit; text-decoration: none;
}
@media (hover: hover) and (pointer: fine) {

.tsk:hover { background: var(--fill); }
}
.tsk-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
/* ★ 主行**只有标题与状态**（2026-10-14 阿凯：「不要把年月日都标在训练名称的
   一旁，太拥挤」）。日期挪到副行 meta 里，与时段、地点并列 ——
   行业里待办/邮件列表全是这个结构（Things 3 / Apple Reminders / Gmail）：
   主行是"这一条叫什么"，副行是"它是哪天的、什么时候、在哪儿"。 */
.tsk-top { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.tsk-head {
  font-size: 15px; font-weight: 600; color: var(--t1);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%;
}
/* 副行是元数据：日期 + 时段 + 地点。等宽数字让"19:00 / 19:30"这一列对齐，
   一列日期扫下来才对得齐。 */
.tsk-meta { font-size: 13px; color: var(--t2); font-variant-numeric: tabular-nums; }
/* 行末那个 › 是"可以进去"的提示。手机上它是唯一的信号 —— 没有它，
   一列文字看起来就只是文字。 */
.tsk-go { flex: none; font-size: 18px; line-height: 1; color: var(--t3); }

/* ============================================================
   训练复盘：评论区形态（2026-10-14 阿凯定形）
   ------------------------------------------------------------
   原话：「训练复盘不要弄很多框，我看到的是一个大框包了很多小框，小框又包了
          小框。可以弄成评论区那样，用户划到下面显示"复盘"，再往下滑可以看到
          队员的复盘……不要弄多余的东西……像短视频或者帖子评论那样干净。」
   ⇒ 本版的形状：
     ① **没有外层 .panel** —— 这一页从此只有两段：上面的帖子正文、下面的
        评论区（.rv-sec），中间一条发丝线。再套一层框就是"框里有框"。
     ② 每条复盘**仍是**一张浅灰卡片（他 2026-10-13 要的「从姓名到内容被一个
        框包住」），但卡片**内部不再有框**：互评是缩进、输入框是裸的。
     ③ 写复盘的入口是一颗**绿色长条**，点了才展开输入框。
   ⚠️ 本轮**整块退役**的类名（不是改样式，是没有使用者了）：
      .rv-mine / .rv-withdraw / .rv-files / .rv-file / .rv-file-x / .rv-up /
      .rv-up-btn / .rv-attach / .rv-attach-btn / .rv-attach-on / .rv-quick /
      .rv-quick-btn / .rv-toast / .tag-accent
      删除前在 views/ 与 scripts/ 里搜过一遍 —— 只剩 check-overflow.js 的
      **用法示例注释**提到 .rv-toast（那是在教人怎么写命令，不是页面在用）。
   ============================================================ */

/* ---- 「复盘」这一节 ----
   阿凯：「用户划到下面显示"复盘"，再往下滑可以看到队员的复盘」
   ⇒ 它是 section 头，不是一块 panel 的标题：与上面的正文之间一条发丝线，
      右侧挂「已交 N / 共 M 人」。 */
.rv-sec { margin-top: 22px; padding-top: 16px; border-top: 1px solid var(--line); }
.rv-sec-head { display: flex; align-items: baseline; gap: 10px; }
.rv-sec-t { font-size: 16px; font-weight: 600; color: var(--t1); }
/* 右侧那串计数。等宽数字让「已交 12 / 共 15 人」在两条训练之间不跳。 */
.rv-sec-n {
  margin-left: auto; font-size: 12px; color: var(--t3);
  font-variant-numeric: tabular-nums;
}
.rv-sec-n b { color: var(--t2); font-weight: 600; }

/* 交作业进度条：谁还没交（2026-10-12）。
   教练把这页当「收作业」用，所以他第一眼要看的不是「有几个人写了」，
   而是「还差谁」—— 所以未交名单要能一眼扫到，且**不做成红色警告**：
   没交不等于犯错，它只是一件待办。用中性底 + 次级文字。 */
.rv-todo {
  font-size: 13px; color: var(--t2); line-height: 1.7;
  background: #FAFAFA; border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 9px 12px; margin: 12px 0 0;
  word-break: break-all;
}
.rv-todo-ok { color: var(--t3); }

/* ---- 写复盘的入口：一颗**小按钮**（2026-10-15 改小）----
   【为什么是一颗按钮而不是常开输入框】评论区（贴吧 / 抖音 / 小红书）都是这个
     形态：先给一个醒目的"写评论"入口，点了才展开输入框。
     ① 不写的人不被一个空输入框占掉半屏；
     ② 绿色是这一页唯一有彩色的东西，找它不用读字。
   【为什么是淡绿不是实心绿】阿凯要的是"绿色清新一点"。淡绿底 + 深绿字
     读起来不刺眼，也和一页的白/灰不打架。
     要更醒目：把 background 换成 var(--accent)、color 换成 #fff，
     就一行 —— 别顺手加阴影（本项目无阴影）。

   ★★ 2026-10-15 改小：原来是 `display:block; width:100%` 的**满宽长条**。
     阿凯原话：「复盘那里写得太长了，那个绿色的部分太长了……
     如果以后有这种按钮，是按钮状的，就弄得比较小一点」。
     ⇒ 宽度**跟内容走、不跟容器走**（`inline-block` + padding 撑开），
       形状回到全站小按钮那一套（`.btn-sm` 的胶囊 + 13px）。

   【标准是什么（他这轮问过）】按钮宽度跟内容走，是 Apple HIG / Material
     一致的原则；满宽按钮（fill width）**只保留给页面级主操作**
     （登录、提交、结算）—— 因为那是用户来这一页的**唯一目的**。
     「写复盘」不是：这一页的主体是**读别人写了什么**，它是次级入口。
     用主操作的体量（满宽 + 12px 内边距）去承载一个次级入口，
     体量和重要性不匹配 —— 这就是"太长"的真正来源，不是审美偏好。
     ⚠️ 缩小有下限：触摸目标别低于 ~34px 高（Apple 建议 44、WCAG 2.2 AA 是 24），
        所以 padding 只收到这个位置，别再往下压。 */
.rv-cta {
  display: inline-block; margin-top: 14px;
  font-family: inherit; font-size: 13px; font-weight: 500; line-height: 1.4;
  color: var(--accent-text); background: var(--accent-soft);
  border: 0; border-radius: 999px;
  padding: 9px 18px; cursor: pointer;
  transition: background .15s;
}
@media (hover: hover) and (pointer: fine) {

.rv-cta:hover { background: #DCF6E7; }
}
.rv-cta:active { background: #D0F3DE; }

/* 展开后的输入区：**没有框**。评论区里"写评论"展开出来就是一根输入框，
   外面再套一个浅底卡片就回到了"框里套框"（阿凯明确点出的那个观感）。
   ★ 2026-10-15 跟着入口一起收小：`min-height` 76→56、内边距 10→8。
     阿凯原话：「如果是要填什么东西的话，弄小一点就行了」——
     它是**下笔的地方**，不是正文区；写多了框自己会滚，也能往下拖大，
     首屏该留给别人的复盘（那才是这页的主体）。 */
.rv-write { margin-top: 10px; }
.rv-write textarea {
  width: 100%; font-family: inherit; font-size: 14px; color: var(--t1);
  padding: 8px 12px; border: 1px solid var(--line-strong); border-radius: 9px;
  background: #fff; outline: none; transition: border-color .15s;
  resize: vertical; min-height: 56px; line-height: 1.6;
}
.rv-write textarea:focus { border-color: var(--t2); }
.rv-write-foot { display: flex; gap: 10px; align-items: center; margin-top: 8px; }

/* 一条都没有时的兜底一句话。 */
.rv-none { margin-top: 14px; font-size: 13px; color: var(--t3); }

/* ---- 评论列表 ---- */
.rv-list { display: flex; flex-direction: column; gap: 12px; margin-top: 14px; }
/* 复盘卡片：逐条错峰入场（见文件末尾动效层）。它们本来就是一条条独立的气泡，
   一起淡入会像整块刷新，错开 30ms 才像一条条贴上来。 */
/* ★ 每条仍是一张灰底卡片（2026-10-13 阿凯定形，2026-10-14 保留）。
   原话：「从姓名开始到复盘内容都要被一个框给包裹住，这个状态框要有背景色
          可以是灰色，主要用于区别背景的白色。」
   ⇒ 一条 = 一张卡片，把「谁写的 + 写了什么 + 谁评了什么」圈在一起。
   ⚠️ 底色用 --fill（#F5F5F5）—— 比页面白底浅一档，刚好"看出来是个框"，
     又不抢正文；用 --line-strong 那种深灰会把一屏切成好几块。
   ⚠️ 2026-10-14 起卡片**内部**不再有框（互评缩进、输入框裸着）——
     "一个框包住一条"和"框里套框"是两件事，别一刀切回到发丝线列表。 */
.rv {
  padding: 12px 14px;
  border-radius: var(--radius);
  background: var(--fill);
}
.rv-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.rv-who { font-size: 14px; font-weight: 600; }
.rv-time { font-size: 12px; color: var(--t3); font-variant-numeric: tabular-nums; }
/* 「我写的」——不抢眼，但能一眼认出自己那条，省得在一列里找 */
.rv-mine-tag {
  font-size: 11px; line-height: 1; color: var(--t3);
  border: 1px solid var(--line-strong); border-radius: 4px; padding: 3px 5px;
}
/* 删除推到最右、离姓名远一点 —— 它就挨着别人写的东西，别给误点的机会 */
.rv-del { margin-left: auto; }
/* .tag 自带 margin-left: 8px，而头部是 gap:8px 的 flex，两份会叠成 16px。
   归零，让间距只由 gap 说了算。 */
.rv-head .tag, .cmt-head .tag { margin-left: 0; flex: none; }

/* 复盘正文。同样是 textarea 来的多行文本，pre-wrap 不能少 */
.rv-body {
  margin-top: 8px;
  font-size: 14px; line-height: 1.8; color: var(--t1);
  white-space: pre-wrap; overflow-wrap: break-word;
}

/* 长复盘先折起来 4 行（2026-10-13）。
   阿凯：「复盘内容如果过多可以展开折叠，至于折叠收缩的字数多少你定，你来定」
   ⇒ 服务端按字数（100）决定**要不要给那颗展开按钮**，真正的截断交给 CSS：
     正文一个字符都没被改写，复制 / 页内搜索 / 屏幕阅读器拿到的仍是全文。
   ⚠️ -webkit-box 会把 display 从 block 换掉，但 white-space: pre-wrap 仍然生效 ——
     换行保住，这正是复盘正文该有的样子。 */
.rv-clamp {
  display: -webkit-box;
  -webkit-line-clamp: 4;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.rv-more {
  margin-top: 6px; padding: 0; border: 0; background: none;
  font-family: inherit; font-size: 13px; line-height: 1.6; color: var(--t3);
  cursor: pointer;
}
@media (hover: hover) and (pointer: fine) {

.rv-more:hover { color: var(--t1); }
}

/* 互评：**只缩进**，不画线、不加框（2026-10-14）。
   原来这里有一条 border-left 竖线表示"挂在上面那条下面"——
   阿凯要的是"像短视频或者帖子评论那样干净"，而竖线也是一层框。
   层级靠**缩进 + 字色**就够：名字用次级灰，正文用主色。 */
.rv-reps { margin-top: 10px; padding-left: 14px; }
.rv-rep {
  display: flex; align-items: baseline; gap: 8px;
  padding: 3px 0; font-size: 13px;
}
.rv-rep-who { flex: none; color: var(--t2); font-weight: 500; }
.rv-rep-body { flex: 1; min-width: 0; color: var(--t1); overflow-wrap: break-word; }
.rv-rep-x {
  flex: none; background: none; border: 0; padding: 0;
  font-family: inherit; font-size: 12px; color: var(--t3); cursor: pointer;
}
@media (hover: hover) and (pointer: fine) {

.rv-rep-x:hover { color: var(--danger); }
}

/* 对这条说一句。一根圆角输入框 + 一颗幽灵按钮 —— 评论区就这两样。 */
.rv-rep-form { display: flex; gap: 8px; align-items: center; margin-top: 10px; }
.rv-rep-form input {
  flex: 1; min-width: 0;
  font-family: inherit; font-size: 13px; color: var(--t1);
  padding: 7px 13px; border: 1px solid var(--line-strong); border-radius: 999px;
  outline: none; transition: border-color .15s;
}
.rv-rep-form input:focus { border-color: var(--t2); }
.rv-rep-form .btn-sm { flex: none; }


/* 「删除」这类纯文字按钮用的红字（.btn-sm.ghost.danger-tx） */
.danger-tx { color: var(--danger); }
@media (hover: hover) and (pointer: fine) {

.btn-sm.ghost.danger-tx:hover { border-color: var(--danger); background: var(--danger-soft); }
}

/* .muted 原来只在表格里定义（.tbl .muted），表格外用它等于没样式。
   补一条通用的：只给颜色、**不给字号** —— 表格里那条更具体，不受影响。 */
.muted { color: var(--t3); }

/* ---------- 窄屏 ---------- */
/* ⚠️ 2026-10-15 清掉了一整组**旧版战术板**的窄屏规则（.tac-grid / .tac-grid-sm /
   .tac-tools / .tac-scroll / .tac-ins input，以及 `.tac-save{width:100%}`）——
   那些类名（列表页网格、旧工具栏、画布滚动框、选中属性条）连同旧版整块退役了，
   留着只是死规则。特别是 `.tac-save` 那条会把「保存」在窄屏下撑成一整行，
   与参考图那一排工具栏（阵型名 · 保存 · 撤销/重做）冲突。 */
@media (max-width: 640px) {
  .rv-mine { padding: 14px 14px; }
  /* 评价那行：名字与内容太挤时让它换行，但按钮始终能点到 */
  .rv-rep { flex-wrap: wrap; }
  .rv-rep-body { flex: 1 0 100%; }
}


/* ============================================================
   动效层（2026-10-17）
   ------------------------------------------------------------
   阿凯：「整个网站的全部功能设计都很苹果化，做到很丝滑的动画效果」，
   并选定 **「纯极简 + 动效」+「克制」两档** —— 意思是：
   **材质一个字都不动**（纯白底 / 发丝线 / 无阴影 / 12px 圆角照旧），
   只把"手感"补上：短、快、几乎不察觉，用完只会觉得"顺"。

   ⇒ 这一节只做三件事，别的一律不碰：
     ① 反馈：按下缩 3%、悬停换底色（都是 120ms，不给阴影、不放大）；
     ② 状态：tab / 导航 / 卡片边框换色（180ms）；
     ③ 入场：页头与列表项淡入 + 上移 6px（240ms），列表按序错峰 30ms。

   ⚠️ 三条红线（改之前先读）：
     1. **不引入 box-shadow / 毛玻璃 / 视差 / 3D 倾斜**。那会推翻 2026-09 定下的
        「无阴影」规范，而这次选的就是"纯极简"。全站唯一允许的阴影仍然是
        .modal-box 那一处（弹层浮起来需要它，2026-10 就在）。
     2. **不碰 .tac-p（战术板棋子）**。它的 transition 有独立守卫，而且
        `.tac-p.is-drag { transition: none }` 是拖拽必须的（否则棋子追手指像拖皮筋）。
     3. **所有动效必须能被 prefers-reduced-motion 一票关掉**（本节最末那条）。
        这不是可选项：系统里关了动效的人，任何位移都可能是眩晕源。
   ============================================================ */

/* ---------- ① 反馈：按下与悬停 ---------- */

/* 按钮：按下去缩 3%，140ms 强 ease-out 回来（不用过冲曲线，见令牌注释）。
   为什么是 3%：2% 在手机上几乎看不到，5% 会显得"果冻"。
   ⚠️ 只对**真的能按**的东西做：label.modal-btn 也是按钮（点它弹层），
      所以一起进来；.btn-sm.ghost 这些变体共用同一套。 */
.btn-sm,
.lp-btn,
.modal-btn,
.modal-x {
  transition: transform 140ms var(--ease-out),
              background-color var(--dur-1) var(--ease-out),
              color var(--dur-1) var(--ease-out),
              border-color var(--dur-1) var(--ease-out);
}
.btn-sm:active,
.modal-btn:active {
  transform: scale(.97);
}
/* .lp-btn:hover 本来是 translateY(-1px)，按下时两者叠加会让它"往下掉"——
   统一成缩放，别让两套位移打架。 */
.lp-btn:active { transform: scale(.97); }

/* 导航与 tab：底色和字色渐变，不给位移（侧栏一行行挪动会显得页面在抖） */
.adm-nav a,
.adm-1,
.adm-grp-t,
.adm-tabs a,
.adm-sub-bar a,
.user-menu-btn {
  transition: background-color var(--dur-2) var(--ease-out),
              color var(--dur-2) var(--ease-out),
              border-color var(--dur-2) var(--ease-out);
}

/* 卡片：边框先动、底色跟着来。按下去收 1%——只在大卡片上做，
   小胶囊按钮不做（否则一排按钮按下去像在呼吸）。 */
.stat-card,
.ent-card {
  transition: border-color var(--dur-2) var(--ease-out),
              background-color var(--dur-2) var(--ease-out),
              transform var(--dur-2) var(--ease-out);
}
a.stat-card:active,
a.ent-card:active { transform: scale(.99); }

/* 列表行：悬停只换底色，不动位置；按下同样只换底色。
   训练条 .tsk 本来就是整条可点，所以它也算"按钮"，但**不缩放** ——
   一行文字缩一下会带着旁边那条发丝线一起抖。 */
.tsk,
.list > .row { transition: background-color var(--dur-1) var(--ease-out); }

/* ---------- ② 入场：页头 + 列表错峰 ---------- */

@keyframes rise-in {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}

/* 页头（公开页的 .page-head / 管理台的 .adm-head）：进来时轻轻抬一下。
   ⚠️ 只给**页头**做整块入场，不给 .wrap / .adm-main 这种容器做 ——
      那些容器里装着 .modal-mask（position:fixed），祖先一旦有 transform，
      fixed 就会改成相对它定位，弹层会瞬间错位。页头里没有 fixed 元素。 */
.page-head,
.adm-head { animation: rise-in var(--dur-3) var(--ease-out) both; }

/* 列表项：同一条动画，按序错峰 30ms。
   只排到第 6 项（150ms）——30 人的名册如果每行都加延迟，最后一行要等 900ms，
   那不叫丝滑，叫卡。第 6 项以后统一 150ms 一起进。 */
.tsk,
.list > .row,
.stat-card,
.ent-card {
  animation: rise-in var(--dur-3) var(--ease-out) both;
}
.tsk:nth-child(2), .list > .row:nth-child(2),
.stat-card:nth-child(2), .ent-card:nth-child(2) { animation-delay: 30ms; }
.tsk:nth-child(3), .list > .row:nth-child(3),
.stat-card:nth-child(3), .ent-card:nth-child(3) { animation-delay: 60ms; }
.tsk:nth-child(4), .list > .row:nth-child(4),
.stat-card:nth-child(4), .ent-card:nth-child(4) { animation-delay: 90ms; }
.tsk:nth-child(5), .list > .row:nth-child(5),
.stat-card:nth-child(5), .ent-card:nth-child(5) { animation-delay: 120ms; }
.tsk:nth-child(6), .list > .row:nth-child(6),
.stat-card:nth-child(6), .ent-card:nth-child(6) { animation-delay: 150ms; }
.tsk:nth-child(n+7), .list > .row:nth-child(n+7),
.stat-card:nth-child(n+7), .ent-card:nth-child(n+7) { animation-delay: 150ms; }

/* 提示条：从上方 4px 落下来（它是"刚才那件事的结果"，位置感要轻） */
@keyframes toast-in {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: none; }
}
.toast { animation: toast-in var(--dur-3) var(--ease-out) both; }

/* ---------- ③ 弹层：遮罩淡入 + 弹框弹簧 ----------
   ⚠️ 这里只能靠"元素从 display:none 变成 block 时自动播放 animation"，
      不能用 transition —— display 不参与过渡（本项目坚持零 JS 弹层）。 */
@keyframes mask-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes modal-in {
  from { opacity: 0; transform: translate(-50%, -50%) scale(.96); }
  to   { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}
/* 窄屏是**贴底抽屉**（≤640px 时 transform 被改成 none），
   所以它得用另一条只做位移的曲线，否则桌面那条 translate(-50%,-50%)
   会把抽屉弹回屏幕中间去。 */
@keyframes sheet-in {
  from { opacity: 0; transform: translateY(16px); }
  to   { opacity: 1; transform: none; }
}

.modal-tgl:checked ~ .modal-mask {
  animation: mask-in var(--dur-3) var(--ease-out) both;
}
.modal-tgl:checked ~ .modal-box {
  animation: modal-in var(--dur-3) var(--ease-out) both;
}
@media (max-width: 640px) {
  .modal-tgl:checked ~ .modal-box {
    animation-name: sheet-in;
    animation-timing-function: var(--ease-drawer);
  }
  /* 贴底抽屉要给 home 指示条留位置，否则最后一行按钮压在手势区里 */
  .modal-box { padding-bottom: env(safe-area-inset-bottom, 0px); }
  /* 抽屉内部滚到尽头时别把页面一起带着走 */
  .modal-box { overscroll-behavior: contain; }
}

/* ---------- ④ 锚点跳转（训练复盘 #reviews 那种）----------
   从微信群点进来直接落到评论区时，平滑滚过去比瞬移少一次"我在哪儿"。 */
html { scroll-behavior: smooth; }

/* ---------- ⑤ 关掉动效的那把闸 ----------
   系统里开了「减弱动态效果」的人，一律退化成"瞬间完成"：
   动画时长压到 0.01ms（不是 0 —— 有些浏览器对 0 干脆不触发 end 事件），
   延迟清零，平滑滚动关掉。 */
@keyframes fade-only {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  /* ★ 减弱 ≠ 全关（apple-design 14 节）。关掉的人依然需要"东西变了"的提示，
     所以这里**保留透明度**、去掉全部位移与缩放 —— 一刀切压成 0.01ms
     等于把提示也一起删了，那不是无障碍，是另一种损坏。 */
  .page-head,
  .adm-head,
  .tsk,
  .list > .row,
  .stat-card,
  .ent-card,
  .rv,
  .adm-todo,
  .toast {
    animation: fade-only 200ms ease both;
    animation-delay: 0ms;
  }
  .modal-tgl:checked ~ .modal-mask,
  .modal-tgl:checked ~ .modal-box {
    animation: fade-only 200ms ease both;
  }
  .btn-sm:active,
  .modal-btn:active,
  .lp-btn:active,
  a.stat-card:active,
  a.ent-card:active { transform: none; }
  html { scroll-behavior: auto; }
}

/* 复盘卡片（训练详情页）与待办条（队伍页）—— 同一条入场曲线，参与错峰。
   ⚠️ .rv 只在 .rv-list 里出现，卡片之间 gap 12px，位移 6px 不会互相压住。 */
.rv { animation: rise-in var(--dur-3) var(--ease-out) both; }
.rv:nth-child(2) { animation-delay: 30ms; }
.rv:nth-child(3) { animation-delay: 60ms; }
.rv:nth-child(4) { animation-delay: 90ms; }
.rv:nth-child(5) { animation-delay: 120ms; }
.rv:nth-child(n+6) { animation-delay: 150ms; }

/* 待办条：它是"你现在要处理的事"，进来时比正文早一点出现更像在提醒你 */
@keyframes todo-in {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: none; }
}
.adm-todo { animation: todo-in var(--dur-3) var(--ease-out) both; }


/* ============================================================
   iOS 材质层（2026-10-17，ADR-0003）
   ------------------------------------------------------------
   这一层取代了 2026-09 的「纯白底 + 无阴影」（ADR-0001，已 Superseded）。
   依据是 emilkowalski/skills 的 apple-design 第 12 节：
   **iOS 的层次不是靠白底 + 细线，而是分组背景 + 材质 + 一层极淡阴影。**

   继承下来、没动的东西（别以为这轮把老规矩全推翻了）：
     · 发丝线仍是唯一的边框语言（只是换成半透明黑，见 --line）
     · 绿只做点缀、表格无竖线无斑马纹、大留白、8 的倍数间距
     · 动效三条曲线与三个时长（ADR-0002 原样有效）

   ⚠️ 三条规矩，破了页面就会显脏：
     1. **卡片不套卡片** —— 一个容器里已经有 .panel，里面的表格/列表就不要再上表面。
     2. 内部行（.row / .tsk / .tbl tr）**永远不加阴影**，层次只在「整块」这一级。
     3. 颜色只能来自令牌。组件里写死 #fff 就等于绕过 --surface 改了半张脸。
   ============================================================ */

/* ---------- ① 分组背景：内容落在灰底上，卡片浮起来 ----------
   ⚠️ 落地页（/)例外，见下一条 —— 它是「海报」，不是「App」。 */
body { background: var(--bg); }

/* 营销页保持纯白：它有自己的一整块渐变首屏，套上分组灰底会在首屏下面留一条
   灰带（2026-10-17 试过，很难看）。判据用 :has(.lp)，不新增页面参数。 */
body:has(.lp) { background: #fff; }

/* ---------- ② 顶栏：吸顶 + 半透明玻璃，内容从下面滚过 ----------
   这是全站「像 iOS」最省力的一处：顶栏不再是白条，而是一层玻璃。
   ⚠️ z-index 必须低于弹层（.modal-mask 90 / .modal-box 91），否则弹层会被顶栏压住。 */
nav {
  position: sticky; top: 0; z-index: 80;
  background: rgba(255, 255, 255, .72);
  -webkit-backdrop-filter: var(--blur);
  backdrop-filter: var(--blur);
  border-bottom: 1px solid var(--line);
}

/* ---------- ③ 卡片：表面 + 发丝边 + 一层贴地阴影 ----------
   ⚠️ .tbl-scroll 只给「直接躺在页面上的表格」上表面；在 .panel 里的不加（下面有抵消规则）。
   ⚠️ 不加 overflow:hidden —— .tbl-scroll 自己要横向滚动（手机上六列的赛果表）。 */
.panel,
.stat-card,
.ent-card,
.ex-card,
.pc,
.tbl-scroll,
.tsk-list,
.adm-main > .list {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-1);
}

/* 表格容器：桌面上贴着页面，圆角由内层单元格兜住；窄屏横向滚动照旧 */
.tbl-scroll { overflow-x: auto; }

/* 列表容器上表面之后，行需要左右内缩，否则发丝线会顶到卡片边上 */
.adm-main > .list { padding: 0 16px; }
.adm-main > .list > .row:last-child { border-bottom: 0; }
.adm-main > .tsk-list { padding: 2px 16px; }
.adm-main > .tsk-list > .tsk:last-child { border-bottom: 0; }

/* ★ 抵消：已经在 .panel / .pc / .ex-card 里的容器不要再长出一层表面 ——
   卡片套卡片是这一层最容易犯的错，也是页面显脏的头号原因。 */
.panel .tbl-scroll,
.panel .tsk-list,
.panel .list,
.pc .tbl-scroll,
.pc .list {
  background: transparent;
  border: 0;
  box-shadow: none;
  border-radius: 0;
}
.panel .tbl-scroll,
.panel .list,
.panel .tsk-list { padding-left: 0; padding-right: 0; }

/* ---------- ④ 浮层：更沉的遮罩 + 更深的阴影 ----------
   材质「显形」时要一起动模糊与缩放（这是 ADR-0003 对 ADR-0002 的唯一补充），
   只在遮罩上做模糊，弹框本身不做半透明 —— 里面是表单，透过去会看不清字。 */
.modal-mask {
  background: rgba(20, 20, 22, .34);
  -webkit-backdrop-filter: blur(10px) saturate(120%);
  backdrop-filter: blur(10px) saturate(120%);
}
.modal-box { box-shadow: var(--shadow-2); }

/* ---------- ⑤ 侧栏：iPad 那种「玻璃侧边」 ----------
   ⚠️ backdrop-filter 会建立层叠上下文。侧栏里没有 position:fixed 的子元素，
      所以安全；将来若往里塞固定定位的东西，要先读这一条。 */
@media (min-width: 861px) {
  .adm-side {
    background: rgba(255, 255, 255, .55);
    -webkit-backdrop-filter: blur(18px) saturate(160%);
    backdrop-filter: blur(18px) saturate(160%);
    border-right: 1px solid var(--line);
  }
}

/* ---------- ⑥ 队伍页（管理台内）：大标题留白，下面每个分区各自成卡 ----------
   为什么 Hero 不装进卡片：iOS 的「大标题」就长在背景上，卡片是从标题下面才开始分的。
   装进去反而变成"一张卡里再切几块"，层次立刻糊。
   ⚠️ .t-show.in-adm 的 section 原来是一条 28px 的纯留白（§「队伍页=公开页」那套），
      现在改成卡；公开页 /t/:id 用的是同一份标记，所以只对 .in-adm 生效。 */
.t-show.in-adm .hero { border-bottom: 0; padding-bottom: 10px; }

.t-show.in-adm .stats {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow-1);
  padding: 18px 22px;
  margin-top: 20px;
}

.t-show.in-adm section {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-1);
  padding: 22px 22px 6px;
  margin-bottom: 16px;
}
.t-show.in-adm section:last-of-type { margin-bottom: 0; }

/* 卡里的「队长卡」不要再画一层边 —— 一层卡已经框住了，里面再框就是框套框 */
.t-show.in-adm .cap-card { border-color: transparent; background: var(--fill); }
