/* ============================================================
   ARENA — Swiss / International Typographic Style
   Palette: #FFFFFF background, #111111 text, #FF0000 accent only.
   ============================================================ */

:root {
  --c-bg: #FFFFFF;
  --c-ink: #111111;
  --c-red: #FF0000;
  --c-grey: #6b6b6b;
  --c-line: #111111;
  --c-hair: rgba(17,17,17,0.14);

  --font-display: "Archivo", "PingFang SC", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif;
  --font-body: "Space Grotesk", "PingFang SC", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif;

  --col-gap: 20px;
  --edge: 48px;
}

/* 中文没有真正的 900 Black 字重（PingFang SC 最粗到 Semibold、微软雅黑
   最粗到 Bold），跟 Archivo 900 混排在大字号标题里会显得偏细——给纯中文
   内容描一层极细边补粗视觉重量，不影响本来就够粗的英文/数字（见
   js/script.js 的 setText()，只有命中中文正则才会加这个类）。
   这层边是照大字号标题定的固定 0.55px，落到 13px 的"进行中 · LIVE"状态
   标签上就占了近 4% 的字身，笔画密集的字字腔会被糊死，所以限定只在标题
   行生效；小标签本身就是 700 字重，不需要补。 */
.hero-title-line.text-cjk-bold,
.detail-id-name.text-cjk-bold {
  -webkit-text-stroke: 0.55px currentColor;
}

* { margin: 0; padding: 0; box-sizing: border-box; }

/* 全站没有任何 @media 断点——12 列网格（.grid-container 的 1440px
   max-width）是按这个宽度调好的固定版式，不是"随窗口自适应"的响应式
   设计。刻意的取舍：给 html/body 这个最外层盒子一个 min-width（跟
   .grid-container 的 max-width 锁同一个数），窗口比这个值还窄时，宁可
   出现横向裁剪（配合下面已有的"隐藏滚动条"三件套，效果是内容超出部分
   直接看不见/滚不到，不是响应式挤压），也不让 12 列网格被压缩变形——
   导航挤在一起、.match-row 好几列文字撞在一起、筛选行三个维度叠成一坨
   这类"版式因为窗口变窄而错乱"的问题，用响应式断点一个个补是无穷尽的，
   这个项目现阶段选择直接锁死最小宽度、根治整类问题，而不是逐个打补丁。 */
html {
  scroll-behavior: smooth;
  scrollbar-width: none; /* Firefox */
  -ms-overflow-style: none; /* legacy Edge/IE */
  min-width: 1440px;
}

html::-webkit-scrollbar { display: none; } /* Chrome/Safari/新版 Edge */

body {
  background: var(--c-bg);
  color: var(--c-ink);
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.4;
  -webkit-font-smoothing: antialiased;
  min-width: 1440px;
  overflow-x: hidden;
  scrollbar-width: none;
  -ms-overflow-style: none;
}

body::-webkit-scrollbar { display: none; }

a { color: inherit; text-decoration: none; }

/* ---------- 12-col grid container ---------- */
.grid-container {
  max-width: 1440px;
  margin: 0 auto;
  padding: 0 var(--edge);
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  column-gap: var(--col-gap);
}

/* ============ HEADER ============ */
.site-header {
  position: sticky;
  top: 0;
  z-index: 100;
  background: var(--c-bg);
  border-bottom: 1px solid var(--c-line);
}

.header-grid {
  align-items: center;
  height: 84px;
}

.logo {
  grid-column: 1 / 4;
  display: flex;
  align-items: baseline;
  gap: 6px;
}

/* 字标 viewBox 高 104.38（含圆形 G 超出 A/F 帽高的部分），
   所以 height 要乘 1.0438 才能让 A/F 的帽高等于目标 px */
.logo-gafa {
  height: 23px;
  width: auto;
}

.logo-text {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 32px;
  letter-spacing: -0.02em;
}

.main-nav {
  grid-column: 5 / 13;
  display: flex;
  /* align-items:center 是给右上角那个登录挂件加的：里面的头像（32px）比
     13px 的导航文字高出一截，不居中的话文字链接会被拉伸、跟头像错开。
     对原来清一色的文字链接来说这条是空操作——它们本来就一样高。 */
  align-items: center;
  justify-content: flex-end;
  gap: 36px;
}

.nav-link {
  font-family: var(--font-body);
  font-weight: 500;
  font-size: 13px;
  letter-spacing: 0.03em;
  display: flex;
  align-items: baseline;
  gap: 6px;
  position: relative;
  padding-bottom: 4px;
}

.nav-index {
  color: var(--c-red);
  font-weight: 700;
  font-size: 11px;
}

.nav-link::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  width: 0%;
  height: 2px;
  background: var(--c-red);
  transition: width 0.25s ease;
}

.nav-link:hover::after { width: 100%; }

/* ---------- 右上角 Steam 登录挂件（由 js/login.js 渲染） ---------- */
/* login.js 自己往 .main-nav 末尾塞一个 .nav-login，所以这里只写它内部的
   那几个类。未登录 = 一个「登录」文字链；已登录 = 32px 方头像（点进本人
   的 player-detail.html）+ 一个「退出」。两个文字链同色，都是全站的墨黑。 */
.nav-login {
  display: flex;
  align-items: center;
  gap: 14px;
  /* 左侧一条细分隔线，跟前面五个导航项拉开；.main-nav 的 36px gap 加上
     这段 padding，分隔线离最后一个导航项约 60px。 */
  padding-left: 24px;
  border-left: 1px solid var(--c-hair);
}

.nav-login-link,
.nav-logout {
  font-family: var(--font-body);
  font-weight: 500;
  font-size: 13px;
  letter-spacing: 0.03em;
  white-space: nowrap;
  position: relative;
  padding-bottom: 4px;
  color: var(--c-ink);
}

/* 悬停红下划线，跟 .nav-link 同一套（含那条 0.25s 的展开过渡） */
.nav-login-link::after,
.nav-logout::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  width: 0%;
  height: 2px;
  background: var(--c-red);
  transition: width 0.25s ease;
}

.nav-login-link:hover::after,
.nav-logout:hover::after { width: 100%; }

.nav-avatar-link {
  display: block;
  line-height: 0; /* 去掉 img 底下那几像素行高留白，否则头像会被顶偏 */
}

.nav-avatar {
  display: block;
  width: 32px;
  height: 32px;
  object-fit: cover;
  /* 跟选手页那张 .player-photo 同一套：近直角 + 1px 黑边 */
  border-radius: 2px;
  border: 1px solid var(--c-ink);
  transition: border-color 0.2s ease;
}

.nav-avatar-link:hover .nav-avatar { border-color: var(--c-red); }

/* ============ HERO ============ */
.hero {
  padding: 56px var(--edge) 64px;
  border-bottom: 1px solid var(--c-line);
  position: relative;
}

.hero .grid-container { padding: 0; max-width: 1344px; }

/* 首屏顶部一格：左侧三行大标题，右侧"进行中"状态标签。
   第一行"系列名"（IEM）比下面两行短得多，右端会空出一大块，把状态
   标签放进这块留白，它的右边缘也就跟下方四格信息条的右边缘落在同一
   条竖线上（都收在 12 栏网格右端），首屏右侧有了一条贯穿的对齐线。 */
.hero-head {
  grid-column: 1 / 13;
  display: grid;
  grid-template-columns: 1fr auto;
  column-gap: var(--col-gap);
  align-items: start;
  margin-bottom: 56px;
}

.hero-meta {
  display: flex;
  align-items: center;
  gap: 10px;
  /* ★ 2026-10-12：状态标签改**向上对齐**（用户原话"这个应该改向上对齐，
     css 修改"），跟第一行"系列名"的顶边齐平。原来垫 28px 是把它压在那一
     行的中线上（旧口径："贴上边缘会显得浮在标题上空"），这次按用户要求
     改成贴顶，那 28px 连同它的换算说明一起去掉了。 */
}

/* ⚠️ 2026-10-10：「点」全部改成**方块**了（用户要求"网站里所有的圆形点，
   全部改成方形"）。前台一共就三个 9px 的小点——这个 .live-dot、
   .live-row-dot、.status-dot——三个都是 border-radius: 0 的直角方块，
   一并改的。**只改其中一两个会让版式看起来不一致，动之前先搜一遍
   `border-radius: 0`。** 呼吸动画照旧（transform: scale，方块缩放还是
   方块，不用动）。 */
.live-dot {
  width: 9px;
  height: 9px;
  background: var(--c-red);
  border-radius: 0;
  display: inline-block;
  animation: pulse-dot 1.4s ease-in-out infinite;
}

/* 非"进行中"的赛事由 js 给这个点加上 hidden，但上面的 display:
   inline-block 是作者样式，会盖掉浏览器默认的 [hidden] { display:
   none }，导致红方块照常显示——这里补一条把它压回去。 */
.live-dot[hidden] { display: none; }

@keyframes pulse-dot {
  0%, 100% { opacity: 1; transform: scale(1); }
  50% { opacity: 0.35; transform: scale(0.8); }
}

.hero-label {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 13px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--c-red);
}

.hero-label--grey { color: var(--c-grey); }

/* 字号原来是 clamp(64px, 10.5vw, 148px)——vw 是"随浏览器窗口实时
   重算"的单位，跟 html/body 已经锁死的 min-width:1440px 这套"不响应式、
   宁可裁剪也不挤压变形"的设计取向直接冲突：窗口拖窄时标题字号会跟着
   vw 一路缩水（首页首屏赛事名、赛事详情页标题都是这个类），版式的其它
   部分（12 列网格、头像、统计横幅）已经因为 min-width 锁死不再跟着变，
   只有它还在"响应式"，看起来就是"名字自己缩小了"。改成固定 px——按
   全站统一的参照宽度 1440px 算一遍 10.5vw = 151.2px，被上限 148px
   封顶，直接写 148px，不再有 vw。 */
.hero-title {
  font-family: var(--font-display);
  font-weight: 900;
  line-height: 0.88;
  letter-spacing: -0.02em;
  font-size: 148px;
  text-transform: uppercase;
}

.hero-title-line { display: block; }
.hero-title-accent { color: var(--c-red); }

/* 第一行是系列/主办方标识（IEM、ESL、校内赛……），只是个分类标签，不该
   跟第二行赛事主体、第三行年份抢同样的巨型字号——但降级幅度也不能太狠，
   否则跟后面两行巨型字号断层、显得像孤立的小标签而不是"同一套标题体系
   里降一级"。按主标题字号的一半左右取值，字重、字距跟大标题保持一致。

   这一行现在跟评级角标（#detail-tier）并排，所以外层多包了一层
   .hero-title-top（flex 行，扛下原来 :first-child 上的 margin-bottom，
   把系列标识行和评级角标放在同一个 flex 行里）——用 .hero-title-line--badge
   这个显式类选中系列标识行，不再依赖 :first-child（包一层 wrapper 后
   它已经不是 .hero-title 的第一个子元素了，结构性选择器会失效）。
   align-items 用 center：评级角标现在是 .tier-badge--hero，尺寸跟着
   系列标识文字的字号一起等比取值（约文字字高的 70%），两者比例已经
   匹配，居中对齐看起来是正常的"文字旁边并排一个印章"，不会再有比例
   失调的问题。两者原来都是 vw 响应式 clamp()，跟全站已经锁死的
   min-width:1440px 不响应式取向冲突（窗口拖窄时这行字会跟着缩小），
   现在按参照宽度 1440px 统一算成固定 px：5.2vw = 74.88px 被上限
   74px 封顶，直接写 74px；.tier-badge--hero 那两个尺寸同理见下方
   注释。 */
.hero-title-top {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 8px;
}

.hero-title-line--badge {
  font-size: 74px;
  letter-spacing: -0.01em;
  line-height: 1;
}

/* ============ TIER BADGE（赛事评级角标，S/A/B/C/D）——跟 .event-badge
   （IEM/校内赛这种系列标识）视觉语言一致：黑底白字、字母居中的正方形，
   形状上就能跟装文字缩写的 .event-badge 胶囊区分开，不用再靠文字后缀
   （"S级"这种）说明。五档评级不做颜色区分——S 级不特殊标红，评级本身
   不是"需要被优先看到"的紧急信息，不占用红色这个唯一强调色。

   默认尺寸给 event.html 卡片列表用，跟旁边的 .event-badge 并排展示——
   两个黑底方块必须严格等高。中间踩了两次坑：先是直接写死 20px，跟
   .event-badge 实际渲染出来的高度（11px 字号 + 4px 上下内边距 + body
   继承的 1.4 行高 ≈ 23px）对不上；改成用 `aspect-ratio: 1` 让宽度
   跟着高度自动算，结果在 .event-card-badges 这个横向 flex 容器里，
   aspect-ratio 对 flex 子项的主轴（宽度）不生效——量了实际渲染像素，
   高度确实撑到了 23px，宽度却只跟着"S"这一个字符的内容宽度走，缩成了
   一个 8px 宽的窄条，完全不是正方形。索性不再依赖任何"自动算"的机制，
   宽高都写死成实测出来的 23px（直接量 .event-badge 实际渲染像素得出
   的数字，不是理论计算值），简单、不依赖 flex/aspect-ratio 的隐藏
   交互问题，缺点是以后 .event-badge 的字号/内边距如果改了，这个数字
   要跟着手动改一遍，但眼下这是能让两个黑框稳定等高的最简单做法。

   Hero（index.html 首屏）不能用卡片列表这个尺寸——首屏
   系列标识（"校内赛"）字号是 74px 的大字，固定 23px 摆在旁边太小、不成
   比例，所以另开 .tier-badge--hero。方块边长和字号原来各自单独 clamp，
   调过两轮都不理想：边长顶到标识字号 70% 时方块比文字本身还显眼，喧宾
   夺主；跟着一起把字号也缩小到 40% 时，字母本身又小得看不清，反而更像
   "变小了"而不是"更精致了"。问题出在把"缩小外框"和"缩小文字"当成
   同一件事一起改——现在拆开处理：字号维持在原来那档看得清楚的大小
   （原来是 `clamp(14px, 2.2vw, 30px)`）不变，只收紧外面黑色方块的边长
   （原来是 `clamp(22px, 2.6vw, 38px)`，比字号大 6-8px，刚好包住字母、
   不留大片多余留白），观感是"给字母紧贴着描了个方框"，不是"字母和方框
   一起缩水"。这两个 clamp() 后来统一改成了固定 px（跟 .hero-title/
   .hero-title-line--badge 同一次改动，理由见那边注释——vw 会随窗口
   实时重算，跟全站锁死的 min-width:1440px 不响应式取向冲突），按参照
   宽度 1440px 算：边长 2.6vw = 37.44px（没到 38px 上限，就是这个数，
   不额外四舍五入去凑整数）、字号 2.2vw = 31.68px 被上限 30px 封顶。 */
.tier-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 23px;
  height: 23px;
  flex-shrink: 0;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 11px;
  /* flex 居中是按字符的"排印前进宽度"（advance width）算的正中间，不是
     按字形本身的墨迹轮廓——像 A 这种带尖顶的字母，墨迹重心天然偏离
     前进宽度的几何中心，实测截图量出来大概偏右 2px（38px 的方框里，
     字形左边留白 10px、右边只留白 6px）。加一点不对称的右内边距把它
     视觉上"顶"回正中间，用 em 而不是固定 px，字号跟着 .tier-badge--hero
     的字号变化时这个补偿量也跟着等比例缩放（em 本身就是相对当前元素
     字号算的，不需要字号是 vw 才能联动，固定 px 字号一样适用）。 */
  padding-right: 0.08em;
  color: #fff;
  background: var(--c-ink);
  border-radius: 2px;
}

.tier-badge--hero {
  width: 37.44px;
  height: 37.44px;
  font-size: 30px;
}

/* .tier-badge 声明了 display:inline-flex，会盖掉浏览器内置的
   [hidden]{display:none}——跟全站 .live-dot[hidden]/.ext-link[hidden]/
   .filter-bar[hidden] 是同一个坑，#detail-tier 没有 tier 数据时
   renderContextEventHead() 会设 hidden=true，没有这条会变成显示一个
   空黑底方块。 */
.tier-badge[hidden] { display: none; }

.hero-score-row {
  grid-column: 1 / 13;
  /* .match-head-status（比赛详情页的状态）的绝对定位基准 */
  position: relative;
}

.hero-score-card {
  display: grid;
  grid-template-columns: 1fr auto auto auto 1fr;
  align-items: center;
  gap: 22px;
  border-top: 1px solid var(--c-line);
  border-bottom: 1px solid var(--c-line);
  padding: 28px 0;
}

.hero-team {
  display: flex;
  align-items: center;
  gap: 16px;
  /* 左队整块**靠右**：这样它末尾的徽标紧贴比分左侧（2026-09-27 用户要求"徽标贴
     在比分左右"，与队名的顺序也一并改成 队名 → 徽标）。右队反过来靠左，见下。 */
  justify-content: flex-end;
}

.hero-team--right {
  justify-content: flex-start;
}

/* 比赛详情页头部那两个徽标比站内默认的 40px 大一号：它跟 64px 的比分同行，
   40px 在旁边显得偏小（2026-09-27 用户："比例要优化，战队logo可以放大一些"）。
   只作用于 .hero-score-card 里的徽标，站内其它 40px 的 .team-badge 不受影响。 */
.hero-score-card .team-badge {
  width: 84px;
  height: 84px;
}

.hero-team-name {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 24px;
  /* ⚠️ 别再加 text-transform: uppercase——队名按录入原样显示（小写就是小写），
     大写只属于官方 UI 文案。2026-10-11 全站的队名/选手名一起摘掉的。 */
}

.hero-score-num {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: 64px;
  line-height: 1;
}

.hero-score-sep {
  font-weight: 300;
  font-size: 40px;
  color: var(--c-grey);
}

/* 比赛详情页头部的状态（进行中 / 已结束 / 开赛时间 + LIVE 呼吸点）：贴在比分区
   **右上角**。2026-09-27 用户："这行不要，进行中保留，进行中放到绿色框的位置"——
   原来它跟赛事名共用一个整行（`.hero-score-sub`）摆在比分区下方，那一行连同赛事名
   一起删了（赛事名在下面信息卡的 EVENT 格里已有），状态改成绝对定位挂在这里。 */
.match-head-status {
  position: absolute;
  top: 28px;                    /* 与 .hero-score-card 的 padding-top 对齐 */
  right: 0;
  display: inline-flex;
  align-items: center;
  gap: 8px;                     /* 跟 .hero-meta（标题旁的状态标签）同一个值 */
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 13px;
  letter-spacing: 0.06em;
  color: var(--c-grey);
}

.match-head-status--live { color: var(--c-red); }

/* 删掉 `.hero-score-sub` 那一行之后，比分区与下面信息卡之间原本留了一段 34px 空档，
   两条边框（比分卡底边 + 信息卡顶边）夹着它——用户看过后："内容没了，但是横线还在"。
   所以空档取消、两卡贴紧，两条边只留比分卡那条当分隔（否则会叠成 2px 粗线）。
   两条规则都只命中 match-detail（另外两个用 .hero-score-row 的页面各只有一个该行）。 */
.hero-score-row + .hero-score-row .hero-info-card {
  border-top: 0;
}

.hero-info-card {
  /* 这条 grid-column 只有在 .hero-info-card 是 .grid-container 的"直接
     子元素"时才会生效——index.html/match-detail.html
     里它嵌在 .hero-score-row 内层（.hero-score-row 自己不是 grid
     容器），不受影响。team-detail.html/player-detail.html 身份区下方的
     统计区**都不再用这个类**（原来用过，后来换成 .stat-panel，2026-09 又
     统一成 .stat-table 报表格子——那两个页面读的是同一份数据表结构，
     见 .stat-table 那段注释）。 */
  grid-column: 1 / 13;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  border-top: 1px solid var(--c-line);
  border-bottom: 1px solid var(--c-line);
  padding: 28px 0;
}

.hero-info-item {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 0 28px;
}

.hero-info-item:first-child {
  padding-left: 0;
}

.hero-info-item:not(:first-child) {
  border-left: 1px solid var(--c-hair);
}

.hero-info-label {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--c-grey);
}

.hero-info-value {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 24px;
  line-height: 1.25;
  text-transform: uppercase;
  color: var(--c-ink);
}

/* ============ TEAM BADGES（徽标图片容器） ============
   之前这里是纯 CSS 拼出来的文字方块（黑底白字 + .team-badge--xxx 按队伍
   定制颜色），现在徽标改成真正的图片（<img class="team-badge">，src 来自
   data/teams.js 的 logoUrl 或后台上传裁剪后的 /uploads/xxx.png），颜色
   由图片本身决定，不再需要按队伍生成的颜色规则。

   **2026-09 起全站徽标一律四边描边**（`1px` 墨黑），跟头像同一条规矩。
   原先的取舍是"头像是头像、徽标是徽标"——描边只给头像，所以选手列表那列
   单独用 `.rank-list[data-source="players"]` 圈了一下，战队列表/对阵图/卡片
   里的徽标保持无框。用户看到战队列表那一列没有框，要求全站徽标都带上，
   于是并进这条基础规则，那条只圈选手列表的规则就多余、删掉了。

   尺寸不受影响：box-sizing 是全局 border-box，40px（小号 24px）的总宽高
   不变，只是图片可见区各少 1px，没有任何版式位移。 */
.team-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  object-fit: cover;
  border-radius: 2px;
  border: 1px solid var(--c-ink);
  flex-shrink: 0;
}

/* ============ SECTIONS (shared) ============ */
/* 块与块之间的留白——原来是 96px，跟发丝线一起在每个 section 边界叠成
   接近 192px 的空白（本 section 的底 padding + 下一个 section 的顶
   padding），页面滚动起来大段留白显得很空。先收紧到 64px，2026-09-28
   用户要求再收到 **32px**（"每个板块的上 padding 和下 padding，改成 32px"），
   边界处现在是 64px。
   标题那条线也已经从"区块底部的区域分割线"挪到了各板块抬头的框上（见
   .bracket-demo-head），所以这 64px 是一整段不带线的空白，不再被线劈开。
   这条规则是全站所有 section 边界共用的唯一出处，改这一处即可影响所有页面。 */
.section {
  padding: 32px var(--edge);
  border-bottom: 1px solid var(--c-line);
  position: relative;
}

.section .grid-container { padding: 0; max-width: 1344px; }

/* ============ 一条整屏宽、两条收进 1344 ============
   用户先要求"贯穿全屏的横线都改成 1344"，随后补一句"顶栏和底栏的横线可以无限宽"
   ——所以：**页头底边、页脚上方那条保持整屏宽**，只有**内文（区块之间）**的线
   收进 1344 宽的内容区。

   页脚自己原本没有边框（"底栏那条线"其实是最后一个区块的底边），所以这里给它补
   一条整屏宽的 border-top；同时把最后一个区块的线去掉，否则两条会叠在一起。 */
.site-footer {
  border-top: 1px solid var(--c-line);
}

/* 内文：原来是 border 直接挂在 .hero / .section 上（元素本身整屏宽），所以线顶到
   屏幕两端；改成**居中、固定 1344 宽**的伪元素，不动各页面的 DOM。1344 =
   .grid-container 的 max-width 1440 减去两侧各 48px 的 --edge，与内文内容框左右
   严格对齐。`.site-header` 不在这条里——它保留自己的 border-bottom（整屏宽）。 */
.hero,
.section {
  border-bottom: 0;
}

.hero::after,
.section::after {
  content: "";
  position: absolute;
  bottom: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 1344px;
  height: 1px;
  background: var(--c-line);
  pointer-events: none;
}

/* 主内容里**最后一个还可见的区块**不画自己的线——那一段交给页脚上面那条整屏宽的线，
   否则两条会在页脚上方挨着叠成一段"中间粗"的线。空区块是用 hidden 属性藏的
   （见 js/script.js 的 `section.hidden = true`），所以判据写成"后面还有没有可见的
   兄弟元素"：没有的那个就是最后一个可见区块。页脚是 <main> 的兄弟、不在 main 里，
   因此必须限定 `main >`。 */
main > .hero:not(:has(~ :not([hidden])))::after,
main > .section:not(:has(~ :not([hidden])))::after {
  display: none;
}

/* 首页 + match-detail / player-detail / team-detail **整体不再画区域分割线**
   ——2026-09-28 用户："红色的横线改成放到绿线的位置，不再需要区域分割线，
   而是改成标题的上方和下方都有横线。目的是减少线条带来的多余空白行距"。
   线挪到了各板块标题的上下两侧（见上方
   .bracket-demo-head 的 border-top/bottom），区块之间只剩一段完整留白。

   判据原来是"这个页面有没有面板抬头"，靠一个 :has() 认，不用给每个 HTML 各加
   一个 body class：两套页面不混用——首页 + 四个详情页用 .bracket-demo-head，
   列表页（event / schedule / teams / players）用 .section-head、一个都没有。
   ⚠️ 2026-10-01：首页那三条抬头（对阵图 / 参赛队伍 / 奖金分配）撤掉了，这条
   代理判据对首页不再成立——但结论不该跟着变（抬头都撤了、线更没理由回来），
   所以首页改成显式认 <body class="page-home">。
   ⚠️ **2026-10-02：选手 / 战队两个详情页也踩了同一个坑**——用户要求去掉
   「比赛数据」「比赛记录」「所获荣誉」那四个抬头框，抬头一没，:has() 就失配，
   分割线整片回来了。同样改成显式认 <body class="page-detail">（这两页的抬头
   是被**用户撤掉的**，结论当然更不该跟着变）。
   ⚠️ **2026-10-11：match-detail.html 第三次踩同一个坑**——「历史交锋」也撤了
   （比赛成绩、对位两块更早些时候已撤），这一页的面板抬头**一条都不剩**，
   :has() 自然失配。同样补上 <body class="page-detail">，三个详情页从此走
   同一条显式判据；下面那条 :has() 现在只剩首页面的赛事卡在用（如果哪天也没
   人用了，可以直接删）。
   ⚠️ **列表页那套原样不动**：它们的标题是跟内容并排的左侧目录（1/4 列），
   不是独占一整行的抬头，"上下各一条线"在那里立不住（下面那条 1344 的线会
   横切过右边的列表行）；要改得先动列表页的版面结构，那是另一件事。
   ⚠️ **身份区那几块（选手/战队/比赛详情页的第一个 section）自己不带抬头，
   但它们的区域分割线也一起去掉**——留着的话会在下一个板块标题线的正上方
   64px 处多出一条平行线，并排看就是"没改完"。 */
body.page-home .hero::after,
body.page-home .section::after,
body.page-detail .hero::after,
body.page-detail .section::after,
body:has(.bracket-demo-head) .hero::after,
body:has(.bracket-demo-head) .section::after {
  display: none;
}

.section-head {
  grid-column: 1 / 4;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.section-index {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 14px;
  color: var(--c-red);
  letter-spacing: 0.05em;
}

.section-title {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: 40px;
  line-height: 1.02;
  text-transform: uppercase;
  letter-spacing: -0.01em;
}

.section-title-en {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.1em;
  color: var(--c-grey);
  text-transform: uppercase;
}

/* ============ MATCH LIST（进行中/未开始/已结束三种状态统一同一套
   版式——原本 live-row/match-row/result-row 三套长得完全不同的模板，
   现在合并成一套 .match-row：序号/呼吸点 + 双方队名 + 比分 + BOx 标签
   + 时间 + 赛事名，三种状态只是往同样的槽位里填不同字段（见
   js/script.js 的 matchRowsHtml）。.live-list/.match-list/.result-list
   三个容器 class 还留着（schedule.html 三个板块分别按这三个 class 查询
   容器），但渲染出来的行现在统一是 .match-row，不再各有各的行 class。
   ============ */
.live-list,
.match-list,
.result-list {
  grid-column: 4 / 13;
  display: flex;
  flex-direction: column;
}

/* 五格：双方队名（**比分夹在两个队名中间**）/ BOx / 时间 / 阶段 / 赛事名。
   **阶段那一列三处消费方一律都有**（schedule.html 三个板块、team-detail.html
   近期赛程/历史战绩、match-detail.html 历史交锋），不按页面删减——这套行本来
   就是同一个组件，见 js/script.js 的 matchRowsHtml 注释。

   ★ 2026-10-11：比分原来是**单独一格**（队名 1fr ｜ 比分 76px ｜ …），
   队名那格里面夹一个 "vs"。用户要求去掉 VS、把比分直接放进两个队名之间
   （原话："这样能省空间"）——比分那一整列连同间距一共 92px 全省下来，
   全部还给队名列（1fr 吃余量）。

   列宽怎么分（★ 2026-10-11 重算过一轮，**数字都是实测的，别凭感觉改**）：
     时间 122  ← 装 "2026.10.10 20:00" 只要 114.4px，余 7.6px。这串**长度是固定的**
                 （永远是 YYYY.MM.DD HH:mm 18 个字符），所以这点余量够用、不会飘
     阶段  80  ← 装 "1/4 决赛" 47.7px、"季军赛" 39px；**最长的是双败赛制的
                 "胜者组半决赛 / 败者组半决赛"，实测 78px**——原来给 70px 是装的
                 不下的，打到半决赛就会截（2026-10-11 量出来，同日补到 80）
     赛事名 142 ← 线上最长 "IEM Katowice 2026 S1" 实测 134.8px，余 7.2px
   剩下全给队名列（1fr）。队名是 21px 大字，是整行最该读的内容——**再挤它就没有
   余量了**。线上队名最多 6 个汉字（实测最长的「萌新银河战舰」= 126px），
   赛程页单侧约 147px、余 20px 出头。

   ⚠️ 2026-10-11 这一轮为什么匀：比赛行加了 40×40 的队徽，每个半边（队徽 + 10px
   间距）吃掉 50px，赛程页的队名列从 145px 掉到 129px——只剩 3px 余量，稍有更长的
   队名就截。所以从"时间"（当时 160px，富余 45px）匀了 30 给队名；同日修阶段列时
   又"时间 −8、赛事名 −2、阶段 +10"，这一档总量不变、队名列不受影响。 */
.match-row {
  display: grid;
  grid-template-columns: 1fr 90px 122px 80px 142px;
  align-items: center;
  /* 行高**写死 61.6px**（★ 2026-10-11 用户给定），左右内边距保留、上下给 0
     ——上下那点空间由 `align-items: center` 把每一格居中腾出来，不靠 padding 凑。
     为什么不用 padding 反推：这台机器的显示缩放是 125%，1px 的发丝边框实际
     渲染成 0.8px，用 2+2×9.8+40 去凑，量出来是 61.2 而不是 61.6——写死高度
     才不受这种设备像素取整的影响。
     ⚠️ **全行最高的东西是那对 40×40 的队徽**。改队徽尺寸（或往行里塞更高的
     东西）**要连着这个数一起改**，否则内容会溢出这 61.6px 的框。 */
  height: 61.6px;
  padding: 0 16px;
  border: 1px solid var(--c-line);
  gap: 16px;
  position: relative;
}

/* 上下两条边框原来只画在行与行之间共用的那一条线上（.match-row 自己
   只有 border-bottom，第一行额外补一条 border-top）；现在每一行都是
   四边独立描边的完整方框，相邻两行的边框会紧贴在一起变成视觉上的 2px——
   用经典的表格 border-collapse 负 margin 手法把它们叠成一条线，跟
   之前"只有一条线"的粗细保持一致，不会因为改成方框就平白多出一倍粗的
   分隔线。 */
.match-row + .match-row { margin-top: -1px; }

/* （曾经有过一条 .match-row--stage：阶段列只在 schedule.html 出现、靠这个修饰类
   换一套列定义。2026-09-27 用户要求复用组件三处同步，阶段列成了所有 .match-row
   都有的固定一格，修饰类和那一整套开关一并收掉了。） */

/* 阶段那一格跟"赛事名"同一种灰字/13px——两格都是"这场比赛属于哪儿"的元信息，
   并排看是一套；比它左边的比分/时间（墨黑、更大）轻一档，不抢阅读主线。 */
.match-col--stage {
  font-size: 13px;
  color: var(--c-grey);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* 行首原来有一格 .match-col--num（未开始/已结束的 01/02/03 + 进行中的呼吸点），
   2026-09-27 整格撤掉，规则一并删除——见 js/script.js 的 matchRowsHtml 注释。 */

/* 全站"LIVE = 同一个呼吸方块"的统一符号，跟 Hero / event.html 卡片（状态胶囊里的
   那个）共用同一套关键帧。**2026-09-27 起比赛行里不再用它**——行首那一格
   （序号/呼吸点）整个撤掉了，理由见 js/script.js 的 matchRowsHtml 注释。
   这里保留是因为 Hero 和赛事卡还在用。
   **2026-10-10 起是方块不是圆点**——见 .live-dot 上面那条说明。 */
.live-row-dot {
  display: inline-block;
  width: 9px;
  height: 9px;
  background: var(--c-red);
  border-radius: 0;
  animation: pulse-dot 1.4s ease-in-out infinite;
}

.match-col--teams {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 8px;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 21px;
  /* 里面装的是队名——不加 text-transform，见 .hero-team-name 那条注释 */
}

/* 队名 + 队徽那半边（★ 2026-10-11 加的队徽，三个页面都走这一处）。
   **队徽贴比分那一侧、队名在外**，两队镜像对称——跟比赛详情页头部那对比分区
   （.hero-team / .hero-team--right）同一条规矩。
   队名是可伸缩的那个（flex + min-width:0 + 省略号），队徽不缩
   （.team-badge 自带 flex-shrink: 0）。 */
.match-team-side {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
}

.match-team-side--a { justify-content: flex-end; }
.match-team-side--b { justify-content: flex-start; }

.match-team {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  /* 省略号要生效得让它缩得动：flex 子项的自动最小尺寸默认是内容宽度，按掉 */
  min-width: 0;
}

/* ⚠️ 2026-10-11 起**比赛行里不再有 "vs"**（比分顶掉了它，见上面 .match-row 的
   注释），但这条规则别删：admin.html 也引这一份 style.css，后台对阵图那棵树里的
   `.tree-match-teams .vs`（见 css/admin.css）只补了颜色 / 字重 / 间距，字号和
   lowercase 是从这里继承的。 */
.vs {
  color: var(--c-grey);
  font-weight: 500;
  font-size: 14px;
  margin: 0 8px;
  text-transform: lowercase;
}

/* 比分槽位——**夹在两个队名中间的那一格**（.match-col--teams 的 `1fr auto 1fr`
   里那个 auto）。三种状态共用同一套样式，只是数字不同：进行中与已结束读的都是
   **系列赛比分**（赢了几张图，见 js/script.js 的 matchRowsHtml），胜方那一侧用
   红色强调（"红色=比分获胜方"的全站规则，见设计理念文档），**进行中不标红**
   ——系列赛还没打完，"谁赢了"此时不成立；未开始的比赛显示占位的 "0 : 0"，
   不额外弱化颜色。
   ⚠️ 这个类原来叫 `.match-col--score`，那时比分自己占一整格；2026-10-11 搬进
   队名格之后 "col" 就不再名实相符，改叫 `-slot`。**这一层必须留着**——它带着
   22px / 900 / Archivo 这套比分专用字号，让 .match-score 裸挂在队名格里会被
   那格的 21px / 700 压下去，比分反而比两边队名还小。 */
.match-score-slot {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: 22px;
  text-align: center;
  /* 跟两边队名拉开一点：`.match-col--teams` 自己的 gap 只有 8px，而比分是两三个
     数字的大字，贴着队名读起来糊成一团（原来 "vs" 也是靠它自己的 margin 撑开）。 */
  margin: 0 10px;
}

.match-score--win { color: var(--c-red); }

.match-score-sep {
  color: var(--c-grey);
  margin: 0 2px;
}

/* 弃权标（.match-forfeit-tag）2026-10-11 撤掉：比赛行上比分后面那个小标，
   用户要求去掉，三处消费方一并去。CSS 规则随之删除——要恢复就把 js/script.js
   的 matchRowsHtml() 里那段标记和这里的样式一起加回来。 */

.match-col--bo {
  font-weight: 700;
  font-size: 13px;
  color: #fff;
  background: var(--c-ink);
  text-align: center;
  padding: 5px 0;
  border-radius: 2px;
}

.match-col--time {
  font-weight: 600;
  font-size: 14px;
}

/* 赛事名那一格。**右边原来还有个 24px 的虚线方框给赛事 logo 占位**
   （"素材还没接进来，先占住位置"）——2026-09 按用户要求去掉，赛事徽标
   一直没落地，留着就是个空框。那一格只剩赛事名一个子元素，所以给
   "名字 ↔ logo" 留的 gap 一并删掉；flex-end 让短赛事名也靠右对齐，
   跟行右边界齐平。 */
.match-col--event {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  font-size: 13px;
  color: var(--c-grey);
  text-align: right;
}

.match-event-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ============ BRACKET SECTION (static demo grid, G-Loot 风格直角连线 + 悬停高亮) ============
   改这里之前先看 js/brackets.js 文件头的「★ 版本标记」——那里列了对阵图散在哪些
   文件、以及改动不该破坏的 5 条基线行为。 */
.section--bracket {
  overflow: hidden;
  position: relative;
}

.wipe-block {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: var(--c-red);
  transform: scaleX(0);
  transform-origin: left;
  z-index: 5;
  pointer-events: none;
  display: flex;
  align-items: center;
  justify-content: center;
  /* 字标保持原尺寸（JS 每帧抵消父级 scaleX），所以色块窄的时候字标会超出块宽，
     靠这里裁回红块内——可见范围永远等于红块本身。 */
  overflow: hidden;
}

/* 直接贴在红块上的白字标：自己没有任何动画，红块扫到哪它到哪。
   尺寸按红块自身的宽高留一成/两成余量、用 preserveAspectRatio 保比例——所以
   字标总是尽量填满红块，但区块变高变矮它都跟着走，也不会撑出色块（字标宽高比
   约 3.59:1，区块高的时候是宽度先到顶、区块矮的时候是高度先到顶）。

   ⚠️ 这东西是 index.html 里一段**内联** svg（class="wipe-gafa-inline"），
   不是 <img>。原来是图片，浏览器要按"显示成多大"另外算一张位图，第一次进站
   赶不完就是糊的；内联之后没有那一步，多大尺寸都现画现清楚。
   所以这里**没有 object-fit**（那是给替换元素用的，内联 svg 用不上），
   比例由 svg 自己的 preserveAspectRatio="xMidYMid meet" 保证；
   也**没有 filter**——原来那条 brightness(0) invert(1) 是把红图翻白的，
   现在 svg 里直接填的就是 #FFFFFF。 */
.wipe-gafa-inline {
  width: 90%;
  height: 76%;
  display: block;
}


.bracket-demo-list {
  grid-column: 1 / 13;
  display: flex;
  flex-direction: column;
  gap: 64px;
}

/* 对阵图的外壳（2026-10-01）。三条板块抬头撤掉后对阵图就没有任何边界了；
   它跟下面「参赛队伍」「奖金分配」不一样——那两块本来就是一行行带 1px 边
   的小卡片拼成的网格，自己就成形，对阵图则是散落的方框 + 连接线，裸着看
   像飘在版心里。所以单独给它补一个 1px 的框。
   内边距 16px 是照「参赛队伍」卡片之间那个间距（.team-lineup-list 的 gap）
   取的，不另起一个数。上下实际留白比 16px 略大，因为 .bracket-tree-wrap
   自己还带着 32px 上 / 8px 下（那 32px 是给树顶那排轮次标签留的位置）。
   ⚠️ 只包对阵图这一块；「参赛队伍」「奖金分配」不加框。
   ⚠️ 这个类只有 index.html 在用（其它详情页只有 .bracket-demo-head）。 */
.bracket-demo {
  border: 1px solid var(--c-line);
  padding: 16px;
}

/* 板块标题行：**只有「标题 ····· 英文」两件东西**，左边不放任何东西。
   2026-09 之前左边是一块黑色方块——先是 01 / 02 / 03 的序号（全站 12 处），
   后来换成显示"赛事类型"的角标（26×26 装不下"职业赛事"四个字，会折成两行，
   看着就是坏的），都删了；中间还试过改放**赛事 Logo**，效果不好也撤了。
   所以 `.bracket-demo-index` / `.bracket-demo-logo` 两条规则现在都不存在。
   英文那侧靠 margin-left: auto 贴右，跟左边有没有东西无关。 */
/* 2026-09-28：改成**四边闭合的方框**（原来只有下面一条、后来只加了个上边）。
   上边那条接的是原本挂在区块底部的"区域分割线"（`.section::after`，同页已
   整条去掉，见下方那条 `body:has(.bracket-demo-head)` 规则）：区块之间的留白
   不再被一条线从中间劈成两段（线两侧各 64px），而成了一整段干净的空白，
   框贴着标题、读作"这块的抬头"，不是"上一块的收尾"。
   padding 上下 14px（标题在两条横线之间居中，不贴着上面那条）、左右 16px
   ——左右那 16px 是照着下面那些整宽行方框（`.match-row` / `.boxscore-row`
   / `.stat-cell` 全是 `padding: … 16px`，盒子左缘都在内容区的 48px 处）取的，
   所以框里的标题跟下面表格里的正文**左对齐在同一条竖线上**，不是随手给个数。
   margin-bottom 2026-09-28 由 32px 收到 **16px**（就是 devtools 里框下面那条
   橙色带）：框已经把抬头和正文分开了，底下再留 32px 显得空。 */
.bracket-demo-head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 14px;
  margin-bottom: 16px;
  padding: 14px 16px;
  border: 1px solid var(--c-line);
}

.bracket-demo-title {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 20px;
  text-transform: uppercase;
}

.bracket-demo-en {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  color: var(--c-grey);
  text-transform: uppercase;
  margin-left: auto;
}

.bracket-tree-wrap {
  overflow-x: auto;
  padding: 32px 0 8px;
  cursor: grab;
  scrollbar-width: none;
  -ms-overflow-style: none;
}

.bracket-tree-wrap::-webkit-scrollbar { display: none; }

.bracket-tree-wrap.is-dragging {
  cursor: grabbing;
  user-select: none;
}

.bracket-tree {
  position: relative;
}

.tree-svg {
  position: absolute;
  top: 0;
  left: 0;
  overflow: visible;
}

/* 连接线 **2px**（2026-09-28 当天试过收到 1px，又加了回来）。
   原因：**SVG 描边不做设备像素吸附**。把这条线按用户屏 DPR 1.25 栅格化、
   竖线落在设备像素 x=295，逐列墨量是

     stroke-width: 2  →  0.25 / 1 / 1 / 0.25   （2 列实心 + 两侧一点虚边）
     stroke-width: 1  →  0.62 / 0.62           （没有任何一列实心、整条发灰）

   1px 那一版线确实细了一半（总墨量 2.5 → 1.25 个设备像素），但因为它发灰、
   而方框是 CSS border（吸附成实心 2 个设备像素），三种元素（外框 / 框内
   分隔线 / 连接线）就凑不成同一档观感了——用户判定"看上去粗细不一致"。
   回到 2px 后三者都在 2 个设备像素上下（连接线 2.5、边缘略柔），视觉一致。
   ⚠️ 想让线更细只能改**颜色**（比方框浅一点），宽度上 1 个设备像素已经是
   这个 DPR 的下限，再往下没有可用的中间档。 */
.tree-line {
  fill: none;
  stroke: var(--c-line);
  stroke-width: 2;
}

.tree-line-highlight {
  fill: none;
  stroke: var(--c-red);
  stroke-width: 2;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.15s ease;
}

.tree-line-highlight.is-team-active {
  opacity: 1;
}

.tree-round-label {
  position: absolute;
  top: -30px;
  width: 208px;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 11px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--c-grey);
}

.match-box {
  position: absolute;
  width: 208px;
  transform: translateY(-50%);
  background: var(--c-bg);
}

.match-row2 {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9px 12px;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 13px;
  position: relative;
  /* 四边 2px；框内那条分隔线由"上行的下 1px + 下行的上 1px"拼成 2px，跟外框
     一样粗——**这三条边（外框三边、分隔线、连接线）必须同一档**，见下面的说明。
     ★ 2026-09-28 试过收细（外框 1px、分隔线单侧 0.5px、连接线 1px），用户
     看后判定不行，理由值得记下：**在 1 个设备像素这个量级上，"数学上相等"
     和"看上去一样粗"是两回事**。实测（用户屏 DPR 1.25）：
       外框 1px        → 1 个设备像素，实心、最硬
       分隔线 0.5+0.5  → 每条 0.5px 都被**逐边**抬到 1 个设备像素 → 合计 2 个，
                          反而比外框粗一倍
       连接线 1px SVG  → 1.25 个设备像素，描边不做设备像素吸附、被抗锯齿摊成
                          两列 62.5% 的墨 → 看上去最柔最浅
     三种元素三种观感，怎么调都凑不齐。回到 2px 这一档之后：外框 2 个设备
     像素、分隔线 1+1 = 2 个、连接线 2px SVG = 2.5 个（边缘略柔），三者一致。
     结论：**别在这个图上做亚像素微调，1px / 2px 两档之外没有可用的中间档**
     （与"亚像素线宽是空操作"那条同源）。 */
  border: 2px solid var(--c-line);
  border-bottom-width: 1px;
  transition: border-color 0.15s ease;
  /* 悬停红底是下面那条 ::after，它靠 z-index:-1 压到文字底下。负 z-index 要
     生效，这一层必须自己建层叠上下文，否则红底会跑到行的背景后面、整块看不见。
     **删不得**（行内没有别的 z-index，加这一条不影响任何东西）。 */
  isolation: isolate;
}

.match-row2:last-child {
  border-top-width: 1px;
  border-bottom-width: 2px;
}

.match-row2[data-team] { cursor: pointer; }

/* 悬停高亮 = 2px 红描边 + **整行叠一层 25% 红**。原来只有描边变色，
   在一张满是方框的图上不够显眼，用户要求"叠红色、让队伍更突出"。
   叠法是一层 25% 红**压在行自己的底色之上**，**不是把底色换掉**——下面压着
   什么颜色都不影响这层红的读法，文字色也不动。25% 是强度旋钮（试过 45%，
   偏浓），嫌浓嫌淡改这一个数即可。

   ⚠️ 这一段原来还写着"胜者行 #404040、败者行白底，各叠 25% 红之后分别是偏暗
   的酒红和浅粉红，两边都读得出谁赢谁输"。**2026-10-01 核过：这个"深 + 浅两行"
   的底色在 CSS 里并没有实现**——`.match-row2` 没有任何 background 规则，
   js/brackets.js 也不产出 `--win` / `--dark` 这两个类，还在描述它的只剩本文件
   下面那条"胜者行 = 75% 黑底"的注释和 js/brackets.js 的"基线行为"第 1 条。
   所以这层红目前实际压在纯白上。
   换成伪元素**不改变**这个现状：background-image 和伪元素都画在
   background-color 之上，压的是同一层。

   ⚠️ **2026-10-01：这层红从 background-image 换成了伪元素。** 原来是直接往
   background-image 上叠一层红渐变，但 background-image 是**不可过渡**的属性
   ——同一秒里描边在 .15s 渐红、连接线在 .15s 淡入，红底却是"啪"地整块冒出来，
   三样东西三套节奏。换成伪元素过渡 opacity 之后，三者回到同一条 .15s。
   覆盖范围也一模一样：伪元素只覆盖内容和内边距，跟 background-clip 的默认
   行为（background 铺到边框下）在视觉上等价——边框是不透明的，本来就盖着。 */
.match-row2::after {
  content: "";
  position: absolute;
  inset: 0;
  background: rgba(255, 0, 0, 0.25);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.15s ease;
  z-index: -1;
}

.match-row2.is-team-active::after { opacity: 1; }

.match-row2.is-team-active {
  border-color: var(--c-red);
}

/* 框内那条分隔线由**两条边拼成**（上行的下 1px + 下行的上 1px），命中其中
   一行时另一行那条边还是黑的，就会"半红半黑"。所以两个方向都要把邻行那条
   贴着的边一起染红——这两条是配套的，删一条就会出现半截黑线。 */
.match-row2.is-team-active + .match-row2 {
  border-top-color: var(--c-red);
}

.match-row2:has(+ .match-row2.is-team-active) {
  border-bottom-color: var(--c-red);
}

/* 胜者行 = **75% 黑底 `#404040` + 90% 白字 `#E6E6E6`**。底色不用中灰、也
   不用纯黑：25% 灰 `#BFBFBF` 太浅（跟白底的败者行分不出来）、100% 纯黑
   `#000000` 太重（赛程图里约一半的行都是胜者行，整张图会变成黑白棋盘）、
   50% 灰 `#808080` 中灰配白字最多只有 3.9:1 发闷。落在 75% 黑之后，
   90% 白字对比度 6.8:1，比纯白柔和、比 75% 白立得住。
   **徽标描边不在这里特殊处理**——就让它走 `.team-badge` 那条基础规则
   （1px `var(--c-ink)`），跟站里其它所有徽标同一套。试过在这一行里把描边
   反白，用户否决了：徽标框是全站复用的一套框架，不该为一个底色开例外。 */
.match-name {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.match-score { font-weight: 900; }

.team-badge--sm {
  width: 24px;
  height: 24px;
}

.bracket-empty-hint {
  font-size: 14px;
  color: var(--c-grey);
  padding: 24px 0;
}

/* ============ TEAM / PLAYER LIST ============ */
.rank-list {
  grid-column: 4 / 13;
}

/* 行首是徽标（40px），**没有序号列**（2026-09 去掉）——这两张表不再做
   排名，序号（01/02/03）失去意义，删掉后整个模板少一列。列宽：
   40（徽标）+ 名字 + 100（rating 值）+ 40（涨跌）。后两格是给还带
   value/trend 字段的数据留的，当前战队/选手数据都没有这两项，实际
   只用得上前两格（见下面 .rank-list--compact）。 */
.rank-row {
  display: grid;
  grid-template-columns: 40px 1fr 100px 40px;
  align-items: center;
  gap: 16px;
  padding: 10px 16px;
  border: 1px solid var(--c-line);
  position: relative;
}

/* 跟 .match-row 同一套处理：四边独立描边后相邻两行会叠出 2px 粗的
   分隔线，用负 margin 把共用边收回成 1px。 */
.rank-row + .rank-row { margin-top: -1px; }

.rank-name {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 20px;
  /* 排行榜里的队名 / 选手名，按录入原样显示 */
}

.rank-points {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: 26px;
  text-align: right;
}

.rank-trend {
  text-align: center;
  font-size: 16px;
  font-weight: 700;
}

.rank-trend--up { color: var(--c-red); }
.rank-trend--down { color: var(--c-ink); opacity: 0.55; }
.rank-trend--flat { color: var(--c-grey); }

/* 无 Rating/趋势字段的列表（目前是选手列表）——去掉那两列后
   姓名列直接撑满剩余空间，不留空白 */
.rank-list--compact .rank-row {
  grid-template-columns: 40px 1fr;
}

/* ============ 战队列表右侧五列 + 表头（2026-09 加） ============
   只有 teams.html 那张列表有这几列。数值由 js/script.js 的 teamsWithStats
   读时算出来（赛事次数 / 赛程次数 / 赛事名次 / 赛程胜率 / RATING 2.0，
   口径见那段注释），渲染时按"这批数据带不带 teamStats"决定要不要出现
   ——选手列表带的是它自己的 playerStats，两套列互不影响。

   列宽怎么来的：`.rank-list` 占 12 栏里的 4~13（约 1003px），行内边距左右各
   16px、6 格之间有 5 道 16px 间隙，固定格是 40（徽标）+ 4×96（前四个统计格）
   + 112（RATING 2.0）= 536，加间隙 80 → 队名列还剩约 355px。队名是
   20px 的 Archivo 900，最长的"广州美术学院"7 个汉字约 140px，够用。
   前四个统计格取**等宽 96px**：表头是 4 个汉字（10px 字号约 40px），值是
   "6.5" / "100%" / "15" 这类短串，96px 两边都留得住呼吸感，也不会像武器表
   那样把几列摊成 250px 一张（那次教训见 `.boxscore-table-head--weapons`）。
   最后一格「RATING 2.0」的表头仍是这几格里最长的（1 个空格 + 9 个拉丁字符），
   单给它 112px——96px 会让表头折成两行、把整行撑高。

   表头跟行共用同一条 grid 模板（下面这个选择器列表），相邻边框照旧用负
   margin 叠成 1px——跟 `.boxscore-table-head` / `.boxscore-row` 完全同一套做法。 */
.rank-list--teamstats .rank-row,
.rank-list--teamstats .rank-head {
  grid-template-columns: 40px 1fr 96px 96px 96px 96px 112px;
}

.rank-head {
  display: grid;
  align-items: center;
  gap: 16px;
  padding: 10px 16px;
  border: 1px solid var(--c-line);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 10px;
  letter-spacing: 0.06em;
  color: var(--c-grey);
  text-align: center;
}

/* 表头到第一行之间也要叠边，不然那一条是 2px、跟行与行之间的 1px 不一致。 */
.rank-head + .rank-row { margin-top: -1px; }

/* "战队"/"选手"与"所属战队"这几个表头标签跟它们下面的值一样靠左，
   其余数字标签居中（跟下面的值对齐）。 */
.rank-head-name,
.rank-head-team { text-align: left; }

/* 各统计格。字号比队名的 20px 小一档，跟 `.rank-points` 那种"主角数字"
   （26px）也拉开距离——这几列是补充信息，主角还是队名。 */
.rank-stat {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 15px;
  text-align: center;
}

/* ============ 选手列表右侧那几列 + 表头（2026-09 加） ============
   只有 players.html 那张列表有这些列。跟战队列表那五列同源，但两头各多一列：
   最前是文字列「所属战队」（左对齐，跟人名同侧），最后是「RATING 2.0」。

   列宽怎么来的：`.rank-list` 占 12 栏里的 4~13（约 1003px），行内边距左右各
   16px、8 格之间有 7 道 16px 间隙，固定格是 40（徽标）+ 150（所属战队）+
   80×4（四个统计格）+ 96（RATING）= 606，加间隙 112 → 人名列还剩约 251px，
   放得下最长的中文人名/队名。
   前四个统计格比战队列表的 96px **收窄到 80px**：表头还是 4 个汉字（10px
   约 40px）、值都是 "5" / "5.50" / "60%" 这类短串，80px 够；腾出来的宽度
   给「所属战队」那一列（队名比数字长得多）。RATING 2.0 的表头是 10 个字符
   （约 64px），所以单独给它 96px。

   五列的值现在**只在队伍有解析产出时**才有（见 js/script.js 的 playersWithStats）：
   解析产出目前只覆盖决赛那一场，所以只有那 10 位选手五列是真值，其余仍显示
   "—"。版式对两种情形都成立——"—"和 "1.20" 都是短串，落在同一格里居中。 */
.rank-list--playerstats .rank-row,
.rank-list--playerstats .rank-head {
  grid-template-columns: 40px 1fr 150px 80px 80px 80px 80px 96px;
}

/* 所属战队：文字列，跟人名列一样**左对齐**（表头同步），字号比 20px 的人名
   小一档、跟右侧那几个数字列同级。一条归属都没有时渲染成"无战队"。 */
.rank-team {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 15px;
}

/* ============ PAGINATION ============ */
.pagination {
  grid-column: 4 / 13;
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-top: 4px;
  padding-top: 12px;
}

.pagination-btn {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 13px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  background: none;
  border: none;
  color: var(--c-ink);
  cursor: pointer;
  padding: 4px 0;
  position: relative;
}

.pagination-btn::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  width: 0%;
  height: 2px;
  background: var(--c-red);
  transition: width 0.25s ease;
}

.pagination-btn:hover:not(:disabled)::after { width: 100%; }

.pagination-btn:disabled {
  color: var(--c-grey);
  opacity: 0.4;
  cursor: default;
}

.pagination-info {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 14px;
  letter-spacing: 0.06em;
}

.pagination-info-current { color: var(--c-red); }

.pagination-info-sep,
.pagination-info-total { color: var(--c-grey); }

/* ============ RESULTS（.result-list 容器规则已合并进上面的
   "MATCH LIST" 那个统一 .match-row 版式块，这里不再重复定义；旧版
   result-row/result-team/result-score/result-colon 那套专属模板已随
   已结束比赛改用统一 .match-row 版式一起下线，不再需要） ============ */

/* ============ EVENTS ============ */
.event-grid {
  grid-column: 4 / 13;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 32px;
}

/* 原来只有顶部一条 3px 描边，卡片左右/底部完全没有边界——行与行之间
   全靠留白分隔，读起来分不清"卡片内部的行距"和"卡片与卡片之间的间距"，
   尤其在整体间距收紧之后更明显。改成四边统一 1px 墨黑描边围出完整方框
   （不再单独加粗顶边，四边粗细一致，观感更轻更克制；颜色仍是墨黑
   var(--c-line) 而不是发丝线的浅灰 var(--c-hair)，卡片边界要清楚可辨，
   不能因为改细了就顺带变淡），卡片边界一眼可辨；hover 时四边统一变红。
   内边距也从"只有顶部留白"改成四边都留白，避免文字贴着新描出来的
   边框。 */
.event-card {
  border: 1px solid var(--c-line);
  padding: 18px 20px 20px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  transition: border-color 0.25s ease;
}

.event-card:hover { border-color: var(--c-red); }

/* 卡片第一行：左边"标识组"（赛事类型胶囊 + 评级角标）、右边状态标签，
   space-between 把两者推到两端。中间一度去掉了状态、也试过改放赛事 Logo，
   都撤回了——状态是卡片上唯一能一眼看出"这场在打 / 没开始 / 打完了"的地方
   （板块标题只在列表页有，卡片本身不带这个信息）。 */
.event-card-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

/* 标识组：赛事类型胶囊 + 评级角标。两者并排算一组，不会被外层拆开。 */
.event-card-badges {
  display: flex;
  align-items: center;
  gap: 8px;
}

.event-badge {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 11px;
  letter-spacing: 0.04em;
  color: #fff;
  background: var(--c-ink);
  padding: 4px 8px;
  border-radius: 2px;
}

/* 卡片右上角的状态标签（`EVENT_STATUS_TEXT`）。**只有中文**（进行中 / 未开始 /
   已结束），不带 LIVE / UPCOMING / FINISHED 的英文尾缀——跟全站统计标签同一
   条规矩，2026-09 改的。 */
.event-status {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--c-grey);
}

/* "进行中"那档多一个红点（`.live-row-dot`，全站同一个呼吸点符号），两截并排
   成一行。**2026-09-27 起这是赛程行之外少数还在用它的地方**——比赛行行首那格
   （序号/呼吸点）整个撤掉了，见 js/script.js 的 matchRowsHtml 注释。 */
.event-status--live {
  color: var(--c-red);
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.event-name {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 21px;
  line-height: 1.2;
  text-transform: uppercase;
  /* 卡片改两列排版后单张卡片宽了不少，标题基本不会再折成两行，
     之前"固定预留两行高度、避免同一排两张卡片的下方内容错位"的做法
     现在反而在几乎所有单行标题的卡片上凭空留出一整行空白——去掉
     预留高度，标题按实际内容多高就多高，万一个别标题仍然很长真的
     折了两行，那一排右边卡片的下方内容跟着往下错位也是可以接受的
     代价（概率很低，不值得为它常态性地留白）。 */
}

/* 日期/地点/奖金原来是三行堆叠的纯说明文字，同灰色同字号，读起来是一
   整块分不清主次的灰字，纵向也占了不少行距。改成跟 .hero-info-card 同一
   套"灰色小字 label + 黑色粗体 value"语言的四列迷你信息条——日期/场地/
   奖金/战队数横排成一行，用发丝竖线分隔，纵向只占一行的高度，比三行
   堆叠紧凑得多；奖金字段原来是 "¥1,000,000 · 24 支战队" 拼在一起的一个
   字符串，这里按 " · " 拆成金额和战队数两列（js/script.js 渲染时拆分，
   数据文件不用改）。 */
.event-meta {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 4px 10px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--c-hair);
}

.event-meta-item {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding-left: 10px;
  border-left: 1px solid var(--c-hair);
  min-width: 0;
}

.event-meta-item:first-child {
  padding-left: 0;
  border-left: none;
}

.event-meta-label {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 9px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--c-grey);
}

.event-meta-value {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 13px;
  line-height: 1.25;
  color: var(--c-ink);
  /* 场地名（VENUE）最长，遇到"Malta（线下总决赛）/ Online 小组赛"这种
     会一路折到三四行，把整条四列信息条越撑越高、跟旁边短场地名的卡片
     高度差一大截。限死最多两行，超出的部分用省略号收掉——日期/奖金/
     战队数这三列本来就不会长到两行，一起套同一条规则不会误伤。 */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  text-overflow: ellipsis;
}

.event-extra {
  padding-top: 2px;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 12px;
  letter-spacing: 0.03em;
  display: flex;
  align-items: center;
  gap: 8px;
}

/* .event-extra 是赛事卡片（event.html）的收尾那一行，按状态填不同内容：
   进行中 → 当前阶段（红字），未开始 → 当前阶段（灰字），已结束 → 战队徽标 +
   冠军名。
   **这三条（--live / --upcoming / --done）都是原本就有的设计**，2026-09 随 `stage`
   字段删除时一并删过 `--live` 和 `--upcoming`，2026-09-27 把阶段改成从对阵图算
   出来（见 js/brackets.js 的 bracketCurrentRound）之后按原样还原——**别自己另
   发明一套**：这一行是纯文案，没有 label，字重/字号就是上面 .event-extra 自己的
   700/12px，两行只靠颜色分（红 = 进行中、灰 = 未开始，跟右上角 .event-status
   同一套读法）。
   ⚠️ **跟原设计有一处偏差：两行都没有前面的圆点**。原设计里进行中是个红色呼吸点、
   未开始是个灰色静态点（`.event-extra-dot`），2026-09-27 用户要求去掉红点——卡片
   右上角的状态胶囊里本来就有一个红点，行内再来一个是重复；未开始那行也就跟着不
   加点了。要恢复圆点就把 `.event-extra-dot`（灰）和 `.live-row-dot` 加回标记里。
   **第四个情形（2026-10-11）**：已结束但**没挂冠军**时退回显示阶段，那行**不加
   任何修饰类**（= 继承 body 的墨黑）——用户要求"已结束的赛事不需要显示灰色"。 */
.event-extra--live {
  color: var(--c-red);
}

.event-extra--upcoming {
  color: var(--c-grey);
}

.event-extra--done {
  color: var(--c-ink);
}

.event-extra--done .team-badge { flex-shrink: 0; }

/* .event-extra 本身不再自带顶部描边（分隔线的职责交给它前面那个元素：
   event.html 卡片里是 .event-meta 新增的发丝线下边框），所以这里不需要再为
   ".hero-score-row 后面紧跟的 .event-extra" 单独去一次顶部描边。 */

/* ============ EVENT DETAIL（match-detail.html 头部） ============
   头部几段（比分区、四格信息条）直接复用 Hero 的
   .hero-score-card / .hero-score-row 系列类（见上方 Hero 相关
   规则），这里只保留头部容器本身的网格定位。
   ⚠️ 这个名字是历史遗留：2026-10-01 首页与赛事详情页合并后，它跟
   "赛事详情"已经没关系了，只剩 match-detail.html 在用；改名要连
   match-detail.html 的 DOM 一起动，收益不大，先留着。 */
.event-detail-head {
  grid-column: 1 / 13;
}

/* ============ FOOTER ============ */
.site-footer {
  padding: 40px var(--edge);
}

.footer-grid {
  align-items: center;
  max-width: 1344px;
}

.footer-logo {
  grid-column: 1 / 3;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 26px;
  letter-spacing: -0.02em;
}

.footer-gafa {
  height: 19px;
  width: auto;
}

.footer-copy {
  grid-column: 3 / 10;
  font-size: 12px;
  color: var(--c-grey);
}

/* 「为爱发电」（finance.html）与「返回顶部 / 返回首页」两个链接。
   2026-09-30：它们原先各自占一格、各自 text-align:right，视觉间距其实是
   "返回"那一格左边的空白——随文字长度在 116~142px 之间浮动，想调只能动
   栅格。改成同一条 flex 行之后，间距就是下面这一个 gap 值。
   用户要求在原间距基础上缩小 75%（≈130px → 32px）。 */
.footer-links {
  grid-column: 10 / 13;
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 32px;
  font-size: 13px;
  font-weight: 600;
}

/* 两个链接同款下划线，读起来是同一个可点物件 */
.footer-donate a,
.footer-back a {
  border-bottom: 1px solid var(--c-line);
  padding-bottom: 2px;
}

/* 备案号（2026-10-15）。工信部要求备案号展示在网站底部，惯例是链到
   beian.miit.gov.cn。与上面两个链接同款下划线，读起来是同一个可点物件。
   它是 .footer-copy 里的第二行——版权独占一行、备案独占一行。 */
.footer-icp {
  border-bottom: 1px solid var(--c-line);
  padding-bottom: 2px;
}

/* ============ CLICKABLE ROW HOVER（比赛详情/战队详情/选手详情三个新页面
   上线后，赛程/战队列表/选手列表/赛事列表的每一行都能点进详情页——补一条
   悬停态让"可点击"更明确。.rank-row/.match-row/.boxscore-row 现在统一是
   四边独立描边的完整方框（相邻行靠负 margin 叠成 1px 共用边），hover 时
   四边一起变红——不再是只变一条底边的"下划线感"。因为用了负 margin 叠
   边框，后一行在 DOM 顺序/层叠上下文里天然压在前一行上面，悬停前一行时
   它那条红色下边会被后一行盖住的黑色上边挡住一半——hover 时提到
   z-index:1，让被悬停的整条方框边框完整浮出来，不会出现"半红半黑"。
   .boxscore-row 原来是"只变底边颜色"的轻量悬停（当时的理由是"跟报表式
   列表更配"），后来发现跟站内其它可点击行的悬停语言不统一，改成同一套
   四边描边 + hover 全红，见下面 BOXSCORE 一节的边框改动说明。 */
.rank-row:hover,
.match-row:hover,
.boxscore-row:hover {
  border-color: var(--c-red);
  z-index: 1;
}

/* ============ BOXSCORE（比赛详情页"比赛成绩"区块，match-detail.html；
   同一套 .boxscore-table-head/.boxscore-row 也被 player-detail.html
   "比赛数据"逐场列表复用） ============ */
.boxscore-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 40px;
}

.boxscore-team {
  display: flex;
  flex-direction: column;
}

/* 表头和下面的 .boxscore-row 原来只有 border-bottom（外加行首补一条
   border-top）画出的发丝线分隔，跟 .rank-row/.match-row 那套"每行都是
   四边独立描边的完整方框"不统一——改成同一套处理：表头和每一行都是
   `border: 1px solid var(--c-line)` 的完整方框，相邻行（含表头到第一
   行）之间用负 margin 把共用边叠回 1px，不会因为改成方框就平白多出
   一倍粗的分隔线。横向也补上 16px 内边距（原来是 0，文字贴着容器左右
   边缘），不然加了左右描边后文字会贴着边框写，跟 .rank-row/.match-row
   的呼吸感不一致。 */
.boxscore-table-head {
  display: grid;
  grid-template-columns: 1fr 84px 52px 52px 72px;
  gap: 8px;
  padding: 10px 16px 8px;
  border: 1px solid var(--c-line);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 10px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--c-grey);
}

/* 表头一律不折行。标签都是短词，折行只会把那一行撑高、跟下面的数据行错位
   ——2026-10-08 把「RATING」写成「RATING 2.0」之后就折过一次（两列原来按
   6 个字符给的宽）。**nowrap 只是兜底，列宽照旧按标签长度单独给**（见下面
   各修饰类的 grid-template-columns），别拿它代替列宽：列太窄时 nowrap 会让
   文字溢出到相邻列去。 */
.boxscore-table-head span { text-align: center; white-space: nowrap; }
.boxscore-table-head span:first-child { text-align: left; }

.boxscore-row {
  display: grid;
  grid-template-columns: 1fr 84px 52px 52px 72px;
  align-items: center;
  gap: 8px;
  padding: 12px 16px;
  border: 1px solid var(--c-line);
  position: relative;
  font-size: 13px;
}

.boxscore-table-head + .boxscore-row,
.boxscore-row + .boxscore-row {
  margin-top: -1px;
}

/* ── 战队页「地图战绩」「选手统计」的列宽 ──────────────────────
   基类那套 `1fr 84px 52px 52px 72px` 是给"比赛 / KDA / ADR / HS% /
   RATING 2.0"五列配的固定模板（末列 2026-10-08 由 56px 放到 72px——标签从
   6 个字符变成 10 个，56px 装不下、表头就折成了两行），这里按各自的列数覆盖。
   **必须放在 .boxscore-row 之后**
   ——同为单类选择器，靠文件顺序决胜。
   地图战绩后四列是首杀后 / 先掉人后回合胜率，中间 2026-10-02 插进 CT / T
   两列胜率（8 列 → 10 列）。这两列跟完美那张表的列序一致，都排在
   「首杀 / 首死」之前——它们都是**回合**级指标，按"阵营 → 首杀"的顺序读。 */
.boxscore-table-head--maps,
.boxscore-row--maps {
  grid-template-columns: 1fr 72px 56px 56px 64px 72px 64px 64px 76px 84px;
}

/* ★ 2026-10-06 末尾补一列「命中率」（最宽 "100%"，64px 够）。 */
.boxscore-table-head--players,
.boxscore-row--players {
  grid-template-columns: 1fr 72px 72px 88px 56px 56px 56px 64px 64px;
}

/* ── 比赛详情页「比赛成绩」那张表的列宽 ──────────────────────────
   2026-09 下旬加了「职位」列；★ 2026-10-11 用户要求把 KDA 那一格拆成
   K / D / A 三列（"把表头拆开，自己负责自己的整数"），顺手把整张表的列宽
   按**实测内容宽度**重算了一遍。

   ⚠️ 这一版是算出来的，不是拍的，改之前先读这段账：
     `.section` 整屏宽（1440px 视口）→ 减左右各 48px → `.grid-container`
     收到 max-width 1344px → `.boxscore-grid` 两列 + 40px 间隔
     → **每支队 652px**，再减左右内边距 16px×2 和上下描边 1.6px
     → **一行的内容盒 = 618.4px**。

     列      内容最宽的实测值              旧列宽  空着   新列宽
     选手    24 头像 + 10 间隔 + 名字      182.4    —     1fr=176.4
             + MVP 标（最长名字 ~80）
     职位    「替补 狙击手」61.1           84      22.9   80
     K/D/A   两位数 15.5（旧的是三数拼成  84      34.5   30 × 3
             一串 "24-14-5" = 49.5）
     ADR     「100.5」35.0                52      17.0   54
     HS%     「100%」35.9                 52      16.1   54
     RATING  表头「RATING」42.3           56      13.7   62
     命中率  「100%」35.9                 60      24.1   54

   两件事：
   ① **KDA 拆成三列不花额外宽度**——旧那一格 84px 里内容只占 49.5px，
      三个 30px 的格子（含两个间隔）= 90 + 12 就是从那 34.5px 空白里出的。
   ② 定宽列旧的空着 131px、而选手列 182.4px 里名字只用得掉 ~130px——
      两头都有余量。这版把定宽列收到"内容 + 15~23px 白边"，多出来的
      12px 让给选手列，整张表**没有加宽**（一行仍是 618.4px）。

   间距也一起收：基类 `gap: 8px` 是给五列用的，这里九列就有 8 个间隔，
   在 `--match` 上覆盖成 6px——8×6 = 48，跟旧的 6×8 正好一样，
   所以"收间距"省下来的宽度其实是用来抵掉多出来的两个间隔的。
   `column-gap` 写在 `gap` 之后、且选择器多一个类，两条都压得住。

   ⚠️ 必须用修饰类、不能改基类那五列——选手页「比赛数据」逐场列表复用同一个
   基类（那边第一格是"对手 + 时间"，没有职位，也不是 KDA 这几列），
   改基类会把它带歪。 */
.boxscore-table-head--match,
.boxscore-row--match {
  column-gap: 6px;
  grid-template-columns: 1fr 80px 30px 30px 30px 54px 54px 62px 54px;
}

/* 职位列表头跟那一格的内容一样**右对齐**（基类的 span 一律居中）。 */
.boxscore-table-head--match span:nth-child(2) { text-align: right; }

/* 职位格：就是一段文字（"狙击手" / "替补 狙击手" / "替补"），跟列表头一样
   右对齐。**替补不做成胶囊、也不换色**——它跟位置是同一类信息（他这场打
   什么），所以同一套样式，只是一个空格隔开（用户 2026-09 定的）。
   颜色 2026-09-27 由灰改成 ink：它跟同一行的 KDA / ADR / HS% 一样是**这场的
   实测值**，不是旁注，灰字会被读成"次要说明"（用户要求"和正常数据一个颜色"）。 */
.boxscore-row--match .boxscore-player-pos {
  text-align: right;
  color: var(--c-ink);
}

/* 选手那一格：名字**按内容宽度**（flex:0 1 auto，而不是基类的 flex:1），
   这样后面紧跟着的 MVP/SVP 标才贴在名字右边。用户 2026-09 定的："它评的是
   这个人，就该跟人待在一起"——所以它跟着名字走，不跟位置文字一起漂。
   名字太长时照旧收缩并出省略号（`min-width:0` + `text-overflow` 在基类里）。 */
.boxscore-row--match .boxscore-player-name {
  flex: 0 1 auto;
}

/* ── 选手页「地图战绩」的列宽（player-detail.html）────────────────
   跟战队页那张「地图战绩」是同一套形式，但看的是"这名选手在这张图上
   打得怎么样"，所以后面接了他自己的成绩列，一共 18 列——比上面两张
   宽得多，固定列收到 44~60px，剩下的都给地图名。
   「加时」（★ 2026-10-04 新增，紧挨「回合」）是个两位数以内的小整数，
   跟「首杀 / 首死」同宽 48px。
   ★ 2026-10-08：「RATING」改叫「RATING 2.0」后 60px 装不下（表头折行），
   放到 72px；地图名那一列（1fr）跟着让出 12px，本来就宽得很。 */
.boxscore-table-head--playermaps,
.boxscore-row--playermaps {
  grid-template-columns: 1fr 56px 44px 44px 56px 56px 48px 72px 44px 44px 44px
                         56px 56px 60px 52px 48px 48px 56px;
}

/* ── 实体页（选手页 / 战队页）的比赛卡片：多一格「状态」（★ 2026-10-11）──
   `.match-row` 基类是五格（队名·比分·队名 / BOx / 时间 / 阶段 / 赛事名）；
   这两个页面的列表是**一张不分板块的混合列表**，所以每行最前面多一格状态。
   赛程页按"进行中 / 未开始 / 已结束"分成三个板块，板块标题已经说了状态，
   **不带这一格**——两版网格都在这儿，别把基类改成六格。

   62px：状态是三个汉字，12px 下约 37px，留了余量。这一格连同间距一共
   78px 是从队名列（1fr）让出来的，队名自己带省略号。
   后面那几列的数值跟基类同一套（★ 2026-10-11 一起重算过，见上面 .match-row），
   **两边改一处就得改另一处**。
   行选择器 / 为什么只有这两个页面这样，见 js/script.js 的 matchRowsHtml()。 */
.match-row--status { grid-template-columns: 62px 1fr 90px 122px 80px 142px; }

/* 状态那一格。三档**只有文字不同**：文字本身已经把状态说完了，再给
   "已结束 / 未开始"各配一个颜色是多余的信息（跟站里"状态只写三个词、不缀
   英文"同一条）。只有"正在打"值得一个强调色——跟比赛详情页头部那颗状态
   同一条规则：一起红。 */
.match-col--status {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 12px;
  letter-spacing: 0.04em;
  white-space: nowrap;
}

.match-col--status-live { color: var(--c-red); }

.boxscore-player {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 14px;
  /* 这一格装的是选手名（不加 text-transform，见 .hero-team-name 那条注释）。
     ⚠️ 但**地图名**用的也是同一个 .boxscore-player-name——那是官方口径、要继续
     大写，所以在下面按行类型补回来了。两个行类型见 js/script.js 的
     boxscore-row--maps / --playermaps。 */
}

.boxscore-player-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.boxscore-row--maps .boxscore-player-name,
.boxscore-row--playermaps .boxscore-player-name {
  text-transform: uppercase;
}

a.boxscore-player:hover .boxscore-player-name { color: var(--c-red); }

/* 战队页「成员」表名字前面那个小头像（2026-09 加）。`.boxscore-player` 本身
   就是 flex + gap:10px，所以直接塞进去就行。24px 会让那一行比原来的 46px
   略高一点——这是要的，头像才看得清；没高到把整张表撑散。没配头像的落兜底
   图，不会出现空 src。
   **四边都描边**（`1px solid --c-ink`），跟身份区那两个 96px 头像
   （.detail-id-roster-avatar / .player-photo）同一套——站里头像一律带框，
   小尺寸的也不能例外，否则彩色占位图边缘糊在表格线上分不清。 */
.boxscore-player-avatar {
  width: 24px;
  height: 24px;
  object-fit: cover;
  border-radius: 2px;
  border: 1px solid var(--c-ink);
  flex-shrink: 0;
  display: block;
}

/* 比赛详情页成绩表里的**职位**格：场上位置（狙击手 / 指挥 / …），这场是替补
   就在前面加「替补」两个字——同一套样式，一个空格隔开。
   2026-09 下旬从"塞在名字那一格里"改成独立一列（见上面 `--match` 的列宽
   说明），所以现在它自己就是一个 grid 格：定宽 84px、内容右对齐。
   字号/颜色故意压得比名字轻：它是补充信息，主角是名字和右边的数据。 */
.boxscore-player-pos {
  flex-shrink: 0;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.05em;
  color: var(--c-grey);
  white-space: nowrap;
}

.boxscore-stat {
  text-align: center;
  font-family: var(--font-display);
  font-weight: 700;
  white-space: nowrap;
}

.boxscore-stat--rating { color: var(--c-ink); }

.mvp-tag {
  display: inline-block;
  flex-shrink: 0;
  padding: 2px 6px;
  background: var(--c-red);
  color: #fff;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 10px;
  letter-spacing: 0.04em;
  border-radius: 2px;
  vertical-align: middle;
}

/* SVP（**输的那一方** rating2 最高的那位）用黑底：红色是"赢家"的强调色，
   败者方跟赢家共用一个色会把"谁赢了"这个信息糊掉。形状与 .mvp-tag 完全
   相同，只换底色——一红一黑，一眼分得清赢家与败方的最佳。 */
.mvp-tag--svp { background: var(--c-ink); }

/* ============ MAP TABS（比赛详情页"比赛成绩"区块的地图 / 综合数据切换，
   match-detail.html；只有传过分图数据的比赛才会多出几档，"综合数据"那一档
   永远在）。★ 2026-10-11 起它跟右边那排 ALL / T / CT 并成一行，位置由外层
   .score-tabs 摆（见上面那段说明）。具体形状见下面紧随的注释。 ============ */
/* ★ 2026-10-11：地图切换（左）和阵营切换 ALL / T / CT（右）**并成一行**——
   左边地图占大部分、右边阵营三个小格占一小部分（用户要求）。两排各管各的
   状态，点击逻辑一个字没动，只是从"一上一下"改成"一左一右"。
   两块之间的间距原来挂在 .map-tabs 自己的 margin-bottom 上，现在挪到这一层
   （留着的话右边那三个小格会被顶上来的间距推得跟地图卡片不对齐）。 */
.score-tabs {
  display: flex;
  /* ★ 2026-10-11：原先是 flex-start（右边那三个小格只有 26px 高、飘在顶部）。
     用户要求"拉高到跟地图卡片等高"，所以改成 stretch——这一行的高度由左边
     地图卡片决定，右边那一块跟着撑满，读作一个竖着的三列块。 */
  align-items: stretch;
  gap: 24px;
  margin-bottom: 32px;
}

/* 地图那一排吃掉剩下的宽度；min-width:0 让里面的卡片在窄屏下还能正常收缩 */
.score-tabs .map-tabs {
  flex: 1 1 auto;
  min-width: 0;
}

/* ★ 2026-10-11 重塑：从"一格一张独立卡片"改成**一整条共边的切换条**。
   三点，改之前先读：
   ① **格子共边**——相邻两格各自带完整 1px 描边，靠 `margin-left: -1px` 把两条
      并成一条（跟全站表格"相邻行 margin-top:-1px"完全同一个手法）。整条读作
      一个连续的切换控件，不再是一排各自为政的卡片。
   ② **选中的那格用红色描边**（用户 2026-10-11 定的：不要墨底填色那一版）。
      ⚠️ **然后就是那个必踩的坑**：共边的两条线里，谁是"上面"那条取决于 DOM
      顺序——后画的盖前面的。选中格如果只把**自己**那条边染红，它跟左邻共用的
      那条线很可能仍是右边那格的墨黑，交界处看起来"没高亮"（用户原话就是
      "描边线和描边线的交界可能会不高亮"）。解法是给选中格 `z-index: 1`，
      让它四条边一起抬到邻居之上，红色把交界处那条线整个盖住。
      `position: relative` 是为了让 z-index 生效，不是拿来定位的。
   ③ **两侧队名不再写进格子里**（模板在 js/script.js 的 scoreHtml）。
      同一场比赛的几张图并排摆着，队名每格重复一遍是纯噪音；谁是 A 谁是 B
      由比赛头部的大标题负责说（那里写着 "X vs Y"）。格子因此也矮了下来
      （79 → 69），右边那排 ALL/T/CT 跟着等高，三格从 21px 缩到 18px（实测
      文字没被顶出）。
   整条仍由外层 .score-tabs 摆在左边、吃掉剩下的宽度（见上面那段说明）。 */
.map-tabs {
  display: flex;
  flex-wrap: nowrap;
  gap: 0;
}

.map-tab {
  flex: 1 1 0;
  min-width: 0;
  position: relative;
  background: var(--c-bg);
  border: 1px solid var(--c-ink);
  cursor: pointer;
  padding: 0 18px;
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  transition: border-color 0.15s ease, background 0.15s ease;
}

/* 相邻两格把共用的那条线叠回 1px（不叠的话交界处是 2px 粗） */
.map-tab + .map-tab { margin-left: -1px; }

.map-tab:hover { background: #fafafa; }

/* 选中：红色描边 + 抬到邻居之上（见上面②那条说明），四边一起亮 */
.map-tab.is-active {
  border-color: var(--c-red);
  z-index: 1;
}

.map-tab.is-active .map-tab-title { color: var(--c-red); }

.map-tab-title {
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 12px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--c-ink);
}

.map-tab-score {
  flex: 0 0 auto;
  display: flex;
  align-items: baseline;
  gap: 6px;
  font-family: var(--font-display);
}

.map-tab-num {
  font-weight: 900;
  font-size: 15px;
  color: var(--c-ink);
}

.map-tab-num--win { color: var(--c-red); }

/* 弃权图的 tab：那一格没有比分可显示，摆一个「弃权」小标顶上。
   这是全站现在**唯一**出现「弃权」标记的地方——比赛行上那个小标
   2026-10-11 已撤掉（见 .match-col--bo 上面那条说明）。 */
.map-tab-forfeit {
  display: inline-block;
  padding: 1px 5px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.06em;
  color: var(--c-red);
  border: 1px solid var(--c-red);
  border-radius: 2px;
}

.map-tab-sep {
  font-weight: 400;
  font-size: 14px;
  color: var(--c-grey);
}

/* ============ SIDE TABS（比赛详情页"比赛成绩"区块的 ALL / T / CT 阵营
   切换，match-detail.html；跟 .map-tabs 是两组独立的 tab——地图/
   综合数据切的是"看哪一份数据源"，这里切的是"数据源里读哪一列"，同时
   存在互不影响）。★ 2026-10-11 起**挪到地图那一排的右侧**、并成一行
   （用户要求"放到地图的右侧，占一个小部分"），位置由外层 .score-tabs 摆，
   所以自己不再留上边距、也不再靠右对齐；随后又按要求**竖着排成 3 行**、
   跟地图卡片等高（外层 align-items: stretch 把这一块撑满，这里三行等分）。
   三格**共边无缝**（用户 2026-10-11 定的，先要过"保留间隙"、看了共边那版又要
   回来）：各带完整 1px 描边，靠 `margin-top: -1px` 把相邻两条叠成一条——跟左边
   地图那排、跟全站表格都是同一个手法。79px 里塞 3 行不用再扣两条缝，每格从
   18px 回到约 21px，按钮的上下内边距仍是 2px（文字本来就是 flex 居中的）。
   ⚠️ 跟地图那排同一个坑：选中格要 `z-index: 1` 把红色描边抬到邻居之上，否则
   跟上一格共用的那条线还是墨黑，交界处看着"没高亮"（见上面 .map-tab 那条说明）。
   沿用同一条"红色描边=选中"规则，做成更轻量的小胶囊按钮。 ============ */
.side-tabs {
  flex: 0 0 auto;
  display: grid;
  grid-template-columns: auto;
  grid-template-rows: repeat(3, minmax(0, 1fr));
  gap: 0;
}

.side-tab {
  /* 按钮自己要撑满格子并居中——.score-tabs 撑高之后它不再靠 padding 决定高度，
     不写这两条文字会贴在格子顶部。 */
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  background: var(--c-bg);
  border: 1px solid var(--c-ink);
  cursor: pointer;
  /* 上下内边距 2px：这一排三行共边叠成一块，每格自然高度约 18px。用户
     2026-10-11 先要"高度加一点"（2px → 4px，每格 18 → 22/23），看过之后又
     要求"高度减少"，改回 2px。**它同时决定整行的高**——外层 .score-tabs 是
     align-items: stretch，地图那排跟着撑到一样高。想再高/再矮就改这一个数
     （3px 是两者之间的中间档，每格约 20px）。
     左右内边距 18px → 12px（用户 2026-10-11 又说"宽度也缩小"）：这一排的
     列宽是 auto，由最宽那格撑开，所以三格一起变窄（约 61 → 49）。再窄就往下
     调这个数，别小于文字宽度（"ALL" 三个字母约 21px + 左右各 4px 才不挤）。 */
  padding: 2px 12px;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--c-ink);
  transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
}

/* 相邻两格把共用的那条线叠回 1px（跟左边地图那排同一个手法） */
.side-tab + .side-tab { margin-top: -1px; }

.side-tab:hover { background: #fafafa; }

/* 选中：红色描边 + 抬到上一格之上，否则跟它共用的那条线还是墨黑（见上面
   .side-tabs 那条说明）。红色描边也是这一排从 2026-10-06 起就在用的规则。 */
.side-tab.is-active {
  border-color: var(--c-red);
  color: var(--c-red);
  z-index: 1;
}

/* ============ PANEL（比赛/战队/选手详情页专用——跟 schedule.html 等
   列表页的"01/02/03 红色序号 + 大标题 + 左侧目录栏"完全不同的一套外壳。
   详情页的身份区（战队/选手/对阵头部）不带任何标签、直接进正文；往下
   每一块内容复用 .bracket-demo-head 那套"黑色方块序号 + 粗体小标题 +
   灰色英文小字 + 底部发丝线"的轻量面板抬头（本来是给晋级之路的 5 组
   Demo 用的，视觉上跟 .section-head 判若两物，正好拿来给详情页当分区
   标签），面板内部再按内容类型各写一套不同的版式（卡片格子/贴纸条/
   报表），不会出现"每一块看起来都是同一个列表模板"的重复感。.panel
   占满整行（1/13）——不再需要 .section-head 那样让出左侧 1/4 目录栏。 */
.panel {
  grid-column: 1 / 13;
}

.panel-subhead {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 12px;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--c-grey);
  margin: 32px 0 14px;
}

/* 紧跟在面板抬头（.bracket-demo-head，自带 margin-bottom:32px）后面的
   第一个小标签不需要再叠一次 32px 上边距，否则跟标题的间距会比其它
   小标签之间的间距明显宽出一截。 */
.bracket-demo-head + .panel-subhead { margin-top: 0; }

/* ============ TROPHIES（战队详情页"所获荣誉"，team-detail.html）
   ——横向贴纸条，故意不用列表行，跟下面阵容格子/比赛列表都不一样。 ============ */
.trophy-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 1px;
  background: var(--c-line);
}

/* 一条奖杯都没有时（js/script.js 的 renderTrophyStrip 打这个类）——
   上面那个 background 是当"贴纸之间的 1px 缝"用的，空态下里面只剩一句
   灰字，留着会画成一整条黑板、字几乎看不见。2026-10-02 去掉板块抬头
   （「所获荣誉」那个框）之后更明显，所以摘掉底色。 */
.trophy-strip--empty { background: transparent; }

.trophy-chip {
  flex: 1 1 240px;
  background: var(--c-bg);
  padding: 22px 20px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  transition: background 0.2s ease;
}

a.trophy-chip:hover { background: #fafafa; }
a.trophy-chip:hover .trophy-chip-name { color: var(--c-red); }

.trophy-chip-top {
  display: flex;
  align-items: center;
  gap: 10px;
}

.trophy-chip-year {
  margin-left: auto;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 12px;
  color: var(--c-grey);
}

.trophy-chip-name {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 16px;
  line-height: 1.3;
  text-transform: uppercase;
}

.trophy-chip-meta {
  font-size: 12px;
  color: var(--c-grey);
}

/* ============ PRIZE LIST（index.html 对阵图下方的"奖金分配"区块）——
   名次分档结构来自 js/brackets.js 的
   window.BRACKET_PLACEMENTS，行数随赛制变化（2~8 档不等）。原先是
   "每行 1px 完整描边、相邻行用负 margin 叠成共用边"的单列列表，改成
   多列卡片网格——布局跟 .event-grid 同一套做法：容器
   display:grid + grid-template-columns: repeat(4, 1fr) + gap 留白，
   每张卡片（.prize-row）保留原来四边 1px 完整描边，卡片之间靠 gap
   分隔，不再需要负 margin 叠边（叠边是单列纵向列表专属的技巧，放进
   网格里相邻行会互相盖住，必须去掉）。

   **列数 2026-09 从 3 改成 4（用户要求"四列 n 行"）**：五种赛制的行数是
   2 / 4 / 4 / 8 / 8，4 列之下 4 行和 8 行那四种刚好铺满（4×1、4×2），
   只有 2 队赛制那 2 行会空两格——但 2 条记录在 3 列下同样要空一格，
   换 4 列没有变差。3 列时 8 行那份是"3+3+2"，最后一行永远缺一格，最显眼。
   并列名次（如 5-8TH）仍按站内约定的并列队数逐个占一格、不合并；
   冠军(1st)不再单独标红，跟其它档位统一用同一套灰色 label + 黑色 value，
   避免红色被这里的"数据展示"抢戏——红色继续只留给比分胜负、真正的赛果信息。

   **2026-09-29 每张卡片多了一行队名**（.prize-row-team）：名次和队伍现在由
   js/brackets.js 的 placementRoster 一起算出来（后台那个奖金编辑器调的是
   同一个函数），金额才是后台手填的。卡片内宽只有 327px，横着塞"标签 + 队名
   + 金额"三列会挤——队名长短不可控（上一版注释里算的 179px 是按纯 label +
   金额算的，再塞一列就不成立了），所以改成**上下两行**：第一行"名次标签 …
   金额"左右对齐，第二行队名整行铺开、超长省略。队名还没算出来时（赛程没
   排到队 / 那场没分出胜负）显示"待定"，用灰字弱化。 ============ */
.prize-list {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 12px;
}

.prize-row {
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-areas:
    "label value"
    "team  team";
  align-items: baseline;
  gap: 2px 16px;
  padding: 14px 16px;
  border: 1px solid var(--c-line);
  position: relative;
}

.prize-row-label {
  grid-area: label;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 14px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--c-grey);
}

/* 队名占第二行整行。overflow 三件套保证再长的队名也撑不破卡片。 */
.prize-row-team {
  grid-area: team;
  font-size: 13px;
  color: var(--c-ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* 队名还没算出来（赛程没排到队 / 那场没打完）→ 灰字"待定" */
.prize-row-team--tbd {
  color: var(--c-grey);
}

.prize-row-value {
  grid-area: value;
  font-family: var(--font-display);
  font-weight: 900;
  font-size: 18px;
  text-align: right;
  white-space: nowrap;
}

/* ============ TEAM LINEUP LIST（index.html 对阵图下方的"参赛队伍"板块，
   js/script.js teamLineupCardHtml() 渲染）——徽标+队名一行
   （.team-lineup-head，可点进战队详情页）+ 下方成员名单。布局跟
   .event-grid 同一套"卡片四边完整描边 + gap 分隔"做法，不用 .prize-row
   那套负 margin 叠边——4 列 n 行，最多 8 支队伍正好铺满 4 列 2 行。
   成员分两段：**现役在上**（指挥排最前），**教练组在下**、中间一条横线
   （见 .team-lineup-coaches）——教练不是场上那 5 个人，混在中间会被当成
   第 6 名队员。（2026-10-01 之前这张卡片干脆不列教练，现在补上了。）
   每行是"头像 姓名 位置"三件套（.team-lineup-member）——2026-09 改的，
   之前是纯文字的"位置 姓名"、不带头像：当时的理由是要塞的是身份区那套
   96px 头像，收窄到 4 列之后放不下；换成 24px 之后这条理由不成立了。
   头像走 `.boxscore-player-avatar` 同一套尺寸和描边（站里头像一律带框，
   见那条规则上的说明），位置挪到行尾、灰色小号 label 右对齐，姓名黑色粗体
   吃掉中间的剩余宽度——跟比赛详情页成绩表 `.boxscore-player` 那一行是完全
   同一种排法（头像 + 姓名 + 场上位置靠右），两处并排看不觉得是两套语言。
   **教练那行的位置格写的是身份**（教练 / 助教），不是他打球时的定位。
   .team-lineup-roster 在没成员时渲染出空字符串，靠 :empty 自动收起，
   卡片上只剩"徽标+队名"一行（跟站内 .detail-id-roster-members:empty 处理
   "没数据"的方式保持一致）。"这一届还没编报名名单"也是同一种空（2026-10-04
   起不再退回当前编制）——原来垫一句「本届名单未编」，2026-10-11 随全站占位文
   一起撤了，现在就是**空着**。 ============ */
.team-lineup-list {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 16px;
}

.team-lineup-card {
  border: 1px solid var(--c-line);
  padding: 16px 18px 18px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.team-lineup-head {
  display: flex;
  align-items: center;
  gap: 10px;
  color: inherit;
  text-decoration: none;
}

.team-lineup-name {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 15px;
  letter-spacing: 0.02em;
  /* 阵容卡的队名，按录入原样显示——见 .hero-team-name 那条注释 */
}

.team-lineup-roster {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding-top: 12px;
  border-top: 1px solid var(--c-hair);
}

.team-lineup-roster:empty { display: none; }

/* 教练组那一段：垫在现役下面，上面一条横线跟现役分开。线挂在这一层、
   不挂在每一行上——1 位教练 + 若干助教时，教练之间不该再画线。
   ⚠️ 没有教练时这一段整个不渲染（见 teamLineupCardHtml），横线跟着一起
   消失，卡片不会凭空多一条线。 */
.team-lineup-coaches {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 8px;
  padding-top: 12px;
  border-top: 1px solid var(--c-hair);
}

.team-lineup-member {
  display: flex;
  align-items: center;
  gap: 8px;
  color: inherit;
  text-decoration: none;
}

/* 行首那个小头像。24px + 1px 墨黑描边，跟比赛详情页成绩表
   （.boxscore-player-avatar）逐条一致——两处的姓名都是 13px，同一屏里
   并排看不会一个偏大一个偏小。**四边都描边是站里头像的固定规矩**
   （96px 的 .player-photo/.detail-id-roster-avatar 也是这一条），小尺寸
   的不能例外，否则彩色头像的边缘糊在卡片边框上分不清。没配头像的选手
   落 DEFAULT_LOGO 兜底图，不会出现空 src。 */
.team-lineup-member-avatar {
  width: 24px;
  height: 24px;
  object-fit: cover;
  border-radius: 2px;
  border: 1px solid var(--c-ink);
  flex-shrink: 0;
  display: block;
}

/* 姓名吃掉头像和位置之间的全部剩余宽度，位置因此被顶到行尾右对齐
   （靠 flex:1 推，不给位置那格加 margin-left:auto——没位置时那格根本
   不渲染）。单行省略号：加头像之后这一行的可用宽度比以前少了 32px，
   "KioShiMa@20148LDLC"这种长昵称会顶到位置那格上，截断比文字互相压
   好收拾；战队详情页身份区不截断，看全名去那里。 */
.team-lineup-member-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 13px;
  letter-spacing: 0.01em;
}

/* 行尾那格：**场上位置**（狙击手 / 指挥 / …）。2026-09 从行首挪到行尾，
   理由见上面 TEAM LINEUP LIST 那段。原来固定 38px 是给行首那列齐宽用的，
   挪到行尾后没有对齐需求，改成 nowrap 保证三个汉字不被挤成两行就行
   ——位置最长三个字（狙击手/突破手/步枪手/道具手/自由人）。
   没填位置的人这个 span 整个不渲染（见 js/script.js 的 teamLineupCardHtml）。 */
.team-lineup-member-role {
  flex-shrink: 0;
  white-space: nowrap;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 10px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--c-grey);
}

/* ============ DETAIL ID ROSTER（战队详情页身份区里的"选手"，
   team-detail.html，renderTeamRoster() 渲染）——贴着队名右侧、占满身份
   区右侧剩余空间，margin-left:auto 贴住这行本来就空着的留白
   （player-detail.html 身份区右上角放 CS2 时长/主页链接的
   .detail-id-extra 是同一个位置，team-detail.html 没有那些字段，之前
   一直空着）。

   头像用 96px——跟队徽同尺寸，不缩水；**姓名单独一个黑底标签**（职务不写在
   里面了，见下条）。角色只保留中文、去掉英文后缀，全站不再靠颜色区分身份
   ——角标跟队员/教练一样是黑底白字，不再标红。头像描边统一黑色，跟站内
   其它头像（.player-photo 等）的描边颜色保持一致。

   **场上位置是头像右上角的角标（.roster-position-badge），不跟姓名挤在同一个
   标签里**——这是 2026-09 改的。姓名标签那一格只有 96px（贴齐头像），
   "位置 姓名"一起写时前缀白占掉一个多字的位置，长昵称很容易被省略号吞掉：
   实测"西伯利亚泰坦龙单手捏爆小行星"整串要 188px。把位置拿出去之后，姓名
   自己就能用满那 96px。角标绝对定位压在头像角上，不占布局、也不撑宽那一列。
   **没填位置的人整个角标都不渲染**（见 js/script.js 的 renderTeamRoster）。

   .detail-id-roster-member 固定宽度 96px（贴齐头像宽度）——姓名长短
   不一，标签文字这块本来想过交给内容自然撑开，但那样每个人的标签宽度
   都不一样，几个头像之间的间距就跟着一个宽一个窄，一整排看下来对不齐；
   改成固定 96px 之后，标签比头像窄的居中显示，比头像宽的靠 .role-tag
   的 white-space:nowrap + text-overflow:ellipsis 强制单行、超出用省略号
   截断，不再自动换行——头像本身的宽度和间距永远整齐划一，牺牲的只是
   "姓名特别长时标签会被省略号吞掉一部分"这一点，换来的是整排头像
   严格对齐、每个标签都保持同一行高。selector 用 flex-wrap，选手多、
   身份区本身又不算特别宽时会自动换行，不会把队名挤没或溢出。
   教练不算"阵容成员"，不在这里、已经挪到队名正下方（见
   .detail-id-coach）。绝大多数战队目前没有配阵容数据时容器渲染出空
   字符串，靠 :empty 自动收起、不占位置。 ============ */
.detail-id-roster-members {
  margin-left: auto;
  min-width: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 10px;
}

.detail-id-roster-members:empty { display: none; }

.detail-id-roster-member {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  width: 96px;
  text-align: center;
}

.detail-id-roster-member .role-tag {
  max-width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* 头像这一格要包一层，角标才能绝对定位挂上去。包层是 block、尺寸就等于
   头像，不影响上面那套"每人一列 96px"的排版。 */
.detail-id-roster-photo {
  position: relative;
  display: block;
  width: 96px;
  height: 96px;
}

/* 场上位置角标（狙击手 / 指挥 / 突破手 / 步枪手 / 道具手 / 自由人），压在
   头像**右上角**、不占布局——它不挤占下面姓名标签那 96px，这正是把位置从
   "位置 姓名"里拿出来的目的（见上）。**没填位置时这个 span 根本不渲染**，
   所以不用担心空方块。圆角只切右上（跟头像那 2px 对齐）和左下，另外两个角
   是直的，免得看起来像贴了张独立的小卡片。 */
.roster-position-badge {
  position: absolute;
  top: 0;
  right: 0;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 10px;
  line-height: 1;
  letter-spacing: 0.05em;
  color: #fff;
  background: var(--c-ink);
  padding: 3px 5px;
  border-radius: 0 2px 0 2px;
}

.detail-id-roster-avatar {
  width: 96px;
  height: 96px;
  object-fit: cover;
  border-radius: 2px;
  border: 1px solid var(--c-ink);
  display: block;
}

.role-tag {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 10px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: #fff;
  background: var(--c-ink);
  padding: 4px 9px;
  border-radius: 2px;
  flex-shrink: 0;
}

/* ============ DETAIL ID COACH（战队详情页身份区里的"教练组"标注，收在
   队名正下方，.detail-id-info 的第三行）——教练/助教都不算"阵容成员"、
   不跟选手头像并排，只是"教练 姓名"这样的黑底标签链接（跟上面选手用的是
   同一个 .role-tag 组件），视觉权重明显低于选手区。
   **一行里可能有好几个**（1 位教练 + 若干助教，见 data/roster.js 的
   role="assistant"），所以用 flex + gap 排开、放不下就换行——身份已经写在
   标签文字里（"教练" / "助教"），不靠颜色或位置区分。
   没有教练数据时渲染空字符串，靠 :empty 收起，不在队名下面留一条空隙。 */
.detail-id-coach {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}

.detail-id-coach:empty { display: none; }

.roster-coach-item {
  display: inline-flex;
  align-items: center;
}

/* ============ EMPTY STATE（原 `.empty-hint` —— ★ 2026-10-11 已退役） ============
   用户："这些占位文都不要，没有数据时什么都没有"——HTML 里那些节点（筛选行的
   三段空态、选手页/战队页的「暂无比赛记录」「暂无夺冠记录」、比赛页的「暂无
   历史交锋记录」、财务的「还没有记录任何收支流水」…）连同照看它们的 JS
   （筛选行 apply() 里的开关、各渲染函数的 else 分支）一起撤了，样式表这两条
   已经没有任何消费者，于是也删掉。
   （`.bracket-empty-hint` 是**另一套东西**，留着：那两句是"后台还没配赛制 /
   还没钉赛事"的**操作指引**，不是"没有数据"的占位。）

   ★ 紧接着用户又提了一句："占位文去掉了，但空白区还在，能不能不要空白区"——
   于是"内容是**一张卡片列表 / 一条贴纸条**、自己没有板块标题"的区块，在列表里
   一行可见的都没有时**整块收起**（见下面"空区块整块收起"）。 */

/* ============ 空区块整块收起 ============
   为什么必须"整块"：光把内容清空不够——`.section` 自己的上下 32px 内边距照旧
   占着，页面上就是一道 64px 的空白（页脚上方那道最明显）。

   ⚠️ 判据写成 `:not(:has(...))` 而不是在渲染函数里 toggle `hidden`，两条理由：
   ① 这几块要跟着**筛选行**变（筛到 0 条也算空），而筛选只给每行加
      `.is-filtered-out`，不会再跑一遍渲染函数——JS 里 toggle 追不上；
   ② JS 跑起来之前（数据还在 fetch）区块本来就是空的，CSS 判据顺手把那次
      "先进页面闪一下空区块"也挡掉。

   ⚠️ **只有"内容从头到尾就是这一张列表"的区块能这么写**。带板块标题的那种
   不能用——标题本身就是内容，藏掉会让编号（01/02/03）断档。另有几处同类判断
   只能留在 JS 里：战队页的 `.detail-subtable`（是个子块、不是 section）、
   荣誉墙（要按赛事列表算），见 js/script.js 的 renderTeamMatches() /
   renderTrophyStrip()。

   ⚠️ 这三个 id 都只在 body.page-detail 的页面上（该页区块分割线是关掉的），
   所以用 `display: none` 不会让"最后一个可见区块"那条判据出错——它认的是
   `[hidden]` 属性（见上方 `main > .section:not(:has(~ :not([hidden])))`）。 */
#player-stats:not(:has(.match-row:not(.is-filtered-out))),
#h2h:not(:has(.match-row:not(.is-filtered-out))),
#trophies:not(:has(.trophy-chip)) {
  display: none;
}

/* ============ PLAYER MATCH STATS（选手详情页"比赛数据"，复用
   boxscore-row/boxscore-table-head 的列宽，只是内容从"选手"换成
   "比赛"） ============ */
/* player-detail.html 的比赛数据列表直接复用 .boxscore-row/
   .boxscore-table-head 的列宽，容器本身（#player-match-stats）不需要
   额外样式，宽度由外层 .panel 撑满；分页用的是 js/script.js 里
   setupPagination() 那套通用逻辑（行选择器传 .boxscore-row），按钮/
   页码样式直接复用 players.html 同一套 .pagination 组件，这里也不需要
   额外 CSS。 */

/* ============ DETAIL LIST（战队/比赛详情页里"近期赛程/历史战绩/历史
   交锋"这几处复用统一 .match-row 模板渲染的容器——故意跟 schedule.html
   专用的 .live-list/.match-list/.result-list 分开命名，避免 js/script.js
   里那段"页面一加载就自动找 .live-list 等 class 渲染全部比赛"的逻辑，
   把这几个"只显示某支战队/某场比赛相关记录"的容器也误当成全量列表
   覆盖掉） ============ */
.detail-list {
  display: flex;
  flex-direction: column;
}

/* ============ PLAYER PHOTO（选手详情页大头像，player-detail.html） ============ */
.player-photo {
  width: 96px;
  height: 96px;
  object-fit: cover;
  border-radius: 2px;
  border: 1px solid var(--c-ink);
  flex-shrink: 0;
}

.detail-id-row {
  grid-column: 1 / 13;
  display: flex;
  align-items: center;
  gap: 24px;
  margin-bottom: 40px;
}

/* ============ DETAIL ID TEAM LOGO（战队详情页队徽，team-detail.html）
   ——单独拆出来，不跟 player-detail.html 大头像共用 .player-photo，
   固定 128x128（比选手头像/阵容头像的 96px 大一档），player-detail.html
   的大头像不受影响，还是 .player-photo 的 96x96。 ============ */
.detail-id-team-logo {
  width: 128px;
  height: 128px;
  object-fit: cover;
  border-radius: 2px;
  border: 1px solid var(--c-ink);
  flex-shrink: 0;
}

.detail-id-info {
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-width: 0;
}

/* 选手/战队名（player-detail.html/team-detail.html 身份区共用这个类）
   原来是 clamp(32px, 4.2vw, 56px)，同样是随窗口实时重算的 vw——头像
   （.player-photo/.detail-id-team-logo）早就是固定 px，名字却还在跟着
   窗口变窄一路缩小，头像和名字的比例会跟着窗口宽度跑偏，是"选手的名字/
   战队的名字缩小了"这个问题的直接来源。改成固定 px，按参照宽度 1440px
   算：4.2vw = 60.48px 被上限 56px 封顶，直接写 56px，不再响应式。 */
.detail-id-name {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: 56px;
  line-height: 1;
  /* 选手页 / 战队页身份区那个大字名，按录入原样显示——见 .hero-team-name
     那条注释。不加 text-transform。 */
  letter-spacing: -0.01em;
}

.detail-id-sub {
  display: flex;
  align-items: center;
  gap: 10px;
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 14px;
  color: var(--c-grey);
}

/* 没有战队时这一行留空（js/script.js 给空字符串），但空节点**照样占掉
   `.detail-id-info` 那一格的 10px 间距**、在选手名下面留一条空隙——跟
   `.detail-id-coach:empty` 同一个理由，靠 :empty 整个收起。
   （`gap: 10px` 本身是"队徽+队名"和它右边那组职业胶囊之间的距离，有队时要用。） */
.detail-id-sub:empty { display: none; }

.detail-id-sub a:hover { color: var(--c-red); }

.detail-id-sub-team {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  /* 队名用墨黑，不用这一行默认的灰（2026-09-27 用户："广州美术学院改成黑色"）。
     "他在哪支队"是有用的内容、不是次要小字标注——跟身份区把总奖金
     从灰改成黑是同一条理由（见下方那段注释）。没有战队时这一行不渲染任何
     节点（2026-10-14 用户定），所以这条规则只作用于有队的选手。 */
  color: var(--c-ink);
}

/* 身份区的职业 / 定位胶囊（2026-09-27 用户定："现役 教练 狙击手，改成黑色
   胶囊风格"）。**原样复用 `.event-badge`**——就是赛事卡片上那个"类型"黑胶囊，
   同字号、同内边距、同 2px 圆角，所以它跟 `.tier-badge` 写死的 23px 对齐
   （见上方 tier badge 注释）也跟着自动对；**别给它加尺寸修饰**，那会让那个
   硬编码数字失准。
   间距 8px 抄的是 `.event-card-badges`（卡片上"类型胶囊 + 评级方块"那一组的
   间距），全站胶囊组只有这一个间距值。 */
.detail-id-sub-badges {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

/* ============ 身份区归属行 —— 只显示主队（2026-10-12 定稿） ============
   这一行是"这是谁"，不是"他挂过哪些队"：**只渲染 `primaryTeamId()` 那一条
   归属**，第二支及以后的归属整条不渲染（节点见 player-detail.html 的
   `#player-detail-affs`）。口径跟选手列表/榜单的「所属战队」列同源。
       [徽] 我的顶顶顶的 [现役][狙击手][教练]

   沿革，别再往回搬：2026-09-27 起是"队徽队名全排完 → 职业全排完"的两段式
   （`#player-detail-team` + `#player-detail-role` 两个并列 span），挂两支队的人
   身上「现役/教练/助教」跟哪支队都对不上号、黑胶囊比队名还重、主次颠倒；
   2026-10-12 当天先做了一版"一条归属一组、组间一条竖线"的完整展开
   （`.detail-id-sub-aff` / `.detail-id-sub-aff-rule` 两个类随那一版一起删掉了），
   用户看过之后选了更干脆的：**第二支队直接不显示**。

   所以这一行现在就是"队徽+队名"和"它的职业胶囊组"两个子节点，间距走
   `.detail-id-sub` 的 10px（`.detail-id-sub-team` / `.detail-id-sub-badges`
   两条规则见上）。⚠️ 别把职业胶囊挪到队徽前面、也别拆成两队并列——一个选手
   一行只有一个队。 */

/* ============ `.detail-id-prize` —— ★ 2026-10-11 已退役（规则删掉） ============
   身份区队名下面那行「总奖金」用的就是它。这块位置的沿革，别再往回搬：
   它原来放 "RANK NO.1"（跟数据区那个 RANK 项重复）→ 换成总奖金（"有用内容"，
   所以用跟队名一样的纯黑、`--font-display` 13px/800）→ 2026-09-29 撤掉右边
   那个写死的「TOP.2」角标（假数据），只剩一个 span → **2026-10-11 用户：
   "战队名下方的总奖金不要了"**，整行撤掉，这个类也就没有消费者了。
   ⚠️ 选手页身份区**从来没用过这个类**（那边 2026-10-11 短暂挂过一次、用户说
   不好看，当天就挪回数据表——见 player-detail.html 里那段注释）。
   ⚠️ 两边现在的口径一样：**总奖金只在数据表里**（选手页在「荣誉与生涯」段、
   跟 MVP/SVP 一段；战队页在「荣誉」段、跟冠军 / 亚军 / 季军一段），
   同一个 teamTotalPrize() / playerTotalPrize()。
   ---- */

/* ============ DETAIL ID EXTRA（选手详情页身份区右上角的次要信息块）
   ——跟 .detail-id-row 的头像/昵称同一行，不单独占一行打断"名字 →
   重要数据"的阅读顺序。整块竖排、右对齐，分三排（2026-10-11 定），
   上中下依次是：实时徽标「在线」「正在玩」 / CS2 时长 / 查看主页。
   （STEAM ID 曾于 2026-09-29 加过，2026-10-11 撤掉。） ============ */
.detail-id-extra {
  margin-left: auto;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 8px;
  text-align: right;
  flex-shrink: 0;
}

.detail-id-extra-item {
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 13px;
  color: var(--c-grey);
}

.detail-id-extra-item strong {
  font-family: var(--font-display);
  font-weight: 800;
  color: var(--c-ink);
}

/* 身份区这几排——整块 .detail-id-extra 是竖排的（右对齐），每排自己
   横过来。自上而下：实时徽标 / CS2 时长 / 查看主页。 */
.detail-id-extra-row {
  display: flex;
  align-items: center;
  gap: 10px;
}

/* 上排两个徽标**都** hidden 时整行塌掉：否则这一行虽然是空的，仍会占掉
   .detail-id-extra 的 8px gap、在顶部留一条空隙。:has() 站里已在用
   （见上面那条 `main > .hero:not(:has(~ :not([hidden])))`）。
   判的是 hidden 属性本身，不是可见性——徽标的 display:inline-flex 是
   作者样式，本来就会盖住浏览器的 [hidden]。 */
.detail-id-extra-row--live:not(:has(> *:not([hidden]))) {
  display: none;
}

/* "正在玩 X"徽标。红方块复用全站那个呼吸方块 .live-dot（跟赛程页"进行中"
   同一颗）——它跟"进行中"表达的是同一件事：此刻真的在发生。
   游戏名由 js 填进 #player-info-playing-text，跟着 Steam 返回走。 */
.player-playing {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 12px;
  font-weight: 700;
  color: var(--c-red);
  white-space: nowrap;
}

/* 在线状态徽标。**不能用绿色**——全站调色板是写死的"只有红一种强调色"
   （见文件头），所以沿用站里"红 = 此刻真在"的语义：红=人在 Steam 上
   （在线/忙碌/离开/打盹/想交易/想玩游戏都算），灰=离线。
   点用独立的 .status-dot 而不是复用 .live-dot：那颗是**会呼吸的**，两个
   呼吸方块并排在身份区里太吵；这里要的是一个安静的实心方块。
   **它一开始就亮着、文字是"状态"**（2026-10-11 用户要的占位：不占位的话
   数据后到会把整块顶一下）；/api/public/steam-status 回来后才换成
   在线/离线/…，拿不到就留着这个灰占位。 */
.player-state {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 12px;
  font-weight: 700;
  color: var(--c-grey);
  white-space: nowrap;
}

.status-dot {
  width: 9px;
  height: 9px;
  border-radius: 0;
  display: inline-block;
  background: var(--c-grey);
}

.player-state--online { color: var(--c-red); }
.player-state--online .status-dot { background: var(--c-red); }

/* 跟 .live-dot[hidden] 同一个坑：上面 display: inline-flex 是作者样式，
   会盖掉浏览器默认的 [hidden] { display: none }，不补这两条徽标会一直在。 */
.player-playing[hidden],
.player-state[hidden] { display: none; }

/* ============ 数据表（.stat-table）============
   这是一张**闭合的报表格子**：.stat-table 只画上、左两条边，每一格只管画
   自己的右、下两条边——每条线都只画一次、相邻格子共用同一条，不会出现
   2px 的粗缝；格子的上下左右因此都是描边的。

   选手页与战队页的数据表现在都是**分段版**——"一段一条分组带"，
   见下面 .stat-table--banded 那一段。

   ⚠️ **分段版的上、左两条边不画在这里**，改由方块自己画（容器一条边都不画）。
   理由是入场动效只动"方框"（顶栏三格 + 每个分段）、容器不在名单里：上/左
   留在容器上，动画一播就是"线钉在空框上、里面的方块往上飘"，那两条边硬邦邦
   先出现，跟正在淡入的右/下线对不上。挪进方块之后每条线都跟着自己那一块
   一起出现，整张表是一格一格长出来的。

   另外两处还在用这一套：finance.html 的收支总览（只有一格 .stat-grid--l1），
   以及所有格子共用的 .stat-cell 本身。

   数值字号：基准两档（40 / 24px），分段版里定为 34 / 21px，见
   .stat-table--banded 那段。 */
.stat-table {
  grid-column: 1 / 13;
  border-top: 1px solid var(--c-line);
  border-left: 1px solid var(--c-line);
}

.stat-cell {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 14px 16px;
  min-width: 0;
  position: relative;
  border-right: 1px solid var(--c-line);
  border-bottom: 1px solid var(--c-line);
}

/* finance.html「收支总览」那三个大号数用的网格（3 等分）。
   ⚠️ 原来还有 .stat-group-a / .stat-grid--l2 / .stat-grid--l3 / .stat-split /
   .stat-cell--pad 一组，是"左右两半平铺"那版用的——选手页 2026-10-01 改分段、
   战队页同日跟上之后就没有调用方了，一并删除。 */
.stat-grid--l1 { display: grid; grid-template-columns: repeat(3, 1fr); }

/* 数字等宽（tabular-nums）：Archivo 默认是比例字宽，"0.58" 跟 "0.60" 的
   小数点占宽不一样，上下两行的数字竖着就对不齐。报表格子里的数字必须
   等宽，这是瑞士风数据排版的基本功。 */
.stat-cell-value {
  font-family: var(--font-display);
  line-height: 1;
  color: var(--c-ink);
  font-variant-numeric: tabular-nums;
}

.stat-cell-value--l1 { font-weight: 900; font-size: 40px; }
.stat-cell-value--l2 { font-weight: 800; font-size: 24px; }

/* ============ 分段版数据表（选手页与战队页，2026-10-01 起）============
   两个页面的数据表都不是"左右两半平铺"，而是**一段一条标签带**；一段占半行，
   **一行放两段**（段数为奇数时最后一段独占整行），段内格子平分这半行的宽度。

     一级（.stat-bandrow--hero）  3 个大格，独占一整行
     每段： .stat-bandgroup        半行的容器
              ├ .stat-band        标签条
              └ .stat-bandcells   段内格子，--n 决定几等分

   为什么要分两栏：一段独占整行时，格子数少的那段会被拉得极散（3 格的那段
   每格 448px，里面只装一个数字）。并排之后每格约 96~224px，密度合适，
   整张表也矮了近一半。

   为什么分段：平铺版把二三十个数铺成一片、只按字号分档，没有主题分组——
   「首杀 / 首死 / 首杀差」会被「总伤害」从中间劈开，读不出哪些数在说同一件事。
   分段之后每一簇有自己的名字，且**能容纳更多格子**（选手页 27 → 39 格，
   战队页 25 → 32 格，把原来没地方装的数都摆上了台面）。

   ⚠️ **全部只挂在 .stat-table--banded 下**。finance.html 用的是同一个
   .stat-table，不加这层前缀会串过去。

   尺寸是"紧凑"档（2026-10-01 在测试区跟用户一起定的）：格子内边距、字号、
   标签带高度整体压一档捞回来一点。 */
.stat-table--banded {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  /* 容器一条边都不画——上/左两条挪给方块自己了，见下面那一段 */
  border-top: 0;
  border-left: 0;
}

/* 外框的上、左两条边，改由**最上面那一行**和**最左那一列**的方块自己画。
   ⚠️ 这不是为了好看，是为了入场动效：动效名单里只有方框
   （js/script.js 的 FIRST_SCREEN_BLOCKS 里那条
   `.stat-bandrow--hero > .stat-cell, .stat-bandgroup:not(...)`），
   容器不在里面。线留在这两处方块上，才会跟着它们一起淡入/上浮；
   留在容器上就会先在空框里硬出现，跟正在动的右/下线不同步。

   左边那条按**列**给：分段是两列网格、行序填充，所以奇数号（1/3/5…）
   就是左列。段数为奇数时 JS 补的那个 .stat-bandgroup--empty 落在末尾的
   偶数号上（右列），正好不会多出一条线。 */
.stat-table--banded .stat-bandrow--hero > .stat-cell {
  border-top: 1px solid var(--c-line);
}
.stat-table--banded .stat-bandrow--hero > .stat-cell:first-child,
.stat-table--banded .stat-bands > .stat-bandgroup:nth-child(odd) {
  border-left: 1px solid var(--c-line);
}

/* 一级：三个大号数占满整行，仍是表头，不参与下面的两栏排布。 */
.stat-bandrow--hero {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
}

/* 分段的挂载点：**两列网格**，一段（.stat-bandgroup）占一列 → 一行放两段。
   段数为奇数时，最后一行右边会空一块——JS 会补一个空的 .stat-bandgroup
   把它占住，见 statBandsHtml。 */
.stat-table--banded > .stat-bands {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
}

/* 一段 = 标签带 + 段内格子。
   底线画在这里（段内格子自己不画底边，免得叠成 2px）；
   右边线由标签带和最后一个格子各自画，两截正好落在同一个 x 上、接成一条。 */
.stat-bandgroup {
  min-width: 0;
  border-bottom: 1px solid var(--c-line);
}

/* 段数为奇数时，JS 会补一个空的 .stat-bandgroup 占住最后一个右半格
   （只为把最后一行那条底线铺满整宽，见 statBandsHtml）。这里不额外写规则。 */

/* 分组带：这一段的标签条（宽度跟着段走，不再整行） */
.stat-band {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 22px;
  padding: 4px 10px;
  background: #F4F4F2;
  border-right: 1px solid var(--c-line);
  border-bottom: 1px solid var(--c-line);
}

/* 带头的红方块是全站唯一那个红（--c-red），一枚 7px 的小方点，只给"这一段
   的起点"打个记号，不做任何装饰性描边。 */
.stat-band-name {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 11px;
  letter-spacing: 0.1em;
  color: var(--c-ink);
}

.stat-band-name::before {
  content: "";
  width: 7px;
  height: 7px;
  background: var(--c-red);
}

.stat-band-en {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 9px;
  letter-spacing: 0.16em;
  color: var(--c-grey);
  text-transform: uppercase;
}

/* 段内格子：平分**这一段**的宽度（--n 由 JS 按本段格子数写进 style）。
   不补空格子——"整表锁死 4 列"那版会在格子数不是 4 的倍数时甩出一片空框
   （选手页曾出现 8 个），看着像没填满。代价是各段格子宽度不一样、竖线对不齐；
   但段与段之间隔着标签带，读起来是"一段一段"。 */
.stat-bandcells {
  display: grid;
  grid-template-columns: repeat(var(--n, 4), 1fr);
}

/* 段内格子：左右内边距压到 10px（一级那三格仍是 14px）。
   一段只占半行（约 670px），格子最多的那一段（8 格）每格只剩约 84px——
   还按 14px 留内边距的话，最长的标签「下半场胜率」会被挤到折行，一折行
   就把那一段撑破、行高又不齐。10px 让同样的格子多出 8px 余量。
   底边**不画**：由 .stat-bandgroup 统一画一条，格子再画就叠成 2px。 */
.stat-table--banded .stat-bandcells .stat-cell {
  padding: 0 10px;
  border-bottom: 0;
}

/* 格子：**所有段同一个内边距、同一个字号**——行高才能从头到尾齐平。
   ⚠️ 别给某一段单独调字号／内边距：每个分段各是一行独立网格，行高由**自己的
   内容**决定，一调就立刻把行高扯成 64 / 49 / 49 / 64 那样忽高忽低
   （2026-10-01 用户报的就是这个）。层次交给分组带的名字去承担。
   一级那三个大号数（34px）是表头，不在此列。 */
/* 行高**写死**：分段格子 50px，一级那三格 80px（2026-10-01 用户定）。
   ⚠️ 高度定死之后，里面的文字只能在高度内**自适应**——所以靠 flex 居中
   （`justify-content: center`）把"标签 + 数值"这一竖串摆在格子中间，上下
   留白由剩余空间自动平分；字号按这个高度配：21px 数值 + 12px 标签 + 3px
   间距 ≈ 41px，塞进 49px 的内容盒还剩约 4px 上下余量——**再往上加字号就会
   顶破格子**，要加就得同时把高度也放开。
   ⚠️ **标签必须保持单行**：分段里每个分段各是一行独立网格，哪个标签折了行，
   那一段就被撑破、行高又不齐了。加新格子前先量一下标签在**最窄那一段**
   （格子最多的那段，选手页是 8 格的「道具使用」）放不放得下。 */
.stat-table--banded .stat-cell {
  height: 50px;
  padding: 0 14px;
  gap: 3px;
  justify-content: center;
}

.stat-table--banded .stat-bandrow--hero .stat-cell {
  height: 80px;
  gap: 6px;
}

.stat-table--banded .stat-cell-value--l1 { font-size: 34px; }
.stat-table--banded .stat-cell-value--l2 { font-size: 21px; }

/* ============ 通用浮层（.stat-tip）============
   全站浮层就这一个壳：白底 + **四边 1px 红描边**，无圆角无阴影。
   挂在 <body> 下、自己定死（position: fixed），由调用方算好 translate。
   宽高由调用方那层类决定（见本文件的 .stat-tip--usage）。

   目前唯一的用户是「为爱发电」流水表的**长文本注释浮层**——
   js/finance.js 的 initUsageTip：那四列文本单行截断，鼠标停上去弹全文。 ============ */
.stat-tip {
  position: fixed;
  left: 0;
  top: 0;
  z-index: 999;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  padding: 11px 14px;
  background: var(--c-bg);
  border: 1px solid var(--c-red);
  opacity: 0;
  visibility: hidden;
  /* visibility 必须跟着一起过渡：只写 opacity 的话，移除 .is-on 的那一帧
     visibility 就已经是 hidden，这 0.1s 的淡出根本播不出来——出现是渐显、
     消失是硬切（2026-10-01 补）。visibility 在过渡期间按规范保持 visible，
     正好等于"显示立刻生效、隐藏延到淡出结束"，两个方向都对。 */
  transition: opacity 0.1s ease, visibility 0.1s;
  /* 永远不接鼠标事件——否则浮层会从光标底下"抢走" hover，鼠标一抖就闪 */
  pointer-events: none;
  text-align: left;
}

.stat-tip.is-on {
  opacity: 1;
  visibility: visible;
}

/* ============ 身份区下挂的子表（选手页的地图战绩 / 武器，战队页的地图战绩 /
   选手统计）============
   它们跟上面那张一级/二级/三级报表格子**不是同一条表**：不共用边框、
   不连着排，中间靠这段留白分组。也正因为有留白当分隔，它们不需要自己的
   标题——空白本身就是分隔。
   宽度占满 12 栏，跟上面的表格对齐同一条左右边。

   两个页面共用这一条规则（原来叫 .player-mappool / .player-weapons，
   2026-09 合成一条并改成中性名，战队页也要用）。各表列数不同，列宽由
   .boxscore-table-head--maps / --playermaps / --players / --weapons
   那几个修饰类各自去定。 ============ */
.detail-subtable {
  grid-column: 1 / 13;
  margin-top: 56px;
}

/* ⚠️ 2026-10-06：这里原来有 `.subtable-head` / `.subtable-head-stat`
   （子表上方的「表头条」，放**粒度跟表里的行对不上**的数）。撤掉了——
   它唯一的用户是选手页武器表上方那条「整场命中率」，而那个数跟表里
   "每把枪"的行对不上，同页并排两个同名数只会误导（见 player-detail.html
   里那段注释）。真要再放这种数，先想清楚它跟表的行是什么关系。 */

/* 武器表的列宽。**原来写的是 `repeat(5, 1fr)`**——5 列等宽，收窄之前每列
   255.7px，表头五个标签两两相距 264px。这张表的兄弟表（同页上面那张「地图
   战绩」，`.boxscore-table-head--playermaps`）固定列只有 44~60px、标签相距
   52~66px，两张表上下摞在一起，武器表那张被拉得稀稀拉拉，2026-09 用户报
   "表头的间距问题"就是这个。

   改成跟兄弟表同一个套路：**首列 1fr 吃掉余量，其余四列按内容定宽**。
   四列的取值看实际内容——类别最长"狙击枪/冲锋枪/霰弹枪/投掷物"（3 个汉字，
   13px 下约 39px）、击杀最多三位数、占比与爆头率最宽是 "100%"（约 40px），
   所以 76 / 60 / 68 / 80 都留了余量。
   首列因此拿到约 996px（外框固定占满 12 栏，见上面 `.detail-subtable`），
   「武器」名自己撑不满，但这是刻意的：跟地图表一样，名字列当余量的去处，
   右边四列才能密起来。

   ★ 2026-10-06 加了第五列「首杀」（这把枪替他拿过几次首杀，最多两位数），
   补一个 60px。
   ★ 同一天又加了第六列「命中率」（每把枪的命中率，插在「爆头率」右边，
   最宽也是 "100%"），跟「占比 / 爆头率」同宽 68px。 */
.boxscore-table-head--weapons,
.boxscore-row--weapons {
  grid-template-columns: 1fr 76px 60px 68px 80px 68px 60px;
}

/* ============ 对位矩阵（★ 2026-10-06 新增，全站第一个矩阵表） ============
   行 = A 队 N 人、列 = B 队 N 人，每格 = 两人互相对上的「击杀 / 伤害」。

   ★ 2026-10-11 重塑版式。用户拿了一张外部平台的对位截图，要求"只参考它的
   比例，样式还是用我们的"——取的是**每个格子做成横长的块、上面比击杀、下面
   比伤害**这个骨架，形状语言仍是我们自己那套：1px 发丝线、Archivo 800、
   红=胜方（跟比分 / 冠军条同一条规则）。
   三处要点，改之前先读：

   ① **每一列等宽**，包括最左边那一列名字。用 grid-auto-flow: column +
      grid-auto-columns 表达"每个子元素各占一列、每列等宽"，列数由 DOM 自己
      数（表头行 = 名字列 + N 个列头，数据行 = 名字列 + N 个格子），于是
      5v5 / 3v3 / 1v1 一律同宽。内联的 `--cols`（js/script.js 里那个）**新方案
      已经不用了**，留着只是不碍事。
      早先名字列单独给过 1.4fr，结果是 272.65 vs 194.75 两档宽，用户要求拉平。

   ② **格子的高 = 宽的四分之一**（aspect-ratio: 4/1）。先是正方（1/1），用户量到
      211.35 说"高了，减少 50%" → 2/1；再一轮"表的高度再减少 50%" → 4/1。
      每次减半都只是这一个数字，格子和列头一起跟着走。
      表头那一行的列头用**同一个比例**，列宽跟下面每格一一对应，所以表头行的
      高度天然等于格子高度——不用再去对数字。
      ⚠️ 高度全用 aspect-ratio 表达，**不要写死 px**：列宽随视口变，写死就破比例。
      ⚠️ 格子越扁，里面那两段的余地越小：padding / gap / 字号是跟着这个比例配的，
      再改比例要连它们一起调，否则数字会被挤到贴边。

   ③ **行内边距归零**。这套行是从旧版式接过来的，旧版把内边距放在行上
      （padding: 10px 16px），新版放在格子自己身上；不归零每行会凭空多 20px 高、
      表头行还会多出上下不对称的 10/8。同理 `.duel-row--head` 旧的字号/字距/
      uppercase 也要清掉。

   边框语言跟 `.boxscore-table-head` + `.boxscore-row` 一致：1px 发丝线 +
   相邻行 `margin-top:-1px` 把双线并成一条，整块看起来还是一张表。 */
/* ★ 2026-10-11：这 32px 是**对位矩阵跟上面成绩表之间**的距离——两块从"前后两个
   section"（各带 32px 上下内边距，之间空着 64px）并进了同一个 panel，间距收到
   32px。矩阵是 #match-duels 的唯一子元素、那层又没有 padding/border，所以这个
   margin-top 会塌到 panel 内的上边界，正好等于用户要的那 32px。 */
.duel-matrix { margin-top: 32px; }

.duel-row {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  align-items: stretch;
  gap: 0;
  padding: 0;
  border: 1px solid var(--c-line);
}

.duel-row + .duel-row { margin-top: -1px; }

.duel-corner,
.duel-rowhead,
.duel-colhead { padding: 14px 16px; }

/* 表头行：旧版把字号/字距/uppercase 挂在行上，新版挂在列头上，这里清掉 */
.duel-row--head { padding: 0; font-size: inherit; text-transform: none; }

.duel-colhead {
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 4 / 1;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 12px;
  line-height: 1.25;
  color: var(--c-ink);
  text-align: center;
  border-left: 1px solid var(--c-line);
}

.duel-cell {
  display: flex;
  flex-direction: column;
  aspect-ratio: 4 / 1;
  gap: 3px;
  padding: 5px 8px;
  text-align: center;
  border-left: 1px solid var(--c-line);
}

/* 上半 = 击杀（占得多一点）；下半 = 伤害条 */
.duel-kills {
  display: flex;
  align-items: stretch;
  justify-content: center;
  flex: 1.3;
}

.duel-dmg {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1;
  font-family: var(--font-body);
  font-size: 10px;
  font-variant-numeric: tabular-nums;
  color: var(--c-grey);
}

/* 伤害对比条：一条 2px 发丝线，红段画在**打得多的那一边**——
   左边多就从左往右长、右边多就从右往左长（见 js/script.js 的 renderMatchDuels
   给 `.is-right` 打的那个类）。不是永远从左边长：那样"123 : 237"看起来
   像左边占了大半，方向是反的。 */
.duel-dmg-track {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  height: 2px;
  background: var(--c-hair);
}

.duel-dmg-track i { display: block; height: 100%; background: var(--c-red); }
.duel-dmg-track i.is-right { margin-left: auto; }

/* 击杀那一半的两个数：两个互扣的斜切块（左块右边沿斜切、右块左边沿斜切，
   负 margin 让它们扣在一起），赢的那个填红底白字、输的压灰。 */
.duel-flag {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(13px, 1.5vw, 20px);
  line-height: 1;
  font-variant-numeric: tabular-nums;
  color: var(--c-ink);
  background: #0000000a;
}

.duel-flag--a {
  clip-path: polygon(0 0, 100% 0, calc(100% - 8px) 100%, 0 100%);
  padding-right: 8px;
}

.duel-flag--b {
  clip-path: polygon(8px 0, 100% 0, 100% 100%, 0 100%);
  padding-left: 8px;
  margin-left: -8px;
}

.duel-flag.is-win { color: #fff; background: var(--c-red); }
.duel-flag.is-lose { color: var(--c-grey); }

/* 两人完全没交过手 → 整格压灰，一眼扫得出"这两格不用看" */
.duel-cell.is-empty .duel-flag,
.duel-cell.is-empty .duel-dmg { color: var(--c-grey); opacity: 0.45; }
.duel-cell.is-empty .duel-dmg-track i { background: var(--c-grey); }

/* 整场还没采到成绩（js/script.js 的 renderMatchDuels 打这个类）：格子照出、
   数字全 "—"、比例条留空。跟上面那张成绩表的占位是同一套读法。 */
.duel-cell.is-placeholder .duel-flag { color: var(--c-grey); background: #0000000a; }
.duel-cell.is-placeholder .duel-dmg-track i { display: none; }

.duel-rowhead {
  display: flex;
  align-items: center;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 13px;
  line-height: 1.25;
  color: var(--c-ink);
  /* 用户名**允许换行**（2026-10-11 用户要求）：长名字不再省略号截断，在格子
     宽度内折行；英文/数字连成一串时也断得开。 */
  white-space: normal;
  overflow-wrap: anywhere;
}

.duel-note {
  margin-top: 12px;
  font-size: 12px;
  line-height: 1.6;
  color: var(--c-grey);
}

.duel-note b { color: var(--c-ink); font-weight: 700; }

/* 空态占位行（选手页「地图战绩」「武器」两张表）：表头照常立着、底下垫一行
   「暂无数据 + —」。2026-10-01 用户要求——没数据时别整块隐藏，页面结构才能
   稳定，一眼也看得出"这张表存在、只是还没有数据"。
   ⚠️ **必须把 hover 那条红边按掉**：`.boxscore-row:hover` 会把四边描边染红，
   而这不是一行可点击的数据，跟着变红等于骗人。这条规则靠"位置在后"压过它
   （同特异性 0,2,0），别挪到 `.boxscore-row:hover` 前面去。
   文字也压灰：它是占位、不是内容。 */
.boxscore-row--placeholder,
.boxscore-row--placeholder:hover {
  border-color: var(--c-line);
  z-index: auto;
  cursor: default;
}

.boxscore-row--placeholder .boxscore-player-name,
.boxscore-row--placeholder .boxscore-stat {
  color: var(--c-grey);
}

/* ============ EXTERNAL LINK BUTTON（选手详情页 Steam 主页外链） ============ */
/* Steam 主页链接在 .detail-id-extra 的最下一排（身份区右上角的次要信息块），
   跟 STEAM ID 同排、排在它右侧。字号跟同一行其它文字一致（13px）。
   2026-10-11 用户要求**去掉下划线**，所以这里不再有 border-bottom，
   靠 hover 变红表达"这是个链接"。 */
.ext-link {
  display: inline-block;
  font-size: 13px;
  font-weight: 700;
  text-transform: none;
  letter-spacing: normal;
  transition: color 0.2s ease;
}

/* .ext-link 自己声明了 display:inline-block（作者样式），会盖掉浏览器
   内置的 [hidden]{display:none}——跟全站 .live-dot[hidden]/
   .filter-bar[hidden] 那两条是同一个坑，没查的选手（profileUrl 为空）
   点击"查看 STEAM 主页"链接会跟"暂无 STEAM 主页"文案一起显示，这里补
   一条显式覆盖。 */
.ext-link[hidden] { display: none; }

.ext-link:hover {
  color: var(--c-red);
}

/* ============ REVEAL BASE ============
   [data-reveal] / [data-rank-row] 的"初始隐藏态"**不在这里**——2026-10-01 挪到了
   js/script.js 的 revealBatch()，由 gsap.set() 在渲染的同一个同步任务里打上
   （元素本来全是 JS 渲染出来的，渲染和隐藏之间不会插进一帧画面，所以不会闪）。
   两个原因，都不是随手挪的：
     1. 打隐藏态**之前**要先量出元素本来该有的不透明度，揭示动画的终点得用它；
        写在 CSS 里先隐藏掉，量到的就只剩 0 了。
     2. 动画库万一没加载成功，这里就不会打隐藏态，页面照常可读；写在 CSS 里的话
        没库就是一片空白——列表和首屏全看不见。
   **别把它们挪回 CSS。** */

/* ============ FILTER BAR（event.html/schedule.html/teams.html/
   players.html/team-detail.html/player-detail.html 六个页面共用同一个
   组件，由 js/script.js 的 initFilterBars() 渲染进空壳
   [data-filter-bar]——不新发明控件视觉语言，年份/类型/评级/规模/赛事五个下拉
   抄的是 .hero-info-card 的"灰色小字 label + 黑色粗体 value"节奏，展开面板
   抄的是 .rank-row/.match-row 的"hover 变红"规则，右侧计数与重置按钮
   直接复用 .pagination-info/.pagination-btn，不重写。
   刻意做成很轻的单行工具条，不是一个跟 .section-head/.hero-info-card
   同等分量的"内容板块"——label+value 收在同一行、只留一条发丝线（不再
   用 border-top 的实线黑边），字号也比照抄来源的组件缩小一档，筛选行
   本身只是辅助工具，不该比它筛的内容还抢视觉。
   六个页面统一放在同一个位置：main 顶部、第一个 section 之前，独占一行、
   占满 1/13（teams.html/players.html 最初试过跟 .section-head 同一行收窄
   到 4/13，跟下方列表左边缘对齐，但那样每个页面筛选行的位置不统一，
   已经改成跟 event.html/schedule.html 完全同一个位置）；team-detail.html/
   player-detail.html 嵌在非 grid 的 .panel 里，grid-column 自动失效，
   照样占满面板宽度。 ============ */
.filter-bar {
  grid-column: 1 / 13;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  border-bottom: 1px solid var(--c-hair);
  padding: 10px 0;
  margin-bottom: 24px;
}

/* 目标列表压根没有可筛选内容时（比如某位选手暂无正式比赛数据），JS 会
   给 .filter-bar 加 [hidden] 隐藏整条筛选行——但 .filter-bar 自己已经
   声明了 display:flex（作者样式），会盖掉浏览器内置的
   [hidden]{display:none}（同全站 .live-dot[hidden] 那条规则一样的坑），
   这里补一条显式覆盖。 */
.filter-bar[hidden] { display: none; }

/* event.html/schedule.html/teams.html/players.html 里筛选行独立占一行、
   直接挂在 main 顶部、第一个 section 之前（不在 .section 内部）。
   这里原本有一条 `.filter-bar-row + .section { padding-top: 24px }`，用来
   收紧紧跟在筛选行后面那个 .section 的顶部内边距——当时的理由是"filter-bar
   自己的 margin-bottom 24px + section 的 padding-top 64px = 88px"叠得太空。
   2026-09-28 section 的上下 padding 统一收到 32px 之后，这一处叠出来是
   24 + 32 = 56px，跟普通边界（32 + 32 = 64px）已经差不多，那条例外失去了
   理由，删掉——现在每个板块的上下内边距都是 32px，没有例外。 */
.filter-item {
  position: relative;
  display: flex;
  align-items: baseline;
  gap: 6px;
  padding: 0 16px;
  border-left: 1px solid var(--c-hair);
}

.filter-item:first-child {
  padding-left: 0;
  border-left: none;
}

.filter-label {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 10px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--c-grey);
}

.filter-trigger {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  background: none;
  border: none;
  padding: 0;
  margin: 0;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 12px;
  text-transform: uppercase;
  color: var(--c-ink);
  cursor: pointer;
}

.filter-trigger:hover,
.filter-trigger.is-open { color: var(--c-red); }

.filter-caret {
  display: inline-block;
  font-size: 8px;
  color: var(--c-grey);
  transition: transform 0.2s ease, color 0.2s ease;
}

.filter-trigger.is-open .filter-caret {
  transform: rotate(180deg);
  color: var(--c-red);
}

.filter-panel {
  position: absolute;
  top: calc(100% + 10px);
  left: 0;
  min-width: 180px;
  background: var(--c-bg);
  border: 1px solid var(--c-line);
  z-index: 30;
  display: none;
}

.filter-panel.is-open { display: block; }

.filter-option {
  display: flex;
  align-items: baseline;
  gap: 10px;
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  border-bottom: 1px solid var(--c-hair);
  padding: 8px 12px;
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 12px;
  color: var(--c-ink);
  cursor: pointer;
}

.filter-option:last-child { border-bottom: none; }

.filter-option:hover { background: #fafafa; color: var(--c-red); }

.filter-option.is-selected {
  color: var(--c-red);
  font-weight: 700;
}

.filter-option-en {
  margin-left: auto;
  flex-shrink: 0;
  font-size: 9px;
  letter-spacing: 0.05em;
  color: var(--c-grey);
  text-transform: uppercase;
}

.filter-result {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 14px;
  padding-left: 16px;
}

/* 被筛掉的行/卡片直接隐藏，不做二次淡入——跟分页模块"整页切换"的
   即时感保持一致。 */
.is-filtered-out { display: none; }

/* ============ 为爱发电 · 财务公示（finance.html） ============
   两块正文都让开左侧 1/4 目录栏，跟列表页 .section-head 的版式对齐
   （.section .grid-container 是 12 列栅格，section-head 占 1/3）。 */
.finance-overview,
.finance-book {
  grid-column: 4 / 13;
}

/* 列头行和数据行**共用这一套六列栅格**（日期 / 名称 / 商家 / 用途 / 备注 /
   金额），所以表头跟下面的数据天然对齐。金额与日期定宽（内容长度固定），
   中间四列按 fr 分——用途最宽（一句话的长文本多半写在这里），其余三等分。
   全站没有断点、版式锁在 1440px（见文件开头的说明），所以这里也不写
   窄屏规则：窗口更窄时是整页横向裁剪，不是这几列塌掉。 */
.finance-row {
  display: grid;
  grid-template-columns: 100px 1fr 1fr 1.6fr 1fr 140px;
  column-gap: 16px;
  align-items: center;
  padding: 12px 4px;
  border-bottom: 1px solid var(--c-hair);
  font-size: 13px;
  line-height: 1.4;
}

/* 翻页时整页的行会被 hidden——⚠️ 必须显式写这一条：.finance-row 自己的
   display: grid 是作者样式，会盖掉浏览器默认的 [hidden]{display:none}。
   站里 .live-dot / .ext-link / .filter-bar / .tier-badge 都是同一个坑。 */
.finance-row[hidden] { display: none; }

.finance-head {
  border-bottom: 1px solid var(--c-line);
  font-family: var(--font-display);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--c-grey);
}

/* 名称/用途可能很长，**截断而不折行**——整行高度固定，长内容靠浮层看全
   （见 js/finance.js 的 initUsageTip）。 */
.finance-cell {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.finance-col-amount {
  text-align: right;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* 收入墨色、支出红——跟后台列表的金额配色同一套语义 */
.finance-row .is-in { color: var(--c-ink); }
.finance-row .is-out { color: var(--c-red); }

/* .pagination 自带 display: flex，同样会盖掉默认的 [hidden]{display:none}
   ——零条流水时整条翻页栏要收起来。 */
.pagination[hidden] { display: none; }

/* 用途的浮层：套 .stat-tip 那个壳（白底 + 四边红描边、无圆角无阴影），
   只改尺寸——一条长用途按固定宽度会被拉成一根细长的柱子，所以宽度改成
   跟着内容走、封顶 560。高度封在视口内，超出截断（鼠标浮层不该盖满整屏）。 */
.stat-tip--usage {
  width: auto;
  max-width: 560px;
  max-height: calc(100vh - 32px);
  overflow: hidden;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  font-size: 13px;
  line-height: 1.6;
}
