/* ============================================================
 * 散打比赛报名系统 · 样式
 * 浅色专业风：深红主色（竞技/武术）+ 干净的中性灰底
 * ============================================================ */

:root {
  --brand:       #B42318;
  --brand-dark:  #8A1B12;
  --brand-soft:  #FEF3F2;
  --gold:        #B54708;
  --gold-soft:   #FFFAEB;
  --ok:          #067647;
  --ok-soft:     #ECFDF3;
  /* 信息蓝：专门给「不是操作、只是告诉你状态」的徽标用。
     原先「已报套路 N 项」的徽标复用了品牌红（--brand-soft / --brand-dark），
     与行首序号（那个方块点一下**会删掉这名队员**）撞成同色 —— 用户分不清
     哪个是数字、哪个是删除入口。蓝与红一眼分得开，也不跟绿色（通过/正确）混。 */
  --info:        #175CD3;
  --info-dark:   #153F92;
  --info-soft:   #EFF6FF;
  --bg:          #F7F8FA;
  --card:        #FFFFFF;
  --text:        #1A202C;
  --text-2:      #475467;
  --muted:       #7A8699;
  --line:        #E4E7EC;
  --line-strong: #D0D5DD;
  --radius:      10px;
  --shadow:      0 1px 2px rgba(16, 24, 40, .05);
  --shadow-lg:   0 8px 24px rgba(16, 24, 40, .08);
}

* { 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 {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
               "Hiragino Sans GB", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif;
  font-size: 14px;
  line-height: 1.6;
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
}

/* ---------- 顶栏 ---------- */

.topbar {
  background: linear-gradient(135deg, var(--brand-dark), var(--brand));
  color: #fff;
  /* 刘海屏 / home 指示条：viewport-fit=cover 时内容会顶到屏幕边缘，
     不补安全区就会被状态栏和底部横条压住。不是刘海屏时 env() 为 0，等于没加。 */
  padding: calc(14px + env(safe-area-inset-top, 0px)) 0 14px;
  padding-left: env(safe-area-inset-left, 0px);
  padding-right: env(safe-area-inset-right, 0px);
  box-shadow: var(--shadow);
}

.topbar-inner {
  max-width: 1180px;
  margin: 0 auto;
  padding: 0 18px;
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}

.brand-mark {
  width: 34px;
  height: 34px;
  border-radius: 9px;
  background: rgba(255, 255, 255, .16);
  border: 1px solid rgba(255, 255, 255, .25);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 17px;
  flex: 0 0 auto;
}

.topbar h1 {
  font-size: 16px;
  font-weight: 600;
  margin: 0;
  letter-spacing: .3px;
}

.topbar .sub {
  font-size: 12px;
  opacity: .82;
  margin-top: 1px;
}

.topbar .spacer { flex: 1 1 auto; }

.topbar nav {
  display: flex;
  gap: 6px;
  align-items: center;
}

.topbar nav a {
  color: rgba(255, 255, 255, .88);
  text-decoration: none;
  font-size: 13px;
  padding: 6px 12px;
  border-radius: 7px;
  transition: background .15s;
}

.topbar nav a:hover { background: rgba(255, 255, 255, .14); }
.topbar nav a.active { background: rgba(255, 255, 255, .2); color: #fff; }

/* ---------- 赛事 logo（顶栏，赛事名称之前） ---------- */

/* 只在「已设赛事 logo」时改形态：退掉「武」字方块的底色与固定宽度，
   让 logo 按自身比例铺开；没设 logo 时保持原样，版面不会空一块。 */
.brand-mark.has-logo {
  width: auto;
  height: 40px;
  padding: 0;
  background: transparent;
  border-color: transparent;
}

.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: #fff;
  background: rgba(255, 255, 255, .14);
  border: 1px solid rgba(255, 255, 255, .22);
  border-radius: 999px;
  padding: 4px 11px;
  white-space: nowrap;
}

/* ---------- 赛事 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: 6px 18px calc(34px + 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;
}
.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: #fff; 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 #FEDF89;
  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: 22px 18px 64px;
}

.card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  margin-bottom: 16px;
  overflow: hidden;
}

.card-head {
  padding: 14px 18px;
  border-bottom: 1px solid var(--line);
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

.card-head h2 {
  font-size: 15px;
  font-weight: 600;
  margin: 0;
}

.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: var(--info-soft);
  border-bottom: 1px solid #B2DDFF;
}
.admin .card-head h2 { color: #0C447C; }
.admin .card-head .hint { color: #185FA5; }

.card-body { padding: 18px; }
.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: #FCFCFD;
}
/* 还没有文件时用虚线框，与证件照空态的观感一致 */
.file-box.file-empty {
  border-style: dashed;
  border-color: var(--line-strong);
  background: #fff;
}
.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;
}

/* 导入卡的页签（散打组别 / 套路项目）——
   原来两项各占一张大卡，页面很长；合并成一张卡 + 页签后只渲染当前那一项。 */
.imp-tabs {
  display: flex;
  gap: 6px;
  border-bottom: 1px solid var(--line);
  margin-bottom: 8px;
}
.imp-tab {
  appearance: none;
  border: none;
  background: transparent;
  padding: 7px 12px 8px;
  margin-bottom: -1px;
  font: inherit;
  font-size: 13px;
  color: var(--text-2);
  cursor: pointer;
  border-bottom: 2px solid transparent;
  border-radius: 4px 4px 0 0;
}
.imp-tab:hover { color: var(--text); background: var(--bg); }
.imp-tab.on {
  color: var(--accent);
  font-weight: 600;
  border-bottom-color: var(--accent);
}
.imp-tab i {
  font-style: normal;
  font-size: 11.5px;
  color: var(--muted);
  margin-left: 2px;
}
.imp-tab.on i { color: var(--accent); }

/* 提交按钮下方的说明（报名页）：解释「提交报名」到底做什么，
   免得大家以为不点就没保存（改动其实是自动同步到云端的） */
.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"],
select,
textarea {
  width: 100%;
  padding: 9px 11px;
  font-size: 14px;
  font-family: inherit;
  color: var(--text);
  background: #fff;
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  outline: none;
  transition: border-color .15s, box-shadow .15s;
}

input:focus, select:focus, textarea:focus {
  border-color: var(--brand);
  box-shadow: 0 0 0 3px rgba(180, 35, 24, .1);
}

/* ---------- 散打组别下拉的两种状态色 ----------
 * 排在这里（基础 select 规则之后）是为了让顺序也站在我们这边：
 * `select.lv-todo` 的确比裸 `select` 的权重高（0,1,1 对 0,0,1），单靠权重就能赢；
 * 但 `input:disabled` 这类 `:disabled` 规则权重**相同**、又写在后面，
 * 顺序放后面可以少一层「靠权重险胜」的心智负担。
 * 以后往这里加状态类，也一律放这个位置。 */

/* 选了级别但与年龄 / 体重不符时高亮（琥珀金） */
select.lv-invalid {
  border-color: #F0B429;
  background: var(--gold-soft);
  color: #7A5600;
}

/* 「还没选散打组别」——用**控件自身的背景色**提示，不再用页面顶部的橙色通告。
   原因（用户要求）：顶部的通告离问题太远，而且「没选级别」的人里混着
   「只报套路、本来就不该选散打级别」的队员，一条通告会引起歧义。
   用信息蓝（而不是 lv-invalid 的琥珀金）：金色是本行「选了但不符」的语义，
   这里只是「还没填」的待办提醒，两者不该用同一个颜色。
   判定与提交校验一致 —— 只报套路的队员不会落到这个类上（见 team.js athleteRow）。
   历史：曾为了「一眼看得出」加过一道左侧实色条，用户确认蓝底看得见之后
   又要求去掉（2026-10-05）—— 蓝底 + 蓝边 + 深蓝字已经够醒目，色条反而多余。 */
select.lv-todo {
  border-color: var(--info);
  background: var(--info-soft);
  color: var(--info-dark);
}

input:disabled, select:disabled, textarea:disabled {
  background: #F2F4F7;
  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;
  border-radius: 8px;
  border: 1px solid var(--line-strong);
  background: #fff;
  color: var(--text-2);
  cursor: pointer;
  transition: all .15s;
  white-space: nowrap;
}

.btn:hover { border-color: var(--muted); color: var(--text); }

.btn-primary {
  background: var(--brand);
  border-color: var(--brand);
  color: #fff;
}
.btn-primary:hover { background: var(--brand-dark); border-color: var(--brand-dark); color: #fff; }

.btn-ghost {
  border-color: transparent;
  background: transparent;
  color: var(--brand);
  padding: 6px 9px;
}
.btn-ghost:hover { background: var(--brand-soft); }

.btn-danger-ghost {
  border-color: transparent;
  background: transparent;
  color: var(--brand);
  padding: 6px 9px;
}
.btn-danger-ghost:hover { background: var(--brand-soft); }

/* 添加类按钮：淡雅底色。它是「增量操作」，不该和主操作的实心红抢注意力，
   但又要一眼能认出是「可以点、点了会多一行」 */
.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; }

.btn:disabled { opacity: .5; cursor: not-allowed; }

.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: #fff;
  border: 1px solid var(--line);
  border-radius: 10px;
}
.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 9px;
  border-radius: 999px;
  font-size: 12px;
  font-weight: 500;
  border: 1px solid transparent;
  /* 表格被挤窄时不许换行 —— 一换行，「3 / 12」就断成两行，
     相邻两列看上去就错位了。 */
  white-space: nowrap;
}
.badge-open   { background: var(--ok-soft);   color: var(--ok);   border-color: #ABEFC6; }
.badge-closed { background: #FEF3F2;          color: var(--brand); border-color: #FECDCA; }
.badge-info   { background: #EFF8FF;          color: #175CD3;     border-color: #B2DDFF; }
.badge-gray   { background: #F2F4F7;          color: var(--text-2); border-color: var(--line); }
.badge-warn   { background: var(--gold-soft); color: var(--gold);   border-color: #FEDF89; }
/* 证件照进度：用紫色系，和「人数」的蓝、状态的绿/红区分开，一眼能分辨是哪一类信息 */
.badge-photo  { background: #F4F3FF;          color: #5925DC;       border-color: #D9D6FE; }

/* 可以点的徽标（例如「证件照 3 / 12」）：保持徽标的观感，但明确表示能点开 */
.badge-btn {
  cursor: pointer;
  font-family: inherit;
  transition: filter .15s, box-shadow .15s;
}
.badge-btn:hover { filter: brightness(.955); box-shadow: 0 1px 2px rgba(16, 24, 40, .12); }
.badge-btn:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }

/* 只读的辅助文字单元格 */
.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;
  color: var(--text-2);
  background: #F9FAFB;
  padding: 9px 12px;
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}

table.data td {
  padding: 8px 12px;
  border-bottom: 1px solid var(--line);
  vertical-align: middle;
}

table.data tr:last-child td { border-bottom: none; }
table.data tbody tr:hover { background: #FAFBFC; }

table.data td input,
table.data td select {
  padding: 5px 8px;
  font-size: 13px;
}

.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: #FFFBEB; }
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: 8px;
  font-size: 13px;
  border: 1px solid;
  margin-bottom: 14px;
}
.notice-info  { background: #EFF8FF; border-color: #B2DDFF; color: #175CD3; }
.notice-warn  { background: var(--gold-soft); border-color: #FEDF89; color: var(--gold); }
.notice-err   { background: var(--brand-soft); border-color: #FECDCA; color: var(--brand); }
.notice-ok    { background: var(--ok-soft); border-color: #ABEFC6; 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; }

/* ---------- 可折叠的「说明」 ----------
 * 管理端原先把解释性文字都铺成彩色 .notice，一屏好几个框，看久了分不清
 * 哪块才是真要动手处理的状态。约定：
 *   · 彩色框只留给「需要处理的状态」——报错、风险提示、报名开关；
 *   · 解释性的内容（怎么算、会怎样、和别的功能什么关系）一律折进这里，默认收起。
 * 用原生 <details>，不依赖 JS。 */
.help {
  margin: 0 0 12px;
  font-size: 12.5px;
  line-height: 1.7;
  color: var(--text-2);
}
.help > summary {
  cursor: pointer;
  color: var(--muted);
  user-select: none;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  list-style-position: inside;
}
.help > summary:hover { color: var(--brand); }
.help > .help-body {
  margin-top: 6px;
  padding-left: 2px;
  border-left: 2px solid var(--line);
  padding: 2px 0 2px 10px;
}
/* Chrome 里 details 收起时子元素仍会占着盒子（会顶掉下面的间距），必须显式塌掉 */
.help:not([open]) > .help-body { display: none; }
/* 紧跟表格 / 另一段说明之后时补一点上间距（.help 自身只留了下间距） */
.table-wrap + .help,
.help + .help,
.help + .field-inline { margin-top: 12px; }
.help-body b, .help-body strong { color: var(--text); font-weight: 600; }
.help-body code {
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 4px;
  padding: 0 4px;
  font-size: 12px;
}

/* ---------- 一行式紧凑字段 ----------
 * 行内人一眼就懂的设置（如体重上浮容差）不该为它铺一整块两列说明区。 */
.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; }

/* ---------- 大项开关 ----------
 * 散打 / 套路是两个并列大项，一支队可只报其一或两项都报。
 * 开关状态用整行的底色与「本届开设 / 不开设」双表达，不靠颜色单独传递信息。 */
.dz-list { display: grid; gap: 10px; }
.dz-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 14px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: #fff;
  cursor: pointer;
  font-size: 13.5px;
  font-weight: 500;
  color: var(--text);
}
.dz-row.on { border-color: var(--line-strong); background: var(--brand-soft); }
.dz-row input[type="checkbox"] { width: 16px; height: 16px; margin: 0; cursor: pointer; }
.dz-name { font-weight: 600; }
.dz-state { margin-left: auto; font-weight: 400; font-size: 12.5px; color: var(--muted); }
.dz-row.on .dz-state { color: var(--brand); font-weight: 500; }
.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;
}
.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: 38px 18px;
  text-align: center;
  color: var(--muted);
  font-size: 13.5px;
}
.empty .big { font-size: 28px; margin-bottom: 8px; opacity: .55; }

/* ---------- 统计块 ---------- */

.stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
  gap: 12px;
}

.stat {
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 14px 16px;
}
.stat .n { font-size: 24px; font-weight: 700; color: var(--brand); line-height: 1.2; }
.stat .l { font-size: 12.5px; color: var(--muted); margin-top: 3px; }

/* ---------- 分组标签页 ---------- */

.tabs {
  display: flex;
  gap: 4px;
  border-bottom: 1px solid var(--line);
  padding: 0 18px;
  overflow-x: auto;
}

.tabs button {
  border: none;
  background: none;
  font-family: inherit;
  font-size: 13.5px;
  color: var(--text-2);
  padding: 11px 14px;
  cursor: pointer;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  white-space: nowrap;
}

.tabs button:hover { color: var(--text); }
.tabs button.active { color: var(--brand); border-bottom-color: var(--brand); font-weight: 600; }

/* 独立成块的标签栏（管理端顶部） */
.tabs.standalone {
  background: #fff;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  margin-bottom: 16px;
  padding: 0 10px;
}

/* ---------- 勾选胶囊 ---------- */

.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: 8px;
  font-size: 13px;
  color: var(--text-2);
  background: #fff;
  cursor: pointer;
  user-select: none;
  transition: border-color .15s, background .15s, color .15s;
}

.chip:hover { border-color: var(--muted); }

.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;
}

.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: #F4F3FF;
  border: 1px solid #D9D6FE;
  color: #4A1FB8;
  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: #4A1FB8;
  border-right-width: 0;
  transition: transform .15s;
}
.photo-note[open] > summary::before { transform: rotate(90deg); }
.photo-note-body {
  padding: 0 12px 10px 24px;
  color: #5748A8;
}
/* 折叠时必须真的把内容摘掉：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: #fff;
  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: #F5F6F8;
  color: #98A2B3;
}
.ph-cell.ph-void:hover { border-color: var(--line-strong); background: #F5F6F8; 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: #fff;
  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: #fff;
  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: #fff;
  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: #fff;
}

/* 列头行与数据行共用同一套列宽，保证上下对齐。
   ⚠️ 最小宽度不是随手写的：断点 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: #F9FAFB;
  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: #FCFCFD; }
.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: #fff;
  outline: none;
}

/* 「套路报项」数字徽标与备注同占一格。
   ⚠️ 不单独开一列：宽屏那一行的最小宽度是贴着 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: #fff;
  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: #fff;
  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: #fff; 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: #FCFCFD;
}
.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: #fff;
  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: #fff;
  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: #FCFCFD;
  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: #fff;
  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: #fff;
}
.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: #FCFCFD; 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);
}

/* 只办套路时「体重」与「参赛组别」两列没有意义 —— 整列抽掉，剩下的列自动补位。
   列数变了必须连列头一起改（列头与数据行共用同一套模板），否则上下会错位。 */
.ath-table.no-sanda .ath-thead,
.ath-table.no-sanda .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: #fff;
  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: #F9FAFB;
  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: #FCFCFD;
    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-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%;
  }
  .topbar nav { flex-basis: 100%; justify-content: flex-end; }
  .topbar nav a { 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;
  }
  /* （出生日期 / 其它控件的盒子统一已在基础规则里做过，见 .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(26, 32, 44, .93);
  color: #fff;
  padding: 9px 20px;
  border-radius: 8px;
  font-size: 13px;
  max-width: 86vw;
  text-align: center;
  opacity: 0;
  pointer-events: none;
  transition: opacity .22s, transform .22s;
  z-index: 999;
}

.toast.show { opacity: 1; transform: translate(-50%, 0); }

/* ---------- 证件照面板（组委会查看 / 打印证件牌） ---------- */

.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, .5);
}

.sheet-body {
  position: absolute;
  left: 50%;
  top: 5vh;
  transform: translateX(-50%);
  width: min(1020px, 94vw);
  max-height: 90vh;
  background: #fff;
  border-radius: 12px;
  box-shadow: var(--shadow-lg);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.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: #fff;
}

.id-photo {
  flex: 0 0 auto;
  width: 62px;
  height: 82px;
  border-radius: 4px;
  overflow: hidden;
  background: #F2F4F7;
}
.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: #fff; }
  .card { border: none; box-shadow: none; }

  /* 点「打印证件牌」时给 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; }
}

/* ---------- 报项检查表（提交前自检，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: #F9FAFB; }
.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: #EFF8FF; color: #175CD3; border: 1px solid #B2DDFF;
}
/* 三类报项三色区分（2026-10-07 用户要求）：散打级别红、个人套路蓝、对练/集体紫 */
.chk-item.chk-sanda { background: #FEF3F2; color: #B42318; border-color: #FECDCA; }
.chk-item.chk-taolu { background: #EFF8FF; color: #175CD3; border-color: #B2DDFF; }
.chk-item.chk-squad { background: #F4F3FF; color: #5925DC; border-color: #D9D6FE; }
.chk-none { color: var(--brand); }

/* ---------- 规程文件页内全屏预览（2026-10-07） ----------
   微信内置浏览器拦截 window.open 弹窗（「不允许弹出窗口」），
   改成全屏遮罩 + iframe，零弹窗；遮罩上另给「下载」按钮兜底
   （部分安卓 WebView 预览不了 PDF，下载后用别的应用打开）。 */

.fileview-mask {
  position: fixed; inset: 0; background: #fff; 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%; }
