/* ============================================================
 * 体育比赛管理系统 · 样式
 * 浅色专业风：深红主色（竞技体育）+ 干净的中性灰底
 *
 * 设计令牌集中在 :root —— 改配色 / 圆角 / 阴影 / 动效只改这一处。
 * ⚠️ 这里的变量名被全表 2000+ 行引用：**新增可以，重命名要全局搜**。
 * ⚠️ 正文仍有 52 处硬编码 hex（32 个色值，多为一次性深灰 / 近白 / 阴影色）。
 *    盘点与迁移口径见 README「硬编码色值现状」与 `.workbuddy/tools/css-hex-audit.js`。
 * ============================================================ */

:root {
  /* ---------- 主色：竞技深红 ---------- */
  --brand:       #B42318;
  --brand-dark:  #8A1B12;
  --brand-deep:  #5E150E;   /* 顶栏渐变最深处 */
  --brand-soft:  #FEF3F2;
  --brand-ring:  rgba(180, 35, 24, .16);
  --brand-line:  #FECDCA;   /* 品牌色的淡边：关闭徽标 / 错误通告 / 判胜红 */

  /* ---------- 语义色 ---------- */
  --gold:        #B54708;
  --gold-soft:   #FFFAEB;
  --gold-line:   #FEDF89;   /* 提示的淡边：「待补」「含 0 分队」这类不拦人的提醒 */
  --ok:          #067647;
  --ok-soft:     #ECFDF3;
  --ok-line:     #ABEFC6;
  /* 信息蓝：专门给「不是操作、只是告诉你状态」的徽标用。
     原先「已报套路 N 项」的徽标复用了品牌红（--brand-soft / --brand-dark），
     与行首序号（那个方块点一下**会删掉这名队员**）撞成同色 —— 用户分不清
     哪个是数字、哪个是删除入口。蓝与红一眼分得开，也不跟绿色（通过/正确）混。 */
  --info:        #175CD3;
  --info-dark:   #153F92;
  --info-deep:   #0C447C;   /* 卡片标题蓝：比 --info 再深一档 */
  --info-soft:   #EFF6FF;
  --info-line:   #B2DDFF;

  /* ---------- 警示色：与「提示金」是两套语气 ----------
     金 = 提醒（你可以先不管）；警示 = 拦你（得先处理才能继续）。
     校验不过的输入框、成绩册「还没录全」的按钮都走这一族。 */
  --warn-line:   #F0B429;
  --warn-text:   #7A5600;
  --warn-strong: #92400E;

  /* ---------- 证件照 / 集体项（紫族）----------
     只用于「跟图像或集体有关」的元信息：证件照进度徽标、集体项目提示。
     --photo-deep 是左边色条与标题、--photo-text 是徽标字、
     --photo-text-2 是说明正文（比标题淡一档，与 --text / --text-2 同构）。 */
  --photo-soft:   #F4F3FF;
  --photo-line:   #D9D6FE;
  --photo-text:   #5925DC;
  --photo-text-2: #5748A8;
  --photo-deep:   #4A1FB8;

  /* ---------- 判胜的青色档（蓝 / 红 / 青三选一）----------
     蓝红两档复用 --info-* 与 --brand-*，青色此前没有令牌、是个孤岛。 */
  --cyan:        #0E7490;
  --cyan-soft:   #ECFEFF;
  --cyan-line:   #A5F3FC;

  /* ---------- 中性色 ---------- */
  --bg:          #F5F6F8;
  --card:        #FFFFFF;
  --card-2:      #FCFCFD;   /* 卡片内的近白填充（统计块渐变的上端）*/
  --fill-soft:   #F9FAFB;   /* 近白区块底，比卡片再深一丝 */
  --fill:        #F2F4F7;   /* 次级填充：只读块 / 灰徽标 */
  --fill-strong: #EAECF0;   /* 再深一档：进度槽 / 表头 */
  /* 表格隔行底色（zebra）：比白底深约一档，肉眼可辨但不抢内容。
     用变量而不是散落的硬编码 —— 想调深浅只改这一处。 */
  --row-alt:     #F7F8FA;
  --text:        #141A24;
  --text-2:      #475467;
  --muted:       #7A8699;
  --line:        #E6E8EC;
  --line-strong: #D3D8E0;

  /* ---------- 反白前景 / 页面底 ----------
     --on-brand 与 --card 同值但**不是一回事**：卡片底要随主题变暗，
     深色顶栏 / 判胜按钮上的反白字却必须恒为白 —— 合并会让深色模式读不清。
     --page 是打印时的纸面底色（屏幕上的页面底是 --bg 那层灰）。 */
  --on-brand:    #FFFFFF;
  --page:        #FFFFFF;

  /* ---------- 圆角 ---------- */
  --radius-xs:   6px;
  --radius-sm:   8px;
  /* --radius 是「面板级」圆角：卡片 / 统计块 / 独立标签栏 / 开关行共用，
     统一调大会一起变大 —— 这四处都是容器，视觉上正好同步。 */
  --radius:      12px;
  --radius-lg:   16px;
  --radius-pill: 999px;
  /* 吸顶子表头（日程页批量行）的 top 偏移。**运行时**由 admin.js 按 .topbar
     实测高度覆盖（窄屏标签换行会让顶栏变高）；这里是首帧/无 JS 时的兜底值。 */
  --sch-sticky-top: 64px;

  /* ---------- 阴影：两层叠加（近距描边感 + 远距扩散感）----------
     单层大模糊会显得"糊"，两层才有实物般的厚度。 */
  --shadow-xs:   0 1px 2px rgba(16, 24, 40, .04);
  --shadow:      0 1px 2px rgba(16, 24, 40, .05);
  --shadow-sm:   0 1px 3px rgba(16, 24, 40, .06), 0 1px 2px rgba(16, 24, 40, .04);
  --shadow-lg:   0 12px 28px -8px rgba(16, 24, 40, .12), 0 4px 10px -4px rgba(16, 24, 40, .06);
  --shadow-xl:   0 28px 56px -16px rgba(16, 24, 40, .22), 0 8px 20px -8px rgba(16, 24, 40, .10);

  /* ---------- 动效 ---------- */
  --ease:        cubic-bezier(.16, 1, .3, 1);
  --ease-io:     cubic-bezier(.4, 0, .2, 1);
  --dur:         .18s;
}

* { box-sizing: border-box; }

/* hidden 必须真的隐藏。
 * 浏览器默认样式里的 `[hidden] { display: none }` 属于**用户代理样式**，
 * 优先级低于任何作者样式 —— 只要元素上写了 `display: block`（如 `.comp-logo`），
 * 光设 `hidden` 属性是没用的：赛事 logo 未设置时就会露出一个「断裂图片」。
 * 加 !important 把 `hidden` 的语义钉死，避免以后新增样式再踩同一个坑。 */
[hidden] { display: none !important; }

html, body {
  margin: 0;
  padding: 0;
}

/* 手机上禁止系统/微信「自动放大文字」：微信有字体大小设置，
   iOS 也会对窄屏做字体膨胀，一放大版面就全乱。
   注意只关自动调整，不禁用户手动缩放（没有 maximum-scale）。 */
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }

/* 滚动条**常驻预留**：页面内容变短到不需要滚动条时，内容区会凭空宽出 9px
   （本项目的自定义滚动条正好 9px），右对齐的东西就会跟着右移 —— 典型场景是
   「收缩全部项目」按钮：一收，页面矮了、滚动条没了、按钮右移 9px（实测
   1000px 档按钮右边缘 954→963）。用户要的是「位置固定」，所以这里把滚动条槽位
   永远留出来：内容长短都不影响可用宽度。
   scrollbar-gutter: stable 是现代浏览器的正解（只预留、不画槽）；overflow-y:scroll
   作为兜底（老 Edge 不支持 scrollbar-gutter，会真的常显一条滚动条，但同样达到
   「宽度恒定」的效果）。 */
html {
  overflow-y: scroll;
  scrollbar-gutter: stable;
}

body {
  /* 中文优先的字体栈：拉丁字形交给系统 UI 字体，汉字交给各平台最佳中文字体。
     顺序不能颠倒 —— 把中文字体放前面，数字与英文会变成全角观感。 */
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
               "Hiragino Sans GB", "Noto Sans SC", "Source Han Sans SC",
               "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif;
  font-size: 14px;
  line-height: 1.6;
  color: var(--text);
  /* 极淡的品牌色光晕落在顶栏下方，让页面不再是"一片死灰"。
     用 radial-gradient + no-repeat 而不是 background-attachment: fixed ——
     后者在 iOS 上滚动会掉帧、且与滚动条槽位计算打架。 */
  background-color: var(--bg);
  background-image: radial-gradient(1200px 460px at 50% -180px, rgba(180, 35, 24, .05), transparent 70%);
  background-repeat: no-repeat;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  /* 本系统到处是数字（人数 / 级别 / 号码 / 日期）。
     全局等宽数字，纵向队列的数字右缘才天然对齐，不靠逐处 font-variant。 */
  font-variant-numeric: tabular-nums;
}

/* ---------- 顶栏 ---------- */

.topbar {
  /* 吸顶：长表单页滚到中段时仍能看到赛事名与「报名中」状态。
     z-index 要高于卡片，但必须低于 toast(999) 与证件照面板(1000)。 */
  position: sticky;
  top: 0;
  z-index: 50;
  color: var(--on-brand);
  /* 两层渐变叠加：底层是斜向的品牌红，再叠一层右上角暖光。
     单一线性渐变在宽屏上会很"平"，一点径向光斑就有体积感。 */
  background-color: var(--brand-dark);
  background-image:
    radial-gradient(120% 190% at 86% -50%, rgba(255, 166, 128, .30), transparent 60%),
    linear-gradient(135deg, var(--brand-deep) 0%, var(--brand-dark) 38%, var(--brand) 100%);
  /* 刘海屏 / home 指示条：viewport-fit=cover 时内容会顶到屏幕边缘，
     不补安全区就会被状态栏和底部横条压住。不是刘海屏时 env() 为 0，等于没加。 */
  padding: calc(15px + env(safe-area-inset-top, 0px)) 0 15px;
  padding-left: env(safe-area-inset-left, 0px);
  padding-right: env(safe-area-inset-right, 0px);
  /* 内描边 + 外投影：上缘一道高光，让整条像"贴"在页面上而不是浮着 */
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .12),
              0 8px 24px -12px rgba(94, 21, 14, .55);
}

.topbar-inner {
  max-width: 1180px;
  margin: 0 auto;
  padding: 0 18px;
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}

.brand-mark {
  width: 36px;
  height: 36px;
  border-radius: 10px;
  background: linear-gradient(160deg, rgba(255, 255, 255, .26), rgba(255, 255, 255, .09));
  border: 1px solid rgba(255, 255, 255, .28);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .30);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 17px;
  font-weight: 600;
  flex: 0 0 auto;
}

.topbar h1 {
  font-size: 16px;
  font-weight: 600;
  margin: 0;
  letter-spacing: -.1px;
}

.topbar .sub {
  font-size: 12px;
  opacity: .8;
  margin-top: 2px;
}

.topbar .spacer { flex: 1 1 auto; }

.topbar nav {
  display: flex;
  gap: 4px;
  align-items: center;
  /* 分段控件观感：两个入口像一组"开关"，而不是两个孤立链接。
     凹槽底色 + 内阴影，让被选中的那一项真正"凸"出来。 */
  padding: 3px;
  border-radius: var(--radius-pill);
  background: rgba(0, 0, 0, .15);
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, .14);
}

.topbar nav a {
  color: rgba(255, 255, 255, .86);
  text-decoration: none;
  font-size: 13px;
  font-weight: 500;
  padding: 6px 14px;
  border-radius: var(--radius-pill);
  transition: background var(--dur) var(--ease-io), color var(--dur) var(--ease-io),
              box-shadow var(--dur) var(--ease-io);
}

.topbar nav a:hover { background: rgba(255, 255, 255, .16); color: var(--on-brand); }
/* 当前页用「白底 + 品牌红字」反向标记：在红色顶栏上，白块是最强的定位信号 */
.topbar nav a.active {
  background: var(--card);
  color: var(--brand-dark);
  box-shadow: 0 1px 3px rgba(0, 0, 0, .18);
}

/* ---------- 赛事 logo（顶栏，赛事名称之前） ---------- */

/* 只在「已设赛事 logo」时改形态：退掉「武」字方块的底色与固定宽度，
   让 logo 按自身比例铺开；没设 logo 时保持原样，版面不会空一块。 */
.brand-mark.has-logo {
  width: auto;
  height: 42px;
  padding: 0;
  background: transparent;
  border-color: transparent;
  /* 上面的内描边高光必须一并撤掉，否则透明底上会留下一道悬空的白线 */
  box-shadow: none;
}

.comp-logo {
  height: 100%;
  width: auto;
  max-width: 150px;
  object-fit: contain;
  display: block;
  border-radius: 6px;
}

/* ---------- 技术支持宣传语（顶栏，报名状态徽标之前） ---------- */

.topbar .ts-text { letter-spacing: .2px; }

.topbar .tech-support {
  flex: 0 0 auto;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--on-brand);
  background: rgba(255, 255, 255, .13);
  border: 1px solid rgba(255, 255, 255, .22);
  border-radius: var(--radius-pill);
  padding: 5px 12px;
  white-space: nowrap;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .18);
}
.topbar .tech-support:hover { background: rgba(255, 255, 255, .24); }

/* ---------- 顶栏：全局「隐藏说明」开关 ----------
 * 2026-10-15 用户需求：「我用熟系统之后，这些说明都是干扰信息，
 *   只是让别人上手快一些」→ 给一个一键全隐藏的开关。
 * ⚠️ 存 localStorage 而不是 state：**这是当前这台电脑/这个人的界面偏好**，
 *   不是赛事规则。存进云端 state 会跟着赛事同步给所有组委会的人，
 *   把给别人上手用的说明也一起关掉，反而违背需求。 */
.topbar .topbar-help {
  flex: 0 0 auto;
  font-family: inherit;
  font-size: 12.5px;
  font-weight: 500;
  line-height: 1.2;
  color: var(--on-brand);
  background: rgba(255, 255, 255, .13);
  border: 1px solid rgba(255, 255, 255, .22);
  border-radius: var(--radius-pill);
  padding: 5px 12px;
  white-space: nowrap;
  cursor: pointer;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .18);
  transition: background-color var(--dur) var(--ease-io);
}
.topbar .topbar-help:hover { background: rgba(255, 255, 255, .24); }
.topbar .topbar-help:focus-visible {
  outline: 2px solid var(--on-brand);
  outline-offset: 2px;
}
/* 开关打开：全站一个「说明」入口都不留。
 *   display:none 必须能压过 `.card-head > .help { display: contents }`
 *   （那条不写 !important，权重更低），所以这里用 !important。 */
body.help-off .help { display: none !important; }

/* ---------- 赛事 logo 的上传位（管理端「赛事设置」） ----------
 * 格子本身复用证件照那套 .ph-cell / .ph-contain，这里只管外层的排布与说明文字。 */

.logo-row {
  display: flex;
  align-items: flex-start;
  gap: 16px;
  flex-wrap: wrap;
  padding: 12px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--bg);
  margin-bottom: 14px;
}
.logo-tip {
  font-size: 12.5px;
  line-height: 1.75;
  color: var(--text-2);
  max-width: 460px;
}
.logo-tip strong { color: var(--text); }

/* ---------- 页脚（同一条宣传信息，任何屏幕尺寸都在） ---------- */

.site-foot {
  max-width: 1180px;
  margin: 0 auto;
  /* 底部再补一段安全区：iPhone 的 home 指示条会压住最下面一行 */
  padding: 20px 18px calc(38px + env(safe-area-inset-bottom, 0px));
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 7px;
  font-size: 13px;
  color: var(--text-2);
}
.site-foot .foot-sep { color: var(--line-strong); }
.site-foot .foot-text { color: var(--text-2); }
.site-foot .foot-tel {
  color: var(--brand);
  font-weight: 500;
  text-decoration: none;
  transition: color var(--dur) var(--ease-io);
}
.site-foot .foot-tel:hover { text-decoration: underline; }

/* ---------- 队伍 logo ---------- */

/* 比证件照格子宽一些：队标多为横向，太窄就看不出是什么了。
   用 contain 完整显示 —— cover 会把横向队标裁掉两边。 */
.ph-cell.ph-contain { width: 72px; height: 46px; }
.ph-cell.ph-contain img { object-fit: contain; background: var(--card); padding: 2px; }

/* 证件牌面板标题里的队标 */
.sheet-logo {
  height: 30px;
  width: auto;
  max-width: 110px;
  object-fit: contain;
  vertical-align: middle;
  margin-right: 8px;
  border-radius: 4px;
}

@media (max-width: 720px) {
  /* 手机上顶栏挤不下这一句（会把它挤成两三行），隐去顶栏那一份，
     页脚仍在 —— 宣传位不会丢。 */
  .topbar .tech-support { display: none; }
}

/* ---------- 手机竖屏提示条 ----------
 * 手机上录入本来就吃亏。与其硬把表格挤进 390px，不如直说 + 给一条出路
 * （关掉提示 / 切横屏）。是否显示由 JS 判定（窄屏 + 竖屏 + 用户没关过）。 */

/* 它本身也得瘦：手机上如果占了 120px，就变成「为了提示你不要挤，反而先挤掉一块屏」。
   目标是文字 + 两个按钮排在同一行，整条 ≤ 60px。 */
.mobile-tip {
  max-width: 1180px;
  margin: 10px auto 0;
  padding: 8px 12px;
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  background: var(--gold-soft);
  border: 1px solid var(--gold-line);
  border-radius: 10px;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--gold);
}
.mobile-tip .mt-body { flex: 1 1 auto; min-width: 0; }
.mobile-tip b { font-weight: 600; }
.mobile-tip .btn,
.mobile-tip .btn-sm {
  min-height: 36px;
  padding: 4px 11px;
  font-size: 12.5px;
  white-space: nowrap;
  flex: 0 0 auto;
}
.mobile-tip .btn-ghost { color: var(--gold); }

/* ---------- 布局 ---------- */

.container {
  max-width: 1180px;
  margin: 0 auto;
  padding: 24px 18px 64px;
}

.card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  /* 两层阴影：贴边那层给"厚度"，扩散那层给"悬浮"。
     单层阴影要么像描边、要么像糊了一块，两层才有实物感。 */
  box-shadow: var(--shadow-sm);
  margin-bottom: 18px;
  overflow: hidden;
}

.card-head {
  padding: 15px 18px;
  border-bottom: 1px solid var(--line);
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  background: linear-gradient(180deg, var(--card-2), var(--card));
}

.card-head h2 {
  font-size: 15px;
  font-weight: 600;
  margin: 0;
  letter-spacing: -.1px;
  /* 标题前一道品牌色竖条 —— 让"模块起点"一眼可辨，也给整页一点节奏。
     用 flex 而非 border-left：竖条不会跟着多行标题拉长到整块高度。 */
  display: flex;
  align-items: center;
  gap: 9px;
  min-width: 0;
}
.card-head h2::before {
  content: "";
  flex: 0 0 auto;
  width: 3px;
  height: 15px;
  border-radius: 2px;
  background: var(--brand);
}

.card-head .spacer { flex: 1 1 auto; }

.card-head .hint {
  font-size: 12px;
  color: var(--muted);
}

/* 管理端专属（2026-10-07 用户要求）：每个设置模块的标题行带信息蓝软底，
   长页面里一眼分出模块边界、不容易看花眼。scoped 到 body.admin，
   报名端（index.html）的卡片标题保持原样。 */
.admin .card-head {
  background: linear-gradient(180deg, #F5FAFF, var(--info-soft));
  border-bottom: 1px solid var(--info-line);
}
.admin .card-head h2 { color: var(--info-deep); }
.admin .card-head h2::before { background: var(--info-deep); }
.admin .card-head .hint { color: #185FA5; }

.card-body { padding: 18px; }
/* ⚠️ 卡片正文建立 BFC：内部若出现浮动（徽标、角标一类），
     没有它时浮动会把后面的块（下一个字段、按钮行）**缠上来**，
     表现是「按钮行突然窄了一截 / 文字绕到角标右边」。
     ⚠️ 用 display:flow-root 而不是 overflow:hidden —— 两者都能建 BFC，
        但 overflow:hidden 会**裁掉溢出内容**（本项目 .ath-table 踩过同一个坑）。
        flow-root 只隔离浮动，不裁剪，是这里唯一正确的选择。
     ⚠️ 2026-10-15 说明入口改版后，这套 BFC 补丁其实已无用武之地：
        旧版说明圆点是 float/absolute 的，会绕上正文；新版入口在
        .card-head 里、面板全程 position:static。这里保留 flow-root
        与 clearfix 是为了别的浮动元素，不是为说明 —— 别再拿「说明浮层」
        当理由，那套结构已经不存在了。 */
.card-body { display: flow-root; }
.card-body::after,
.card-body.tight::after { content: ""; display: block; clear: both; }
.card-body.tight { padding: 12px 18px; }

.grid-2 {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 14px;
}

.grid-3 {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 14px;
}

/* ---------- 随队人员（教练 / 队医）编辑器 ---------- */

.people-block { margin-bottom: 16px; }

.people-head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 8px;
}
.people-head .people-title {
  font-size: 13px;
  font-weight: 500;
  color: var(--text-2);
}
.people-head .field-hint { margin: 0; }
.people-head .spacer { flex: 1 1 auto; }

.people-list { display: flex; flex-direction: column; gap: 8px; }

.people-row {
  display: flex;
  align-items: flex-end;
  gap: 10px;
  /* 行内字段的 margin-bottom 被清零（为了让同一行里的输入框底边对齐），
     于是「行与行之间、行与下一个区块之间」的间距必须由行自己补回来。
     漏了这条的后果（手机竖屏真机量过）：领队手机号所在行的底边是 429.56px，
     而紧跟的「+ 添加教练」按钮顶边也是 429.56px —— **零间距贴死**，
     看起来就像按钮把领队手机号那一格顶歪了。 */
  margin-bottom: 14px;
}
.people-row .field { flex: 1 1 0; margin-bottom: 0; min-width: 0; }
.people-row .people-del { flex: 0 0 auto; margin-bottom: 1px; }

/* ---------- 表单 ---------- */

.field { display: block; margin-bottom: 14px; }
/* 字段标题：页面里的标记写成 <label class="field"><span>标题</span>…>，
   所以除了 label 子元素，也要覆盖 span 子元素 */
.field > label,
.field > span {
  display: block;
  font-size: 13px;
  font-weight: 500;
  color: var(--text-2);
  margin-bottom: 5px;
}
.field .req { color: var(--brand); margin-left: 2px; }

/* 字段下方的补充说明（如体重与级别联动规则） */
.field .field-hint,
.field-hint {
  display: block;
  margin: 5px 0 0;
  font-size: 12px;
  font-weight: 400;
  line-height: 1.55;
  color: var(--muted);
}

.field .field-hint-warn,
.field-hint-warn { color: var(--gold); }

/* 「同岁数分两组，领队自选」这条属于**须知**不是**警告**：
   它说的是「你有两组都符合，自己挑一个」，不是「你填错了」。
   用信息蓝与 lv-todo 的蓝底同色系 —— 之前用金色时，这一行比下拉的蓝底更显眼，
   领队看着一条橙色提示会以为是自己填错（用户反馈「同时符合两个组别时没有蓝色提示」）。
   2026-10-05 与 lv-todo 一并定色：待办/须知=蓝，填错=金。 */
.field .field-hint-info,
.field-hint-info { color: var(--info); }

/* 提示容器：自身不带字号/间距，样式交给内部的 .field-hint */
.field .field-hint-slot {
  display: block;
  margin: 0;
  font-size: inherit;
  font-weight: inherit;
  color: inherit;
  line-height: inherit;
}

/* ---------- 文件行（竞赛规程上传 / 报名页查看） ---------- */
.file-row { margin-bottom: 14px; }

.file-box {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: 9px;
  background: var(--card-2);
}
/* 还没有文件时用虚线框，与证件照空态的观感一致 */
.file-box.file-empty {
  border-style: dashed;
  border-color: var(--line-strong);
  background: var(--card);
}
.file-box .file-ico { color: var(--brand); flex: 0 0 auto; }
.file-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.file-name {
  font-size: 13px;
  font-weight: 500;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.file-meta { font-size: 12px; color: var(--muted); font-style: normal; }
.file-row .field-hint { margin-top: 6px; }

/* 导入组别数据区块（组别设置页） */
.import-block {
  border-top: 1px dashed var(--line);
  margin-top: 14px;
  padding-top: 14px;
}
.imp-paste { margin-bottom: 10px; font-size: 12.5px; color: var(--text-2); }
.imp-paste > summary { cursor: pointer; }
.imp-paste textarea {
  width: 100%;
  margin-top: 8px;
  font-family: ui-monospace, Menlo, Consolas, monospace;
  font-size: 12.5px;
  line-height: 1.5;
}

/* ---------- 本届大项行 + 导入面板（2026-10-12 合并） ----------
 * .dz-list 里每一项 = .dz-item（大项行 + 可展开的导入面板）。
 * 数量徽标 .dz-count 同时是导入区的开关：点开在行下方展开 .imp-panel。 */
.dz-item {
  border: 1px solid var(--line);
  border-left: 3px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: var(--card);
  overflow: hidden;
  transition: border-color var(--dur) var(--ease-io);
}
.dz-item.on { border-color: var(--line-strong); border-left-color: var(--brand); }
.dz-item.open { border-left-color: var(--brand); }
/* 行在 .dz-item 内不再自画边框（外层容器统一画，展开时上下才连成一块） */
.dz-item .dz-row {
  border: none;
  border-radius: 0;
  background: transparent;
}
.dz-item.on .dz-row { background: var(--brand-soft); }

/* 数量徽标：既显示数量，又是导入区的开关 */
.dz-count {
  flex: 0 0 auto;
  appearance: none;
  border: 1px solid var(--line);
  background: var(--fill);
  color: var(--text-2);
  border-radius: var(--radius-pill);
  padding: 3px 10px;
  font: inherit;
  font-size: 12px;
  font-weight: 500;
  line-height: 1.5;
  white-space: nowrap;
  cursor: pointer;
  transition: background var(--dur) var(--ease-io), color var(--dur) var(--ease-io),
              border-color var(--dur) var(--ease-io);
}
.dz-count.has { background: var(--info-soft); color: var(--info); border-color: var(--info-line); }
.dz-count:hover { background: var(--fill-strong); }
.dz-count:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }
.dz-item.open .dz-count { background: var(--brand-soft); color: var(--brand); border-color: var(--brand-line); }

/* 导入面板：在行下方展开，与行之间用一条分隔线断开 */
.imp-panel {
  border-top: 1px solid var(--line);
  background: var(--card-2);
  padding: 14px 16px 16px;
}
.imp-panel .import-block { border-top: none; margin-top: 0; padding-top: 0; }
.imp-panel .field-hint:last-child { margin-bottom: 0; }

/* 提交按钮下方的说明（报名页）：解释「提交报名」到底做什么，
   免得大家以为不点就没保存（改动其实是自动同步到云端的） */
.foot-tip {
  margin: 10px 0 4px;
  font-size: 12.5px;
  line-height: 1.7;
  color: var(--muted);
}

/* 报名页的「报名须知」正文：保留原文换行 */
.read-notice {
  white-space: pre-line;
  font-size: 13.5px;
  line-height: 1.7;
  color: var(--text-2);
  background: var(--brand-soft);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 10px 12px;
  margin-bottom: 12px;
}

input[type="text"],
input[type="password"],
input[type="date"],
input[type="datetime-local"],
input[type="number"],
input[type="tel"],
input[type="email"],
input[type="search"],
select,
textarea {
  width: 100%;
  padding: 9px 11px;
  font-size: 14px;
  font-family: inherit;
  color: var(--text);
  background-color: var(--card);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  outline: none;
  transition: border-color var(--dur) var(--ease-io), box-shadow var(--dur) var(--ease-io),
              background-color var(--dur) var(--ease-io);
}

/* 悬停先给一点"可以点"的暗示，聚焦再给完整的高亮环 */
input:hover:not(:disabled):not(:focus),
select:hover:not(:disabled):not(:focus),
textarea:hover:not(:disabled):not(:focus) { border-color: var(--muted); }

input:focus, select:focus, textarea:focus {
  border-color: var(--brand);
  box-shadow: 0 0 0 3px var(--brand-ring);
}

::placeholder { color: #9AA4B2; opacity: 1; }
/* 选中文字也用品牌色，全站观感统一 */
::selection { background: rgba(180, 35, 24, .16); }

/* 原生下拉箭头在 Windows 上是个瘦小的灰三角，和本页其它控件不成一套。
   appearance:none 换掉它，改用内联 SVG 折角（URL 编码的 # 必须写成 %23）。
   ⚠️ 下方的 .lv-todo / .lv-invalid 只能用 background-color，
      一旦用 background 简写就会把这张图一并清掉。 */
select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 32px;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none' stroke='%23667085' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'><path d='M2.5 4.25 6 7.75l3.5-3.5'/></svg>");
  background-repeat: no-repeat;
  background-position: right 10px center;
  background-size: 12px 12px;
}
select:focus {
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none' stroke='%23B42318' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round'><path d='M2.5 4.25 6 7.75l3.5-3.5'/></svg>");
}

/* ---------- 散打组别下拉的两种状态色 ----------
 * 排在这里（基础 select 规则之后）是为了让顺序也站在我们这边：
 * `select.lv-todo` 的确比裸 `select` 的权重高（0,1,1 对 0,0,1），单靠权重就能赢；
 * 但 `input:disabled` 这类 `:disabled` 规则权重**相同**、又写在后面，
 * 顺序放后面可以少一层「靠权重险胜」的心智负担。
 * 以后往这里加状态类，也一律放这个位置。 */

/* 选了级别但与年龄 / 体重不符时高亮（琥珀金） */
select.lv-invalid {
  border-color: var(--warn-line);
  /* 只能用 background-color —— background 简写会连带清掉上面那张下拉箭头 */
  background-color: var(--gold-soft);
  color: var(--warn-text);
}

/* 「还没选散打组别」——用**控件自身的背景色**提示，不再用页面顶部的橙色通告。
   原因（用户要求）：顶部的通告离问题太远，而且「没选级别」的人里混着
   「只报套路、本来就不该选散打级别」的队员，一条通告会引起歧义。
   用信息蓝（而不是 lv-invalid 的琥珀金）：金色是本行「选了但不符」的语义，
   这里只是「还没填」的待办提醒，两者不该用同一个颜色。
   判定与提交校验一致 —— 只报套路的队员不会落到这个类上（见 team.js athleteRow）。
   历史：曾为了「一眼看得出」加过一道左侧实色条，用户确认蓝底看得见之后
   又要求去掉（2026-10-05）—— 蓝底 + 蓝边 + 深蓝字已经够醒目，色条反而多余。 */
select.lv-todo {
  border-color: var(--info);
  /* 同 .lv-invalid：必须用 background-color，别用 background 简写 */
  background-color: var(--info-soft);
  color: var(--info-dark);
}

input:disabled, select:disabled, textarea:disabled {
  /* background-color 而非简写：否则禁用态的下拉会连箭头一起消失 */
  background-color: var(--fill);
  color: var(--muted);
  cursor: not-allowed;
}

textarea { resize: vertical; min-height: 68px; }

/* ---------- 按钮 ---------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 9px 16px;
  font-size: 13.5px;
  font-weight: 500;
  font-family: inherit;
  line-height: 1.2;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line-strong);
  background: var(--card);
  color: var(--text-2);
  cursor: pointer;
  white-space: nowrap;
  box-shadow: var(--shadow-xs);
  /* 逐属性过渡，不用 transition: all —— all 会把布局属性也带上，
     悬停时可能触发不必要的重排。 */
  transition: background var(--dur) var(--ease-io), border-color var(--dur) var(--ease-io),
              color var(--dur) var(--ease-io), box-shadow var(--dur) var(--ease-io),
              transform .1s var(--ease-io);
}

.btn:hover { border-color: var(--muted); color: var(--text); box-shadow: var(--shadow-sm); }

/* 按下去的实体感：轻微缩小 + 收掉阴影，模拟"压下去" */
.btn:active { transform: scale(.98); box-shadow: none; }

/* 键盘可达性：用 outline 而不是 box-shadow —— box-shadow 会被父级 overflow 裁掉，
   且在 Windows 高对比度模式下不生效。鼠标点击不触发（:focus-visible）。 */
.btn:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
}

.btn-primary {
  background: linear-gradient(180deg, #C42A1D, var(--brand));
  border-color: var(--brand-dark);
  color: var(--on-brand);
  box-shadow: 0 1px 2px rgba(138, 27, 18, .30), inset 0 1px 0 rgba(255, 255, 255, .16);
}
.btn-primary:hover {
  background: linear-gradient(180deg, var(--brand), var(--brand-dark));
  border-color: var(--brand-dark);
  color: var(--on-brand);
  box-shadow: 0 3px 10px -2px rgba(138, 27, 18, .38), inset 0 1px 0 rgba(255, 255, 255, .16);
}
.btn-primary:active { box-shadow: none; }

.btn-ghost {
  border-color: transparent;
  background: transparent;
  color: var(--brand);
  padding: 6px 9px;
  box-shadow: none;
}
.btn-ghost:hover { background: var(--brand-soft); border-color: transparent; }
.btn-ghost:active { box-shadow: none; }

.btn-danger-ghost {
  border-color: transparent;
  background: transparent;
  color: var(--brand);
  padding: 6px 9px;
  box-shadow: none;
}
.btn-danger-ghost:hover { background: var(--brand-soft); border-color: transparent; }
.btn-danger-ghost:active { box-shadow: none; }

/* 添加类按钮：淡雅底色。它是「增量操作」，不该和主操作的实心红抢注意力，
   但又要一眼能认出是「可以点、点了会多一行」 */
.btn-accent {
  background: var(--brand-soft);
  border-color: #F5C9C5;
  color: var(--brand);
}
.btn-accent:hover {
  background: #FCE7E5;
  border-color: var(--brand);
  color: var(--brand-dark);
}

.btn-sm { padding: 5px 11px; font-size: 12.5px; border-radius: var(--radius-xs); }

.btn:disabled {
  opacity: .5;
  cursor: not-allowed;
  box-shadow: none;
}
.btn:disabled:hover { border-color: var(--line-strong); color: var(--text-2); }
.btn:disabled:active { transform: none; }

.btn-row {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  align-items: center;
}

/* 下载链接用 <a download>（原生下载最稳，不依赖 JS），当成普通按钮用 */
a.btn { text-decoration: none; }

/* ---------- 报名入口（二维码 + 网址）----------
   二维码是**预生成的静态图** assets/qr-signup.png（由 tools/make-qr.py 生成），
   运行时不引任何 QR 库 —— 与整站「零依赖」保持一致。
   为什么不做成「页面里用 JS 现画」：那要么引第三方库，要么自己实现整套 QR 编码，
   而报名网址几十年才变一次，预生成一张图最省事也最可靠。 */
/* 窄屏自动换行靠 flex-wrap + flex-basis，**不写媒体查询**：
   ① 少一处断点要维护；
   ② 回归是按「第一个 `@media (max-width:620px)` 块」提取手机端 CSS 的，
      在基础区再插一个同名断点会把真正那段顶掉、让一批手机端断言集体假红（踩过）。 */
.qr-row { display: flex; align-items: flex-start; flex-wrap: wrap; gap: 18px; }
.qr-img {
  flex: 0 0 auto;
  width: 196px; height: 196px;
  padding: 7px;                 /* 留一圈白边：贴出去裁边时不伤静区 */
  box-sizing: border-box;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 10px;
}
/* 二维码尚未生成时的占位（本项目发布前运行 make-qr.py 产出真图） */
.qr-img.qr-placeholder {
  display: flex; align-items: center; justify-content: center;
  text-align: center; line-height: 1.5;
  color: var(--muted); font-size: 12px;
  background: var(--bg);
  border-style: dashed;
}
.qr-img.qr-placeholder small { color: inherit; opacity: .8; }
.qr-side { flex: 1 1 220px; min-width: 0; }   /* 窄到放不下就整块落到下一行 */
.qr-side .field { margin-bottom: 12px; }
.copy-row { display: flex; gap: 8px; }
.copy-row input {
  flex: 1 1 auto; min-width: 0;
  font-family: ui-monospace, Menlo, Consolas, monospace;
  font-size: 12.5px;
}

/* ---------- 徽标 ---------- */

.badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 10px;
  border-radius: var(--radius-pill);
  font-size: 12px;
  font-weight: 500;
  line-height: 1.5;
  letter-spacing: .1px;
  border: 1px solid transparent;
  /* 内高光让胶囊有一点"实体感"，在彩色底上尤其明显 */
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .6);
  /* 表格被挤窄时不许换行 —— 一换行，「3 / 12」就断成两行，
     相邻两列看上去就错位了。 */
  white-space: nowrap;
}
.badge-open   { background: var(--ok-soft);   color: var(--ok);   border-color: var(--ok-line); }
.badge-closed { background: var(--brand-soft);          color: var(--brand); border-color: var(--brand-line); }
.badge-info   { background: var(--info-soft);          color: var(--info);     border-color: var(--info-line); }
.badge-gray   { background: var(--fill);          color: var(--text-2); border-color: var(--line); }
.badge-warn   { background: var(--gold-soft); color: var(--gold);   border-color: var(--gold-line); }
/* 证件照进度：用紫色系，和「人数」的蓝、状态的绿/红区分开，一眼能分辨是哪一类信息 */
.badge-photo  { background: var(--photo-soft);          color: var(--photo-text);       border-color: var(--photo-line); }

/* 可以点的徽标（例如「证件照 3 / 12」）：保持徽标的观感，但明确表示能点开 */
.badge-btn {
  cursor: pointer;
  font-family: inherit;
  transition: box-shadow var(--dur) var(--ease-io), transform .1s var(--ease-io);
}
.badge-btn:hover {
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .6), 0 2px 7px -1px rgba(16, 24, 40, .20);
}
.badge-btn:active { transform: scale(.97); }
.badge-btn:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

/* 只读的辅助文字单元格 */
.text-muted { color: var(--muted); font-size: 12.5px; }

/* ---------- 表格 ---------- */

.table-wrap { overflow-x: auto; }

table.data {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}

table.data th {
  text-align: left;
  font-weight: 600;
  font-size: 12.5px;
  letter-spacing: .2px;
  color: var(--text-2);
  background: var(--fill-soft);
  padding: 10px 12px;
  /* 表头底线用重一档的灰：把"表头"和"数据"在视觉上分层 */
  border-bottom: 1px solid var(--line-strong);
  white-space: nowrap;
}

table.data td {
  padding: 9px 12px;
  border-bottom: 1px solid var(--line);
  vertical-align: middle;
}

table.data tr:last-child td { border-bottom: none; }
table.data tbody tr { transition: background var(--dur) var(--ease-io); }

/* 隔行异色（2026-10-12）：长表格逐行读时不容易串行。
 * 偶数行给淡底；**必须写在 tr:hover 之前** —— 两条规则权重完全相同
 * （table.data tbody tr:nth-child(even) 与 table.data tbody tr:hover 都是 0,2,3），
 * 靠书写顺序决胜负，hover 在后才盖得住斑马底。
 * 语义色行（.age-manual / .draw-future / .draw-bye-row）的背景写在 **td** 上，
 * td 背景绘制在 tr 之上，会自然盖住这里的斑马底 —— 互不干扰，无需额外排除。 */
table.data tbody tr:nth-child(even) { background: var(--row-alt); }
table.data tbody tr:hover { background: rgba(180, 35, 24, .055); }

table.data td input,
table.data td select {
  padding: 5px 8px;
  font-size: 13px;
}
/* 单元格里的下拉要额外留出箭头的位置（基础 select 规则给了 32px，
   这里被上面的简写 padding 覆盖成 8px，必须补回来） */
table.data td select { padding-right: 26px; background-position: right 7px center; }

.col-num { width: 46px; color: var(--muted); }
.col-act { width: 1%; white-space: nowrap; text-align: right; }
/* 勾选列：窄且居中，别让复选框贴着左边框 */
table.data th.col-center,
table.data td.col-center { width: 1%; text-align: center; white-space: nowrap; }
table.data td.col-center input[type="checkbox"] { margin: 0; cursor: pointer; }
/* 「领队自选组」整行淡底：区间与别组相同是故意的，一眼能认出不是配错 */
table.data tr.age-manual td { background: var(--gold-soft); }
table.data tbody tr.age-manual:hover td { background: #FEF6D8; }

/* 「运动员」这类纯数字列：等宽数字 + 居中，上下几行的数字右缘才对得齐 */
table.data td.col-count {
  width: 64px;
  text-align: center;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--text);
}

/* 「证件照 n / m」格子按钮：给个最小宽度，个位数与两位数不会左右跳 */
.badge-btn[data-photos] {
  min-width: 56px;
  justify-content: center;
  font-variant-numeric: tabular-nums;
}

/* ---------- 提示 ---------- */

.notice {
  padding: 11px 14px;
  border-radius: var(--radius-sm);
  font-size: 13px;
  line-height: 1.6;
  border: 1px solid;
  /* 左侧加粗成一条色条：整块铺色太"吵"，一条色条既醒目又克制，
     扫读时视线也能顺着左缘迅速定位。 */
  border-left-width: 4px;
  margin-bottom: 14px;
}
/* 左侧那条 4px 色条用**饱和色**，其余三边才用淡色 —— 淡色描边负责"边界"，
   饱和色条负责"一眼归类"。两者同色的话，色条会淡到看不出来。 */
.notice-info  { background: var(--info-soft); border-color: var(--info-line); border-left-color: #2E90FA; color: var(--info); }
.notice-warn  { background: var(--gold-soft); border-color: var(--gold-line); border-left-color: var(--warn-line); color: var(--gold); }
.notice-err   { background: var(--brand-soft); border-color: var(--brand-line); border-left-color: var(--brand); color: var(--brand); }
.notice-ok    { background: var(--ok-soft); border-color: var(--ok-line); border-left-color: var(--ok); color: var(--ok); }

/* 导入出错的提示面板（2026-10-05）：替代 window.alert。
   报错现在会带上具体区间和「多半是页面没刷新」这类排障信息，一条比一条长 ——
   alert 装不下（手机上更是一屏放不下），改成页面内可滚动的面板。
   文本选 `text-align:left`：警告框默认继承居中，长句子居中极难读。 */
.import-errors { text-align: left; margin-bottom: 12px; }
.import-errors strong { display: block; margin-bottom: 6px; }
.import-err-list {
  margin: 0 0 2px;
  padding-left: 20px;
  max-height: 40vh;
  overflow: auto;
}
.import-err-list li { margin-bottom: 6px; word-break: break-word; }
/* 「首级带下限」核对面板（2026-10-15）：两种展开结果并排摆出来让用户点一下选。
   数字用等宽排（tabular-nums）—— 判断办法是「数级别个数」，两组名字对不齐就没法数。 */
.import-warns { text-align: left; margin-bottom: 12px; }
.import-warns strong { display: block; margin-bottom: 6px; }
.import-warns li { line-height: 1.75; font-variant-numeric: tabular-nums; }
.import-warns .field-hint { text-align: left; color: var(--text-2); font-size: 12.5px; }

/* ---------- 可折叠的「说明」 ----------
 * 管理端原先把解释性文字都铺成彩色 .notice，一屏好几个框，看久了分不清
 * 哪块才是真要动手处理的状态。约定：
 *   · 彩色框只留给「需要处理的状态」——报错、风险提示、报名开关；
 *   · 解释性的内容（怎么算、会怎样、和别的功能什么关系）一律折进这里，默认收起。
 * 用原生 <details>，不依赖 JS。 */
/* ---------- 说明：折叠成卡片标题行里的「说明」按钮 ----------
 * 2026-10-15 用户核定（改版第二轮）。上一版是收起态的「?」圆点，
 * 用户报出两个问题，本段按这两点重做：
 *   ① 同一卡片里并排三四个一模一样的圆点，标题只藏在 hover 里 ——
 *      用户点之前无从判断哪个圆点讲哪件事，造成困惑；
 *   ② 圆点用 position:absolute 骑在前一块上，撞到表格就压住
 *      单元格里的「删除」按钮。
 * 现在三条铁律：
 *   · 入口**带文字**（「说明」），不再靠猜；
 *   · 同一个卡片的说明**合并成一份**，正文里用小标题分节；
 *   · 面板**全部在文档流里**（position:static），展开只把卡片撑高，
 *     结构上压不到任何东西 —— 这类 bug 从此不会再出现。
 * 用原生 <details>，不依赖 JS。
 *
 * ⚠️ 2026-10-15 用户第三轮反馈（这条是本段最关键的技术选择）：
 *   「点了之后说明按钮会跳到新的一行改变了位置，希望位置不要变，
 *     看完说明之后鼠标原位点击就能收起」。
 *   病因：details 整体是标题行的一个 flex item，展开时靠
 *   `.help[open]{flex:0 0 100%}` 换行独占一整行 —— **换的是 details 盒子，
 *   里面的 summary 被一起带到了第二行**，按钮就此跳位，得挪鼠标才点得到。
 *   （用户原话「看完说明之后鼠标原位点击就能搜索」就是要原地收起。）
 *
 *   ⚠️⚠️ 走过的两条弯路，别再试（都真机量过）：
 *   ① `display: contents`（details 不生成盒子，把 summary 与面板一起提升成
 *      标题行的 flex item，纸面上完美）—— **实测失败**：Chrome 把提升后子元素
 *      的百分比 flex-basis 解析到 details 自己（宽度 0），`flex:0 0 100%`
 *      实际拿到 max-content（真机量：声明 100%、实宽 342.8px，容器内容宽
 *      671px），面板没换行反而把 summary 挤到行中间 —— 比原来更糟。
 *   ② 让 details 自己换行、面板 absolute 覆盖 —— 铁律明令禁止（压内容）。
 *
 *   ✅ 最终方案：**按钮留在标题行，面板搬进卡片正文顶部**
 *   （admin.js 的 helpRelocate() 在每次 render 后搬）。于是：
 *     · details 里永远只剩 summary 一个可见子元素 → 展开/收起时标题行
 *       布局**逐像素相同**，按钮位置由构造保证不会动；
 *     · 面板进了 .card-body 就是普通块级元素，宽 100%、把卡片撑高、
 *       压不到任何东西（仍在流内，不靠 absolute）；
 *     · 收起靠 hidden 属性，面板零版面成本。
 *   所以本段**不再需要任何 `[open]` 换行规则** —— 面板早就不在标题行了。
 *   加新规则前请先读 helpRelocate() 的注释。 */
.help {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.7;
  color: var(--text-2);
  display: block;
}
/* ⚠️ 挂在标题行里：靠 margin-left:auto 推到行尾（行内已有 .spacer 时
 *   两者都能把按钮推到右侧，spacer 优先，不冲突）。
 *   **这里刻意没有任何 `[open]` 规则**：面板已被 helpRelocate() 搬进
 *   .card-body，details 里永远只有 24px 高的 summary，展开不改变它的
 *   高度 —— 按钮不动的保证来自「面板不在这里」，不是来自这里的 CSS。
 * ⚠️ 选择器列了两种标题行：卡片头（.card-head）与编排场次表的
 *   大项分组头（.sheet-group-head）。后者不是 .card 的头，但同样是
 *   「一行标题 + 右侧操作按钮」的形状，说明该跟在哪。 */
.card-head > .help,
.sheet-group-head > .help {
  margin-left: auto;
  flex: 0 0 auto;
}
/* 入口按钮本体。⚠️ 必须 list-style:none 并去掉 ::marker，
 *   否则 summary 左边会挂一个小三角（details 默认是列表项）。 */
.help > summary {
  cursor: pointer;
  list-style: none;
  user-select: none;
  display: inline-flex;
  align-items: center;
  height: 24px;
  padding: 0 10px;
  border-radius: 12px;
  font-size: 12px;
  line-height: 1;
  color: var(--info-dark);
  background: var(--info-soft);
  border: 1px solid var(--info-line);
  white-space: nowrap;
  transition: background-color var(--dur) var(--ease-io),
              color var(--dur) var(--ease-io);
}
.help > summary::-webkit-details-marker { display: none; }
.help > summary::marker { content: ''; }
.help > summary:hover {
  background: var(--info);
  border-color: var(--info);
  color: var(--on-brand);
}
.help > summary:focus-visible {
  outline: 2px solid var(--info);
  outline-offset: 2px;
}
/* 展开后的说明面板：白底 + 边框，在流内把卡片撑高，
 *   无论卡片标题栏是什么底色都读得清。
 * ⚠️ 被 helpRelocate() 搬进 .card-body 的那些面板，收起态靠 hidden 属性
 *   隐藏（原生 details 的规则管不到它们了 —— 它们已经不是 details 的子节点）。
 *   .help-body 自身**不要**声明 display，否则会盖掉 UA 的 [hidden]{display:none}。 */
.help-body[hidden] { display: none; }
.help-body {
  /* 上下留白对称：面板被 helpRelocate() 搬进 .card-body 顶部，
     上下一样宽才不会「贴着上一块、悬在下一块」的偏心感。 */
  margin: 10px 0;
  padding: 12px 14px;
  background: var(--bg);
  border: 1px solid var(--line);
  border-left: 2px solid var(--info-line);
  border-radius: 8px;
}
.help-body b, .help-body strong { color: var(--text); font-weight: 600; }
.help-body p { margin: 0 0 8px; }
.help-body p:last-child { margin-bottom: 0; }
/* 分节：同一个卡片的几条说明合并后，用小标题隔开。
 *   首节不额外加顶距（.help-body 已有 padding）。 */
.help-sec + .help-sec {
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px dashed var(--line);
}
.help-sec > h4 {
  margin: 0 0 6px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text);
}
.help-body code {
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 0 4px;
  font-size: 12px;
}
/* ⚠️ 兼容旧结构：万一某处的 .help 不在 .card-head 里（浮层、卡片正文），
 *   面板就留在 details 内、由原生 details 负责显隐（这就是默认行为，
 *   这里不需要任何东西）。helpRelocate() 只搬标题行里的那些。 */
.card-body > .help { margin: 12px 0 0; }

/* ---------- 窄字段：只填一个数的输入框 ----------
 * 2026-10-14 用户反馈「名次分，只需要输入一个数字，却占了整行宽度」。
 * 根因是基础规则 `input[type="number"] { width: 100% }` —— 它是为「体重」
 * 「出生日期」这类**需要读出量级**的字段写的，而「每队计分项数 = 5」
 * 这种字段拿同一条规则就是灾难：它把一个 1 字符的输入摊成 500px 的空地，
 * 旁边真正的说明文字反倒被挤到换行。
 *
 * 这里给 `.field-narrow` 一个 96px 的宽度（够放两位数 + 上下箭头）。
 * 凡是**输入长度有上限、且上限很小**的字段都用它，别再靠内联 style 写宽度。 */
.field-narrow { display: inline-block; }
.field-narrow > input[type="number"],
.field-narrow > input[type="text"] {
  width: 96px;
  text-align: center;
}
/* 与标签同行的版本：标签在左、数字框紧随其后，说明文字继续向右铺。
   这是把「标签 + 控件 + 说明」压成一行的地方 —— 原来是三行。 */
.field-line {
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 14px;
}
.field-line > .field-line-label {
  flex: 0 0 auto;
  font-size: 13px;
  font-weight: 500;
  color: var(--text-2);
}
.field-line > input[type="number"] {
  flex: 0 0 auto;
  width: 96px;
  text-align: center;
}
/* 说明文字在这一行里可宽可窄，不再被输入框挤到下一行 */
.field-line > .field-line-hint {
  flex: 1 1 220px;
  min-width: 0;
  margin: 0;
  font-size: 12px;
  line-height: 1.55;
  color: var(--muted);
}

/* ---------- 一行式紧凑字段 ----------
 * 行内人一眼就懂的设置（如体重上浮容差）不该为它铺一整块两列说明区。 */
.field-inline {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  font-size: 13px;
  font-weight: 500;
  color: var(--text);
}
.field-inline input[type="number"] { width: 96px; }
.field-inline input[type="text"] { width: 200px; }
.field-inline .unit { color: var(--text-2); font-weight: 400; }
.field-inline .hint { font-weight: 400; }

/* ---------- 本届大项（2026-10-12 改版） ----------
 * 一行一个大项：勾选开关 · 名称 · 报名形态 · 数量 · 状态 · 删除，全部收在同一行。
 *
 * 改版要点（用户反馈「太丑了」）：
 *   1. 名称改成**无边框输入**（平时就是一行粗体标题，悬停/聚焦才显出可编辑），
 *      不再是第二个「方框」与下拉框抢视觉。
 *   2. 报名形态做成**紧凑胶囊**（灰底 + 圆角），与输入框拉开层次。
 *   3. 左侧一道状态竖条 + 整行底色，替代原来挤在中间的状态文字。
 *   4. 本卡与上方「导入组别与项目」卡之间拉开更大间距（.dz-card），避免两卡贴一起。 */
.dz-card { margin-top: 0; }
.dz-list { display: grid; gap: 8px; }

.dz-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 14px 8px 11px;
  border: 1px solid var(--line);
  border-left: 3px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: var(--card);
  font-size: 13.5px;
  color: var(--text);
  transition: border-color var(--dur) var(--ease-io), background var(--dur) var(--ease-io);
}
.dz-row.on {
  border-color: var(--line-strong);
  border-left-color: var(--brand);
  background: var(--brand-soft);
}
.dz-row input[type="checkbox"] {
  flex: 0 0 auto; width: 16px; height: 16px; margin: 0; cursor: pointer;
}

/* 大项名称：无边框，平时读作标题；悬停给淡底、聚焦给品牌描边环。
 * ⚠️ 选择器必须写 `.dz-row input.dz-nameinput`（权重 0,2,1）——
 *    基础规则 `input[type="text"]` 是 (0,1,1)，单靠 `.dz-nameinput` (0,1,0) 压不住它，
 *    width/padding/border 会全部被基础规则赢走（原写法靠行内 style 才生效，去掉后就露馅）。
 *    hover/focus 同理：基础 `input:hover:not(:disabled):not(:focus)` 是 (0,3,1)。 */
.dz-row input.dz-nameinput {
  flex: 0 0 auto;
  width: 136px;
  padding: 3px 6px;
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
  background-color: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-xs);
}
.dz-row input.dz-nameinput:hover:not(:focus) {
  background-color: rgba(16, 24, 40, .05);
  border-color: transparent;
}
.dz-row input.dz-nameinput:focus {
  background-color: var(--card);
  border-color: var(--brand);
  box-shadow: 0 0 0 3px var(--brand-ring);
}

/* 报名形态：紧凑胶囊下拉。⚠️ 只用 background-color 与 padding 覆盖，
   不能用 background 简写 —— 会把基础 select 的箭头图清掉（见上方 select 注释）。
   同理用 `.dz-row select.dz-mode` 提高权重压过基础 `select` 与 `select:hover…`。 */
.dz-row select.dz-mode {
  flex: 0 0 auto;
  width: auto;
  padding: 3px 24px 3px 10px;
  font-size: 12.5px;
  color: var(--text-2);
  background-color: var(--fill);
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  background-position: right 8px center;
  background-size: 11px 11px;
  cursor: pointer;
}
.dz-row select.dz-mode:hover:not(:focus) {
  border-color: transparent;
  background-color: var(--fill-strong);
}
.dz-row select.dz-mode:focus {
  background-color: var(--card);
  border-color: var(--brand);
  box-shadow: 0 0 0 3px var(--brand-ring);
}

.dz-row .badge { flex: 0 0 auto; }
.dz-row .spacer { flex: 1 1 auto; }
.dz-state { flex: 0 0 auto; font-weight: 400; font-size: 12.5px; color: var(--muted); }
.dz-row.on .dz-state { color: var(--brand); font-weight: 500; }
.dz-row .btn-dzdel { flex: 0 0 auto; }

/* 新增大项：与大项列表用一条虚线分隔，排成一行（原先是裸 div，输入框各自占满整行、
   下拉与按钮被挤到下一行 —— 这次一并收拾）。 */
.dz-new {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: 12px;
  padding-top: 14px;
  border-top: 1px dashed var(--line-strong);
}
.dz-new input[type="text"] { flex: 1 1 200px; width: auto; min-width: 140px; }
.dz-new select { flex: 0 0 auto; width: auto; }
.dz-new .btn { flex: 0 0 auto; }

/* 新增组别行（2026-10-12 并入「现有组别」卡底部，原独立卡已删）：
   与 .dz-new 同一种「卡内一行表单」风格，上方虚线分隔 —— 与表格拉开层次又不另起一卡。
   ⚠️「现有组别」卡没有 card-body，内容裸放，所以左右用 18px margin 对齐其它元素。 */
.grp-new {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin: 14px 18px;
  padding-top: 14px;
  border-top: 1px dashed var(--line-strong);
}
.grp-new-lead { flex: 0 0 auto; font-size: 12.5px; font-weight: 600; color: var(--text-2); }
/* ⚠️ 用「父类 + 元素 + 类型」提权压过基础 input[type="…"]（0,1,1）：见 .dz-nameinput 注释 */
.grp-new select { flex: 0 0 auto; width: auto; }
/* 宽度按**内容的重要度**分档，不是按类型统一：
   级别名称（60公斤级）是这一行的主字段，却是位次最低的那一栏 ——
   原来 text 只有 118px，placeholder「级别名称，如 60公斤级」根本显示不全，
   而 92px 的 kg 数字框却和它差不多宽。主次颠倒，一律拉回来。 */
.grp-new input[type="text"] { flex: 0 0 auto; width: 168px; }
.grp-new input[type="number"] { flex: 0 0 auto; width: 78px; }
.grp-new .btn { flex: 0 0 auto; }

.dz-rule {
  margin-top: 10px;
  font-size: 12.5px;
  font-weight: 400;
  color: var(--text-2);
}
/* 跨项开关（「允许一人同时报散打与套路」）：排在两个大项之后、与它们是同一组语义，
   所以比上面两行缩进一点、字号小一号，读起来像「大项的一个附带规则」而不是第三个大项。 */
.dz-cross {
  margin-top: 10px;
  padding: 9px 14px;
  font-size: 12.5px;
  font-weight: 400;
  background: var(--bg);
  border-style: dashed;
  cursor: pointer;
}
.dz-cross.on { background: var(--brand-soft); border-style: solid; }
.dz-cross .dz-name { font-weight: 500; color: var(--text-2); }
.dz-cross.on .dz-name { color: var(--text); }
.empty code {
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 0 4px;
  font-size: 12px;
}

.empty {
  padding: 40px 18px;
  text-align: center;
  color: var(--muted);
  font-size: 13.5px;
  line-height: 1.8;
}
.empty .big {
  font-size: 26px;
  line-height: 1;
  letter-spacing: 2px;
  margin-bottom: 10px;
  /* 压得更淡：空状态里这行只是"氛围"，不该抢说明文字 */
  opacity: .4;
}
.empty a { color: var(--brand); }

/* ---------- 统计块 ---------- */

.stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
  gap: 12px;
}

.stat {
  position: relative;
  background: linear-gradient(170deg, var(--card), var(--card-2));
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 15px 16px 14px;
  overflow: hidden;
}
/* 顶沿一道渐隐的品牌色细线：给数字块一个统一的"数据卡"身份，
   渐隐到透明是为了不抢数字本身的视觉权重。 */
.stat::before {
  content: "";
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 2px;
  background: linear-gradient(90deg, var(--brand), rgba(180, 35, 24, 0));
}
.stat .n {
  font-size: 26px;
  font-weight: 700;
  color: var(--brand);
  line-height: 1.15;
  letter-spacing: -.6px;
  font-variant-numeric: tabular-nums;
}
.stat .l { font-size: 12.5px; color: var(--muted); margin-top: 4px; }

/* ---------- 分组标签页 ---------- */

/* overflow-y 必须显式写 hidden（2026-10-13）：
   CSS 规范里，只要一个轴不是 visible，另一个轴的 visible 就**计算为 auto**。
   原先只写 overflow-x: auto，而标签带 margin-bottom:-1px（压住底边那条线），
   内容因此溢出 1px → 底部冒出一条竖向滚动条。
   标签栏本意就是横向滚动，纵向一律不许滚。 */
.tabs {
  display: flex;
  gap: 4px;
  border-bottom: 1px solid var(--line);
  padding: 0 18px;
  overflow-x: auto;
  overflow-y: hidden;
}

.tabs button {
  position: relative;
  border: none;
  background: none;
  font-family: inherit;
  font-size: 13.5px;
  font-weight: 500;
  color: var(--text-2);
  padding: 11px 14px;
  cursor: pointer;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  white-space: nowrap;
  transition: color var(--dur) var(--ease-io), background var(--dur) var(--ease-io);
}

/* 选中指示也不用整条底边，而是内缩的一段圆角短线 —— 比通栏底线精致，
   也不会因为按钮宽度不同而长短不一。 */
.tabs button::after {
  content: "";
  position: absolute;
  left: 12px;
  right: 12px;
  bottom: -1px;
  height: 2px;
  border-radius: 2px;
  background: transparent;
  transition: background var(--dur) var(--ease-io);
}

.tabs button:hover { color: var(--text); background: rgba(16, 24, 40, .035); }
.tabs button.active { color: var(--brand); font-weight: 600; }
.tabs button.active::after { background: var(--brand); }
.tabs button:focus-visible { outline: 2px solid var(--brand); outline-offset: -3px; }

/* 独立成块的标签栏（管理端顶部） */
.tabs.standalone {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  margin-bottom: 18px;
  padding: 0 10px;
}

/* ---------- 页内标签（.sub-tabs）----------
 * 2026-10-14 用户需求：「各个模块下，面太长、滚动超过三次的，都将卡片做成标签」。
 * 放在**卡片外、页面里** —— 与顶层标签栏（.standalone，8 个模块）视觉上必须分得开，
 * 否则用户会以为点错了层级、以为跳到了别的模块。
 * 判据：底色 var(--card) + 完整边框 + 圆角 = 「卡片」，里面躺着卡片，视觉上从属关系清楚。 */
.tabs.sub-tabs {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  margin-bottom: 14px;
  padding: 0 10px;
}
/* 页内标签选中色改用信息蓝（与顶层标签栏的品牌红区分开）。
   ⚠️ 顶层那个红是「我在哪个模块」，页内这个蓝是「我在这个模块的哪一段」——
      两个层级用同一个颜色会让人分不清自己点到了哪儿。 */
.tabs.sub-tabs button.active { color: var(--info-dark); }
.tabs.sub-tabs button.active::after { background: var(--info); }
.tabs.sub-tabs button:hover { background: rgba(23, 92, 211, .05); }
.tabs.sub-tabs button:focus-visible { outline-color: var(--info); }

/* ---------- 勾选胶囊 ---------- */

.chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  font-size: 13px;
  color: var(--text-2);
  background: var(--card);
  cursor: pointer;
  user-select: none;
  box-shadow: var(--shadow-xs);
  transition: border-color var(--dur) var(--ease-io), background var(--dur) var(--ease-io),
              color var(--dur) var(--ease-io), box-shadow var(--dur) var(--ease-io),
              transform .1s var(--ease-io);
}

.chip:hover { border-color: var(--muted); box-shadow: var(--shadow-sm); }
.chip:active { transform: scale(.985); }

.chip input { margin: 0; accent-color: var(--brand); }

.chip:has(input:checked) {
  border-color: var(--brand);
  background: var(--brand-soft);
  color: var(--brand);
  font-weight: 500;
  box-shadow: 0 1px 2px rgba(180, 35, 24, .12);
}

/* 键盘可达：里面的复选框拿到焦点时，整颗胶囊一起给出焦点环 */
.chip:focus-within { outline: 2px solid var(--brand); outline-offset: 2px; }

.chip-sub { font-size: 11.5px; color: var(--muted); }
.chip:has(input:checked) .chip-sub { color: var(--brand); opacity: .75; }

/* ---------- 证件照 ----------
 * 统一的「照片格子」：空态是虚线框，填好后是缩略图并在悬停时露出右上角的 ×。
 * 三种尺寸：运动员行 40×52、随队人员行 34×44（.ph-sm）、窄屏整行 44×58。 */

.photo-note {
  background: var(--photo-soft);
  border: 1px solid var(--photo-line);
  color: var(--photo-deep);
  border-radius: 8px;
  font-size: 12.5px;
  line-height: 1.6;
  margin-bottom: 14px;
}
/* 说明默认折叠成一行：手机上原文要占四行，会把队名和输入框整段往下顶 */
.photo-note > summary {
  cursor: pointer;
  padding: 9px 12px;
  font-weight: 500;
  display: flex;
  align-items: center;
  gap: 6px;
}
.photo-note > summary::before {
  content: '';
  width: 0; height: 0;
  border: 4px solid transparent;
  border-left-color: var(--photo-deep);
  border-right-width: 0;
  transition: transform .15s;
}
.photo-note[open] > summary::before { transform: rotate(90deg); }
.photo-note-body {
  padding: 0 12px 10px 24px;
  color: var(--photo-text-2);
}
/* 折叠时必须真的把内容摘掉：Chrome 给 details 内容用的是 ::details-content /
   content-visibility 那套，实测未展开时子元素的 getBoundingClientRect 仍有 83px
   —— 盒子没塌，视觉上会压到下面的队名输入框上。显式 display 才可控。 */
.photo-note:not([open]) > .photo-note-body { display: none; }

.ph-cell {
  position: relative;
  flex: 0 0 auto;
  width: 40px;
  height: 52px;
  border: 1px dashed var(--line-strong);
  border-radius: 6px;
  background: #FAFAFB;
  color: var(--muted);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  overflow: hidden;
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
  transition: border-color .15s, background .15s, color .15s;
}
.ph-cell:hover,
.ph-cell:focus-visible {
  border-color: var(--brand);
  background: var(--brand-soft);
  color: var(--brand);
  outline: none;
}
.ph-cell .ph-plus { font-size: 15px; line-height: 1; }
.ph-cell .ph-word { font-size: 10px; line-height: 1.1; }

.ph-cell.ph-sm { width: 34px; height: 44px; }
.ph-cell.ph-sm .ph-word { font-size: 9px; }

/* 已有照片：实线边框 + 缩略图（cover 裁切，证件照比例能填满） */
.ph-cell.ph-on {
  border-style: solid;
  border-color: var(--line);
  background: var(--card);
  padding: 0;
}
.ph-cell.ph-on:hover { border-color: var(--brand); }
.ph-cell img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* 只读且没有照片：不可点，避免误以为能上传 */
.ph-cell.ph-void {
  cursor: default;
  background: var(--bg);
  color: #98A2B3;
}
.ph-cell.ph-void:hover { border-color: var(--line-strong); background: var(--bg); color: #98A2B3; }

/* 删除照片：只在悬停/聚焦时出现，平时不干扰 */
.ph-x {
  position: absolute;
  top: 0;
  right: 0;
  width: 16px;
  height: 16px;
  padding: 0;
  border: 0;
  border-radius: 0 5px 0 6px;
  background: rgba(26, 32, 44, .66);
  color: var(--on-brand);
  font-size: 13px;
  line-height: 15px;
  font-family: inherit;
  cursor: pointer;
  display: none;
}
.ph-cell.ph-on:hover .ph-x,
.ph-cell.ph-on:focus-within .ph-x { display: block; }
.ph-x:hover { background: var(--brand); }

/* ---------- 证件照悬停预览（2026-10-11 用户要求：鼠标划过大图看） ----------
 * 长边 = --ph-zoom-edge（由 photo.js 的 PREVIEW_EDGE 写进来；这里的 320px 只是兜底，
 * 两处必须一致 —— 改一处记得改另一处）。
 * 为什么是 fixed + 由 JS 写 left/top，而不是就地绝对定位：
 *   格子住在 .card{overflow:hidden} 与 .table-wrap{overflow:auto} 里，就地放放大图
 *   **两层祖先都会裁**（跟套路报项的悬停清单同一个坑）。fixed 不受祖先 overflow 影响。
 * pointer-events:none 是必须的：否则指针一旦落到预览层上就触发格子的 mouseout →
 *   收起 → 又 mouseover → 弹出，来回闪烁。 */
.ph-zoom {
  position: fixed;
  left: 0;
  top: 0;
  z-index: 1100;              /* 高于 .sheet(1000)：管理端证件牌面板里的照片也要能放大 */
  display: none;
  padding: 4px;
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  background: var(--card);
  box-shadow: var(--shadow-lg);
  pointer-events: none;
}
.ph-zoom.ph-zoom-on { display: block; }
.ph-zoom img {
  display: block;
  max-width: var(--ph-zoom-edge, 320px);
  max-height: var(--ph-zoom-edge, 320px);
}
@media print { .ph-zoom { display: none !important; } }

/* 运动员行里的格子：靠列头对齐，和输入框顶端对齐 */
.ath-row > .ph-cell { margin-top: 1px; }

/* ---------- 报名页：宽屏标签页（手机窄屏不分页） ----------
 * 电脑端把「报名须知与规程 / 参赛队信息 / 运动员名单」分页切换，不必一路往下滚。
 * 这里是「先隐藏标签栏、宽屏才启用」的写法：窄屏（≤960px）下三个面板全部平铺，
 * 与手机上原来的上下滚动体验完全一致 —— 手机布局是逐项量过的，不要被这条改动碰到。 */
.ath-tabs { display: none; }

.ath-tab {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 9px 14px;
  margin-bottom: -1px;
  border: 1px solid transparent;
  border-bottom: 0;
  border-radius: 8px 8px 0 0;
  background: transparent;
  color: var(--text-2);
  font-family: inherit;
  font-size: 13.5px;
  font-weight: 500;
  cursor: pointer;
}
.ath-tab:hover { color: var(--brand); background: var(--brand-soft); }
.ath-tab.is-on {
  background: var(--card);
  border-color: var(--line);
  color: var(--brand);
}
.ath-tab:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }

.tab-badge {
  padding: 1px 6px;
  border-radius: 999px;
  background: var(--brand-soft);
  color: var(--brand);
  font-size: 11.5px;
  font-weight: 500;
  white-space: nowrap;   /* 与 .badge 同理：挤窄时不能折行 */
}

@media (min-width: 961px) {
  .ath-tabs {
    display: flex;
    flex-wrap: wrap;
    gap: 4px;
    margin-bottom: 14px;
    border-bottom: 1px solid var(--line);
  }
  .tab-panel { display: none; }
  .tab-panel.is-on { display: block; }
}

/* ---------- 运动员名单（一行一人；宽屏靠列头对齐，窄屏自动换两列） ---------- */

/* ⚠️ 这里**不能**写 overflow:hidden：套路报项的悬停清单是绝对定位在徽标下方的，
   表格一裁它就只剩上半截 —— 收起全部项目后表格本来就矮（一人一行），
   清单几乎必然被裁。裁掉的部分没有滚动条，用户只会以为「没报项」。
   改用「给列头/末行自己倒角」来保住圆角外观（见 .ath-thead / .ath-item:last-child）。
   代价：列宽若真被顶破，内容会明晃晃地溢出而不是被裁掉 —— 所以下面的最小宽度
   必须留够余量（量过：tools/measure-taolu.js --w=961）。 */
.ath-table {
  border: 1px solid var(--line);
  border-radius: 9px;
  overflow: visible;
  background: var(--card);
}

/* 列头行与数据行共用同一套列宽，保证上下对齐。
   ⚠️ 最小宽度不是随手写的：断点 961px 时表格内容宽只有 861px（量出来的），
   九列加间距的最小和必须明显小于它，否则最右一列会被挤出可视区。
   删除按钮那一路已在 2026-10-05 去掉（序号是唯一入口），腾出的空间并进备注列；
   当前最小和 = 794 + 8×6 = 842px（余量 ~19px），改任何一列都要重跑
   node tools/measure-taolu.js 确认「表格没有横向顶破」仍通过。 */
.ath-thead,
.ath-row {
  display: grid;
  grid-template-columns:
    32px                  /* 序号（兼删除入口；表头写「序号」，26px 会把两字“序号”挤成省略号） */
    44px                  /* 证件照 */
    minmax(84px, 1.05fr)  /* 姓名 */
    56px                  /* 性别 */
    118px                 /* 出生日期 */
    66px                  /* 体重 */
    minmax(124px, 1.25fr) /* 身份证号 */
    minmax(134px, 1.45fr) /* 参赛组别 */
    minmax(118px, 1.15fr); /* 备注（原删除按钮那一列的空间并进来了） */
  column-gap: 6px;
  row-gap: 0;
  align-items: start;
}

/* .ath-table 不再 overflow:hidden，圆角得自己倒（8 = 外框 9 - 边框 1） */
.ath-thead {
  border-radius: 8px 8px 0 0;
  padding: 7px 12px;
  background: var(--fill-soft);
  border-bottom: 1px solid var(--line);
  font-size: 12px;
  font-weight: 500;
  color: var(--text-2);
}
.ath-thead > span {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ath-thead .req { color: var(--brand); margin-left: 2px; }

.ath-item {
  padding: 7px 12px;
  border-bottom: 1px solid var(--line);
}
/* 同上：末行自己倒下面两个角，悬停底色才不会在圆角外露方角 */
.ath-item:last-child { border-bottom: 0; border-radius: 0 0 8px 8px; }
.ath-item:hover { background: #FBFBFC; }

/* ---------- 名册成员（本届未参加）：只读一行 + 「参加本届」 ----------
   开新赛事后运动员转入队伍名册（joined=false），下一届点一下即可复用。
   ⚠️ 这一行**不套 `.ath-row`**（那是与表头对齐的 Grid），用自适应 flex。 */
.ath-item.ath-out { background: var(--card-2); }
.ath-out-row {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.ath-out-no {
  flex-shrink: 0;
  width: 22px;
  text-align: center;
  font-size: 12px;
  color: var(--muted);
}
.ath-out-name { font-weight: 600; color: var(--text-2); }
.ath-out-meta { font-size: 12.5px; color: var(--muted); }
.ath-out-tag {
  flex-shrink: 0;
  font-size: 11.5px;
  color: var(--muted);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 1px 6px;
  background: var(--card);
}
.ath-out-row .btn { flex-shrink: 0; }

/* 名册条：队里有本届未参加的队员时出现（不进 .ath-bar，那里的按钮位置有硬约束） */
.ath-roster-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin: 0 0 10px;
  padding: 8px 10px;
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--text-2);
  background: var(--info-soft);
  border-left: 3px solid var(--info);
  border-radius: 4px;
}
.ath-roster-bar b { color: var(--text); font-weight: 600; }
.ath-roster-bar .btn { margin-left: auto; flex-shrink: 0; }

/* 行内字段：去掉 .field 的默认下边距，标题由列头行代替 */
.ath-row .field { margin-bottom: 0; min-width: 0; }
.ath-row .field > span { display: none; }
/* 行内控件统一高度：type=date 自带固有尺寸，任何宽度下都比 text 控件高 2px
   （实测 768px 下 37 vs 35、393px 下 41 vs 39），一排里就显得凸出。 */
.ath-row input,
.ath-row select {
  padding: 5px 8px;
  font-size: 13px;
  height: 34px;
  box-sizing: border-box;
}
/* 出生日期：Chrome 有固有尺寸，iOS / WKWebView 上更高更宽（原生控件外观）。
   显式压回与其它控件一致的盒子，并去掉原生外观带来的额外高宽。 */
.ath-row input[type="date"] {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  min-width: 0;
  max-width: 100%;
}

.ath-no {
  width: 22px; height: 26px;
  display: flex; align-items: center; justify-content: center;
  background: var(--brand-soft);
  color: var(--brand);
  border-radius: 6px;
  font-size: 12px;
  font-weight: 500;
  /* 可编辑时序号是 <button>（点它即删除）：清掉 UA 默认的边框/内边距/字体，
     否则方块会被撑大、挤出固定列宽（宽屏序号列只有 26px） */
  border: 0;
  padding: 0;
  font-family: inherit;
}
/* 可编辑态的序号就是删除入口，给出可点、且是危险操作的视觉暗示 */
.ath-no-del { cursor: pointer; }
.ath-no-del:hover,
.ath-no-del:focus-visible {
  background: var(--brand);
  color: var(--on-brand);
  outline: none;
}

/* ---------- 组别格：一个格子装下**所有**「按组别报名」的大项 ----------
 * 为什么叠在一个格子里、而不是每个大项一列：列宽是量着 961px 断点定的
 * （见 .ath-thead 的注释，余量不到三十 px），而大项个数来自数据、组委会随时会加。
 * 每加一个大项就多一列，最右一列（备注）必然被挤出可视区。
 *
 * 只有一个大项时（.ath-grp-stack 不带 .multi）：结构与改版前**逐字相同** ——
 * 一个 select 直接躺在 .ath-grp-cell 里，不出现大项名标签，视觉零变化。 */
.ath-grp-cell { display: block; }
.ath-grp-stack { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.ath-grp-one { display: flex; align-items: center; gap: 5px; min-width: 0; }
/* 大项名标签：只在**多个**分组大项并存时出现（否则与格子的表头重复） */
.ath-grp-tag { display: none; }
.ath-grp-stack.multi .ath-grp-tag {
  display: inline-block;
  flex: 0 0 auto;
  max-width: 54px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 11.5px;
  line-height: 1.2;
  color: var(--text-2);
}
/* select 自己吃掉剩余宽度；min-width:0 是让它在窄列里真的能收缩 */
.ath-grp-one > select { flex: 1 1 auto; min-width: 0; }

/* 报项数字徽标与备注同占一格。
   ⚠️ 不单独开一列：宽屏那一行的最小宽度是贴着 961px 断点量出来的（见 .ath-thead
   的注释，余量不到三十 px），再插一列就会把最右一列挤出可视区。
   徽标是灵活项、备注输入框 min-width:0，两边一起让，列宽一点不变。 */
.ath-remark {
  display: flex;
  align-items: flex-end;
  gap: 5px;
  min-width: 0;
}
.ath-remark .field { flex: 1 1 auto; min-width: 0; }
.ath-tl-bslot { flex: 0 0 auto; display: inline-flex; }
/* 徽标占的每一像素都是从备注框里让出来的（宽屏最挤那一档备注框只剩 ~63px，
   量过：tools/measure-taolu.js），所以别把它做胖 —— 20px 够放两位数了 */
/* 是 <button> 而不是 <span>：它同时是**开关**（点一下展开 / 收起这一名的项目），
   用按钮才有键盘可达性与「可点」的默认语义。 */
.ath-tl-badge {
  position: relative;   /* 悬停清单的定位基准 */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 19px;
  padding: 0 4px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--card);
  color: var(--muted);
  font-family: inherit;
  font-size: 11.5px;
  font-weight: 600;
  cursor: pointer;
}
.ath-tl-badge:hover,
.ath-tl-badge:focus-visible { border-color: var(--info); color: var(--info); outline: none; }
/* 清单最后那句「点一下这个数字…」是操作说明，压低一点别抢了报项本身 */
.ath-tl-tip-how { margin-top: 3px; color: rgba(255, 255, 255, .62) !important; }
/* 已报项徽标用**信息蓝**，不用品牌红 —— 红是「行首序号＝删除入口」的颜色，
   两者挨在同一个人的那一行里，同色会让人以为数字也能删（用户明确提过）。
   蓝色只表示「有几项」这个状态，不暗示任何破坏性操作。 */
.ath-tl-badge.on {
  border-color: var(--info);
  background: var(--info-soft);
  color: var(--info-dark);
}
/* 悬停清单：贴着徽标下方、向左长（right:0），这样既不会撞表格右边界，
   也不会把页面顶出横向滚动条。.ath-table 已改成 overflow:visible，
   清单伸到表格外面也不会被裁（见 .ath-table 的注释）。
   ⚠️ 但**还有一层**会裁：`.card { overflow:hidden }`（卡片的圆角）。名单长的时候
   卡片很高、下面有的是地方；收起全部项目后最后几名下面只剩几十像素，
   清单（~113px，三行）就会被卡片底边切掉 —— 用户看到的是「只报了一项」。
   所以最后三名改成往上开：那几行上面有一整摞队员，空间反而富余。
   三行是量出来的（收起态一行 67px，最后一名下面 46px、下一名 +67…够到 119px 才安全；
   见 tools/measure-taolu.js 的「清单完整落在卡片内」那条）。 */
.ath-item:nth-last-child(-n+3) .ath-tl-tip {
  top: auto;
  bottom: calc(100% + 6px);
}
.ath-tl-tip {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 6;
  display: none;
  min-width: 160px;
  max-width: 280px;
  padding: 7px 10px;
  border-radius: 8px;
  background: #1F2937;
  box-shadow: 0 6px 18px rgba(0, 0, 0, .2);
  color: var(--on-brand);
  font-size: 12px;
  font-weight: 400;
  line-height: 1.6;
  white-space: normal;
  text-align: left;
}
.ath-tl-badge:hover .ath-tl-tip,
.ath-tl-badge:focus .ath-tl-tip { display: block; }
.ath-tl-tip-head { display: block; font-weight: 600; }
.ath-tl-tip-row { display: block; color: rgba(255, 255, 255, .88); }
.ath-tl-tip-row b { color: var(--on-brand); font-weight: 600; margin-right: 5px; }

/* 级别异常提示：单独占一整行；正常状态为空，不产生高度 */
.ath-msg { grid-column: 1 / -1; }
.ath-msg:empty { display: none; }

/* ---------- 套路报项块 ----------
 * 一个人可以报好几个项目，所以是复选框组而不是一个下拉；项目名比级别名长，
 * 挤在姓名那一行里会把其余字段压扁，因此整块独占一行（挂在 .ath-item 下）。 */
.ath-tl {
  margin-top: 6px;
  padding: 8px 10px;
  border: 1px dashed var(--line-strong);
  border-radius: 8px;
  background: var(--card-2);
}
.ath-tl-head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 6px;
}
.ath-tl-title { font-size: 12.5px; font-weight: 600; color: var(--text); }
.ath-tl-count { font-size: 12px; color: var(--muted); }
/* 与行内徽标同一套语义（都是「已报几项」），所以同样用信息蓝，不用品牌红 */
.ath-tl-count.on { color: var(--info); font-weight: 600; }
.ath-tl-rule { font-size: 12px; font-weight: 400; color: var(--text-2); }

/* 「出生日期同时符合多个年龄组」的说明条。
   规程按身份（年级 / 技术等级）划分组别、不给出生日期时，同一名队员会落进好几个组，
   可选项目成倍膨胀。这条说明用**须知蓝**（不是告警金）—— 它是正常情况，只是需要领队自己挑。 */
.ath-tl-multi {
  font-size: 12px;
  line-height: 1.65;
  color: var(--text-2);
  background: var(--info-soft);
  border-left: 3px solid var(--info);
  border-radius: 4px;
  padding: 7px 10px;
  margin-bottom: 8px;
}
.ath-tl-multi b { color: var(--text); font-weight: 600; }
/* 类别分组：规程常常有 40 多个项目，按规程自己的类别分段才找得到。
   但分段之后仍然一屏放不下 44 项，所以类别**默认收起**、点开再看 ——
   展开状态由 JS 记在模块级（勾选会整块重画，状态跟 DOM 走就会一勾一弹）。 */
.ath-tl-group { margin-bottom: 6px; }
.ath-tl-group:last-child { margin-bottom: 0; }
.ath-tl-group.closed .ath-tl-list { display: none; }
.ath-tl-cat {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin: 2px 0 4px;
  padding: 1px 7px 1px 3px;
  border: 0;
  border-radius: 6px;
  background: none;
  font-family: inherit;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--text-2);
  letter-spacing: .3px;
  cursor: pointer;
}
.ath-tl-cat:hover { background: var(--brand-soft); color: var(--brand-dark); }
.ath-tl-cat-n { font-weight: 400; color: var(--muted); }
.ath-tl-cat-n.on { color: var(--brand); font-weight: 600; }
/* 三角用边框拼：字体里的 ▸ 各平台大小不一，一行里会忽大忽小 */
.ath-tl-caret {
  width: 0; height: 0;
  border-top: 4px solid currentColor;
  border-left: 3.5px solid transparent;
  border-right: 3.5px solid transparent;
  transition: transform .12s;
}
.ath-tl-group.closed .ath-tl-caret { transform: rotate(-90deg); }
.ath-tl-all {
  margin-left: auto;
  padding: 1px 7px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--card);
  font-family: inherit;
  font-size: 11.5px;
  color: var(--text-2);
  cursor: pointer;
}
.ath-tl-all:hover { border-color: var(--brand); color: var(--brand); }
.ath-tl-list { display: flex; flex-wrap: wrap; gap: 6px 10px; }
.ath-tl-item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px 4px 8px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--card);
  font-size: 13px;
  cursor: pointer;
  -webkit-user-select: none;
  user-select: none;
}
.ath-tl-item.on {
  border-color: var(--brand);
  background: var(--brand-soft);
  color: var(--brand-dark);
  font-weight: 500;
}
.ath-tl-item input[type="checkbox"] { width: 15px; height: 15px; margin: 0; cursor: pointer; }
.ath-tl-cls { font-size: 11.5px; font-weight: 400; color: var(--muted); }
.ath-tl-none { font-size: 12.5px; color: var(--muted); }
.ath-tl .field-hint { display: block; margin-top: 6px; }

/* ---------- 团体 / 混合项目（对练、集体）----------
 * 与「一人一行」相反：这里是「多人共报一项」，所以是**一组一张卡**，
 * 卡里先选项目、再勾本队队员。人数与性别要求都在项目上（括号里写着），
 * 卡的底部实时报「已选几人 + 符不符合」。 */
.sq-block .card-body { padding-top: 12px; }

.sq-tip {
  font-size: 12.5px;
  color: var(--muted);
  line-height: 1.6;
  margin-bottom: 12px;
}

.sq-list { display: flex; flex-direction: column; gap: 12px; }

.sq-card {
  border: 1px solid var(--line);
  border-radius: 9px;
  background: var(--card-2);
  padding: 10px 12px;
}

.sq-card-head {
  display: flex;
  align-items: flex-end;
  flex-wrap: wrap;
  gap: 8px 12px;
}

/* 「第 N 组」：一行小字，和项目下拉基线对齐（align-items:flex-end） */
.sq-card-no {
  flex: 0 0 auto;
  font-size: 12px;
  font-weight: 600;
  color: var(--text-2);
  letter-spacing: .3px;
  padding-bottom: 9px;   /* 与下面 select 的文字中线对齐 */
}

.sq-pick { flex: 1 1 240px; min-width: 0; margin: 0; }
.sq-pick-label {
  display: block;
  font-size: 12px;
  color: var(--text-2);
  margin-bottom: 3px;
}
/* 项目下拉比普通字段宽一点：项目名 + 「（≥6 人）」这类后缀很长，
   窄了会把后缀截断，而人数要求正是不该被截掉的那一半信息。 */
.sq-pick select { width: 100%; }

/* 删除按钮用现成的 .btn-danger-ghost（管理端各处删除都用它），
   这里只负责把它推到卡片右上角 */
.sq-del {
  flex: 0 0 auto;
  margin-left: auto;
  margin-bottom: 1px;
}

/* 项目的人数 / 性别要求原文，单独一行（下拉里可能被截断，这里完整给出） */
.sq-evmeta {
  font-size: 12px;
  color: var(--info);
  margin-top: 6px;
}

.sq-members {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 8px;
  margin-top: 9px;
}
.sq-members-label {
  flex: 0 0 100%;
  font-size: 12px;
  color: var(--muted);
  margin-bottom: 1px;
}

/* 成员胶囊：勾选态与套路项目那套一致（同是"选中"语义），
   但不复用 .ath-tl-item 类 —— 两者将来会各自演化，耦合会互相牵连。 */
.sq-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px 4px 8px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--card);
  font-size: 13px;
  cursor: pointer;
  -webkit-user-select: none;
  user-select: none;
}
.sq-chip.on {
  border-color: var(--brand);
  background: var(--brand-soft);
  color: var(--brand-dark);
  font-weight: 500;
}
.sq-chip input[type="checkbox"] { width: 15px; height: 15px; margin: 0; cursor: pointer; }
/* 男女用不同底色的小角标：混双要一男一女，勾的时候得一眼看出性别配平没有 */
.sq-chip-sex {
  flex: 0 0 auto;
  font-size: 10.5px;
  font-weight: 600;
  line-height: 1.5;
  padding: 0 5px;
  border-radius: 999px;
  color: var(--on-brand);
}
.sq-chip-sex.m { background: #2E90FA; }
.sq-chip-sex.f { background: #EE46BC; }

.sq-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 10px;
  margin-top: 9px;
}
.sq-size { font-size: 12.5px; color: var(--text-2); }
.sq-size b { color: var(--info); font-weight: 600; }
.sq-foot .field-hint-slot { flex: 1 1 auto; min-width: 0; }
.sq-foot .field-hint { display: inline; }

.sq-none { font-size: 12.5px; color: var(--muted); line-height: 1.6; }

/* ===== 管理端「按团体项目」视图（admin.js squadViewHtml）===== */
/* 表前的项目清单条：一行胶囊，每个项目一个，带人数/混双要求与本届组数。 */
.sq-evbar {
  display: flex; flex-wrap: wrap; gap: 8px;
  margin-bottom: 14px;
}
.sq-evchip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 10px; border: 1px solid var(--line); border-radius: 999px;
  background: var(--card-2); font-size: 12.5px;
}
.sq-evchip b { font-weight: 600; }
.sq-evchip-meta { color: var(--muted); font-size: 12px; }

/* 成员单元格：每人一个内联块（姓名 + 性别角标 + 所报个人项目小字） */
.sq-mbr {
  display: inline-flex; align-items: center; gap: 4px;
  margin: 0 10px 4px 0; white-space: nowrap;
}
.sq-mbr .badge { padding: 1px 5px; font-size: 11px; }
.sq-mbr i { color: var(--muted); font-style: normal; font-size: 11.5px; }

/* 管理端表格里的「项目」名带一个 kind 角标，别让它顶到下一列 */
table.data td .badge { margin-top: 3px; }

/* 「按团体项目」表：成员列内容最多，给它最大的伸缩权重，
   项目/参赛队留够不换行的宽度（否则「徒手对练混双」会被拆成两行，很难读）。 */
table.data.sq-table { table-layout: fixed; }
table.data.sq-table th:nth-child(1) { width: 44px; }
table.data.sq-table th:nth-child(2) { width: 180px; }
table.data.sq-table th:nth-child(3) { width: 160px; }
table.data.sq-table th:nth-child(5) { width: 72px; }
table.data.sq-table th:nth-child(6) { width: 220px; }
/* 「人数」那一列窄而居中会离成员很远 —— 居左贴着成员列，视线才连得上 */
table.data.sq-table th:nth-child(5),
table.data.sq-table td:nth-child(5) { text-align: left; }
/* 项目名允许折行（长名字如「徒手对练混双」在窄屏仍要能放下），但不断词 */
table.data.sq-table td:nth-child(2) strong { word-break: keep-all; }

/* 队伍名单展开后附在人员表下面的「团体 / 混合项目」小表 */
.sq-mini { margin-top: 14px; }
.sq-mini-head {
  display: flex; align-items: center; gap: 8px;
  margin-bottom: 8px; font-size: 12.5px; font-weight: 600; color: var(--text-2);
}

/* 本届**没有任何「按组别报名」的大项**时，「体重」与「参赛组别」两列没有意义 ——
   整列抽掉，剩下的列自动补位。列数变了必须连列头一起改（列头与数据行共用同一套
   模板），否则上下会错位。
   ⚠️ 这个类**只看「有没有分组大项」**，与分组大项的**个数**无关：
      列数是量着 961px 断点定的，大项个数来自数据、会变，列数不能跟着变。
      多个分组大项并列在同一个下拉格里（见 .ath-grp-stack）。 */
.ath-table.no-group .ath-thead,
.ath-table.no-group .ath-row {
  grid-template-columns:
    26px                  /* 序号（兼删除入口） */
    46px                  /* 证件照 */
    minmax(88px, 1.3fr)   /* 姓名 */
    60px                  /* 性别 */
    124px                 /* 出生日期 */
    minmax(130px, 1.5fr)  /* 身份证号 */
    minmax(124px, 1.35fr); /* 备注（原删除按钮那一列的空间并进来了） */
}

/* 名单上方的一句操作说明（当前文案：「点击序号可删除该名队员」） */
.ath-tip {
  font-size: 12.5px;
  color: var(--muted);
  margin-bottom: 10px;
}

/* ---------- 名单上方的操作行 ----------
 * 「收缩全部项目」是三十人名单才用得上的东西：收起后一屏能看十来个人，
 * 报项靠备注旁那个数字核对（悬停出清单），要改就点数字只展开这一名。 */
.ath-bar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px 12px;
  margin-bottom: 10px;
}
.ath-bar .ath-tip { margin-bottom: 0; }
/* 按钮**永远钉在行末**，且两态位置**一模一样**。
   ⚠️ 这条 flex 行里只允许放 [操作说明][按钮]：早先把「已收起…」说明也塞进来，
   窄屏/手机上这行超宽就换行，按钮跟着跳位（1000px 右边缘 954→963、
   手机纵向 1481→1509）。说明文字已挪到行外（见下面的 .ath-bar-tip，独立成块）。 */
.ath-bar > .ath-tl-fold { margin-left: auto; flex-shrink: 0; }
/* 收起态的状态说明：**独立成行**（不是 .ath-bar 的子项），不参与那条 flex 行的宽度计算 */
.ath-bar-tip { font-size: 12px; color: var(--muted); margin: -2px 0 10px; }
/* 操作提示的两套说法：宽屏说「鼠标停在数字上」，手机说「点数字」
   （手机没有鼠标，也藏了备注格 —— 徽标是唯一入口）。断点 ≤620px 切换。 */
.tl-hint-phone { display: none; }
.ath-tl-fold {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 10px;
  border: 1px solid var(--line-strong);
  border-radius: 6px;
  background: var(--card);
  font-family: inherit;
  font-size: 12.5px;
  color: var(--text-2);
  cursor: pointer;
}
.ath-tl-fold:hover { border-color: var(--brand); color: var(--brand); }
/* 收起态：按钮本身给出「现在是收着的」的视觉信号 */
.ath-tl-fold.on {
  border-color: var(--brand);
  background: var(--brand-soft);
  color: var(--brand-dark);
  font-weight: 500;
}
.ath-tl-fold-ico { font-size: 13px; line-height: 1; }

/* 年龄组出生日期对照（属规程内容，默认折叠） */
.ag-ref {
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--fill-soft);
  padding: 8px 12px;
  margin-bottom: 14px;
  font-size: 12.5px;
}
.ag-ref > summary { cursor: pointer; color: var(--text-2); font-weight: 500; }
.ag-ref-body { margin-top: 6px; color: var(--muted); line-height: 1.7; }

/* 窄屏（手机 / 窄窗口）：两列 + 字段名回来，序号移到右上角。
 * 断点取 960px：容器内容宽 = min(视口, 1180) - 36，再减卡片内边距，
 * 一行式表格最少需要约 860px，所以视口 < 960 时才换成两列。 */
@media (max-width: 960px) {
  .ath-table { border: 0; border-radius: 0; background: transparent; overflow: visible; }
  .ath-thead { display: none; }

  .ath-item {
    position: relative;
    border: 1px solid var(--line);
    border-radius: 9px;
    background: var(--card-2);
    padding: 10px 12px;
    margin-bottom: 10px;
  }
  .ath-item:last-child { border-bottom: 1px solid var(--line); }

  .ath-row {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: 12px;
    row-gap: 10px;
  }
  .ath-row .field > span { display: block; }
  .ath-row input,
  .ath-row select { padding: 7px 9px; font-size: 14px; }
  .ath-row select { padding-right: 27px; }

  .ath-no { position: absolute; top: 10px; right: 12px; }

  /* 证件照单独占一行：没传时铺成一条「＋照片」的大点击区，传了就留个缩略图 */
  .ath-row > .ph-cell { grid-column: 1 / -1; margin-top: 0; }
  .ath-row > .ph-cell:not(.ph-on) {
    width: auto;
    height: 54px;
    flex-direction: row;
    justify-content: flex-start;
    gap: 10px;
    padding: 0 12px;
  }
  .ath-row > .ph-cell:not(.ph-on) .ph-plus { font-size: 18px; }
  .ath-row > .ph-cell:not(.ph-on) .ph-word { font-size: 12.5px; }
  .ath-row > .ph-cell.ph-on { width: 40px; height: 52px; }

  /* 随队人员行：窄屏允许换行，否则「照片 + 姓名 + 手机号 + 删除」会挤成一团 */
  .people-row { flex-wrap: wrap; }
  .people-row .field { flex: 1 1 150px; }
  /* 领队手机号是「一队一号」的关键字段，只要不是宽屏就独占整行 ——
     否则在 621–960px 这段它会和领队姓名挤在同一行，输入框被压到只剩半屏 */
  .people-row .field-wide { flex: 1 1 100%; }

  /* 团体卡片：项目下拉独占整行（项目名 + 人数后缀很长），
     「第 N 组」与删除按钮各占一头，不与下拉抢宽度 */
  .sq-pick { flex: 1 1 100%; }
  .sq-card-no { padding-bottom: 0; }
}

/* ---------- 手机竖屏（≤620px）----------
 * 目标不是「一行塞下 9 个字段」—— 390px 里塞不下，硬挤只会误触；
 * 目标是：① 长字段（身份证号 / 参赛组别 / 备注）独占整行，内容看得全；
 *         ② 每行更矮、留白更小，一屏能多看一个人；③ 绝不出现横向滚动。 */
@media (max-width: 620px) {
  .container { padding: 14px 12px 28px; }

  /* 顶栏：手机上原来会折成 3 行、占 102px（近 1/8 屏）。
     做法是①赛事副标题只留一行（超出省略，别把标题挤换行）；
     ②导航强制单独一行（flex-basis:100%），整体稳定为 2 行 ≈ 80px。 */
  .topbar {
    padding: calc(10px + env(safe-area-inset-top, 0px)) 0 10px;
    padding-left: env(safe-area-inset-left, 0px);
    padding-right: env(safe-area-inset-right, 0px);
  }
  .topbar-inner { padding: 0 12px; gap: 8px; row-gap: 5px; }
  /* 行高必须收紧：body 的 line-height:1.6 会把「标题 + 副标题」这一块撑到 43px，
     顶栏怎么减都下不来（实测就是这样，102 → 99 几乎没动）。 */
  .topbar h1 { font-size: 15px; line-height: 1.25; }
  .topbar .sub {
    font-size: 11.5px;
    line-height: 1.25;
    margin-top: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 100%;
  }
  /* 导航自己占满一行并**等分两格**（原来是 flex-end 缩在右边）。
     既然它本来就是分段控件的样子，铺满整行才像"控件"而不是两个飘着的链接；
     两格各占一半，触摸目标也顺带做到 ≈150px 宽。
     注意 padding 仍保持 4px：顶栏的 2 行 ≈80px 高度是量过的，不要在这里加高。 */
  .topbar nav { flex-basis: 100%; justify-content: stretch; }
  .topbar nav a { flex: 1 1 0; text-align: center; padding: 4px 10px; font-size: 12.5px; line-height: 1.3; }
  .brand-mark { width: 30px; height: 30px; border-radius: 8px; font-size: 15px; }
  .brand-mark.has-logo { height: 32px; }

  /* ⚠️ 输入控件必须 ≥16px：iOS / 微信的 WKWebView 在聚焦字号小于 16px 的输入框时，
     会**把整个页面自动放大**，用户得手动缩回去 —— 这正是「手机上布局不理想」的常见元凶。
     宁可每行高 2px，也不能让它放大。 */
  .ath-row input,
  .ath-row select,
  .field input,
  .field select,
  .field textarea { font-size: 16px; }

  /* 触摸目标不小于 44px（苹果人机指南），39px 的按钮手指容易点空 */
  .btn { min-height: 44px; }
  .btn-sm { min-height: 40px; }

  /* 竖屏里每个字段行 ≈61px（label 21 + 输入控件 40），一行就是 1/14 屏；
     行间距从 8 收到 6、卡片内边距 10 收到 9，一行人能少给 14px */
  /* ⚠️ 手机竖屏用「4 列栅格」：序号 | 照片 | 主列 | 副列。
     用户要的这种对齐只有栅格做得到 —— 姓名与出生日期同在**主列** → 右边界齐；
     性别与体重同在**副列** → 左边界齐。之前用 flex 时各字段宽各算各的，对不齐。
     副列用 minmax(72px,1fr)：极窄屏（320px）也不会把卡片撑出横向滚动。 */
  .ath-row {
    display: grid;
    grid-template-columns: 22px 46px minmax(104px, 1.4fr) minmax(72px, 1fr);
    align-items: end;
    column-gap: 10px;
    row-gap: 6px;
  }
  /* 行列都显式指定：行内还有隐藏的备注与级别提示，靠自动放置容易跑偏 */
  .ath-row > .ath-no { grid-column: 1; grid-row: 1; }
  .ath-row > .ph-cell { grid-column: 2; grid-row: 1; }
  .ath-row > .field[data-f="name"] { grid-column: 3; grid-row: 1; }
  .ath-row > .field[data-f="gender"] { grid-column: 4; grid-row: 1; }
  .ath-row > .field[data-f="birthDate"] { grid-column: 3; grid-row: 2; }
  .ath-row > .field[data-f="weight"] { grid-column: 4; grid-row: 2; }
  /* 字段标题挪进输入框里（placeholder），手机上每行省掉 21px 的标签行，
     4 行就是约 84px —— 这是目前最省屏的一刀。
     各类控件的「提示」分别由谁承担：
       姓名 / 体重 / 身份证号 → input 的 placeholder；
       参赛组别 → 下拉首项「请选择参赛组别」；
       性别 → 下拉本身就显示「男 / 女」；
       出生日期 → 浏览器内置的「年/月/日」占位（date 类型忽略 placeholder）。 */
  .ath-row .field > span { display: none; }
  .ath-item { padding: 9px; margin-bottom: 8px; }
  .ath-row .field > span { font-size: 12px; margin-bottom: 2px; }
  /* 统一控件高度：原来 text/number 是 39px、date/select 是 41px，一行里参差不齐 ——
     被测出来的「出生日期比其它控件高」就是这么来的。40px 兼顾紧凑与可点。 */
  .ath-row input,
  .ath-row select {
    padding: 8px 8px;
    height: 40px;
    box-sizing: border-box;
  }
  /* 同上一处：本行 40px 的控件也要给箭头留位置 */
  .ath-row select { padding-right: 28px; }
  /* （出生日期 / 其它控件的盒子统一已在基础规则里做过，见 .ath-row input[type="date"]，
     这里只把高度从 34px 抬到 40px，配合 16px 字号更好点） */
  /* 体重只需 2~3 位数字，没必要占满一整格 */
  .ath-row label[data-f="weight"] > input { max-width: 104px; }

  /* 只有真的放不下的才独占整行。原来「备注」也独占，结果最后两行各占一整屏，
     一张卡片 468px 高；改成备注与删除同一行（删除贴右）后降到 ~370px。 */
  .ath-row > .field[data-f="idCard"],
  .ath-row > .field[data-f="groupId"] { grid-column: 1 / -1; }
  /* 级别提示行也整行（只在异常时才有内容） */
  .ath-row > .ath-msg { grid-column: 1 / -1; }

  /* 手机上不显示备注框（用户要求）：备注极少用，却要占一格、把卡片撑高一行。
     只隐藏显示，不动 DOM 与读写 —— 隐藏的 input 一样能读能写，已填的备注不会丢。
     整格一起藏：套路报项徽标也在这一格里（报了几项，下方「武术套路项目」的抬头
     本来就写着「已报 N 项」，手机上不重复占地方）。 */
  .ath-row > .ath-remark,
  .ath-row > .ath-remark .field[data-f="remark"] { display: none; }
  /* ⚠️ 例外：点了「收缩全部项目」之后，徽标是打开单人项目的**唯一入口**，
     而手机没有悬停 —— 这一档必须把徽标留着（备注框照样藏，只露出那个数字）。 */
  .ath-table.tl-fold .ath-row > .ath-remark { display: flex; }
  /* 同理：操作提示换成「点数字」的说法（手机上没鼠标，悬停那条路不存在） */
  .ath-bar-tip .tl-hint-wide { display: none; }
  .ath-bar-tip .tl-hint-phone { display: inline; }

  /* 序号回到流内第一格、垂直居中（行高只剩输入框）。它同时就是删除入口。 */
  .ath-no {
    position: static;
    align-self: center;
  }

  /* 证件照格子：有没有照片都是固定小格（46×46），跟在序号后面、姓名之前。
     960 段把空态铺成整行的规则在这里被彻底覆盖 —— 那条才是「虚线框特别宽」的根因。 */
  .ath-row > .ph-cell,
  .ath-row > .ph-cell:not(.ph-on),
  .ath-row > .ph-cell.ph-on {
    width: 46px;
    height: 46px;
    flex-direction: column;
    justify-content: center;
    gap: 1px;
    padding: 0;
    align-self: end;          /* 与输入框底部对齐 */
  }
  .ath-row > .ph-cell:not(.ph-on) .ph-plus { font-size: 16px; }
  .ath-row > .ph-cell:not(.ph-on) .ph-word { font-size: 10px; }

  /* 卡片内边距收紧，多让出一点可用宽度 */
  .card-body { padding: 12px; }
  .card-head { padding: 12px 12px; }

  /* 随队人员行：让「姓名 / 手机号」尽量两列并排 */
  .people-row .field { flex: 1 1 120px; }

  /* 管理端表格：手机上横向滑动更顺手，别让手指划到浏览器手势上 */
  .table-wrap { -webkit-overflow-scrolling: touch; }
  .mobile-tip { margin: 10px 12px 0; padding: 9px 11px; font-size: 12.5px; }

  /* ---------- 竖屏去噪：说明性文字只留骨架，别抢输入框的高度 ---------- */
  /* 括号后缀（如「（队伍唯一标识）」）与灰字说明在窄屏收掉，
     文案仍留在 DOM 里，坐标/导出/校验都不受影响 */
  .field .field-opt { display: none; }
  /* 这类字段自成一行：标签不会折行，输入框也拿得到整宽 */
  .people-row .field-wide { flex: 1 1 100%; }
  /* 随队人员（教练 / 队医）的删除贴右 —— 用 margin-left:auto 而不是 flex:1 1 100%：
     后者只让**文字**靠右，按钮盒子本身还是内容宽（实测 51px 宽、离右边 292px，
     看着就像没对齐）。margin-left:auto 才是把盒子推到行尾。
     行内是 .people-row 的最后一格，换行后它会单独占一行并自动落到右端。 */
  .people-del { margin-left: auto; margin-bottom: 1px; }
  .photo-note > summary { padding: 8px 11px; font-size: 12.5px; }
  .photo-note-body { padding: 0 12px 9px 22px; font-size: 12px; line-height: 1.55; }

  /* ---------- 团体 / 混合项目（手机竖屏）----------
   * 卡片窄了之后「第 N 组 / 项目 / 删除」三件东西塞不进一行：
   * 改成两行 —— 第一行「第 N 组 + 删除」靠两头，第二行项目下拉独占整宽。
   * 成员胶囊一行放不下会自然换行，不设固定列数。 */
  .sq-card { padding: 10px; }
  .sq-card-head { align-items: center; row-gap: 8px; }
  /* 第一行：把「第 N 组」与删除按钮分放左右。下拉用 order 提到下一行。 */
  .sq-pick { flex: 1 1 100%; order: 2; }
  .sq-card-no { order: 1; padding-bottom: 0; }
  .sq-del { order: 1; margin-left: auto; }
  .sq-card-head .field > span { display: none; }   /* 手机上「项目」两字省掉，下拉自明 */
  /* 触摸目标 ≥44px（苹果人机指南）：胶囊与下拉点着都不容易空 */
  .sq-chip { min-height: 44px; padding: 6px 12px 6px 10px; font-size: 14px; }
  .sq-chip input[type="checkbox"] { width: 18px; height: 18px; }
  /* 字号 16px 防 iOS 聚焦自动放大（同 .ath-row 那条规则的理由）；
     高度显式给 44px —— 只给字号时算出来是 43px，差 1px 就够不着触摸下限 */
  .sq-pick select { font-size: 16px; height: 44px; box-sizing: border-box; }
  .sq-tip { font-size: 12px; }
}

/* ---------- 页脚 ---------- */

.foot {
  text-align: center;
  color: var(--muted);
  font-size: 12px;
  padding: 22px 18px 0;
}

/* ---------- 细滚动条 ---------- */

::-webkit-scrollbar { width: 9px; height: 9px; }
::-webkit-scrollbar-thumb { background: #D5D9E0; border-radius: 5px; }
::-webkit-scrollbar-thumb:hover { background: #BCC2CC; }

/* ---------- 轻提示 ---------- */

.toast {
  position: fixed;
  left: 50%;
  /* 抬到 home 指示条之上，否则 iPhone 上会被那条横杠压住 */
  bottom: calc(42px + env(safe-area-inset-bottom, 0px));
  transform: translate(-50%, 14px);
  background: rgba(20, 26, 36, .92);
  color: var(--on-brand);
  padding: 10px 20px;
  border-radius: var(--radius-pill);
  font-size: 13px;
  font-weight: 500;
  max-width: 86vw;
  text-align: center;
  opacity: 0;
  pointer-events: none;
  box-shadow: var(--shadow-xl);
  /* 半透明底 + 背景模糊：提示浮在内容之上时仍能透出一点页面，不显生硬 */
  backdrop-filter: blur(8px) saturate(140%);
  -webkit-backdrop-filter: blur(8px) saturate(140%);
  transition: opacity .22s var(--ease-io), transform .28s var(--ease);
  z-index: 999;
}

.toast.show { opacity: 1; transform: translate(-50%, 0); }
/* 错误态（2026-10-17）：「导入完成」与「没同步上去、别刷新」不能长一个样。 */
.toast.err {
  background: var(--brand-dark);
  box-shadow: 0 0 0 1px var(--brand-line) inset;
}

/* ---------- 证件照面板（组委会查看 / 打印证件牌） ---------- */

.sheet {
  position: fixed;
  top: 0; right: 0; bottom: 0; left: 0;
  z-index: 1000;
  display: none;
}
.sheet.show { display: block; }

.sheet-mask {
  position: absolute;
  top: 0; right: 0; bottom: 0; left: 0;
  background: rgba(16, 24, 40, .52);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
}

.sheet-body {
  position: absolute;
  left: 50%;
  top: 5vh;
  transform: translateX(-50%);
  width: min(1020px, 94vw);
  max-height: 90vh;
  background: var(--card);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-xl);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

/* 面板是 display:none → block 切换的，CSS transition 抓不到这一帧，
   所以用 keyframes 做入场（display 一变动画立刻从头播）。 */
.sheet.show .sheet-mask { animation: sheet-fade .2s var(--ease-io) both; }
.sheet.show .sheet-body { animation: sheet-rise .3s var(--ease) both; }

/* 窄面板：只放一个选项组的确认框（如「打印团体总分」），
   用整幅1020px 会在 27 寸上像弹了个空壳。 */
.sheet-body-narrow { width: min(520px, 94vw); }

/* ---------- 打印团体总分面板 ---------- */
.scp-opts {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 14px 0 4px;
}
.scp-opt {
  display: grid;
  grid-template-columns: 18px 1fr;
  align-items: baseline;
  gap: 4px 8px;
  padding: 9px 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius, 8px);
  cursor: pointer;
  line-height: 1.6;
}
/* ⚠️ 选中态别只靠 radio 那个小圆点 —— 现场往往只是扫一眼，
   要整行都给出「就它了」的信号。 */
.scp-opt:has(input:checked) {
  border-color: var(--brand);
  background: var(--brand-soft);
}
.scp-opt input { margin: 0; grid-row: 1; align-self: center; }
.scp-opt strong { grid-column: 2; }
.scp-opt .text-muted { grid-column: 2; font-size: 12px; }

@keyframes sheet-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes sheet-rise {
  from { opacity: 0; transform: translateX(-50%) translateY(-12px) scale(.985); }
  to   { opacity: 1; transform: translateX(-50%); }
}

.sheet-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 13px 18px;
  border-bottom: 1px solid var(--line);
  flex: 0 0 auto;
}
.sheet-head h2 { margin: 0; font-size: 15px; }
.sheet-head .spacer { flex: 1 1 auto; }

.sheet-main { overflow: auto; padding: 18px; }

.print-title { font-size: 15px; font-weight: 600; margin-bottom: 14px; }

/* 证件牌：照片 + 姓名 / 身份 / 组别，打印后沿虚线裁剪即可 */
.id-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
  gap: 12px;
}

.id-card {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px;
  border: 1px dashed var(--line-strong);
  border-radius: 8px;
  background: var(--card);
}

.id-photo {
  flex: 0 0 auto;
  width: 62px;
  height: 82px;
  border-radius: 4px;
  overflow: hidden;
  background: var(--fill);
}
.id-photo img { width: 100%; height: 100%; object-fit: cover; display: block; }

.id-meta { min-width: 0; }
.id-name { font-weight: 600; font-size: 14px; }
.id-role { font-size: 12px; color: var(--brand); }
.id-extra { font-size: 11.5px; color: var(--muted); line-height: 1.45; }

/* ---------- 打印 ---------- */

@media print {
  .topbar, .btn, .tabs, .no-print { display: none !important; }
  body { background: var(--page); }
  .card { border: none; box-shadow: none; }
  /* 打印快照可能在任何一帧取：入场动效必须彻底停掉，
     否则可能印出一张"半透明 + 下移 6px"的页面。 */
  #app > .card,
  #app > .notice,
  #app > .btn-row,
  #app > .tabs { animation: none !important; opacity: 1 !important; transform: none !important; }

  /* 点「打印证件牌」时给 body 加上 .printing：只印证件照面板，
     其余内容（顶栏、正文、遮罩、面板标题栏）全部隐去。 */
  body.printing > *:not(#photoPanel) { display: none !important; }
  body.printing #photoPanel { position: static; display: block; }
  body.printing #photoPanel .sheet-mask,
  body.printing #photoPanel .sheet-head { display: none !important; }
  body.printing #photoPanel .sheet-body {
    position: static;
    transform: none;
    width: auto;
    max-height: none;
    box-shadow: none;
    border-radius: 0;
    overflow: visible;
  }
  body.printing #photoPanel .sheet-main { overflow: visible; padding: 0; }

  /* 照片按实物尺寸印：1 寸 = 25 × 35 mm */
  .id-grid { grid-template-columns: repeat(3, 1fr); gap: 4mm; }
  .id-card {
    break-inside: avoid;
    page-break-inside: avoid;
    border-color: #B9BEC7;
  }
  .id-photo { width: 25mm; height: 35mm; }
}

/* ---------- 后续对阵表：区间面板 + A4 竖向打印（2026-10-14） ---------- */

/* 场地选择条（2026-10-15）。
 * 用户原话两处：
 *   ①「场地选择不要使用下拉框，直接在横排并列显示就可以了，文字如下 X场地（xx场）」
 *   ② 赛程战绩「就要分两个场地，因为各场地由不同的人记录，甚至还要采取措施，
 *      防止误操作到对方场地」
 * 两处用的是**同一套控件**：现场两块地由两组人各带一张纸/一台机子，
 * 谁在哪块地要一眼看全 —— 下拉框得点开才知道有哪几块地、各自多少场，
 * 多一步就是多一个拿错/录错的机会。文案「A场地（18场）」，号段与说明进 title。
 * 类名与 .rg-* 分开：区间面板与赛程战绩共用，不该挂在某个功能的名字下面。 */
.arena-pick { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 14px; }
.arena-pick-btn {
  padding: 7px 16px; font-family: inherit; font-size: 13.5px; font-weight: 600;
  color: var(--text-2); background: var(--card); cursor: pointer;
  border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
  font-variant-numeric: tabular-nums; white-space: nowrap;
  transition: color var(--dur) var(--ease-io), background var(--dur) var(--ease-io),
    border-color var(--dur) var(--ease-io);
}
.arena-pick-btn:hover { color: var(--text); border-color: var(--brand); }
.arena-pick-btn.active {
  color: var(--brand-dark); background: var(--brand-soft); border-color: var(--brand);
}
.arena-pick-btn:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

/* 面板里的区间表单与预览 */
.rg-form { display: flex; gap: 16px; flex-wrap: wrap; margin-bottom: 12px; }
.rg-form label {
  display: flex; flex-direction: column; gap: 4px;
  font-size: 12.5px; color: var(--text-2); font-weight: 600;
}
/* 起止场次手输（2026-10-14 用户：「直接手动输入 A001、A050，或者 1、50」）：
   文本框 + 等宽数字，现场照着纸上抄也能一眼核对。 */
.rg-form input {
  min-width: 150px; padding: 7px 10px; font-size: 14px;
  font-variant-numeric: tabular-nums; letter-spacing: 0.5px;
  border: 1px solid var(--line); border-radius: 8px; background: var(--card);
}
.rg-form input:focus { outline: 2px solid #FEE4E2; border-color: var(--brand); }
/* 已打印记忆提醒条：开面板时置顶，告诉现场「这张单子印到哪了」 */
.rg-mem {
  margin-bottom: 12px; padding: 8px 12px; font-size: 13px;
  background: var(--info-soft); border: 1px solid var(--info-line);
  border-radius: 8px; color: var(--info);
}
.rg-mem strong { font-variant-numeric: tabular-nums; }
.rg-stats { font-size: 13px; color: var(--text-2); margin-bottom: 8px; }
.rg-stats strong { color: var(--text); }
.rg-peek { font-size: 12.5px; }
.rg-peek td { padding: 5px 8px; }
.rg-peek-bye td { color: var(--text-2); }

/* 打印容器：屏幕上一律不可见，只服务于打印 */
#printSheet { display: none; }

/* A4 竖向（2026-10-14 用户定，当晚从横版改过来）：
   对阵表只有 5 列，竖版照样排得开，而每页行数比横版多约四成 ——
   区间动辄几十上百场，少印几张纸、现场翻起来也顺手。 */
@page { size: A4 portrait; margin: 10mm; }

@media print {
  body.printing-sheet > *:not(#printSheet) { display: none !important; }
  body.printing-sheet #printSheet {
    display: block;
    color: #000;
    font-size: 10.5px;
  }
}
.ps-head { margin-bottom: 6px; }
.ps-title { font-size: 14px; font-weight: 700; }
.ps-sub { font-size: 10px; color: #444; margin-top: 2px; }
.ps-arena {
  margin: 8px 0 3px; font-size: 11.5px; font-weight: 700;
  border-bottom: 1.5px solid #000; padding-bottom: 2px;
}
.ps-code {
  display: inline-block; min-width: 20px; padding: 0 5px; margin-right: 6px;
  background: #000; color: var(--on-brand); text-align: center; border-radius: 3px;
}
.ps-disc { font-weight: 600; }
.ps-table { width: 100%; border-collapse: collapse; margin-bottom: 6px; }
.ps-table th, .ps-table td {
  border: 1px solid #666; padding: 2px 5px; text-align: left; vertical-align: top;
}
.ps-table th { background: #EEE; font-weight: 600; }
.ps-table thead { display: table-header-group; }   /* 每页重复表头 */
.ps-table tr { break-inside: avoid; page-break-inside: avoid; }
.ps-no { width: 42px; font-weight: 700; text-align: center; }
.ps-table td.ps-no { text-align: center; }
.ps-bye td { color: #555; background: #F5F5F5; }

/* ---------- 团体总分打印稿 ----------
   窄列一律居中：名次 / 分数 / 计入项 / 第 1-3 名个数都是数字或极短词，
   左对齐会让一列数字上下浮动，扫起来对不齐。
   ⚠️ 这几个类只在 printSheet 里用（#printSheet 平时 display:none），
   所以不需要 @media print 包裹 —— 与 .ps-table 同一套做法。 */
.ps-table.sc-ps td.ps-sc-no { width: 40px; font-weight: 700; text-align: center; }
.ps-table.sc-ps th.ps-sc-no { text-align: center; }
.ps-table.sc-ps td.ps-sc-num,
.ps-table.sc-ps th.ps-sc-num { text-align: center; white-space: nowrap; }
/* 0 分队：纸面没有灰底可依赖（省墨），用「未得分」文字 + 淡字色区分。
   ⚠️ 不能只靠颜色 —— 黑白打印机上一样要能认出这是 0 分队。 */
.ps-table.sc-ps tr.ps-zero td { color: #555; }
.ps-note {
  margin-top: 8px; padding-top: 6px;
  border-top: 1px solid #999;
  font-size: 9.5px; color: #444; line-height: 1.6;
}

/* ---------- 成绩册（2026-10-14 用户需求：备案材料） ----------
   屏上预览与打印共用同一套类名（.bk-*），所以只在 @media print 里
   做「去边框底色 / 加细线」的调整，版式不另起一套 —— 预览即纸面。 */

/* 按钮条：每个大项一行，落在战绩页标签栏下方 */
.bk-bars { display: flex; flex-direction: column; gap: 8px; margin: 10px 0 4px; }
.bk-bar {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 9px 12px; border: 1px solid var(--line);
  border-radius: var(--radius-sm); background: var(--card-2);
}
.bk-bar-name { font-size: 13.5px; font-weight: 600; }
.bk-bar-stat { font-size: 12.5px; color: var(--text-2); }
.bk-bar-stat strong { color: var(--text); }

/* 未打完时按钮给个警示色 —— 别让人顺手把半张表印出去备案 */
.btn-warn { border-color: var(--gold); color: var(--warn-strong); background: var(--gold-soft); }
.btn-warn:hover { background: #FDF0C8; }

.bk-modes {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  padding: 8px 10px; margin-bottom: 10px;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--card-2);
}
.bk-modes label { display: inline-flex; align-items: center; gap: 5px; font-size: 13px; cursor: pointer; }

/* 册页眉 */
.bk-head { margin-bottom: 10px; padding-bottom: 6px; border-bottom: 2px solid #000; }
.bk-title { font-size: 15px; font-weight: 700; }
.bk-sub { font-size: 11.5px; color: #555; margin-top: 3px; }

/* 一个组别/代表队一块 */
.bk-unit { margin-bottom: 14px; break-inside: avoid; page-break-inside: avoid; }
.bk-unit-title {
  font-size: 13px; font-weight: 700; margin-bottom: 4px;
  padding-bottom: 2px; border-bottom: 1px solid #999;
}
.bk-unit-sub { font-size: 11.5px; font-weight: 400; color: #666; }
/* 「未录全」角标：橙色，与正文的黑色区分开，别让人当成已定稿 */
.bk-warn {
  display: inline-block; margin-left: 6px; padding: 0 6px;
  font-size: 11px; font-weight: 600; color: var(--warn-strong);
  background: var(--gold-soft); border: 1px solid var(--gold-line); border-radius: 3px;
}

.bk-table { width: 100%; border-collapse: collapse; }
.bk-table th, .bk-table td {
  border: 1px solid #999; padding: 3px 6px; text-align: left; vertical-align: top;
}
.bk-table th { background: #EEE; font-weight: 600; white-space: nowrap; }
.bk-table thead { display: table-header-group; }   /* 每页重复表头 */
.bk-table tr { break-inside: avoid; page-break-inside: avoid; }
.bk-no { width: 58px; text-align: center; }
.bk-table td.bk-no { text-align: center; }
/* 未录名次的那行压暗 —— 一眼扫出册子里还缺谁 */
.bk-row-dim td { color: #888; background: #FAFAFA; }
.bk-empty { text-align: center; color: #888; padding: 8px 0; }
.bk-note { font-size: 10.5px; color: #666; margin-top: 3px; }

/* 打印时把底色去掉（打印机不打背景），边框加重保证清晰 */
@media print {
  .bk-head { margin-bottom: 6px; }
  .bk-unit { margin-bottom: 8px; }
  .bk-table th { background: transparent; border-bottom: 1.5px solid #000; }
  .bk-row-dim td { background: transparent; color: #555; }
  .bk-warn { background: transparent; }
}

/* ---------- 浮动表头（2026-10-14 用户需求：长表格向下滚时表头跟随） ---------- */

/* 浮层：fixed 贴在顶栏下缘（top 由 JS 按顶栏实际高度写）。
   z-index 40：低于吸顶顶栏(50)，高于卡片内容；
   pointer-events:none：纯展示，不吃点击（点它=点下面的真表）。 */
.fh-layer {
  position: fixed;
  left: 0;
  right: 0;
  top: 0;
  z-index: 40;
  overflow: hidden;
  pointer-events: none;
  display: none;
}
/* .fh-inner 负责横向平移：容器横向滚动时 JS 把克隆表往左挪同样的量，
   列才能和下面的真表对齐。 */
.fh-inner { position: relative; }
/* 克隆表沿用 table.data 的表头样式，补一道投影和内容分层 */
.fh-clone { box-shadow: 0 8px 16px -10px rgba(16, 24, 40, .45); }
.fh-clone th { background: var(--fill-soft); }

/* 打印时浮层必须消失：fixed 元素会在每一页重复出现。
   （区间打印有 body.printing-sheet 兜底，这条管普通 Ctrl+P。） */
@media print {
  .fh-layer { display: none !important; }
}

/* ---------- 报项检查表（提交前自检，2026-10-07） ---------- */

.chk-table { width: 100%; border-collapse: collapse; font-size: 14px; }
.chk-table th, .chk-table td {
  text-align: left; padding: 8px 10px;
  border-bottom: 1px solid var(--line); vertical-align: top;
}
.chk-table th { color: var(--text-2); font-weight: 600; font-size: 13px; background: var(--fill-soft); }
/* 隔行异色（2026-10-12）：与 table.data 同一套斑马底，报名端的报项检查表也逐行好读。
   漏报行（tr.chk-miss）的背景写在 td 上（金色），会盖住这里的斑马底，语义色优先。 */
.chk-table tbody tr:nth-child(even) { background: var(--row-alt); }
.chk-table .chk-no { width: 44px; color: var(--text-2); }
.chk-table .chk-name { width: 110px; font-weight: 600; white-space: nowrap; }
.chk-table tr.chk-miss td { background: var(--gold-soft); }
.chk-item {
  display: inline-block; margin: 0 6px 4px 0; padding: 2px 8px;
  border-radius: 6px; font-size: 13px;
  background: var(--info-soft); color: var(--info); border: 1px solid var(--info-line);
}
/* 三类报项三色区分（2026-10-07 用户要求）：散打级别红、个人套路蓝、对练/集体紫 */
.chk-item.chk-sanda { background: var(--brand-soft); color: var(--brand); border-color: var(--brand-line); }
.chk-item.chk-taolu { background: var(--info-soft); color: var(--info); border-color: var(--info-line); }
.chk-item.chk-squad { background: var(--photo-soft); color: var(--photo-text); border-color: var(--photo-line); }
.chk-none { color: var(--brand); }

/* ---------- 规程文件页内全屏预览（2026-10-07） ----------
   微信内置浏览器拦截 window.open 弹窗（「不允许弹出窗口」），
   改成全屏遮罩 + iframe，零弹窗；遮罩上另给「下载」按钮兜底
   （部分安卓 WebView 预览不了 PDF，下载后用别的应用打开）。 */

.fileview-mask {
  position: fixed; inset: 0; background: var(--card); z-index: 999;
  display: flex; flex-direction: column;
}
.fileview-bar {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 14px; border-bottom: 1px solid var(--line);
  background: var(--info-soft);
}
.fileview-bar .fv-name {
  flex: 1 1 auto; font-weight: 600; font-size: 14px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.fileview-frame { flex: 1 1 auto; border: none; width: 100%; }

/* ============================================================
 * 入场动效（2026-10-12 视觉改版新增）
 * ------------------------------------------------------------
 * 只做「出现」这一下，**不做滚动视差**：本系统是填报工具，
 * 用户在密集表单里滚动时，任何位移都会干扰读数。
 *
 * ⚠️ 两条硬约束（都是踩过的坑）：
 *   1) 只动 opacity / transform —— GPU 属性。动 width/height/top 会触发重排。
 *   2) 终帧必须写死 `transform: none`。若只在起帧写 transform、
 *      终帧不写，animation-fill-mode: both 会把起帧的位移**永久保留**下来；
 *      而任何非 none 的 transform 都会让祖先成为 position:fixed 的包含块 ——
 *      证件照放大镜 .ph-zoom 正是靠 fixed 定位逃出祖先 overflow 的（见其注释），
 *      一旦被"钉"在错误的包含块上，放大图会飘到错误的位置。
 *      写成 transform: none 则动画结束后不残留包含块，两者兼得。
 *
 * #app 每渲染一次就整体替换 innerHTML，所以切页签、加/删运动员时也会重播一次。
 * 只选 .card / .notice / .btn-row 三类：不写 `#app > *`，
 * 免得把 .ph-zoom 这类定位辅助元素也卷进来。
 *
 * ⚠️ **不做错峰**（曾经给前 4 块加过 animation-delay，已撤掉）：
 *   重绘时所有元素都是新的，错峰会让第 2~4 块先"消失 105ms 再淡入"——
 *   首次加载看着像层次感，点一次「删除」就变成整页闪一下，很刺眼。
 *   统一、短促的一次淡入，在重绘场景下才读作"刷新过"而不是"卡了一下"。
 * ============================================================ */

@keyframes view-in {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}

#app > .card,
#app > .notice,
#app > .btn-row,
#app > .tabs {
  animation: view-in .26s var(--ease) both;
}

/* 尊重系统「减弱动态效果」设置：关掉入场位移与所有装饰性过渡，
   但保留颜色/边框这类不产生运动的状态反馈。 */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
  #app > .card,
  #app > .notice,
  #app > .btn-row,
  #app > .tabs { animation: none !important; opacity: 1 !important; transform: none !important; }
}

/* ============================================================
 * 抽签编排模块（2026-10-12 新增）
 * ------------------------------------------------------------
 * 管理端「抽签编排」页签的专用样式。
 *
 * 结构：
 *   .draw-unit            每个可抽签单元的容器（子卡片）
 *     .draw-unit-head      头部：标签 + 徽标 + 操作按钮
 *     .draw-preview        预览区：对阵表 / 出场顺序表
 *       table.data.draw-bracket / table.data.draw-order
 *
 * 设计约定：
 *   - 复用 table.data 的基础表格样式，只加 draw 专属覆盖。
 *   - 颜色 / 圆角 / 阴影一律用 :root 变量，不硬编码。
 *   - .draw-unit 是 .card 内的子卡片，比 .card 更紧凑（更小圆角、更轻阴影）。
 * ============================================================ */

/* 单个抽签单元：嵌入在 .card-body 里的子卡片。
   比 .card 更紧凑——更小的圆角、更轻的边框、无外阴影，
   视觉上是"卡片里的卡片"，层次递进。 */
.draw-unit {
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  margin-bottom: 12px;
  background: var(--card);
  overflow: hidden;
}
.draw-unit:last-child { margin-bottom: 0; }

/* 大项分组标题行：一排单元的「组别头」。
   带左侧品牌色竖条（与 .card-head h2 同手法），让「这是散打」「这是举重」一眼可辨。 */
.draw-disc-head {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  padding: 10px 14px;
  margin-bottom: 10px;
  border-radius: var(--radius-sm);
  background: linear-gradient(180deg, var(--card-2), var(--bg));
  border-left: 3px solid var(--brand);
}
.draw-disc-head strong { font-size: 14px; font-weight: 600; color: var(--text); }
.draw-disc-head .spacer { flex: 1 1 auto; }
.draw-disc-head .hint { font-size: 12px; color: var(--muted); }

/* 抽签编排的大项标签：名字后带「已抽/总数」进度小胶囊。
   全部抽完转绿（done），一眼看出哪个大项还没排完。
   ⚠️ 用 descendant 选择器限定在 .tabs 内，别外溢到顶部主标签栏。 */
.tabs .tab-count {
  display: inline-block;
  margin-left: 5px;
  padding: 1px 7px;
  border-radius: var(--radius-pill);
  font-size: 11px;
  font-weight: 500;
  line-height: 1.6;
  background: var(--fill);
  color: var(--text-2);
  vertical-align: 1px;
  font-variant-numeric: tabular-nums;
}
.tabs button.active .tab-count { background: var(--brand-soft); color: var(--brand-dark); }
.tabs .tab-count.done { background: var(--ok-soft); color: var(--ok); }

/* 头部：标签 + 人数/类型徽标 + spacer + 操作按钮。
   flex-wrap 让窄屏时按钮自动换行到下一行，不挤爆。 */
.draw-unit-head {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  padding: 11px 14px;
  border-bottom: 1px solid var(--line);
  background: linear-gradient(180deg, var(--card-2), var(--card));
}
.draw-unit-head strong {
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
}
.draw-unit-head .spacer { flex: 1 1 auto; }
.draw-unit-head .hint { font-size: 12px; color: var(--muted); }

/* 预览区：给表格留内边距 + 横向滚动。
   对阵表列多（轮次/场次/A/vs/B）窄屏会溢出，必须能横滚。 */
.draw-preview {
  padding: 12px 14px;
  overflow-x: auto;
}

/* 对阵表：「vs」列居中、窄；轮空用淡灰提示 */
table.data.draw-bracket th:nth-child(4),
table.data.draw-bracket td:nth-child(4) {
  width: 1%;
  text-align: center;
  color: var(--muted);
  font-size: 12px;
  white-space: nowrap;
}
/* 轮次列窄一些，场次用等宽数字对齐 */
table.data.draw-bracket th:nth-child(1),
table.data.draw-bracket td:nth-child(1) { white-space: nowrap; }
table.data.draw-bracket td:nth-child(2) {
  text-align: center;
  font-variant-numeric: tabular-nums;
  color: var(--text-2);
}

/* ---------- 编排场次表（全局视图，2026-10-13） ---------- */
/* 场次号是这张表的主角：加粗 + 等宽数字，扫一眼就能数到第几场。 */
table.data.sheet-table td.col-num {
  font-variant-numeric: tabular-nums;
  color: var(--brand-dark);
  font-weight: 600;
}
table.data.sheet-table td.col-num strong { font-weight: 700; }
/* 组别 / 项目、轮次列用次级文字色，让场次号与选手名更跳 */
table.data.sheet-table td.col-unit,
table.data.sheet-table td.col-round { color: var(--text-2); }
table.data.sheet-table td.col-unit { color: var(--text); font-weight: 500; }
/* 赛日列（2026-10-18 自检 P2-5，2026-10-19 去掉时段列）：只在「场次号按赛日
   分段」打开时出现。内容极短，必须 nowrap —— 否则「第1赛日」会被折成两行
   （列宽是按最长内容算的，长组别名会把它们挤扁）；内边距收到 8px 是为了
   列宽预算，1280 宽也不溢出。
   ⚠️ .col-slot 的规则**留着**（时段字段还在 `r.slot` 里，日程校验页在用）：
     停用渲染 ≠ 删样式，将来若要把时段放回某一列，不必重新量一遍列宽。 */
table.data.sheet-table th.col-day,
table.data.sheet-table td.col-day { width: 74px; white-space: nowrap; padding-left: 8px; padding-right: 8px; }
table.data.sheet-table th.col-slot,
table.data.sheet-table td.col-slot { width: 50px; white-space: nowrap; padding-left: 8px; padding-right: 8px; }
table.data.sheet-table td.col-day { color: var(--text-2); font-variant-numeric: tabular-nums; }

/* 赛日分组行（2026-10-19）：赛日折叠的分段标题。
   复用组别表那套 grp-seg 的**观感**（同一套灰底 + ▾/▸），但类名是
   sheet-day-row —— 因为它落在 .sheet-table 里，而 .sheet-table 有自己的
   表头/斑马纹规则，写一份新样式不如复用同一个骨架再补一条场地表的差异。 */
table.data.sheet-table tr.sheet-day-row th {
  background: var(--brand-soft);
  color: var(--text);
  font-weight: 700;
}
table.data.sheet-table tr.sheet-day-row.grp-seg-folded th { color: var(--text-2); }

/* 每个场地（= 大项）一块：块之间留白分隔，一眼看出这是几张独立的单 */
.sheet-group { margin-top: 14px; }
.sheet-group:first-of-type { margin-top: 8px; }
.sheet-group-head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  padding: 9px 12px;
  margin-bottom: 8px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: linear-gradient(180deg, var(--card-2), var(--card));
}
.sheet-group-head strong { font-size: 14.5px; font-weight: 600; color: var(--text); }
.sheet-group-head .spacer { flex: 1 1 auto; }
.sheet-group-head .hint { font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }
/* 场地编号牌：就是一个醒目的字母/数字，裁判和教练一眼认场地 */
.sheet-arena {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 30px;
  height: 30px;
  padding: 0 8px;
  border-radius: var(--radius-sm);
  background: var(--brand);
  color: var(--on-brand);
  font-size: 15px;
  font-weight: 700;
  line-height: 1;
  letter-spacing: .5px;
}

/* 场次表的大项标签：在通用 .tabs 基础上，每颗标签前带一个场地编号小牌。
   .sheet-tabs 单独给一点上边距，让它和上方的操作条分开。 */
.sheet-tabs { margin-top: 16px; padding-left: 12px; }
.sheet-tab-arena {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  margin-right: 6px;
  padding: 0 4px;
  border-radius: 4px;
  background: var(--brand-soft);
  color: var(--brand-dark);
  font-size: 11px;
  font-weight: 700;
  line-height: 1;
  vertical-align: 1px;
}
.tabs button.active .sheet-tab-arena { background: var(--brand); color: var(--on-brand); }

/* 双方称谓与场地编号设置条：与「.grp-new」同一套排版思路（标签 + 控件 + 说明） */
.sheet-sidecfg {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px dashed var(--line);
}
.sheet-sidecfg .spacer { flex: 1 1 auto; }
.sheet-sidecfg-lead {
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
}
.sheet-sidecfg input[type="text"] { width: 96px; }
/* 场地编号输入：更窄（只放 1-2 个字符），前面带大项名 */
.arena-item {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 12.5px;
  color: var(--text-2);
  white-space: nowrap;
}
.arena-item input[type="text"] {
  width: 56px;
  padding: 5px 6px;
  text-align: center;
  font-weight: 600;
  text-transform: uppercase;
}
/* 「无双边」开关（2026-10-13 用户要求：武术套路这类没有对手的项目只设一个称谓）。
   做成与编排方式 radio 同款的胶囊，选中态用主色 —— 一眼看出当前是单边还是双边。 */
.sheet-nosides {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 5px 9px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--card);
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text-2);
  cursor: pointer;
  white-space: nowrap;
}
.sheet-nosides input { margin: 0; }
.sheet-nosides:hover { border-color: var(--brand); }
.sheet-nosides:has(input:checked) {
  border-color: var(--brand);
  background: var(--brand-soft);
  color: var(--brand-dark);
}
/* :has() 不认的老浏览器兜底：勾了框就染底 */
.sheet-nosides input:checked ~ span { color: var(--brand); font-weight: 700; }

/* ---------- 编排方式切换 + 轮次块分隔（2026-10-13） ---------- */
/* 模式条：与 .sheet-sidecfg 同排版语言（虚线分隔 + lead 标签） */
.sheet-modebar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 14px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--brand-soft);
}
.sheet-modebar .sheet-sidecfg-lead { margin-right: 2px; }
/* 编排方式的说明文字：**独立一行，在选项下方**（2026-10-13 用户要求）。
   与下方「场地编号 / 双方称谓」那条说明同位置、同字号、同间距 ——
   这块区域的说明文字必须都在下方，之前一个在右一个在下，看着不统一。 */
.sheet-mode-hint {
  margin-top: 6px;
  font-size: 12px;
  line-height: 1.6;
  color: var(--muted);
}
/* 模式条紧跟这条说明（说明属于下方那块设置区，不属于上面的选项框） */
.sheet-sidecfg { margin-top: 14px; }
/* 单个选项：radio + 文案，整块可点（比只点圆点好按） */
.sheet-mode {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 5px 9px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--card);
  font-size: 12.5px;
  color: var(--text-2);
  cursor: pointer;
  white-space: nowrap;
}
.sheet-mode input { margin: 0; }
.sheet-mode:hover { border-color: var(--brand); }
/* 选中态：主色描边 + 主色底，一眼看出当前用的是哪种排法。
   :has() 在老浏览器上不认，下面那条 [checked] 相邻兄弟规则是兜底 ——
   input 是 .sheet-mode 的第一个子元素，:checked ~ span 能命中它后面的文案。 */
.sheet-mode:has(input:checked) {
  border-color: var(--brand);
  background: var(--brand);
  color: var(--on-brand);
  font-weight: 600;
}
.sheet-mode input:checked ~ span {
  color: var(--brand);
  font-weight: 700;
}
.sheet-mode:has(input:checked) input:checked ~ span { color: var(--on-brand); }

/* 轮次块分隔行：交错模式下「所有组别的同一轮」是一段，
   画一条整宽的带子，现场一眼看出换轮次了。 */
.sheet-table tr.sheet-block-row td {
  padding: 6px 10px;
  background: var(--brand-soft);
  border-top: 1px solid var(--brand);
  border-bottom: 1px solid var(--line);
}
.sheet-block-tag {
  display: inline-block;
  margin-right: 8px;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--brand);
}

/* 场地块数下拉：与 .arena-item 的窄输入同排版语言，但宽度放宽到放得下「6 块（…）」 */
.arena-item select {
  padding: 5px 6px;
  font-size: 12.5px;
  font-weight: 600;
}

/* 场地标签栏（2026-10-16 用户需求：场次表按场地切片）：
   一块地一个标签，一次只渲染一块地 —— 原先所有场地竖着堆在同一张表里，
   A 场的人要往下滚十几屏才找得到自己的号。
   ⚠️ 它是**第二级**标签：上面那条 .sheet-tabs 换大项，这一条换场地。
      两级必须一眼分得开（缩进 + 更矮更轻 + 前面挂一个「场地」字样说清这一层），
      否则用户根本不知道自己点的是哪一层。 */
.sheet-arena-bar { display: flex; align-items: center; gap: 8px; margin: 8px 0 4px 12px; }
.sheet-arena-lead { font-size: 12.5px; font-weight: 600; color: var(--muted); white-space: nowrap; }
.sheet-arena-bar .tabs { border-bottom: none; padding: 0; flex: 1 1 auto; min-width: 0; }
.sheet-arena-tabs button { font-size: 12.5px; padding: 6px 10px; }

/* 组别管理页：报名人数状态灯
   0 人灰（还没人报）、1 人红（没法比，必须处理）、超员橙（人多到该分队了）。
   色一律取既有变量 —— 项目的语义色就 --brand（红，警示）/ --gold（橙，提示）。 */
.grp-n {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}
.grp-n-alert { color: var(--brand); }
.grp-n-over { color: var(--gold); }
.grp-n-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--line-strong);
  flex: 0 0 auto;
}
.grp-n-alert .grp-n-dot { background: var(--brand); }
.grp-n-over .grp-n-dot { background: var(--gold); }

/* 组别管理页：合并单人组的勾选列 —— 收窄，别跟「组别」列抢视线 */
.sheet-table th.col-chk,
.sheet-table td.col-chk { width: 34px; text-align: center; }
.sheet-table td.col-chk input[type="checkbox"] { margin: 0; }

/* ---------- 组别表：横向排版（2026-10-15 用户反馈「显示不全，要横向滚动」） ----------
 * 组别表一有 10 列就必然溢出，加功能只能合并列不能加列。
 * 列合并已落在 admin.js（出生日期范围并进年龄组格、下限上限并成量程格），
 * 这里只管尺寸：**每列都写死宽度 + 控件收窄**，让总宽落在容器内。
 * ⚠️ 铁律 #1：`table.data td input{width:100%}` 是 (0,1,1)，
 *    这里的 `table.data td.grp-c-xxx input` 是 (0,2,1) 才压得住。 */
table.data.grp-sortable { table-layout: auto; }
table.data.grp-sortable th.col-chk,
table.data.grp-sortable td.col-chk { width: 28px; text-align: center; }
table.data.grp-sortable td.col-chk input[type="checkbox"] { margin: 0; }
/* 序号列：2026-10-16 用户报「超过两位数就会被分行」。
   这一格要装两样：拖动柄 `⋮⋮`（~16px，见 .grp-grip 的 margin-right）+ 序号，
   再加单元格左右 padding —— 34px 只够一位数。
   ⚠️ `white-space: nowrap` 必须**同时写 th 和 td**：
      th 与 td 是兄弟、不是父子，`white-space` 虽然可继承但**不会**从表头传到数据格
      （第一版只写了 th → 数据行照样折行，被真机变异测试抓出来）。
   ⚠️ 加宽得从别处找回来（总量受「不许加列、不许横向滚动」约束，见上面那段）：
      同时把「级别名称」列的下限从 76 收到 60 —— 它的内容一般就「60公斤级」5 个字。 */
table.data.grp-sortable th.col-num,
table.data.grp-sortable td.col-num { width: 46px; white-space: nowrap; }

/* 表头排序指示器（2026-10-16 用户需求，按 Excel 习惯）：
   单击 th 排本列、再单击切升降序、Shift+单击追加为次级键（多列时标出主次序号）。
   ⚠️⚠️ 指示器必须**绝对定位、不进文档流**。第一版是内联在文字后面的，
      5 个表头各自被撑宽约 2px → 整张表顶破容器 10px，回到「要横向滚动」的老问题
      （2026-10-15 用户就是为这个报的 bug；本次被真机断言当场抓出来）。
      表头右内边距有 10~12px，6px 的字形放在里面既看得见、也压不到文字。
   ⚠️ 图标用字形（⇅ ▲ ▼）不引图标字体（本项目零依赖）；字号 9px —— 表头一旦被撑高，
      浮动表头与首行对齐会跟着偏（FloatHead 按表头高度定位）。 */
table.data.grp-sortable th[data-sortcol] { position: relative; cursor: pointer; -webkit-user-select: none; user-select: none; }
table.data.grp-sortable th[data-sortcol]:hover { background: var(--brand-soft); }
table.data.grp-sortable th[data-sortcol] .sort-ind {
  position: absolute; right: 2px; top: 50%; transform: translateY(-50%);
  display: inline-flex; align-items: center; gap: 1px;
  pointer-events: none;   /* 点图标与点表头是同一件事，别让图标把点击吃掉 */
}
table.data.grp-sortable th[data-sortcol] .sort-ico {
  font-style: normal; font-size: 9px; line-height: 1; color: var(--muted); opacity: .5;
}
table.data.grp-sortable th[data-sortcol] .sort-ico.on { opacity: 1; color: var(--brand); }
table.data.grp-sortable th[data-sortcol] .sort-pri {
  font-size: 9px; line-height: 1; font-weight: 700; color: var(--brand);
}

/* 「隐藏 0 人组」开关（2026-10-16）：与卡头那两个筛选下拉并排的轻量勾选项。
   nowrap 是必须的 —— 卡头是 flex 行，标签换行会把下拉挤走。 */
.grp-hide0 {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-left: 10px;
  font-size: 13px;
  color: var(--muted);
  cursor: pointer;
  white-space: nowrap;
}
.grp-hide0 input[type="checkbox"] { margin: 0; }
table.data.grp-sortable th.grp-c-sex { width: 50px; }
table.data.grp-sortable th.grp-c-age { width: 90px; }
table.data.grp-sortable th.grp-c-label { width: auto; min-width: 60px; max-width: 140px; }
table.data.grp-sortable th.grp-c-kg { width: 92px; }
/* ---------- 分段标题与搜索（2026-10-18 R2 子项③）----------
   段标题是 tbody 里的 <tr class="grp-seg"><th colspan=N>（表内实现，不新增列）。
   ⚠️ **不做 position:sticky**：这张表住在 .table-wrap{overflow-x:auto} 里，
      那个 div 就是最近的滚动容器 —— 里面写 sticky 会静默失效（不报错、不生效），
      同一个坑 float-head.js 的文件头已经记过一次。而浮动表头是 JS 克隆 + fixed，
      两者若都钉在同一条线上就会打架（日程页吸顶批量行刚被盖过一回）。
      按任务单给的两个选项：保留浮动表头，段标题不 sticky。
   ⚠️ 选择器必须带 tr.grp-seg（0,1,3）：table.data th 是 (0,1,2)，
      裸写 .grp-seg th 会被基础样式里的背景色压掉。 */
table.data tr.grp-seg th {
  cursor: pointer;
  background: var(--card-2);
  color: var(--text);
  font-weight: 600;
  font-size: 13px;
  padding: 8px 12px;
  border-top: 1px solid var(--line-strong);
  -webkit-user-select: none; user-select: none;
}
table.data tr.grp-seg:hover th { background: var(--brand-soft); }
table.data tr.grp-seg.grp-seg-folded th { color: var(--text-2); }
.grp-seg-ico { display: inline-block; width: 15px; color: var(--muted); }
/* 卡片头里的搜索框：宽度固定（跟着 flex 走会被拉开成整行） */
.card-head .grp-search { width: 210px; }
/* 搜索无结果：给一句人话，而不是一张空表 */
.grp-noresult { margin: 14px 18px; }


table.data.grp-sortable td.grp-c-sex select { width: 44px; padding-left: 4px; padding-right: 18px; }
table.data.grp-sortable td.grp-c-age input[type="text"] { width: 100%; }
table.data.grp-sortable td.grp-c-label input[type="text"] { width: 100%; }
/* 量程格：两个数字各 ~36px，中间一个短横。数字窄到只够三位 —— 公斤级没有四位数。
   ⚠️ 2026-10-15 用户看截图发现的：「量程框里的数字显示不全」。
      根因不是框窄，是 **`input[type=number]` 的 spinner 箭头**：
      真机实测 31px 的框里 spinner 占掉约 16px，扣掉 border/padding 只剩 ~27px
      给文字，两位数（14px）加上 spinner 就溢出 → 第二位数被切掉半边。
      关掉 spinner（`-webkit-appearance:none` + `display:none`）后实测
      scrollWidth 由 31 掉到 29 = clientWidth，两位数正好装下；框再放宽到 36px，
      三位数（如 105，实测文本 21px）与占位符「以上」也都有余量。
      spinners 在这种密集表格里本来也点不准（现场都是键盘输入），去掉不损失功能。
      **真机断言**：`scrollWidth <= clientWidth + 1`（逐格查）+ 三位数探针。 */
/* 2026-10-16 用户：「数字挤在了左边」。根因是 spinner 被关掉之后，
   框右侧原本被箭头占掉的那 ~16px 空了出来，而文字仍**左对齐** —— 视觉重心全压左边。
   体重是定长数字，**居中**最好读，也正好补回那块空白。
   （不加左 padding：那只是把「挤左」换成「挤右」；居中是根治。） */
table.data.grp-sortable td.grp-c-kg input[type="number"] { width: 36px; padding: 5px 1px; text-align: center; }
table.data.grp-sortable td.grp-c-kg input[type="number"]::-webkit-inner-spin-button,
table.data.grp-sortable td.grp-c-kg input[type="number"]::-webkit-outer-spin-button {
  -webkit-appearance: none;
  appearance: none;
  margin: 0;
  display: none;
}
/* Firefox 的数字框没有 spinner，但有个「光标停在上面滚轮就改值」的坑，textfield 一并去掉 */
table.data.grp-sortable td.grp-c-kg input[type="number"] { -moz-appearance: textfield; }
table.data.grp-sortable td.grp-c-kg .grp-kg-sep {
  display: inline-block;
  padding: 0 1px;
  color: var(--muted);
}
/* 操作列：5 个按钮是全表最宽的一列（↑↓ 分队 撤销分队 并组 删除）。
   2026-10-15 用户：「把功能文字改成图标以节省空间，不然量程都被挤到两排了」——
   文字按钮（「撤销分队」四个字就 ~52px）把量程格挤窄，两个数字输入框被迫换行。
   现在全改成 **26px 方形图标按钮** + title（悬停出完整说明）+ aria-label（读屏）。
   符号：↑↓ 移动｜✂ 分队｜↩ 撤销分队｜⊕ 并组｜✕ 删除。
   ⚠️ 不引外部图标字体（本项目零依赖），用的都是系统字体自带的单字形符号。
   ⚠️ 选择器写成 `table.data.grp-sortable td.col-act .grp-ico`(0,3,2)，
      压得住上面 `.grp-mv{min-width:30px}`(0,1,0) —— 否则图标按钮被撑回 30px。 */
table.data.grp-sortable td.col-act { padding-left: 4px; }
table.data.grp-sortable td.col-act .btn-ghost,
table.data.grp-sortable td.col-act .btn-danger-ghost { padding: 4px 7px; margin: 0 1px; }
table.data.grp-sortable td.col-act .grp-ico {
  width: 26px;
  min-width: 0;
  padding: 4px 0;
  margin: 0 1px;
  text-align: center;
  font-size: 13px;
  line-height: 1.2;
  vertical-align: middle;
}
/* 量程格不许折行：两个数字被挤成上下两排，就是这一格被别的列压窄了。
   宽度压力已经在操作列那边卸掉了；这里再钉一道，压窄时宁可整表横向滚动
   （special-cdp 有 scrollWidth≈clientWidth 的断言盯着），也不许折成两排。 */
table.data.grp-sortable td.grp-c-kg { white-space: nowrap; }
/* 报名人数格也收一收：徽标（分队/合并）+ 人数 是这一格的主要内容 */
table.data.grp-sortable td .badge { font-size: 11px; padding: 1px 5px; }

/* 「已抽签」标记（2026-10-16）：组别表的宽度预算**零富余**（实测 sw == cw）——
   往表头/单元格里加任何东西都会顶破容器，真机断言「改成图标后仍不横向溢出」当场变红。
   通用 .badge 尺寸（11px + 5px 内边距）量出来约 45px，**比这一格里最宽的一项
   （「NN 人」，约 38px）还宽**，整表就超 7px。收到 10px 字号 + 2px 内边距后
   落在 38px 以内 ⇒ 与「NN 人」同宽，不再撑表（真机实测 sw 回到 707 = cw）。 */
table.data.grp-sortable td .badge.grp-drawn { font-size: 10px; padding: 1px 2px; }
/* 出生日期范围降级成年龄组格里的第二行小字：
   它是按年龄组算的，同列里同年龄组的行文字完全重复，不该独占一列。 */
.grp-age-sub {
  display: block;
  margin-top: 2px;
  font-size: 11px;
  line-height: 1.3;
  color: var(--muted);
  white-space: nowrap;
}
/* 窄屏下这行小字最容易撑宽表格 —— 宁可截断也不许它把表撑出滚动条 */
@media (max-width: 720px) {
  .grp-age-sub { max-width: 108px; overflow: hidden; text-overflow: ellipsis; }
}

/* 编排页单元卡片：可拖动排序的那些 */
.draw-unit-ordered { cursor: default; }
.draw-unit-ordered.grp-dragging {
  opacity: .5;
  background: var(--brand-soft);
}
.draw-unit-ordered.grp-drop-before {
  box-shadow: inset 0 3px 0 var(--brand);
}
.draw-unit-ordered.grp-drop-after {
  box-shadow: inset 0 -3px 0 var(--brand);
}
.draw-unit-grip { margin-right: 2px; }
/* 卡片上的编排序号：让人一眼数到「我现在排第几」。
   序号在组别名称前（2026-10-14 挪位），min-width 要容得下两位数
   （11.5px 字号两位约 13px + padding 10px = 23px）—— 否则到第 10 个
   单元时序号变宽、组别名的左边缘会被顶歪，一列名字就对不齐了。 */
.draw-unit-ord {
  display: inline-block;
  min-width: 24px;
  padding: 1px 5px;
  border-radius: 9px;
  background: var(--brand-soft);
  color: var(--brand);
  font-size: 11.5px;
  font-weight: 700;
  text-align: center;
}

/* ---------- 排序模式（2026-10-13）：折叠对阵表 + 两列网格 ----------
 * 为什么需要：卡片里带完整对阵表（8 人组就 7 行），十几个组别叠起来能拖到几屏长，
 * 把 2 人组从最后挪到中间几乎做不到 —— 拖不到、也看不见落到哪了。
 * 打开后每张卡只剩单行表头，两列排，一屏装得下，落点一目了然。 */
.sortmode-bar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: 12px;
  padding: 8px 12px;
  border: 1px dashed var(--line);
  border-radius: 8px;
  background: var(--card);
}
.sortmode-switch {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
  cursor: pointer;
  white-space: nowrap;
}
.sortmode-switch input { margin: 0; }

/* 两列网格。用 grid 而非 columns —— columns 的拖动落点计算要按几何坐标算，
   两列并排时上下关系会被视觉顺序骗到。
   ⚠️ align-items 必须是 stretch（默认）而不是 start：卡片高度由表头内容决定，
   组别名长到按钮换行的卡片会比别的高一截，start 让每张卡各自为政 → 表头参差。
   stretch 让同一行的卡片等高，整行表头一条线才对得齐。 */
.draw-units-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
  align-items: stretch;
}
/* 窄屏回退单列（手机上两列会把按钮挤到换行，反而更难点） */
@media (max-width: 720px) {
  .draw-units-grid { grid-template-columns: minmax(0, 1fr); }
}
/* 网格里卡片压扁：去掉上下留白与底边框，一行就是一行。
   margin-bottom 也要清 —— .draw-unit 有 12px 下边距，网格里由 gap 负责间距，
   不清会让行距看着不匀。 */
.draw-units-grid .draw-unit-head {
  padding: 8px 10px;
  border-bottom: none;
  gap: 6px;
}
.draw-units-grid .draw-unit {
  margin-bottom: 0;
  border-radius: 6px;
}
/* 排序模式一律**单行**：表头一旦换行（组别名长 → 「解锁」按钮折到第二行），
   卡片就比同排的高一头，表头高度参差不齐，看着像渲染错乱。
   这里关掉换行，并让组别名可省略号截断，把宽度让给固定宽度的序号/按钮。
   完整组别名靠 <strong> 的 title 悬停可见（HTML 里已加）。 */
.draw-units-grid .draw-unit-head { flex-wrap: nowrap; }
.draw-units-grid .draw-unit-head strong {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* 网格里把次要信息让位给「组别名 + 序号」—— 排序时只关心这两个 */
.draw-units-grid .draw-unit-kind,
.draw-units-grid .draw-unit-time { display: none; }
/* 网格里落点线改**竖向**：两列并排时上下横线会指向隔壁列，看不出到底落哪 */
.draw-units-grid .draw-unit-ordered.grp-drop-before {
  box-shadow: inset 3px 0 0 var(--brand);
}
.draw-units-grid .draw-unit-ordered.grp-drop-after {
  box-shadow: inset -3px 0 0 var(--brand);
}
/* 网格内序号更醒目 —— 两列并排时「我现在第几个」是排序时最需要的信息。
   min-width 与外面一致保持 24px：排序模式正是组别最多、序号最常到两位数的时候。 */
.draw-units-grid .draw-unit-ord {
  min-width: 24px;
  background: var(--brand);
  color: var(--on-brand);
}
/* 落点线的语义是「插到这张卡**之前/之后**」，不是「跟这张卡换位」。
   光一条线容易被读成「选中它」，所以再叠一层淡底 —— 底色铺满的是**落位的那一侧**，
   一眼能看出插到上还是插到下。 */
.draw-units-grid .draw-unit-ordered.grp-drop-before { background: var(--brand-soft); }
.draw-units-grid .draw-unit-ordered.grp-drop-after { background: var(--brand-soft); }
/* 末尾落点：指针落在最后一张卡下方的空白（两列网格的卡间缝隙也走这条），
   没有卡片可挂class，所以在容器上画一条底部横线。 */
.draw-units.grp-drop-at-end {
  box-shadow: inset 0 -3px 0 var(--brand);
  border-radius: 8px;
}

/* ---------- 组别拖动排序（2026-10-13） ---------- */
/* 手柄：只在可排序的表里出现。cursor 用 grab / grabbing 让「能拖」一眼可见。 */
.grp-grip {
  display: inline-block;
  margin-right: 4px;
  color: var(--muted);
  cursor: grab;
  font-size: 11px;
  letter-spacing: -1px;
  user-select: none;
  -webkit-user-select: none;
  touch-action: none;      /* 触屏上别让浏览器抢去做滚动 */
  vertical-align: 1px;
}
table.grp-sortable tbody tr { cursor: default; }
/* 拖起来的那行：半透明 + 淡灰底，跟在鼠标下面的落点区分开 */
table.grp-sortable tbody tr.grp-dragging {
  opacity: .45;
  background: var(--brand-soft);
}
/* 落点提示线：用 box-shadow 画一条 2px 的主色横线，不动布局（不会引起行跳动） */
table.grp-sortable tbody tr.grp-drop-before {
  box-shadow: inset 0 2px 0 0 var(--brand);
}
table.grp-sortable tbody tr.grp-drop-after {
  box-shadow: inset 0 -2px 0 0 var(--brand);
}
/* 上下移按钮：窄一点，跟删除按钮并排 */
/* ↑↓ 既是「点击移一位」，也是**拖放落点**（data-unitdrop）。
   用户实测「卡片上半/下半区分不出来」—— 卡片上堆着组别名、人数徽标、导出/锁定/重抽，
   指针停在哪儿全凭手感。箭头自带语义（看见 ↑ 就知道排它前面），是主落点。
   所以必须把它的可接性做足：够大、有手型光标、悬停/拖到时高亮。 */
.grp-mv {
  min-width: 30px;
  padding: 4px 7px;
  cursor: grab;
  touch-action: none;
}
.grp-mv:hover { background: var(--brand-soft); color: var(--brand); }
/* 拖动经过它：实心高亮 + 深色手型，一眼看出「松手就落这」。 */
.grp-mv.grp-slot-on {
  background: var(--brand);
  color: var(--on-brand);
  box-shadow: 0 0 0 2px var(--brand-soft);
}
/* 拖动进行中：把所有箭头染成品牌底色，暗示「这些是落点」。
   只在拖动时出现，平时不干扰。 */
.unit-dragging .draw-unit-ordered .grp-mv:not([disabled]) {
  background: var(--brand-soft);
  color: var(--brand);
  cursor: grab;
}
/* 已手动排序时的提示条 */
.grp-order-tip {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 10px;
  padding: 8px 2px 0;
}

/* 未来轮次行（"胜者第X场"）：整体淡灰，表示尚未确定 */
table.data.draw-bracket tr.draw-future td {
  color: var(--muted);
  background: var(--fill-soft);
}
table.data.draw-bracket tr.draw-future:hover td {
  background: var(--fill);
}

/* 轮空行：淡金底 —— 这不是一场比赛，是"本轮此人不用打"的信息行，
   用语义金色与普通场次、未来轮次的灰色区分开 */
table.data.draw-bracket tr.draw-bye-row td {
  background: var(--gold-soft);
  color: var(--gold);
  font-size: 12.5px;
}

/* 出场顺序表：序号列等宽居中 */
table.data.draw-order td:nth-child(1) {
  width: 56px;
  text-align: center;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--brand);
}

/* 移动端：抽签单元头部更紧凑，表格字号略缩 */
@media (max-width: 640px) {
  .draw-unit-head { padding: 9px 11px; gap: 6px; }
  .draw-preview { padding: 9px 11px; }
  table.data.draw-bracket,
  table.data.draw-order { font-size: 12px; }
  table.data.draw-bracket th,
  table.data.draw-bracket td,
  table.data.draw-order th,
  table.data.draw-order td { padding: 7px 8px; }
}

/* ---------- 赛程战绩（2026-10-13） ---------- */

.results-sync-hint {
  margin-bottom: 12px;
  /* 状态文字 + 右侧「列出轮空行」开关（2026-10-16）：flex 才能把开关推到右边。 */
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
}
.results-sync-hint > .hint { flex: 1 1 auto; min-width: 0; }

.results-unit { margin-bottom: 20px; }
.results-unit-head {
  display: flex; align-items: baseline; gap: 8px;
  margin: 8px 0 8px; padding: 6px 10px;
  background: var(--card); border: 1px solid var(--line); border-radius: 8px;
}
.results-unit-head strong { font-size: 14px; }
.results-unit-sub { color: var(--text-2); font-size: 13px; }
.results-unit-head .badge { margin-left: auto; }

/* 胜负按钮：大按钮、醒目，现场手抖也能点准 */
.results-winbtn {
  min-width: 52px; padding: 6px 10px;
  font-weight: 600; font-size: 13px;
  border-radius: 6px; cursor: pointer;
}
.results-winbtn:active { transform: scale(.97); }

/* 胜负按钮的底色 = 角色（2026-10-14 用户改：「蓝方胜改蓝底、红方胜改红底，现在是反的」）。
   上一版 a 侧套了品牌红、b 侧是中性灰 —— 现场按颜色记胜负会记反。
   真正的配色由 [data-tone] 给：admin.js 按**称谓文字**推角色，
   称谓是按大项各设一套的（蓝方/红方、青方/红方跆拳道、黑方/白方…），
   推不出来才落到下面的 [data-side] 兜底（前者蓝、后者红）。

   2026-10-16 用户：「胜者的颜色标识也跟着当场所在方走，现在全是红色，
   给人感觉都是红方胜，会影响判断」—— 于是这两处**共用同一套 [data-tone] 色**：
     · .results-winbtn   —— 录入按钮（点之前）
     · .results-winbadge —— 录完显示的那个胜者徽标（原先一律 badge-brand 品牌红）
   新增角色色时**两处一起加**（下面每条选择器都是成对写的），别只加一边。 */
.results-winbtn[data-tone="blue"], .results-winbadge[data-tone="blue"] {
  background: var(--info-soft); color: var(--info-dark);
  border: 1px solid var(--info-line);
}
.results-winbtn[data-tone="cyan"], .results-winbadge[data-tone="cyan"] {          /* 青方（跆拳道） */
  background: var(--cyan-soft); color: var(--cyan);
  border: 1px solid var(--cyan-line);
}
.results-winbtn[data-tone="red"], .results-winbadge[data-tone="red"] {
  background: var(--brand-soft); color: var(--brand-dark);
  border: 1px solid var(--brand-line);
}
.results-winbtn[data-tone="dark"], .results-winbadge[data-tone="dark"] {          /* 黑方 */
  background: var(--fill-strong); color: #1D2939;
  border: 1px solid #D0D5DD;
}
.results-winbtn[data-tone="light"], .results-winbadge[data-tone="light"] {         /* 白方 —— 白底，用深字保证可读 */
  background: var(--card-2); color: #344054;
  border: 1px solid #D0D5DD;
}
/* 兜底：自定义叫法（A方/B方、上方/下方…）认不出颜色时，按先后取蓝/红 */
.results-winbtn[data-tone=""], .results-winbadge[data-tone=""] { background: var(--info-soft); color: var(--info-dark); border: 1px solid var(--info-line); }
.results-winbtn[data-side="b"][data-tone=""], .results-winbadge[data-side="b"][data-tone=""] {
  background: var(--brand-soft); color: var(--brand-dark);
  border: 1px solid var(--brand-line);
}

/* 已录的行：浅绿底 + 打勾徽标 */
.results-table tr.results-done { background: #F0FDF4; }
.results-table tr.results-bye { background: var(--fill-soft); color: #94A3B8; }
.results-table .results-round { font-size: 12px; color: var(--text-2); }

/* 表格：窄屏不溢出 */
.results-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.results-table th, .results-table td {
  padding: 7px 9px; text-align: left;
  border-bottom: 1px solid var(--line);
}
.results-table th {
  background: var(--fill-soft); font-weight: 600; color: var(--text-2);
  font-size: 12px; white-space: nowrap;
}
.results-table .col-num { width: 64px; }
.results-table .col-act { min-width: 120px; white-space: nowrap; }

/* 战绩表里的「胜者」徽标（2026-10-13） */
.badge-brand { background: var(--brand-soft); color: var(--brand-dark); border: 1px solid var(--brand-ring); }

/* 冠军条（2026-10-14）：录完决赛置顶显示 —— 上一版录完全部只在表格里多个徽标，
   现场得翻到最后一行才知道冠军是谁。金色 = 金牌语义，与 badge-warn 同源。 */
.results-champs {
  display: flex; flex-wrap: wrap; gap: 8px;
  margin-bottom: 12px;
}
.results-champ {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 12px;
  background: var(--gold-soft);
  border: 1px solid var(--gold-line);
  border-radius: var(--radius-pill);
  font-size: 13px;
}
.results-champ strong { font-size: 14px; color: var(--text); }

/* 大项标签上的「未锁定」计数：金色警示 —— 未锁定的抽签可能被重抽，录了白录 */
.tabs .tab-count-warn { background: var(--gold-soft); color: var(--gold); }

/* ---------- 外部报名数据导入（2026-10-14 用户需求） ---------- */

/* 工作表切换按钮（多个非空 sheet 时才出现） */
.imp-sheets { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 12px; }
.imp-sub { font-size: 13px; font-weight: 600; color: var(--text-2); margin: 14px 0 8px; }
.imp-map th, .imp-map td { padding: 6px 10px; }
.imp-fname { font-weight: 600; white-space: nowrap; }
.imp-req {
  margin-left: 6px; font-size: 11px; font-weight: 500;
  color: var(--brand); background: var(--brand-soft);
  border-radius: var(--radius-pill); padding: 1px 7px;
}
.imp-map select {
  min-width: 180px; padding: 5px 8px; font-size: 13px;
  border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--card);
}
.imp-sample { font-size: 12.5px; max-width: 220px; }
.imp-stats { display: flex; flex-direction: column; gap: 8px; margin-bottom: 10px; }
.imp-stat { font-size: 13.5px; color: var(--text-2); }
.imp-stat strong { color: var(--text); }
.imp-create {
  font-size: 12.5px; color: var(--info);
  background: var(--info-soft); border: 1px solid var(--info-line);
  border-radius: var(--radius-sm); padding: 7px 11px;
}
.imp-prob {
  font-size: 12.5px; color: var(--gold);
  background: var(--gold-soft); border: 1px solid var(--gold-line);
  border-radius: var(--radius-sm); padding: 7px 11px;
}
.imp-prob ul { margin: 4px 0 0; padding-left: 18px; }
.imp-prob li { line-height: 1.7; }
/* 手机号撞车（2026-10-17）：比 imp-prob 更重的语气 —— 它不是"这行会被跳过"，
   而是"这么导整批都进不去"。用警示族（--warn-*）而不是提示金。 */
.imp-dup {
  font-size: 12.5px; color: var(--warn-strong);
  background: var(--gold-soft); border: 1px solid var(--warn-line);
  border-left: 4px solid var(--warn-line);
  border-radius: var(--radius-sm); padding: 8px 12px; margin-top: 8px;
}
.imp-dup ul { margin: 4px 0 0; padding-left: 18px; }
.imp-dup li { line-height: 1.7; }
.imp-dup-act { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 8px; }
.imp-prev { margin-bottom: 12px; }
.imp-prev th, .imp-prev td { padding: 5px 9px; font-size: 12.5px; }
.imp-mode { display: flex; flex-direction: column; gap: 8px; margin: 6px 0 12px; }
.imp-mode label {
  display: flex; align-items: center; gap: 8px;
  font-size: 13px; color: var(--text-2); line-height: 1.6;
}

/* ---------- 团体计分规则 + 团体总分（2026-10-14 用户需求） ---------- */

/* 赛事设置页：规则表单 */
.sc-discs { display: flex; flex-wrap: wrap; gap: 10px 18px; margin-top: 2px; }
.sc-disc { display: inline-flex; align-items: center; gap: 6px; font-size: 13.5px; }
.sc-pts { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
/* ⚠️ 必须写 `input.sc-pt`（0,1,1）而不是 `.sc-pt`（0,1,0）：
     基础规则是 `input[type="number"] { width: 100% }`（0,1,0 —— type
     属性选择器与 class 同权重，靠源码顺序取胜）。写成 `.sc-pt` 时两者
     权重相同、基础规则在前面，于是**名次分的每一个输入框都被拉满整行**
     （实测 1106px），八个框叠成一列 —— 正是用户说的「只需要输入一个
     数字，却占了整行宽度」。这是本轮实测抓出来的，不是推测。 */
input.sc-pt {
  width: 64px; padding: 7px 8px; font-size: 13.5px; text-align: center;
  border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--card);
}
.sc-presets {
  margin-top: 8px; font-size: 12.5px; color: var(--muted);
  display: flex; flex-wrap: wrap; gap: 6px; align-items: center;
}
.sc-mode { display: flex; flex-direction: column; gap: 8px; }
.sc-mode label {
  display: flex; align-items: center; gap: 8px;
  font-size: 13.5px; color: var(--text-2); line-height: 1.6;
}

/* 各大项独立名次分表：一个大项一行，勾了才展开自己的输入框 */
.sc-dpts { display: flex; flex-direction: column; gap: 10px; margin-top: 2px; }
.sc-dpt {
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  padding: 10px 12px; background: var(--card-2);
}
.sc-dpt-own {
  display: flex; align-items: center; gap: 8px;
  font-size: 13.5px; font-weight: 600; cursor: pointer;
}
.sc-dpt-own .hint { font-weight: 400; }
.sc-dpt-pts { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 10px; }
/* hidden 属性由 .sc-dpt-pts 自己兜住：display:flex 会盖掉 UA 的 [hidden]{display:none}，
   这里显式写一份，不依赖全局那条 !important（那条在 :root 之前、优先级不定）。 */
.sc-dpt-pts[hidden] { display: none; }

/* 团体总分页 */
.sc-rule-bar {
  font-size: 13px; color: var(--text-2); line-height: 1.9;
  background: var(--card-2); border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: 10px 14px; margin-bottom: 14px;
}
.sc-rule-bar strong { color: var(--text); }
/* 摘要条上的「含 0 分队」提示 —— 橙色与品牌红拉开，一眼看出这条规则生效了 */
.sc-rule-zero {
  color: var(--gold); background: var(--gold-soft);
  border: 1px solid var(--gold-line); border-radius: var(--radius-xs);
  padding: 1px 7px; font-size: 12px;
}
.sc-rule-bar strong { color: var(--text); }
/* 单队计分明细浮层（2026-10-14 用户需求）：
   上一版把明细做成页面底部一张大卡片、按大项把所有队的项铺开，
   几十支队 × 几十项把页面拉到几千行，没人往下翻。
   现在团体总分每行一个「明细」按钮，点开只看**这一队**。 */
.sc-detail-sum {
  background: var(--card-2); border: 1px solid var(--line);
  border-radius: var(--radius-sm); padding: 9px 13px; margin-bottom: 12px;
  font-size: 13px; color: var(--muted);
}
.sc-detail-sum strong { color: var(--text); }
/* 明细表：列都是窄的（名次/分值/计入），别让内容把列撑开 */
.sc-detail td, .sc-detail th { white-space: nowrap; }
.sc-detail td:nth-child(2) { white-space: normal; min-width: 140px; }
/* 未计入的行压暗 —— 与上一版一致；鼠标悬停出 title 说明原因 */
.sc-detail tr[title] { cursor: help; }
.row-dim td { color: var(--muted); }

/* ============================================================
 * 抽签编排 · 单元设置条 / 编排依据 / 种子面板（2026-10-16）
 * ------------------------------------------------------------
 * 三块都在 .draw-unit 卡片内部，与 .sortmode-bar / .sheet-modebar 同一族观感。
 * ⚠️ 宽度预算：内容一律 flex-wrap: wrap，**不给任何子项写死 width** ——
 *    组别名长短不一（「女子甲组-44kg」vs「竞技男子少儿组29kg」），写死必溢出。
 * ============================================================ */
.seed-panel { padding: 10px 14px; border-bottom: 1px solid var(--line); background: var(--card); }
.seed-head { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin-bottom: 8px; font-size: 13px; }
.seed-head .spacer { flex: 1 1 auto; }
.seed-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.seed-chip {
  display: inline-flex; align-items: center; gap: 5px; padding: 4px 10px;
  border: 1px solid var(--line); border-radius: var(--radius-pill);
  background: var(--bg); font-size: 12.5px; color: var(--text); cursor: pointer;
}
.seed-chip:hover { border-color: var(--brand); }
.seed-chip.on { border-color: var(--brand); background: var(--brand-soft); font-weight: 600; }
.seed-no {
  display: inline-block; min-width: 16px; height: 16px; line-height: 16px; text-align: center;
  border-radius: 50%; background: var(--brand); color: var(--on-brand); font-size: 11px; font-weight: 700;
}
.seed-tip { margin: 8px 0 0; font-size: 12px; line-height: 1.6; color: var(--muted); }

/* 编排依据面板 */
.draw-note { border-top: 1px solid var(--line); background: var(--card); }
.draw-note-bar {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 8px 14px; border-bottom: 1px dashed var(--line);
}
.draw-note-bar .spacer { flex: 1 1 auto; }
.draw-note-body { padding: 10px 16px 14px; font-size: 13px; line-height: 1.75; color: var(--text-2); }
.draw-note-body h3 { margin: 0 0 8px; font-size: 15px; color: var(--text); }
.draw-note-body h4 { margin: 14px 0 6px; font-size: 13px; color: var(--brand); }
.draw-note-body .dn-lead { margin: 0 0 4px; }
.dn-list { margin: 0; padding-left: 20px; }
.dn-list li { margin-bottom: 3px; }
.dn-byes li { color: var(--text); }
.dn-quiet { margin: 0; color: var(--muted); }
.draw-note-body code {
  padding: 1px 5px; border-radius: 4px; background: var(--bg);
  border: 1px solid var(--line); font-size: 12px;
}

/* 打印稿：与 .ps-* 同一套做法 —— #printSheet 平时 display:none，
   所以这些规则不需要 @media print 包裹。纸面一律用纯黑白灰（省墨、黑白打印机可读）。 */
.dn-print { color: #000; font-size: 11px; line-height: 1.7; }
.dn-print h3 { margin: 0 0 6px; font-size: 15px; }
.dn-print h4 { margin: 10px 0 4px; font-size: 12px; border-bottom: 1px solid #999; padding-bottom: 2px; }
.dn-print .dn-list { padding-left: 18px; }
.dn-print li { break-inside: avoid; page-break-inside: avoid; }
.dn-print code { font-family: inherit; font-weight: 700; }
.dn-print .dn-quiet { color: #444; }

/* 隐藏的高级设置块（连点「抽签编排」标题 7 次解锁后才出现，2026-10-16）。
   界面上默认完全看不到 —— 这一条只在解锁后存在，所以样式也不需要额外开关。 */
.draw-adv { border-bottom: 1px solid var(--line); background: var(--bg); }
.draw-adv-bar {
  display: flex; align-items: center; flex-wrap: wrap; gap: 8px;
  padding: 8px 14px; font-size: 13px;
}
.draw-adv-bar .spacer { flex: 1 1 auto; }

/* 场次表上方的「列出轮空行」开关（与 .sheet-mode 同族观感，但它是复选框不是单选） */
.sheet-switch {
  display: inline-flex; align-items: center; gap: 6px; padding: 3px 10px;
  border: 1px solid var(--line); border-radius: var(--radius-pill);
  background: var(--card); font-size: 12px; font-weight: 600;
  color: var(--text-2); cursor: pointer; white-space: nowrap;
}
.sheet-switch:hover { border-color: var(--brand); }
.sheet-switch input { margin: 0; }
.sheet-switch:has(input:checked) { border-color: var(--brand); background: var(--brand-soft); color: var(--text); }


/* ============================================================
 * 赛程战绩 · 判法浮层（2026-10-17 用户修订：行内展开行 → 竖向浮动条）
 * ------------------------------------------------------------
 * 点「蓝方胜 / 红方胜」→ 在按钮旁弹出**竖向**浮层：
 *   [蓝方胜] 怎么赢的
 *   [KO] [TKO] … [不记判法]
 *   [撤销该场]（仅已录行）
 * 点任一判法即写入并收起；ESC / 点浮层外 = 只收起（什么都不写）。
 * ⚠️ 必须 position:fixed 且挂 body：战绩表在 .table-wrap（overflow:auto）里，
 *    absolute 的浮层会被容器**裁掉**（.ph-zoom 踩过同款坑）。
 *    定位由 JS 现算：贴按钮下沿展开，下方放不下就上翻、右侧越界就左移。
 * ⚠️ 侧别标签复用 `results-winbadge` 的 [data-tone] 配色，别在这里另写一套色值。
 * ============================================================ */
.win-act { display: flex; align-items: center; gap: 6px; white-space: nowrap; }
.win-pick-pop {
  position: fixed; z-index: 60; width: 148px;
  background: var(--card); border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm); box-shadow: var(--shadow-lg);
  padding: 8px; display: flex; flex-direction: column; gap: 4px;
  /* 浮层里的一切按竖排读，别继承表格的行高/字号 */
  line-height: 1.4;
}
.win-pick-hd {
  display: flex; align-items: center; gap: 6px;
  padding: 2px 2px 6px; border-bottom: 1px solid var(--line); margin-bottom: 2px;
}
.win-pick-side { font-weight: 600; }          /* 色由 results-winbadge 的 [data-tone] 给 */
.win-pick-label { font-size: 12px; color: var(--text-2); }
.win-pick-list { display: flex; flex-direction: column; gap: 4px; }
.win-pick-btn {
  width: 100%; text-align: left; font-size: 12px; padding: 5px 10px;
  background: var(--card); color: var(--text);
  border: 1px solid var(--line); border-radius: var(--radius-xs);
  cursor: pointer;
}
.win-pick-btn:hover { background: var(--row-alt); }
/* 「不记判法」是**中性**出口：别落品牌红，那看着像删除操作 */
.win-pick-none { color: var(--text-2); }
.win-pick-btn.on { font-weight: 700; box-shadow: inset 0 0 0 2px var(--brand); }
.win-pick-undo {
  width: 100%; margin-top: 2px; font-size: 12px; padding: 5px 10px;
  background: var(--brand-soft); color: var(--brand);
  border: 1px solid var(--brand); border-radius: var(--radius-xs); cursor: pointer;
}

/* 赛程战绩：胜者旁的「怎么赢的」小标（2026-10-17 显示出口）。
   旧数据没有这个字段 → 不渲染（不显示「未填」）。 */
.results-wintype {
  font-size: 11px; font-weight: 600; white-space: nowrap;
  color: var(--gold); background: var(--gold-soft);
  border: 1px solid var(--gold); border-radius: var(--radius-pill);
  padding: 0 6px; margin-left: 2px;
}

/* 成绩册：冠军「决赛怎么赢的」小标注 */
.bk-wintype {
  font-size: 12px; font-weight: 600;
  color: var(--gold); background: var(--gold-soft);
  border: 1px solid var(--gold); border-radius: var(--radius-pill);
  padding: 1px 8px; margin-left: 4px;
}

/* ============================================================
 * 顶栏「网络 / 待同步」徽标（2026-10-17）
 * 三态：队列空 → 隐藏；待同步 N 条 → 金色；离线 → 品牌红。
 * ⚠️ 只用已定义的令牌，不现编颜色。
 * ============================================================ */
.net-badge {
  align-self: center; white-space: nowrap;
  font-size: 12px; font-weight: 600;
  padding: 3px 10px; border-radius: var(--radius-pill);
  border: 1px solid transparent;
}
.net-badge.pending {
  color: var(--gold); background: var(--gold-soft);
  border-color: var(--gold);
}
.net-badge.off {
  color: var(--brand-dark); background: var(--brand-soft);
  border-color: var(--brand-ring);
}
/* ===== 组别页：已抽签时的「查看排序」提示条（2026-10-16 用户规格二）=====
   分层后，已抽签的大项点表头只改查看顺序。这条提示要把「这不是编排顺序」
   说在前面，否则用户会以为刚才那一下已经把秩序单改了 —— 那正是误触的隐患。
   色调取信息蓝（--info），不取品牌红：红在本项目里是「危险 / 删除」的语义，
   用在这里会让人以为出了错。左侧竖线是唯一的形状差异，与 .grp-order-tip 区分。 */
.grp-frozen-tip {
  border-left: 3px solid var(--info);
  padding-left: 10px;
}
.grp-frozen-tip .hint { color: var(--text-2); }

/* ============================================================
 * 日程与休息校验（2026-10-17 规格三）
 * 五区：全局参数 / 时段容量 / 回合参数表 / 单元日程 / 冲突校验。
 * ⚠️ 只用已定义令牌。数值输入的「空 = 跟随上级」靠灰 placeholder 表达，
 *    已填的框加 .on（浅金底）—— 这就是「未配」与「配成默认值」的视觉分界。
 * ============================================================ */
.sch-grid { display: flex; flex-wrap: wrap; gap: 10px 16px; margin-bottom: 8px; }
.sch-f { display: flex; align-items: center; gap: 6px; font-size: 13px; color: var(--text-2); }
.sch-f input[type="number"] { width: 76px; }
.sch-check { gap: 6px; }

/* 自动排程（2026-10-18 方案 D）：选项行 / 每格占用胶囊 / 每单元建议位置。
   超容的胶囊转金（与 badge-warn 同源）—— 排程结果顶到天花板时要看得见。 */
.auto-opts { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 16px; margin-bottom: 8px; }
.auto-prev { border-top: 1px dashed var(--line); padding-top: 10px; }
.auto-cells { display: flex; flex-wrap: wrap; gap: 8px; margin: 6px 0 10px; }
.auto-cell {
  display: inline-flex; align-items: center; gap: 6px; padding: 3px 10px;
  border: 1px solid var(--line); border-radius: var(--radius-pill);
  background: var(--card); font-size: 12px; color: var(--text-2); white-space: nowrap;
}
.auto-cell b { color: var(--text); font-variant-numeric: tabular-nums; }
.auto-cell.auto-over { border-color: var(--gold-line); background: var(--gold-soft); color: var(--gold); }
.auto-units { display: flex; flex-wrap: wrap; gap: 6px 14px; font-size: 12px; color: var(--text-2); }
.auto-units .auto-unit { white-space: nowrap; }

/* 分段编辑（2026-10-19 轮次级日程）：多段单元的摘要 + 展开后的分段行。
   ⚠️ 编辑器是**表格里的一行**（.sch-seg-row），不另起浮层 ——
   浮层要算位置，而表在 .table-wrap（overflow:auto）里，算出来的坐标会被裁掉。 */
.sch-seg-cell { white-space: normal; }
.sch-seg-badge { margin-right: 6px; }
.sch-seg-sum { display: inline-flex; flex-wrap: wrap; gap: 4px 10px; font-size: 12px; color: var(--text-2); }
.sch-seg-sum-item { white-space: nowrap; }
.sch-seg-toggle { margin-left: 8px; }
.sch-seg-row > td { background: var(--page); padding: 10px 12px; }
.sch-segs { display: flex; flex-direction: column; gap: 8px; }
.sch-seg {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px;
  padding: 6px 10px; border: 1px solid var(--line); border-radius: 8px; background: var(--card);
}
.sch-seg-tag {
  min-width: 96px; font-weight: 600; font-size: 13px; color: var(--text);
}
.sch-seg-slots { display: inline-flex; gap: 6px; }
.sch-seg-op { margin-left: auto; }

/* 「分段」按钮在**单元列**里，那一列的宽度是硬预算 —— 按钮要瘦，
   组别名超宽就省略号（title 看全）。别处不许拿宽度换排版。 */
.sch-pick-td .sch-seg-toggle { padding: 0 5px; font-size: 11px; margin-left: 4px; }
.sch-unit-name {
  display: inline-block; max-width: 138px; overflow: hidden;
  text-overflow: ellipsis; white-space: nowrap; vertical-align: middle;
}
.sch-seg-foot { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }

/* 回合参数表：大项小标题 / 默认行 / 年龄组行 */
.sch-cfg th { white-space: nowrap; }
.sch-cfg td { padding: 4px 8px; }
.sch-cfg-head td {
  background: var(--info-soft); font-weight: 600; color: var(--info-dark);
  font-size: 12px; letter-spacing: .02em;
}
.sch-cfg-in { width: 72px; }
.sch-cfg-in.on { background: var(--gold-soft); }
.sch-cfg-in::placeholder { color: var(--text-2); opacity: .55; }

/* 单元日程 */
.sch-disc { margin-bottom: 14px; }
/* 吸顶的批量行（2026-10-18 下午三）：78 组滚到中段时，「应用到选中的 N 个」
   还得滚回块首才能按 —— 那正是这次要消灭的低效。
   · top 用 --sch-sticky-top：顶栏在窄屏会因标签换行变高，写死像素必被盖住；
     那个变量由 admin.js 在每次重绘时按顶栏实测高度写入。
   · z-index 20：低于 .topbar(50)，高于表格行。
   · 背景必须**实色**（半透明会让下方表格文字透上来叠字）。 */
.sch-disc-head {
  display: flex; align-items: center; gap: 10px;
  margin: 6px 0 6px; padding: 6px 10px;
  background: var(--card); border: 1px solid var(--line); border-radius: 8px;
  position: sticky; top: var(--sch-sticky-top, 64px); z-index: 20;
}
/* ⚠️ .card 的 overflow:hidden 会让卡片成为「最近的滚动容器」，里面的 sticky
   于是完全不生效（不报错、不生效）。这张卡单独放开裁剪 ——
   卡片正文有 18px 内边距，表格碰不到圆角，观感不受影响。 */
.card.sch-card { overflow: visible; }
.sch-bulk {
  margin-left: auto; display: flex; align-items: center; gap: 6px;
  /* 2026-10-17 夜：多了一个「应用到选中的 N 个」按钮 —— 允许折行，
     宁可在窄屏上换行，也不要把它挤出卡片（子元素各自 nowrap）。 */
  flex-wrap: wrap; justify-content: flex-end;
  font-size: 12px; color: var(--text-2); white-space: nowrap;
}
/* ⚠️ flex 容器里的裸文本会变成匿名 flex item，宽度不够时**逐字竖排**
   （实测「本大项全部设为：」被挤成一列）。包一层 span 并强制不换行。 */
.sch-bulk-label { white-space: nowrap; }
/* 批量行的 6 个控件（大项名 / 日期 / ▲▼ / 时段 / 两个应用按钮）挤在一行时，
   **日期框会被压成「2026-1…」**（2026-10-18 傍晚）。根因是 flex-shrink：
   width:140px 的输入框被兄弟节点压到 ~90px。
   ⚠️ 一开始还写了 `.sch-bulk > * { flex-shrink: 0 }`，但变异实测**删掉它断言照样绿** ——
      下面那条 min-width 已经把压缩兜死了，多一条规则只会让人以为它在负责什么。删。 */
/* 日期框给足「yyyy/mm/dd + 日历图标」的实测宽度。
   ⚠️ 这里必须带 .sch-disc-head .sch-bulk 前缀（权重 0,3,1）：裸写 input[type=date]
   会被下面 .sch-units input[type=date]{width:120px} 这类规则按顺序盖掉。 */
.sch-disc-head .sch-bulk input[type="date"] { width: 146px; min-width: 146px; }
/* ⚠️ 全局 `select { width: 100% }`（0,0,1）会把时段下拉撑成**整行宽**（实测 687px），
   于是「一行 6 个控件」实际上被它挤成三行 —— 日期框只是最显眼的受害者。
   这里按需宽处理（0,3,1，稳过全局那条）。 */
.sch-disc-head .sch-bulk select { width: auto; min-width: 92px; }
/* 大项名是可伸缩的那一个：宽度不够时它自己截断，绝不挤控件 */
.sch-disc-head > strong {
  flex: 1 1 auto; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* 窄屏放不下时整块换行（而不是压扁日期框）。
   ⚠️ 不要给 .sch-bulk 加 min-width:0 —— 那会让它缩到比「最宽的子控件」还窄，
   子控件 flex-shrink:0 就只能溢出。保持 auto 最小尺寸 = 最宽子控件，由 strong 承担收缩。 */
.sch-disc-head .sch-bulk { flex: 0 1 auto; row-gap: 6px; }
/* ⚠️ 曾想给两个应用按钮之间加竖分隔线（border-left + padding + margin），
   结果 `.sch-bulk` 本来就是**正好 max-content 宽**，多出的 14px 直接让它折成 3 行、
   吸顶行高度从 47 涨到 133 —— 分隔线是装饰，不能为它牺牲布局。
   两个按钮的文案本身已经区分了范围（应用到全大项 / 应用到选中的 N 个）。 */
.sch-units td.num { text-align: right; white-space: nowrap; }
.sch-units tr.sch-unset td { color: var(--text-2); }
.sch-units input[type="date"] { width: 120px; }
.sch-slot-cell { white-space: nowrap; }
/* 框选（2026-10-18）：拖拽矩形。fixed 定位 —— 表格在滚动容器里，
   用 absolute 还得算滚动偏移，那正是要避开的那类坐标换算。 */
.sch-dragbox {
  position: fixed; display: none; z-index: 900; pointer-events: none;
  border: 1px solid var(--brand); background: var(--brand-ring);
  border-radius: 2px;
}
/* 拖拽期间禁止选中文字（否则整片表格被抹蓝） */
.sch-dragging, .sch-dragging * { user-select: none; }
/* 日期 ▲▼（2026-10-18）：竖排两个小按钮，宽度只多占 ~18px */
.sch-datespin { display: inline-flex; align-items: center; gap: 2px; }
.sch-daybtns { display: inline-flex; flex-direction: column; gap: 1px; }
.sch-daybtn {
  border: 1px solid var(--line); background: var(--card); color: var(--text-2);
  border-radius: 3px; font-size: 7px; line-height: 1; padding: 1px 2px; cursor: pointer;
}
.sch-daybtn:hover { color: var(--brand); border-color: var(--brand); }
.sch-date-cell { white-space: nowrap; }
/* 多选（2026-10-17 夜）：勾选框放在「单元」列**内**，不新增列 —— 列数预算是硬约束。
   与文字基线对齐用 -2px（checkbox 的默认基线比文字高 2px）。 */
.sch-units .sch-pick { margin: 0 4px 0 0; vertical-align: -2px; }
.sch-units .sch-pick-th { white-space: nowrap; }
.sch-units td.sch-pick-td { white-space: nowrap; }
.sch-slot { font-size: 12px; padding: 3px 10px; }
.sch-slot.on { font-weight: 700; box-shadow: inset 0 0 0 2px var(--brand); }

/* 冲突卡：红级（撞车 / 超容）与金级（过载 / 连场 / 间隔）分色 */
.sch-conf { display: flex; flex-direction: column; gap: 6px; }
.sch-conf-item {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 8px;
  padding: 8px 10px; border-radius: 8px;
  background: var(--card); border: 1px solid var(--line);
  border-left: 3px solid var(--line-strong);
}
.sch-conf-item.sch-lv-row-overflow,
.sch-conf-item.sch-lv-row-crash { border-left-color: var(--brand); background: var(--brand-soft); }
.sch-conf-item.sch-lv-row-load,
.sch-conf-item.sch-lv-row-tight,
.sch-conf-item.sch-lv-row-rest { border-left-color: var(--gold); background: var(--gold-soft); }
.sch-conf-who { font-weight: 600; }
.sch-conf-msg { font-size: 13px; }
.sch-conf-units { margin-left: auto; font-size: 12px; color: var(--text-2); }

/* 级别徽标（.badge 已有形状，这里只补色） */
.sch-lv-red { background: var(--brand-soft); color: var(--brand-dark); }
.sch-lv-gold { background: var(--gold-soft); color: var(--gold); }

/* ============================================================
 * 卡 6：断网降级条（2026-10-17）
 * ------------------------------------------------------------
 * 版式照 .mobile-tip：整宽、居中、与容器同宽；hidden 时对布局零影响。
 * 配色只取金系令牌（提示色）—— 它是"告诉你现在用的是缓存"，**不是错误**，
 * 所以不用品牌红（那会让人以为坏了）。也正因如此这里**没有新增 hex**：
 * 卡 7 令牌化时不必回来看这一块。
 * ⚠️ 本区块在**文件尾**：卡 7 只动 :root 与正文，禁碰这里。
 * ============================================================ */
.offline-bar {
  max-width: 1180px;
  margin: 10px auto 0;
  padding: 8px 12px;
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  background: var(--gold-soft);
  border-radius: 10px;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--gold);
}
.offline-bar .ob-text { flex: 1 1 auto; min-width: 0; }
.offline-bar .btn { min-height: 36px; }
.offline-bar[hidden] { display: none; }

/* ============================================================
 * 「场地平衡」卡片 + 场次表行内场地下拉（2026-10-19）
 * ------------------------------------------------------------
 * 只用已定义令牌（--gold/--ok/--info/--text-2/muted/card 那一族），
 * 不新增 hex —— 本区块在**文件尾**，与其它尾块同一纪律。
 * ⚠️ CRLF 文件：改锚点只用单行，别写跨行 \n。
 * ============================================================ */
/* 卡片头里的汇总（收起态也能看见"哪儿不均衡"） */
.ab-sum { display: flex; flex-wrap: wrap; gap: 4px 10px; font-size: 12.5px; }
.ab-sum-item.ab-bad { color: var(--gold); font-weight: 600; }
.ab-sum-item.ab-ok { color: var(--ok); }
/* 容差下拉：跟卡片头其它控件同一尺寸，别自己长一截 */
.ab-tol { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--text-2); }
.ab-tol select { width: auto; min-width: 64px; }
.ab-collapsed { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ab-disc + .ab-disc { margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--line); }
.ab-disc-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-bottom: 8px; }
/* 二维表：场地为行、赛日为列。数字用等宽数列，扫一眼就能比大小 */
table.data.ab-grid th, table.data.ab-grid td { padding: 6px 10px; }
table.data.ab-grid .ab-corner { color: var(--text-2); font-weight: 500; }
table.data.ab-grid .ab-total { background: var(--page); font-weight: 600; }
table.data.ab-grid tr.ab-diff th, table.data.ab-grid tr.ab-diff td { border-top: 1px solid var(--line); }
table.data.ab-grid tr.ab-unsched td, table.data.ab-grid tr.ab-unsched th { color: var(--text-2); }
.ab-bad { color: var(--gold); font-weight: 700; }
.ab-ok { color: var(--ok); font-weight: 600; }
/* 建议列表：一行一条，右侧「应用」不换行。
   ⚠️ 行底色的两个类是 ab-tip-ok / ab-tip-no（更优 / 不更优），
      序号那个类是 ab-tip-idx —— **别复用**：一个元素两种语义时，
      改一边必然顺手改错另一边（本轮第一版就把序号也命名成 ab-tip-no）。 */
.ab-tips { display: flex; flex-direction: column; gap: 6px; margin-top: 12px; }
.ab-tip { display: flex; align-items: flex-start; gap: 10px; padding: 8px 10px; border-radius: 8px; border: 1px solid var(--line); }
.ab-tip-ok { background: var(--ok-soft); border-color: var(--ok-line); }
.ab-tip-no { background: var(--gold-soft); border-color: var(--gold-line); }
.ab-tip-idx { color: var(--text-2); font-weight: 700; min-width: 18px; text-align: center; }
.ab-tip-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.ab-tip-after { font-size: 12.5px; color: var(--text-2); line-height: 1.6; }
.ab-tip .btn { flex: 0 0 auto; }
/* 手动指定场地：表格 + 行内下拉 */
.ab-manual { margin-top: 14px; }
.ab-manual-head { display: flex; align-items: baseline; gap: 8px; margin-bottom: 6px; font-weight: 600; }
table.data.ab-manual-tb th, table.data.ab-manual-tb td { padding: 5px 10px; }
.ab-manual-pick { width: auto; min-width: 76px; }
/* 场次表「组别 / 项目」格里的场地下拉：紧凑、不抢组别的宽度 */
.sheet-arena-pick { width: auto; min-width: 62px; margin-left: 6px; font-size: 12px; padding: 1px 4px; vertical-align: baseline; }
