/* ==========================================================================
   志愿者管理系统 · 全局样式
   纯自研 CSS，无任何外部依赖 / 无构建步骤
   设计基调：专业救援蓝 + 干净留白，桌面端侧边栏，移动端抽屉 + 底部导航
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. 设计令牌
   --------------------------------------------------------------------------
   皮肤系统（需求 #66）的全部秘密就在这里：页面里所有颜色都从这里取值，
   换皮肤 = 换这一堆变量的值，一行布局代码都不用动。

   分三层：
     ① 调色板（--c-*）：字面颜色，只在本节出现，别处不许直接写 hex
     ② 语义变量：按用途命名（surface / text / border …），组件只认语义
     ③ 皮肤覆盖：[data-skin="xxx"] 只改语义变量，不改语义变量名

   为什么强调「别处不许直接写 hex」：10 套皮肤必须保证每套都好看，
   只要有一个组件偷偷写死了 #fff，深色皮肤下就会冒出一块刺眼的白。
   -------------------------------------------------------------------------- */

/* 恒量：与皮肤无关的颜色。放在最前面，皮肤覆盖段不许动它们。 */
:root {
  /* 纯白 / 纯黑在需要「绝对对错」的地方使用：
     彩色按钮上的白字、头像白边、遮罩。这些在深色皮肤下也必须保持原样，
     否则按钮上的文字会跟着变深，直接看不清。 */
  --fixed-white: #FFFFFF;
  --mask:        rgba(15, 23, 42, .48);
  --mask-soft:   rgba(15, 23, 42, .28);
}

:root,
[data-skin="blue"] {
  /* 品牌主色（默认：救援蓝） */
  --brand:          #1D5FD0;
  --brand-dark:     #123C86;
  --brand-darker:   #0C2A61;
  --brand-light:    #E8F0FE;
  --brand-lighter:  #F3F7FE;
  --brand-mid:      #5CA0FF;   /* 品牌色阶的浅端：进度条、柱状图渐变 */
  --brand-border:   #CFE0FA;   /* 品牌色系描边（选中态、浅色容器） */
  --brand-ink:      #0C2A61;   /* 品牌色系深字（浅底容器上） */
  /* 品牌渐变：侧栏顶部、登录页背景、海报底图共用 */
  --brand-grad:      linear-gradient(140deg, #3D8BFF, #1D5FD0 60%, #123C86);
  --brand-grad-deep: linear-gradient(158deg, #0A2550 0%, #123C86 48%, #1D5FD0 100%);

  /* 点缀色：用于强调、进行中、需注意 */
  --accent:         #FF6A2B;
  --accent-light:   #FFF1EA;
  --accent-ink:     #C24A12;

  /* 语义色：成功 / 警告 / 危险 / 信息 */
  --ok:             #12A150;
  --ok-light:       #E6F6EC;
  --ok-ink:         #0B7A3B;
  --ok-border:      #B6E2C6;

  --warn:           #E08A00;
  --warn-light:     #FEF4E0;
  --warn-ink:       #A96700;
  --warn-border:    #F0D9A8;

  --danger:         #D93A3A;
  --danger-dark:    #C22F2F;   /* 危险色 hover */
  --danger-light:   #FDECEC;
  --danger-ink:     #B32B2B;
  --danger-border:  #F1BCBC;
  --danger-lite:    #FFFBFB;   /* 危险行的极浅底 */

  --info:           #0E8FA8;
  --info-light:     #E5F6FA;
  --info-ink:       #0A6C7E;

  /* 页面与容器 */
  --bg:             #F4F6FB;
  --surface:        #FFFFFF;   /* 卡片、弹窗、输入框 */
  --surface-2:      #FAFBFE;   /* 次级容器（表头、代码块） */
  --surface-3:      #F2F2F7;   /* 三级容器（灰色药丸、只读区） */
  --surface-hover:  #F7F9FD;
  --border:         #E4E9F2;
  --border-strong:  #D2DAE8;

  --text:           #16233A;
  --text-2:         #47536B;
  --text-3:         #7A879E;

  /* 侧边栏（后台左侧导航）：默认是深色块。
     单独一组变量而不是复用 --brand，因为有的皮肤品牌色很浅（素纸白），
     浅色侧边栏配浅色文字会完全看不见。 */
  --side-bg:        #0E2A5C;
  --side-bg-2:      #0A1F45;
  --side-text:      #DCE6F7;
  --side-text-dim:  #8FAEDC;
  --side-text-3:    #7B9BD0;
  --side-hover:     rgba(255, 255, 255, .07);
  --side-active-bg: rgba(255, 255, 255, .14);

  /* 前台深色头部（与侧边栏同源，但透明度不同） */
  --head-bg:        #123C86;
  --head-text:      #FFFFFF;
  --head-text-dim:  #C3D5EE;

  --radius:         14px;
  --radius-sm:      10px;
  --radius-xs:      8px;
  --radius-pill:    999px;

  --shadow-sm:  0 1px 2px rgba(22, 35, 58, .06);
  --shadow:     0 4px 16px rgba(22, 35, 58, .07);
  --shadow-lg:  0 12px 40px rgba(22, 35, 58, .12);

  --sidebar-w:  248px;
  --topbar-h:   62px;

  --font: -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB",
          "Microsoft YaHei", "Source Han Sans SC", "Noto Sans CJK SC", "Helvetica Neue", Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

  /* 浏览器自绘控件的配色（滚动条、日期选择器、表单默认样式）。
     不声明这个，深色皮肤下日历弹层仍是白底黑字，很跳。 */
  color-scheme: light;
}


/* --------------------------------------------------------------------------
   2. 重置
   -------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

/* [hidden] 必须真的隐藏 —— 这条不加会踩一个很隐蔽的坑。
   浏览器自带的 [hidden]{display:none} 属于 UA 样式表，优先级**低于**任何
   作者样式；只要组件写了 `.xxx { display: flex }`，`<div class="xxx" hidden>`
   就依然会显示出来。实测踩过：状态打卡卡片里「休假截止」那一行本来靠
   hidden 默认收起（只有点了「休假」才由 JS 展开），结果因为 `.sq-vac`
   写了 display:flex，**每次打开工作台都白占一行**，还带一个空的日期框 ——
   正是"一行就能填完的信息被撑得要滚动"的那类毛病。
   站点里所有显隐切换都是用 `el.hidden = true/false` 这个属性做的
   （没有一处用 style.display），所以 !important 不会和 JS 打架：
   属性一去掉，规则自然失效。 */
[hidden] { display: none !important; }

html {
  -webkit-text-size-adjust: 100%;
  -webkit-tap-highlight-color: transparent;
}

body {
  margin: 0;
  font-family: var(--font);
  font-size: 14px;
  line-height: 1.65;
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

h1, h2, h3, h4, h5, h6 { margin: 0; font-weight: 700; line-height: 1.35; color: var(--text); }
h1 { font-size: 24px; }
h2 { font-size: 19px; }
h3 { font-size: 16px; }
p  { margin: 0 0 10px; }

a { color: var(--brand); text-decoration: none; }
a:hover { color: var(--brand-dark); }

img { max-width: 100%; display: block; }
ul, ol { margin: 0; padding-left: 20px; }

button, input, select, textarea { font: inherit; color: inherit; }
button { cursor: pointer; }

table { border-collapse: collapse; width: 100%; }

::-webkit-scrollbar { width: 9px; height: 9px; }
::-webkit-scrollbar-thumb { background: #C6D0E0; border-radius: 6px; }
::-webkit-scrollbar-thumb:hover { background: #A9B6CC; }
::-webkit-scrollbar-track { background: transparent; }

/* --------------------------------------------------------------------------
   3. 应用外壳
   -------------------------------------------------------------------------- */
.app { display: flex; min-height: 100vh; }

/* --- 侧边栏（桌面端） --- */
.sidebar {
  width: var(--sidebar-w);
  flex: 0 0 var(--sidebar-w);
  background: linear-gradient(178deg, var(--brand-darker) 0%, #123566 55%, #143A72 100%);
  color: #D3E0F5;
  display: flex;
  flex-direction: column;
  position: sticky;
  top: 0;
  height: 100vh;
  z-index: 60;
  /* ── v2.23 需求 2：整列一起滚 ────────────────────────────────────────
     用户原话：「后台左上角搜索和功能地图不要一直悬浮啊，下拉页面时和
               其他板块一样上下滑动」。

     改造前是「侧栏 flex 纵向 + .sidebar-nav 自己 overflow-y:auto」：
     品牌区、搜索框、功能地图都在滚动容器**外面**，于是菜单滚到底，
     这三块钉在原地看着像悬浮 —— 正是被指出的那一条。
     现在把滚动权收回到 .sidebar 本身（.sidebar-nav 改成自然高度），
     品牌 / 搜索 / 功能地图 / 菜单全部在同一条滚动流里，一路滚。

     overflow-x: hidden 是必须的：搜索结果的浮层 .ss-pop 虽然 left/right:0，
     但它的阴影与 focus 描边会往外溢 2~3px，不裁掉侧栏会出现一条细缝。 */
  overflow-y: auto;
  overflow-x: hidden;
  overscroll-behavior: contain;
}
.sidebar::-webkit-scrollbar { width: 5px; }
.sidebar::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, .14); border-radius: 3px; }


.sidebar-brand {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 18px 18px 16px;
  border-bottom: 1px solid rgba(255, 255, 255, .09);
}

/* ── v2.19 需求 2：品牌区整块是个链接（点回后台首页）──────────────────
   上面的 <a> 是 includes/layout.php 的 render_sidebar() 输出的。
   这里只做三件事：
   ① 把浏览器给 <a> 的蓝色和下载划线剥掉 —— 侧栏是深底，默认蓝紫链接在
      深底上又暗又脏，队名会从"品牌"变成"一串可点的字"；
   ② 给一个极轻的 hover 底纹，让"可点"这件事在鼠标进来的一刻成立；
      不用位移/缩放，品牌区紧贴侧栏顶边，一动就像整列在抖；
   ③ focus-visible 走 --ring 描边，键盘 Tab 到它时有可见焦点（色弱/键盘用户）。 */
.sidebar-brand,
.sidebar-brand:hover,
.sidebar-brand:focus { color: inherit; text-decoration: none; }

.sidebar-brand { transition: background-color .16s ease; }

.sidebar-brand:hover {
  background: rgba(255, 255, 255, .05);
}

.sidebar-brand:focus-visible {
  outline: 2px solid var(--ring, #4B8CF5);
  outline-offset: -2px;
}

.sidebar-brand:active { background: rgba(255, 255, 255, .08); }

.sidebar-logo {
  width: 38px; height: 38px;
  flex: 0 0 38px;
  border-radius: 11px;
  background: var(--brand-grad);
  display: grid; place-items: center;
  box-shadow: 0 4px 12px rgba(29, 95, 208, .45);
}

.sidebar-logo svg { width: 22px; height: 22px; }

.sidebar-brand-txt { min-width: 0; }
.sidebar-brand-txt strong {
  display: block;
  color: #fff;
  font-size: 15px;
  letter-spacing: .3px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.sidebar-brand-txt span {
  display: block;
  font-size: 11px;
  color: var(--side-text-dim);
  letter-spacing: 1.4px;
  text-transform: uppercase;
}

/* v2.23 需求 2：菜单不再是滚动容器（滚动交给 .sidebar 整列），
   这里只保留内边距。flex:0 0 auto 让它按内容长高，把剩余高度让给滚动条。 */
.sidebar-nav { flex: 0 0 auto; overflow: visible; padding: 12px 10px 20px; }

.nav-group { margin-bottom: 6px; }
.nav-group-title {
  font-size: 11px;
  letter-spacing: 1.2px;
  color: var(--side-text-3);
  padding: 12px 12px 6px;
  font-weight: 600;
}

.nav-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 9.5px 12px;
  border-radius: var(--radius-sm);
  color: var(--head-text-dim);
  font-size: 13.5px;
  margin-bottom: 2px;
  transition: background .16s, color .16s;
  position: relative;
}
.nav-item:hover { background: rgba(255, 255, 255, .08); color: #fff; }
.nav-item.active {
  background: linear-gradient(90deg, rgba(63, 139, 255, .32), rgba(63, 139, 255, .12));
  color: #fff;
  font-weight: 600;
}
.nav-item.active::before {
  content: "";
  position: absolute; left: 0; top: 50%;
  transform: translateY(-50%);
  width: 3px; height: 18px;
  border-radius: 0 3px 3px 0;
  background: #5B9CFF;
}
.nav-item svg { width: 17px; height: 17px; flex: 0 0 17px; opacity: .9; }
.nav-item .nav-badge {
  margin-left: auto;
  background: var(--accent);
  color: #fff;
  font-size: 10.5px;
  font-weight: 700;
  padding: 1px 6px;
  border-radius: var(--radius-pill);
  line-height: 1.6;
}

/* 一级菜单（点击展开二级） */
.nav-head {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border: 0;
  background: transparent;
  border-radius: var(--radius-sm);
  color: #D8E4F5;
  font-size: 13.5px;
  font-weight: 600;
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  transition: background .16s, color .16s;
  margin-bottom: 2px;
  text-decoration: none;
}
.nav-head:hover { background: rgba(255, 255, 255, .08); color: #fff; }
.nav-head.active { background: rgba(63, 139, 255, .28); color: #fff; }
.nav-head .nav-head-ico { display: inline-flex; flex: 0 0 17px; opacity: .92; }
.nav-head .nav-head-txt { flex: 1; }
.nav-head .nav-caret { display: inline-flex; opacity: .55; transition: transform .2s; }
.nav-group.is-open > .nav-head .nav-caret { transform: rotate(180deg); }
.nav-head .nav-badge {
  background: var(--accent);
  color: #fff;
  font-size: 10.5px;
  font-weight: 700;
  padding: 1px 6px;
  border-radius: var(--radius-pill);
  line-height: 1.6;
}

/* 二级菜单：默认收起，一级展开时才显示 */
.nav-sub {
  display: none;
  margin: 0 0 6px 20px;
  padding-left: 8px;
  border-left: 1px solid rgba(255, 255, 255, .14);
}
.nav-group.is-open > .nav-sub { display: block; }
.nav-sub .nav-item {
  padding: 8px 10px;
  font-size: 13px;
  margin-bottom: 1px;
}
.nav-sub .nav-item.active::before { display: none; }
.nav-sub .nav-item.active {
  background: linear-gradient(90deg, rgba(63, 139, 255, .36), rgba(63, 139, 255, .10));
}

.sidebar-foot {
  padding: 12px 16px;
  border-top: 1px solid rgba(255, 255, 255, .09);
  font-size: 11.5px;
  color: var(--side-text-3);
}

/* --- 主区域 --- */
.main { flex: 1; min-width: 0; display: flex; flex-direction: column; }

.topbar {
  height: var(--topbar-h);
  background: rgba(255, 255, 255, .92);
  backdrop-filter: saturate(180%) blur(12px);
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 0 20px;
  position: sticky;
  top: 0;
  z-index: 40;
}

/* overflow: hidden —— 标题被挤到 0 宽时**必须裁掉**，不能让它溢出去。
   实测（1440 视口，admin/member_import.php，需求 #13 加了两个模板按钮之后）：
     .topbar-title  clientW=0  scrollW=24   ← 盒子被挤成 0 宽，内容却还占 24px
     .crumb         overflow-x: visible     ← 里面的面包屑没有任何裁切
   min-width:0 只保证"盒子可以被压小"，压小之后内容仍然会溢出（overflow 默认 visible），
   于是这 24px 直接算进 .topbar 的 scrollWidth，一路把 .main / .app 撑宽 ——
   整页横向滚动条就是这么来的。h1 自己有省略号，crumb 没有，所以漏点在 crumb 上；
   给容器加 overflow:hidden 比给每个子元素加更稳（将来再加子元素也不会漏）。 */
/* flex-shrink: 999 —— 空间不够时**先压标题**，再去压右边的动作条。
   这是原有设计已经定下的取舍，别顺手改掉：改前实测 1280 视口，
   标题本来就是 0 宽、页面横向溢出 2px。v2.5.0 补修只是把"溢出 2px"
   换成"页面级按钮条自己横滑"，优先级不动。
   为什么必须显式写：收缩量按 flex-shrink × 基准尺寸 分摊，两边都是默认的 1 时，
   标题和按钮条会各让一半 —— 实测 1280 下按钮条只剩 148px、三个按钮看不到两个。
   给标题 999 之后它一路缩到 0（min-width:0 + 省略号），缺额才轮到按钮条，
   实测 1280 下按钮条保住 278/299，只切掉最右 20px。 */
.topbar-title { min-width: 0; overflow: hidden; flex-shrink: 999; }
.topbar-title h1 {
  font-size: 17px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* 面包屑同样要走省略号，不能靠容器 overflow:hidden 硬切。
   容器裁切只保证"不溢出"，切在半个汉字上时渲染出来是残缺笔画，看着像页面坏了。
   nowrap + ellipsis 之后显示成「先预览，确认无误后…」，一眼就懂是"位置不够"。
   实测 1440 视口、admin/member_import.php：标题区只剩 146px，
   这一行原来正好被切在字中间。 */
.topbar-title .crumb {
  font-size: 12px;
  color: var(--text-3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* flex: 0 1 auto + 全局控件逐个 0 0 auto —— 动作区只在"只剩页面级按钮条可压"时收缩。
   两段历史都记在这里，免得后人改回去：
     · v2.4 之前写的是 flex: 0 0 auto（完全不收缩）。当时的理由是：默认 flex-shrink:1
       时它会被左边那截标题压扁，而里面的图标、头像、下拉箭头都不能再变窄，
       压缩的结果不是"变紧凑"而是**内容溢出**。实测（390 宽、需求 #12 加了头像区
       下拉箭头之后）：.topbar-actions 盒子 190px、内容 215px；.user-menu 盒子 46px、
       内容 71px；span.uc-caret right=403 > 390 —— 整页横向滚动条就是它撑出来的，
       连带 position:fixed 的底部导航与抽屉遮罩按溢出后的宽度重排，看着像"整页错位"。
     · 但"永不收缩"在宽屏上会撑宽整页（v2.5.0 实测 admin/member_import.php）：
       侧栏 ≥1025 固定 248px，全局控件最小 701px，页面级按钮条 299px
       → 整条顶栏的最小宽度就是 1280px。于是视口 1101 溢出 179px、
       1150 溢出 130px、1200 溢出 80px、1280 恰好卡死（通知角标多一位数字就 2px）。
   两难的解法在 .ta-page：只有它挂 .is-scrollable 时可收缩（min-width:0 +
   overflow-x:auto，自己横滑），于是被压的永远是这段页面级按钮条，
   右侧全局控件的位置与尺寸一个都不动。 */
.topbar-actions { margin-left: auto; display: flex; align-items: center; gap: 10px; flex: 0 1 auto; min-width: var(--ta-floor, auto); }
/* min-width 取 --ta-floor（由 app.js 的 syncTopbarBars() 实测后写到 .topbar 上，
   ≤640 那格用的是同一个变量名、同一个语义），取不到时回落 auto。
   这一格值不值得这么绕，是踩了两次才定的：
     ① flex: 0 0 auto（完全不收缩）—— 宽屏会撑宽整页：侧栏 ≥1025 固定 248px，
        全局控件最小 701px，页面级按钮条 299px，顶栏最小宽度就是 1280px。
        实测视口 1101 溢出 179px、1200 溢出 80px、1280 恰好卡死。
     ② min-width: 0（让盒子随便缩）—— 窄一点的宽屏上盒子被压得比全局控件还窄。
        子项是 flex:0 0 auto 不再缩，就从盒子右边溢出去：实测视口 900，
        .topbar-actions 盒子 511.52px、子项合计 565.52px，最后那个 .user-menu
        被挤出视口 54px，整页照样出现横向滚动条。
     ③ 现在这版：下限 = "全局控件之和 + 间距"，由 JS 量出来。于是被压的永远是
        页面级按钮条（挂 .is-scrollable 时自己横滑），全局控件的位置与尺寸
        一个都不动，也不必为每个断点手算一个魔法数。
   另外要记住：.ta-page 的**最小内容**（三个按钮各自最长不可断词之和，实测 210px）
   会算进盒子默认 min-width:auto 的"自动最小尺寸"里 —— 这正是 ② 之前动作区卡在
   911.41px 再也下不去的原因，所以下限必须显式写，不能指望默认值。 */
/* 全局控件永不压缩。默认的 min-width:auto 其实也拦得住，但那是"内容不肯变小"，
   不是"盒子不许变小"—— 哪天某个控件自己加了 min-width:0 就会漏，显式写死更稳。 */
.topbar-actions > .sound-wrap,
.topbar-actions > .skin-wrap,
.topbar-actions > .topbar-new-mission,
.topbar-actions > .top-quick,
.topbar-actions > .user-menu { flex: 0 0 auto; }

.hamburger {
  display: none;
  width: 38px; height: 38px;
  border: 1px solid var(--border);
  background: var(--surface);
  border-radius: var(--radius-sm);
  align-items: center;
  justify-content: center;
  flex: 0 0 38px;
}
.hamburger span {
  display: block; width: 16px; height: 2px;
  background: var(--text-2); border-radius: 2px; position: relative;
}
.hamburger span::before, .hamburger span::after {
  content: ""; position: absolute; left: 0;
  width: 16px; height: 2px; background: var(--text-2); border-radius: 2px;
}
.hamburger span::before { top: -5px; }
.hamburger span::after  { top: 5px; }

.content { padding: 20px; flex: 1; }
.content-narrow { max-width: 900px; }

/* --- 用户头像菜单 --- */
.user-chip {
  display: flex; align-items: center; gap: 9px;
  padding: 5px 10px 5px 5px;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--surface);
  transition: box-shadow .16s, border-color .16s;
}
.user-chip:hover { box-shadow: var(--shadow-sm); border-color: var(--border-strong); }
.user-chip img { width: 30px; height: 30px; border-radius: 50%; object-fit: cover; }
.user-chip .uc-name { font-size: 13px; font-weight: 600; line-height: 1.2; }
.user-chip .uc-role { font-size: 11px; color: var(--text-3); line-height: 1.2; }
.user-chip .uc-caret { display: inline-flex; color: var(--text-3); transition: transform .16s; }

/* --- 头像下拉菜单（需求 #12）：鼠标悬停展开，触屏点按展开 --- */
/* flex: 0 0 auto 同上：头像 + 姓名 + 角标 + 下拉箭头是一组不可再压缩的控件，
   被压扁只会把箭头挤到视口外（实测 390 宽下溢出 14px）。 */
.user-menu { position: relative; flex: 0 0 auto; }
.user-menu .uc-dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--danger); flex: 0 0 7px; margin-left: -3px;
}
.um-panel {
  position: absolute; top: calc(100% + 10px); right: 0;
  width: 268px; max-width: calc(100vw - 24px);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 6px;
  z-index: 60;
  /* 默认藏起来：用 opacity + visibility 而不是 display，
     这样展开/收起能有一段短过渡；直接 display 切换会「啪」地闪一下。 */
  opacity: 0; visibility: hidden;
  transform: translateY(-6px);
  transition: opacity .16s, transform .16s, visibility .16s;
}
/* 面板顶到视口右边时不留 8px 空地，看着像贴边断了 */
.um-panel::before {
  content: ''; position: absolute; top: -6px; right: 22px;
  width: 10px; height: 10px;
  background: var(--surface);
  border-left: 1px solid var(--border); border-top: 1px solid var(--border);
  transform: rotate(45deg);
}
.user-menu.is-open .um-panel,
.user-menu:hover .um-panel { opacity: 1; visibility: visible; transform: translateY(0); }
.user-menu.is-open .uc-caret,
.user-menu:hover .uc-caret { transform: rotate(180deg); }

.um-head {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 10px 11px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 5px;
}
.um-head img { width: 38px; height: 38px; border-radius: 50%; object-fit: cover; flex: 0 0 38px; }
.um-head div { min-width: 0; }
.um-head b { display: block; font-size: 14px; color: var(--text); line-height: 1.3; }
.um-head small { display: block; font-size: 11.5px; color: var(--text-3); margin-top: 2px; }

.um-item {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 10px; border-radius: var(--radius-sm);
  color: var(--text-2); text-decoration: none;
  transition: background .14s, color .14s;
}
.um-item:hover { background: var(--surface-hover); color: var(--text); }
.um-item.is-danger:hover { background: var(--danger-light); color: var(--danger-ink); }
.um-ico { display: inline-flex; flex: 0 0 16px; color: var(--text-3); }
.um-item:hover .um-ico { color: var(--brand); }
.um-item.is-danger:hover .um-ico { color: var(--danger); }
.um-txt { min-width: 0; flex: 1; }
.um-txt b { display: block; font-size: 13px; font-weight: 600; line-height: 1.35; }
.um-txt small { display: block; font-size: 11px; color: var(--text-3); margin-top: 1px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.um-badge {
  flex: 0 0 auto; min-width: 18px; height: 18px; padding: 0 5px;
  border-radius: 9px; background: var(--danger); color: var(--fixed-white);
  font-size: 11px; font-weight: 700; line-height: 18px; text-align: center;
}
.um-sep { height: 1px; background: var(--border); margin: 5px 6px; }

/* ★ v2.24 需求 10：头像下拉里的「我的档案」变成一个**带右侧胶囊**的复合行。
   外层不再是 <a>（HTML 不允许链接套链接），所以 hover 底色要挪到主链接上，
   否则鼠标停在胶囊上时整行一起亮、看着像能点两下。 */
.um-item.is-split { padding: 0; gap: 0; }
.um-item.is-split:hover { background: transparent; color: var(--text-2); }
.um-item.is-split .um-main {
  display: flex; align-items: center; gap: 10px;
  flex: 1 1 auto; min-width: 0;
  padding: 8px 0 8px 10px;
  color: inherit; text-decoration: none;
  border-radius: var(--radius-sm) 0 0 var(--radius-sm);
  transition: background .14s, color .14s;
}
.um-item.is-split .um-main:hover { background: var(--surface-hover); color: var(--text); }
.um-go {
  flex: 0 0 auto; margin-right: 8px;
  padding: 3px 9px; border-radius: 999px;
  border: 1px solid var(--border); background: transparent;
  font-size: 11px; line-height: 1.5; font-weight: 600;
  color: var(--brand); text-decoration: none; white-space: nowrap;
  transition: background .14s, border-color .14s;
}
.um-go:hover { background: var(--surface-hover); border-color: var(--brand); }

/* --- 底部导航（仅移动端） --- */
.bottom-nav {
  display: none;
  position: fixed;
  left: 0; right: 0; bottom: 0;
  height: 58px;
  padding-bottom: env(safe-area-inset-bottom, 0);
  background: rgba(255, 255, 255, .96);
  backdrop-filter: blur(14px);
  border-top: 1px solid var(--border);
  z-index: 55;
}
.bottom-nav a {
  flex: 1;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: 3px;
  color: var(--text-3);
  font-size: 10.5px;
}
.bottom-nav a svg { width: 19px; height: 19px; }
.bottom-nav a.active { color: var(--brand); font-weight: 600; }

/* --- 移动端抽屉 --- */
.drawer-mask {
  display: none;
  position: fixed; inset: 0;
  background: rgba(12, 28, 56, .48);
  z-index: 58;
  opacity: 0;
  transition: opacity .22s;
}
.drawer-mask.show { opacity: 1; }

/* --------------------------------------------------------------------------
   4. 卡片
   -------------------------------------------------------------------------- */
.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  margin-bottom: 12px;
  overflow: hidden;
}

.card-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 14px;
  border-bottom: 1px solid var(--border);
  background: var(--surface);
  flex-wrap: wrap;
}
.card-head h2, .card-head h3 { font-size: 15px; }
.card-head .card-sub { font-size: 12px; color: var(--text-3); }
.card-head .head-actions { margin-left: auto; display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }

/* 卡片内边距与卡片间距一起收紧（需求 #6）。
   为什么连 .card 的 margin-bottom 也动：后台页面普遍是「一张卡一件事」，
   卡片之间的 16px 空隙在十张卡的页面上就是 160px 的纯空白。 */
.card-body { padding: 14px; }
.card-body.tight { padding: 0; }

.card-foot {
  padding: 10px 14px;
  border-top: 1px solid var(--border);
  background: var(--surface-2);
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
}

/* --------------------------------------------------------------------------
   5. 统计卡
   -------------------------------------------------------------------------- */
.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 14px;
  margin-bottom: 16px;
}

/* 五张卡同排（党团妇工概览加了「退伍军人」之后，需求 5）。
   默认的 minmax(190px,1fr) 在约 1000px 的内容区里放不下 5 列
   （5×190 + 4×14 = 1006 > 1000），第 5 张会单独掉到第二行占一整行宽，
   看起来像"多出来的一张"。收紧到 168px 即可 5 列同排 ——
   这些卡片是「小标题 + 一个大数字 + 一行脚注」，
   168px 放得下最长的「共青团员 50 人」，不必为了凑整数把某张卡拆走。 */
.stat-grid.cols-5 { grid-template-columns: repeat(auto-fit, minmax(168px, 1fr)); }

.stat {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px 18px;
  box-shadow: var(--shadow-sm);
  position: relative;
  overflow: hidden;
}
.stat::after {
  content: "";
  position: absolute; right: -22px; top: -22px;
  width: 78px; height: 78px; border-radius: 50%;
  background: var(--brand-lighter);
}
.stat > * { position: relative; z-index: 1; }
.stat .stat-label { font-size: 12.5px; color: var(--text-3); display: flex; align-items: center; gap: 6px; }
.stat .stat-value {
  font-size: 27px; font-weight: 800; letter-spacing: -.5px;
  color: var(--text); line-height: 1.25; margin-top: 4px;
  font-variant-numeric: tabular-nums;
}
.stat .stat-value small { font-size: 13px; font-weight: 600; color: var(--text-3); margin-left: 3px; }
.stat .stat-foot { font-size: 12px; color: var(--text-3); margin-top: 4px; }
.stat.is-ok::after    { background: var(--ok-light); }
.stat.is-warn::after  { background: var(--warn-light); }
.stat.is-danger::after{ background: var(--danger-light); }
.stat.is-brand::after { background: var(--brand-light); }
.stat.is-muted::after { background: var(--border); }
/* info（青蓝）是需求 5 给「退伍军人」定的色，与 badge-info /
   tone-info 同源。加这一行是为了让第 5 张概览卡右上角那圈淡色底
   也走同一色系 —— 少了它，卡片其它地方是青蓝、水印却是默认的蓝。 */
.stat.is-info::after  { background: var(--info-light); }

/* 统计卡也当筛选入口用（任务管理页：点「已归档」直接跳到该状态的列表）。
   锚点版 .stat 必须显式抹掉下划线与链接色，否则整张卡会变蓝字带下划线。 */
a.stat {
  display: block;
  text-decoration: none;
  color: inherit;
  transition: border-color .16s ease, box-shadow .16s ease, transform .16s ease;
}
a.stat:hover {
  border-color: var(--brand);
  box-shadow: var(--shadow);
  transform: translateY(-1px);
}
a.stat:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
  a.stat, a.stat:hover { transition: none; transform: none; }
}

.stat-icon {
  width: 34px; height: 34px; border-radius: 10px;
  display: grid; place-items: center; margin-bottom: 10px;
  background: var(--brand-light); color: var(--brand);
}
.stat-icon svg { width: 18px; height: 18px; }
.stat.is-ok .stat-icon    { background: var(--ok-light); color: var(--ok); }
.stat.is-warn .stat-icon  { background: var(--warn-light); color: var(--warn); }
.stat.is-danger .stat-icon{ background: var(--danger-light); color: var(--danger); }
.stat.is-info .stat-icon  { background: var(--info-light);   color: var(--info); }

/* --------------------------------------------------------------------------
   6. 按钮
   -------------------------------------------------------------------------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 9px 16px;
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  background: var(--brand);
  color: #fff;
  font-size: 13.5px;
  font-weight: 600;
  line-height: 1.2;
  transition: filter .16s, background .16s, box-shadow .16s, border-color .16s;
  white-space: nowrap;
  text-decoration: none;
}
.btn:hover { filter: brightness(1.07); color: #fff; }
.btn:active { transform: translateY(1px); }
.btn svg { width: 15px; height: 15px; }

.btn-ghost { background: var(--surface); color: var(--text-2); border-color: var(--border-strong); }
.btn-ghost:hover { background: var(--brand-lighter); border-color: var(--brand); color: var(--brand); filter: none; }

.btn-soft { background: var(--brand-light); color: var(--brand-dark); }
.btn-soft:hover { background: #DCE8FD; color: var(--brand-dark); filter: none; }

.btn-accent { background: var(--accent); }
.btn-danger { background: var(--danger); }
.btn-ok { background: var(--ok); }
.btn-warn { background: var(--warn); }

.btn-outline-danger { background: transparent; color: var(--danger); border-color: var(--danger-border); }
.btn-outline-danger:hover { background: var(--danger-light); color: var(--danger); filter: none; }
.btn-outline-ok { background: transparent; color: var(--ok); border-color: var(--ok-border); }
.btn-outline-ok:hover { background: var(--ok-light); color: var(--ok); filter: none; }

.btn-sm { padding: 6px 11px; font-size: 12.5px; border-radius: var(--radius-xs); }
.btn-sm svg { width: 13px; height: 13px; }
.btn-xs { padding: 3px 9px; font-size: 11.5px; border-radius: var(--radius-xs); font-weight: 500; }
.btn-xs svg { width: 12px; height: 12px; }
.btn-lg { padding: 12px 24px; font-size: 15px; }
.btn-block { width: 100%; }
.btn[disabled], .btn.disabled { opacity: .5; pointer-events: none; }

.btn-group { display: inline-flex; gap: 6px; flex-wrap: wrap; }

/* --------------------------------------------------------------------------
   7. 徽章 / 标签
   -------------------------------------------------------------------------- */
.badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 9px;
  border-radius: var(--radius-pill);
  font-size: 12px;
  font-weight: 600;
  line-height: 1.7;
  white-space: nowrap;
  background: var(--surface-3);
  color: var(--text-2);
}
.badge .dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex: 0 0 6px; }
/* 行内小徽章（需求 #8：技能格子里标记「审 / 驳」）。
   技能格子本身只有 ~118px 宽、里面还有一个复选框和技能名，
   用 12px 的 .badge 会把名字挤成两行、整片格子参差不齐，所以单独给一档 10px。 */
.badge-xs {
  padding: 0 5px;
  font-size: 10px;
  line-height: 16px;
  border-radius: 4px;
  font-weight: 700;
  flex: 0 0 auto;
}
.badge-ok      { background: var(--ok-light);     color: var(--ok-ink); }
.badge-warn    { background: var(--warn-light);   color: var(--warn-ink); }
.badge-danger  { background: var(--danger-light); color: var(--danger-ink); }
.badge-brand   { background: var(--brand-light);  color: var(--brand-dark); }
.badge-info    { background: var(--info-light);   color: var(--info-ink); }
.badge-muted   { background: var(--surface-3);             color: var(--text-3); }
.badge-accent  { background: var(--accent-light); color: var(--accent-ink); }

/* 描边徽标：用于「模板预设了什么」这类信息标签。
   实心徽标成排出现会盖过标题，描边的更轻，适合当注释用。 */
.badge-line {
  background: transparent;
  border: 1px solid var(--border-strong);
  color: var(--text-2);
  font-weight: 500;
}

.badge-on      { background: var(--ok-light);     color: var(--ok-ink); }
.badge-standby { background: var(--brand-light);  color: var(--brand-dark); }
.badge-busy    { background: var(--accent-light); color: var(--accent-ink); }
.badge-off     { background: var(--surface-3);             color: var(--text-3); }

.badge .sub { font-weight: 500; opacity: .8; }

/* v2.22 需求 8：已读 / 未读要看一眼就分出来。
   原来这两处都是**普通灰字**（「· 未读」「· 已读 3 分钟前」），
   在一屏十几条的通知列表里根本挑不出来 —— 未读等于没标。
   现在：
     · 未读 = 实心红底 + 白字 + 一个小圆点，是"要去处理"的信号；
     · 已读 = 绿底浅字，安静地退到背景里。
   两者形状、明度、色相三重不同，色弱用户也能靠深浅分开。 */
.badge-unread {
  background: var(--danger);
  color: #fff;
  box-shadow: 0 0 0 3px rgba(192, 57, 43, .12);
}
.badge-unread::before {
  content: '';
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #fff;
  flex: 0 0 6px;
}
.badge-read {
  background: var(--ok-light);
  color: var(--ok-ink);
  font-weight: 500;
}

/* v2.22 需求 8：通知级别改成"点一下就切换"的胶囊按钮（原来是下拉框）。
   不用 :has() —— 老一点的内核还不支持；把 input 放在 span 前面，
   用 `input:checked + span` 就够了，语义仍是 radio，表单不用动。 */
.prio-pick { display: flex; gap: 8px; flex-wrap: wrap; }
.prio-item { position: relative; display: inline-flex; cursor: pointer; }
.prio-item input {
  position: absolute;
  opacity: 0;
  width: 1px;
  height: 1px;
  margin: 0;
}
.prio-item > span {
  padding: 6px 18px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border-strong);
  background: var(--surface);
  color: var(--text-2);
  font-size: 13px;
  font-weight: 600;
  line-height: 1.6;
  transition: background .12s, border-color .12s, color .12s;
}
.prio-item:hover > span { border-color: var(--brand); color: var(--brand); }
.prio-item input:focus-visible + span { outline: 2px solid var(--brand); outline-offset: 2px; }
.prio-item.is-normal input:checked + span {
  background: var(--info-light);
  border-color: var(--info);
  color: var(--info-ink);
}
.prio-item.is-important input:checked + span {
  background: var(--warn-light);
  border-color: var(--warn);
  color: var(--warn-ink);
}
.prio-item.is-urgent input:checked + span {
  background: var(--danger-light);
  border-color: var(--danger);
  color: var(--danger-ink);
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 10px;
  border-radius: var(--radius-xs);
  background: var(--surface-2);
  border: 1px solid var(--border);
  font-size: 12px;
  color: var(--text-2);
}
.chip-brand { background: var(--brand-lighter); border-color: var(--brand-border); color: var(--brand-dark); }

.tag-list { display: flex; flex-wrap: wrap; gap: 6px; }

/* --------------------------------------------------------------------------
   8. 表单

   ★ 紧凑化（需求 #6，全站默认值）
   用户原话：「后台几乎所有页面的编辑框、上传图片、都占用太大空间了，
   都要缩小下，尽量让用户少滑动鼠标」。
   所以这里统一把"一个字段占的高度"降下来，而不是逐个页面改 ——
   逐个改的代价是几十个页面各写一套，迟早又不一致：
     · 输入框 42px → 36px（padding 9.5/12 → 7/10）
     · 文本域 88px → 68px
     · 栅格列宽 230px → 200px、行距 14/16 → 10/12（同样宽度能多放一列）
     · 字段内 label 与控件的间隙 6px → 4px
   一条纪律：**窄屏要还回去**。36px 在手机上低于可点尺寸，
   所以下面的 700px 媒体查询把输入框恢复到 40px，
   桌面省下的空间不能再拿手机的可点性去换。
   -------------------------------------------------------------------------- */
.form-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 10px 12px;
}
.form-grid .span-2 { grid-column: span 2; }
.form-grid .span-3 { grid-column: span 3; }

.field { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.field > label {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text-2);
}
.field > label .req { color: var(--danger); margin-left: 2px; }
.field .hint { font-size: 11.5px; color: var(--text-3); }
/* 警示型提示（如任务指挥下拉里「暂停职务中，不可任命：×××」）。
   必须写成 .field .hint.hint-danger 才能压过上面那条 —— 只写 .hint-danger
   （单个类选择器）优先级更低，颜色会被 .field .hint 吃掉，静默失效。 */
.field .hint.hint-danger { color: var(--danger); font-weight: 600; }

.input, .select, .textarea {
  width: 100%;
  padding: 7px 10px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
  font-size: 13.5px;
  transition: border-color .16s, box-shadow .16s;
  min-height: 36px;
}
.textarea { min-height: 68px; resize: vertical; line-height: 1.6; }
.select {
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5L6 6.5L11 1.5' stroke='%237A879E' stroke-width='1.8' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  padding-right: 34px;
}
.input:focus, .select:focus, .textarea:focus {
  outline: none;
  border-color: var(--brand);
  box-shadow: 0 0 0 3px rgba(29, 95, 208, .12);
}
.input[readonly], .input[disabled], .select[disabled] {
  background: #F5F7FA;
  color: var(--text-3);
}

.form-section-title {
  /* ★ 需求 6（本轮，手机版设置页错位的根因）：这一行必须**自成一个 BFC**。
     标题右侧的 .fst-note 是 float:right。标题自己是普通块级盒子时不收浮动，
     浮动就溢到标题外面；而它的下一个兄弟通常是 .form-grid（display:grid，
     按规范**会新建 BFC**），BFC 盒子的 border box 必须避开同一格式化上下文里的
     浮动 —— 于是它的宽度被按浮动宽度扣掉。
     说明文字长到换行时会占满整行宽度（例：系统设置「审批与财务」页的
     「通用审批」，标题 336px、浮动也 336px），扣完就是 **0**：
     表单在手机上塌成一条竖线、文字逐字换行，整块高到几千像素。
     实测：把标题改成 flow-root（收纳自己的浮动）后宽度立刻回到 336px。
     display:flow-root 不改变盒子的外观与尺寸（只加"包住浮动"这一条），
     所以桌面端一行排得下的说明文字，版式与改动前逐像素一致。 */
  display: flow-root;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--brand-dark);
  padding: 2px 0 0;
  border-bottom: 1px dashed var(--border-strong);
  margin: 4px 0 0;
  grid-column: 1 / -1;
}

/* 窄屏把可点高度还回去。
   桌面 36px 的输入框在手机上偏矮，手指点不准会误触到相邻字段 ——
   而手机上"滑动"本来就不像鼠标那么费劲，用户抱怨的是 PC 端一屏放不下几个字段。
   所以这一条是「桌面省空间、手机保可点性」，不是两边一起缩。 */
@media (max-width: 700px) {
  .input, .select, .textarea { min-height: 40px; padding: 8px 11px; font-size: 14px; }
  .textarea { min-height: 82px; }
  .form-grid { gap: 12px; }
  .check-grid { grid-template-columns: repeat(auto-fill, minmax(132px, 1fr)); gap: 8px; }
  .check-item { padding: 9px 11px; font-size: 13px; }
}

/* 多选技能（需求 #6：一屏多放几项，减少滚动） */
.check-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(118px, 1fr));
  gap: 6px;
}
.check-item {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 9px;
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
  background: var(--surface-2);
  font-size: 12.5px;
  cursor: pointer;
  transition: border-color .15s, background .15s;
}
.check-item:hover { border-color: var(--brand); }
.check-item input { width: 15px; height: 15px; accent-color: var(--brand); flex: 0 0 15px; }
.check-item.checked { background: var(--brand-light); border-color: var(--brand); color: var(--brand-dark); font-weight: 600; }
/* 名额已满时被灰掉的选项（需求 #78-7：求救推送队员上限 10 人）。
   用虚线边框 + 去饱和告诉人「不是坏了，是选满了」，不要用 display:none ——
   选项凭空消失会让人以为名单里没有这个人。 */
.check-item.is-dim {
  opacity: .45;
  cursor: not-allowed;
  border-style: dashed;
  background: var(--surface-2);
  color: var(--text-3);
}
.check-item.is-dim:hover { border-color: var(--border); }

/* --------------------------------------------------------------------------
   分段单选（需求 #12 个人装备的三态：未填写 / 有 / 没有）
   --------------------------------------------------------------------------
   为什么不用 .radio-cards：那是卡片式单选，一屏放得下 3~4 张卡，适合
   「选择出勤状态」这种一次只选一组的场景；个人装备页一屏有十几条、
   每条三个选项，卡片会占满整屏。这里做成一条紧凑的胶囊分段。

   选中态用 :has() 而不是靠 JS 加 class：
   原生 radio 一进 DOM 就是可用状态，不必等 app.js 执行完才有高亮 ——
   首屏闪一下无色、或者 JS 报错就永远不高亮，都是很难看出来的坏法。
   键盘可达性由原生 radio 保证（视觉上用 opacity 藏起来但仍在 DOM 里）。 */
.seg-group {
  display: inline-flex;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-pill);
  background: var(--surface);
  overflow: hidden;
  align-self: flex-start;
}
.seg-group > label {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 60px;
  padding: 6px 12px;
  font-size: 12.5px;
  line-height: 1.4;
  color: var(--text-3);
  cursor: pointer;
  user-select: none;
  transition: background .15s, color .15s;
}
.seg-group > label + label { border-left: 1px solid var(--border); }
.seg-group > label:hover { background: var(--surface-hover); color: var(--text-2); }
.seg-group input {
  position: absolute;
  opacity: 0;
  width: 1px; height: 1px;
  margin: 0;
}
.seg-group > label:has(input:checked) {
  background: var(--brand-light);
  color: var(--brand-dark);
  font-weight: 600;
  box-shadow: inset 0 0 0 1px var(--brand-border);
}
.seg-group > label:has(input:focus-visible) { outline: 2px solid var(--brand); outline-offset: -2px; }
@media (max-width: 640px) {
  .seg-group { width: 100%; }
  /* 三态分段是「有 / 没有」这种一步就能点错的选择，窄屏上必须够大够好按。
     实测原来的 8px 上下留白只有约 35px 高，手指很容易点偏到隔壁 ——
     这里把纵向留白加到 10px、字号提到 13px，实际高度约 40px。 */
  .seg-group > label { flex: 1 1 0; min-width: 0; padding: 10px 6px; font-size: 13px; }
}

/* 权限勾选面板（角色与权限页） */
.cap-groups {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(255px, 1fr));
  gap: 12px;
  margin-top: 12px;
  transition: opacity .2s;
}
.cap-group {
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--surface);
}
.cap-group-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 11px;
  background: var(--surface-2);
  border-bottom: 1px solid var(--border);
  font-size: 13px;
}
.cap-group-head strong { font-size: 13px; }
.cap-group-head .btn { margin-left: auto; }
.cap-group-body { display: flex; flex-direction: column; gap: 5px; padding: 9px; }
.cap-item { font-size: 12.5px; padding: 6px 9px; align-items: flex-start; }
.cap-item > span { display: flex; flex-direction: column; gap: 1px; line-height: 1.35; }
.cap-item .cap-key {
  font-style: normal;
  font-size: 10.5px;
  color: var(--text-3);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-weight: 400;
}
.cap-item.checked .cap-key { color: var(--brand); opacity: .8; }
@media (max-width: 560px) {
  .cap-groups { grid-template-columns: 1fr; }
}

/* 卡片式单选（状态打卡等） */
.radio-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 10px;
}
.radio-card {
  border: 1.5px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 14px 12px;
  text-align: center;
  cursor: pointer;
  background: var(--surface);
  transition: border-color .15s, background .15s, transform .15s;
}
.radio-card:hover { border-color: var(--brand); transform: translateY(-1px); }
.radio-card input { display: none; }
.radio-card .rc-title { font-weight: 700; font-size: 14px; }
.radio-card .rc-desc { font-size: 11.5px; color: var(--text-3); margin-top: 2px; }
.radio-card.checked { border-color: var(--brand); background: var(--brand-lighter); box-shadow: 0 0 0 3px rgba(29, 95, 208, .1); }

.form-actions {
  display: flex;
  gap: 10px;
  align-items: center;
  padding-top: 16px;
  margin-top: 16px;
  border-top: 1px solid var(--border);
  flex-wrap: wrap;
}
.form-actions .right { margin-left: auto; display: flex; gap: 10px; }

/* --------------------------------------------------------------------------
   9. 表格
   -------------------------------------------------------------------------- */
.table-wrap { width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; }

.table { width: 100%; min-width: 640px; font-size: 13.5px; }
.table thead th {
  background: var(--surface-2);
  color: var(--text-2);
  font-weight: 700;
  font-size: 12.5px;
  text-align: left;
  padding: 11px 14px;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
  position: sticky;
  top: 0;
  z-index: 1;
}
.table tbody td {
  padding: 11px 14px;
  border-bottom: 1px solid #EFF2F7;
  vertical-align: middle;
  color: var(--text-2);
}
.table tbody tr:last-child td { border-bottom: none; }
.table tbody tr:hover { background: var(--brand-lighter); }
.table .nowrap { white-space: nowrap; }
.table .col-actions { white-space: nowrap; text-align: right; }
.table .strong { color: var(--text); font-weight: 600; }
.table .num { font-variant-numeric: tabular-nums; }
.table-mini { min-width: 0; font-size: 13px; }

.person-cell { display: flex; align-items: center; gap: 9px; }
.person-cell img { width: 32px; height: 32px; border-radius: 50%; object-fit: cover; flex: 0 0 32px; }
.person-cell .pc-name { font-weight: 600; color: var(--text); line-height: 1.25; }
.person-cell .pc-sub { font-size: 11.5px; color: var(--text-3); line-height: 1.25; }

/* --------------------------------------------------------------------------
   10. 筛选栏
   -------------------------------------------------------------------------- */
.filter-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 12px 10px;
  align-items: flex-end;
}
.filter-bar .field { flex: 1 1 160px; min-width: 140px; }
.filter-bar .field.wide { flex: 1 1 240px; }
.filter-bar .fb-actions { display: flex; gap: 8px; flex: 0 0 auto; }
/* 多选项占满整行时更易点选 */
.filter-bar .field.full { flex: 1 1 100%; }
/* 芯片式多选需要更宽的横向空间，宽屏下允许两列并排 */
.filter-bar .field.chips { flex: 1 1 320px; }

/* 芯片字段比下拉框高，底部对齐会让各字段标签错行；
   报表页筛选区改为顶部对齐，并把操作按钮单独放一行右对齐 */
.filter-bar-tall { align-items: flex-start; }
.filter-bar-tall .fb-actions {
  flex: 1 1 100%;
  justify-content: flex-end;
  margin-top: 4px;
}

/* --------------------------------------------------------------------------
   10.1 芯片式多选（替代 <select multiple>）
   原生多选下拉在移动端与桌面 Chrome 下会渲染成高度重叠的列表框，
   这里改为「隐藏 checkbox + 胶囊标签」，点选直观、触控友好，且保留 name="xx[]" 的提交格式。
   -------------------------------------------------------------------------- */
.msel {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
  padding: 9px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  max-height: 168px;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}
.msel .msel-label {
  flex: 0 0 100%;
  font-size: 11.5px;
  font-weight: 700;
  color: var(--text-3);
  letter-spacing: .02em;
  margin-top: 2px;
}
.msel .msel-label:first-child { margin-top: 0; }
.msel-item { position: relative; display: inline-flex; }
.msel-item input {
  position: absolute;
  width: 1px; height: 1px;
  opacity: 0;
  pointer-events: none;
}
.msel-item > span {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 6px 12px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  color: var(--text-2);
  font-size: 12.5px;
  font-weight: 600;
  line-height: 1.35;
  cursor: pointer;
  user-select: none;
  /* 移动端点击目标不小于 32px，符合触控规范 */
  min-height: 32px;
  transition: border-color .15s, background .15s, color .15s;
}
.msel-item > span:hover { border-color: var(--brand); color: var(--brand); }
/* 芯片右上角的分布计数（"红十字救护员证 9"）。
   纯展示，不参与筛选 —— 所以做成弱化的小字，别让人误以为是筛选条件。 */
.msel-item > span .msel-cnt {
  font-style: normal;
  font-weight: 500;
  font-size: 11px;
  opacity: .6;
  min-width: 10px;
  text-align: right;
}
.msel-item input:checked + span .msel-cnt { opacity: .8; }
.msel-item input:focus-visible + span { box-shadow: 0 0 0 3px rgba(29, 95, 208, .16); }
.msel-item input:checked + span {
  border-color: var(--brand);
  background: var(--brand-light);
  color: var(--brand-dark);
}
.msel-item input:checked + span::before {
  content: "✓";
  font-size: 11px;
  font-weight: 700;
}
.msel-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-top: 6px;
  font-size: 11.5px;
  color: var(--text-3);
}
.link-mini {
  background: none;
  border: none;
  padding: 0;
  color: var(--brand);
  font-size: 11.5px;
  font-weight: 600;
  cursor: pointer;
}
.link-mini:hover { text-decoration: underline; }

/* --------------------------------------------------------------------------
   11. 提示条
   -------------------------------------------------------------------------- */
.alert {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  padding: 12px 15px;
  border-radius: var(--radius-sm);
  font-size: 13.5px;
  margin-bottom: 14px;
  border: 1px solid transparent;
  line-height: 1.6;
}
.alert svg { width: 17px; height: 17px; flex: 0 0 17px; margin-top: 2px; }
/* 需求 #11：工作台上的新人必修提醒。整条就是一个链接，所以要压掉下划线、
   让文字继承提示条的颜色（否则会变成默认的蓝色链接色，跟警示底色打架），
   末位再放一个右箭头示意"可点"。 */
a.alert.train-tip {
  text-decoration: none;
  align-items: center;
  padding: 9px 13px;
  margin-bottom: 12px;
  color: inherit;
  transition: filter .15s;
}
a.alert.train-tip:hover { filter: brightness(.97); }
a.alert.train-tip > svg:last-child { flex: 0 0 15px; margin: 0 0 0 auto; opacity: .65; }
a.alert.train-tip div { margin: 0; }
.alert-ok     { background: var(--ok-light);     border-color: var(--ok-border); color: #0B6B35; }
.alert-warn   { background: var(--warn-light);   border-color: var(--warn-border); color: var(--warn-ink); }
.alert-danger { background: var(--danger-light); border-color: var(--danger-border); color: var(--danger-ink); }
.alert-info   { background: var(--brand-light);  border-color: #C9DCFB; color: var(--brand-dark); }
.alert .alert-close { margin-left: auto; background: none; border: none; font-size: 17px; color: inherit; opacity: .5; padding: 0 2px; }
.alert .alert-close:hover { opacity: 1; }

/* --------------------------------------------------------------------------
   12. 空状态
   -------------------------------------------------------------------------- */
.empty { text-align: center; padding: 46px 20px; color: var(--text-3); }
.empty svg { width: 46px; height: 46px; opacity: .35; margin: 0 auto 12px; }
.empty p { font-size: 13.5px; margin: 0; }

/* --------------------------------------------------------------------------
   13. 分页
   -------------------------------------------------------------------------- */
.pager {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  padding: 14px 18px;
  border-top: 1px solid var(--border);
}
.pager-info { font-size: 12.5px; color: var(--text-3); }
.pager-btns { margin-left: auto; display: flex; gap: 5px; flex-wrap: wrap; }

/* --------------------------------------------------------------------------
   14. 时间线
   -------------------------------------------------------------------------- */
.timeline { position: relative; padding-left: 22px; }
.timeline::before {
  content: "";
  position: absolute; left: 5px; top: 6px; bottom: 6px;
  width: 2px; background: var(--border);
}
.tl-item { position: relative; padding-bottom: 16px; }
.tl-item:last-child { padding-bottom: 0; }
.tl-item::before {
  content: "";
  position: absolute; left: -21px; top: 5px;
  width: 10px; height: 10px; border-radius: 50%;
  background: var(--surface); border: 2.5px solid var(--brand);
}
.tl-item.is-ok::before   { border-color: var(--ok); }
.tl-item.is-warn::before { border-color: var(--warn); }
.tl-item.is-danger::before { border-color: var(--danger); }
.tl-title { font-size: 13.5px; font-weight: 600; color: var(--text); }
.tl-meta  { font-size: 11.5px; color: var(--text-3); }
.tl-desc  { font-size: 13px; color: var(--text-2); margin-top: 2px; }

/* --------------------------------------------------------------------------
   15. 进度条
   -------------------------------------------------------------------------- */
.bar { height: 7px; border-radius: 4px; background: var(--surface-3); overflow: hidden; }
.bar > i { display: block; height: 100%; border-radius: 4px; background: var(--brand); transition: width .5s; }
.bar.is-ok > i    { background: var(--ok); }
.bar.is-warn > i  { background: var(--warn); }
.bar.is-danger > i{ background: var(--danger); }

.bar-row { display: flex; align-items: center; gap: 10px; }
.bar-row .bar { flex: 1; }
.bar-row .bar-val { font-size: 12.5px; color: var(--text-2); font-variant-numeric: tabular-nums; min-width: 54px; text-align: right; }

/* --------------------------------------------------------------------------
   15b. 专业组分布条形图（需求 #10：个人中心「任务覆盖哪些专业组」）
   --------------------------------------------------------------------------
   纯 CSS 横条，不引图表库。填充条内再叠一层「培训类」段：
   同一根条上，深色是任务、浅色是其中属于培训的部分，一眼看出比例。 */
.gbar-chart { display: flex; flex-direction: column; gap: 7px; }
.gbar-row {
  display: flex; align-items: center; gap: 9px;
  padding: 4px 6px; border-radius: var(--radius-xs);
  transition: background .14s, box-shadow .14s;
}
/* 需求 #9：把鼠标停在哪一行说清楚 —— 一眼看出"这一条"与总量、与最长条的关系。
   只做底色高亮，不做位移/缩放：横条一动，长度就没法比了。 */
.gbar-row:hover { background: var(--surface-2); box-shadow: inset 2px 0 0 var(--brand); }
.gbar-rank {
  flex: 0 0 20px; width: 20px; text-align: right;
  font-size: 11px; font-weight: 700; color: var(--text-3);
  font-variant-numeric: tabular-nums;
}
.gbar-row:hover .gbar-rank { color: var(--brand); }
.gbar-name {
  flex: 0 0 96px; width: 96px; font-size: 12.5px; color: var(--text-2);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.gbar-track {
  flex: 1; height: 16px; border-radius: 5px; background: var(--surface-3);
  overflow: hidden; min-width: 40px;
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, .05);
}
.gbar-fill {
  height: 100%; border-radius: 5px;
  background: var(--brand-grad, var(--brand));
  position: relative; transition: width .5s;
  display: flex; align-items: center; justify-content: flex-end;
}
/* 条内百分比：太短的条（<22%）不标 —— 塞进去只会溢出成半截字 */
.gbar-in {
  padding-right: 6px; font-size: 10px; font-weight: 700; color: #fff;
  letter-spacing: .3px; font-variant-numeric: tabular-nums;
  text-shadow: 0 1px 1px rgba(0, 0, 0, .18);
}
/* 培训类那一段：叠在填充条上，用半透明白 + 斜纹，深色底下也能看清 */
.gbar-train {
  position: absolute; top: 0; right: 0; height: 100%;
  background: repeating-linear-gradient(135deg, rgba(255,255,255,.72) 0 4px, rgba(255,255,255,.34) 4px 8px);
}
.gbar-val {
  flex: 0 0 96px; width: 96px; font-size: 12.5px; color: var(--text-2);
  font-variant-numeric: tabular-nums; text-align: right; white-space: nowrap;
}
.gbar-val small { display: block; font-size: 10.5px; color: var(--text-3); }
.gbar-legend {
  display: flex; flex-wrap: wrap; gap: 14px; margin-top: 9px;
  font-size: 11.5px; color: var(--text-3);
}
.gbar-legend span { display: inline-flex; align-items: center; gap: 5px; }
.gbar-legend .lg { width: 12px; height: 8px; border-radius: 2px; display: inline-block; }
.gbar-legend .lg-task  { background: var(--brand-grad, var(--brand)); }
.gbar-legend .lg-train { background: var(--brand); opacity: .42; }
.gbar-legend .gbar-tip { margin-left: auto; opacity: .75; }

@media (max-width: 700px) {
  /* 手机上把名字列压窄、数值列改到第二行，避免横条被挤成几像素长 */
  .gbar-row  { gap: 7px; padding: 3px 4px; }
  .gbar-rank { flex: 0 0 16px; width: 16px; font-size: 10px; }
  .gbar-name { flex: 0 0 66px; width: 66px; font-size: 12px; }
  .gbar-val  { flex: 0 0 82px; width: 82px; font-size: 12px; }
  .gbar-track { height: 14px; }
  .gbar-in   { display: none; }
  .gbar-legend .gbar-tip { display: none; }
}

/* --------------------------------------------------------------------------
   16. 标签页
   -------------------------------------------------------------------------- */
.tabs {
  display: flex;
  gap: 4px;
  border-bottom: 1px solid var(--border);
  overflow-x: auto;
  padding: 0 18px;
  background: var(--surface);
}
.tabs a {
  padding: 12px 14px;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--text-3);
  border-bottom: 2px solid transparent;
  white-space: nowrap;
  margin-bottom: -1px;
}
.tabs a:hover { color: var(--brand); }
.tabs a.active { color: var(--brand); border-bottom-color: var(--brand); }

/* --------------------------------------------------------------------------
   17. 详情列表
   -------------------------------------------------------------------------- */
.dl {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: 0;
}
.dl-item {
  padding: 10px 0;
  border-bottom: 1px dashed var(--border);
  display: flex;
  gap: 10px;
  font-size: 13.5px;
  align-items: baseline;
}
.dl-item dt { color: var(--text-3); flex: 0 0 96px; font-size: 12.5px; }
.dl-item dd { margin: 0; color: var(--text); font-weight: 500; word-break: break-all; }

/* 履历统计（队员档案 · 贡献概览） */
.contrib-meta {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
  gap: 10px;
  margin-top: 14px;
}
.cm-item {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 10px 12px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.cm-label { font-size: 12px; color: var(--text-3); }
.cm-val {
  font-size: 16px;
  font-weight: 700;
  color: var(--text);
  font-variant-numeric: tabular-nums;
  line-height: 1.2;
}
.cm-val small { font-size: 11.5px; font-weight: 500; color: var(--text-3); }

/* --------------------------------------------------------------------------
   17.2 装备详情头部
   -------------------------------------------------------------------------- */
.eq-head { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.eq-head-main { flex: 1 1 auto; min-width: 0; }
.eq-head-name {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  font-size: 20px;
  font-weight: 700;
  color: var(--text);
  line-height: 1.3;
}
.eq-head-meta {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  margin-top: 7px;
  font-size: 13px;
  color: var(--text-3);
}
.eq-head-warn {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 8px 15px;
  border-radius: var(--radius-pill);
  background: var(--warn-light);
  color: var(--warn-ink);
  font-size: 13px;
  font-weight: 600;
  white-space: nowrap;
}
.eq-head-warn b { font-size: 15px; }
.eq-head-warn svg { width: 15px; height: 15px; }
.eq-head-warn.is-overdue { background: var(--danger-light); color: var(--danger-ink); }

/* 按操作类型动态显示 / 隐藏的表单字段（无脚本时全部显示，不影响提交） */
.op-field.is-off { display: none; }

/* --------------------------------------------------------------------------
   17.1 维护 / 到期预警列表（装备、证书等共用）
   -------------------------------------------------------------------------- */
.warn-list { display: flex; flex-direction: column; gap: 8px; }
.warn-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-left: 3px solid var(--warn);
  border-radius: var(--radius-sm);
  background: var(--surface);
  text-decoration: none;
  color: var(--text);
  transition: background .15s, border-color .15s;
}
.warn-item:hover { background: var(--bg); border-color: var(--warn); }
.warn-item.is-overdue {
  border-left-color: var(--danger);
  background: var(--danger-light);
}
.warn-item.is-overdue:hover { border-color: var(--danger); }
.wi-name { font-weight: 600; font-size: 13.5px; }
.wi-meta { font-size: 12px; color: var(--text-3); }
.wi-days {
  margin-left: auto;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--warn-ink);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.warn-item.is-overdue .wi-days { color: var(--danger-ink); }

@media (max-width: 640px) {
  .warn-item { flex-wrap: wrap; gap: 4px 10px; padding: 11px 12px; }
  .wi-name { flex: 1 1 auto; }
  .wi-meta { flex: 0 0 100%; order: 3; }
  .wi-days { margin-left: 0; flex: 0 0 auto; }
  .contrib-meta { grid-template-columns: repeat(2, 1fr); }

  /* 装备详情头部：窄屏改为上下两段，避免名称与倒计时挤在一行 */
  .eq-head { gap: 10px; }
  .eq-head-name { font-size: 17px; gap: 8px; }
  .eq-head-warn { width: 100%; justify-content: center; }
}

/* --------------------------------------------------------------------------
   18. 档案头部
   -------------------------------------------------------------------------- */
.profile-head {
  display: flex;
  gap: 20px;
  /* 需求 #11：原来是 align-items:center —— 右侧三个操作按钮跟着内容高度
     垂直居中，内容一多就"飘"在中间，视觉上把整块挤成一坨。
     改成顶对齐后，按钮贴着第一行站住，正文拿到完整行宽。 */
  align-items: flex-start;
  padding: 22px 22px 18px;
  background: linear-gradient(122deg, var(--brand-darker) 0%, #1B4E9E 52%, #1D5FD0 100%);
  color: #fff;
  border-radius: var(--radius);
  margin-bottom: 16px;
  position: relative;
  overflow: hidden;
  flex-wrap: wrap;
}
/* 装饰层①：右上角一团柔光（原有的，稍微收一点，别糊住名字） */
.profile-head::after {
  content: "";
  position: absolute;
  right: -70px; top: -80px;
  width: 240px; height: 240px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(93, 156, 255, .40), transparent 68%);
  pointer-events: none;
}
/* 装饰层②：左下角一团更暗的光，让渐变有"两个光源"的立体感，
   否则整块只有右上亮、左下死平，看起来像一张纯色底图。 */
.profile-head::before {
  content: "";
  position: absolute;
  left: -90px; bottom: -110px;
  width: 260px; height: 260px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(29, 95, 208, .55), transparent 70%);
  pointer-events: none;
}
/* 装饰层③：一道极淡的斜向发丝线网 —— 纯色大块最容易显得"单调"，
   加一层低到几乎看不见的纹理，屏幕上立刻有质感；比塞一张 PNG 好在
   不占体积、跟着皮肤走、也不会在 Retina 上糊。 */
.profile-head {
  background-image:
    repeating-linear-gradient(115deg, rgba(255, 255, 255, .045) 0 1px, transparent 1px 12px),
    linear-gradient(122deg, var(--brand-darker) 0%, #1B4E9E 52%, #1D5FD0 100%);
}
/* 顶部一道高光细线：把卡片与页面底色"压"开，边界更利落 */
.profile-head > .ph-avatar,
.profile-head > .ph-main,
.profile-head > .ph-actions { position: relative; z-index: 1; }
.profile-head .ph-avatar {
  width: 82px; height: 82px; border-radius: 50%;
  border: 3px solid rgba(255, 255, 255, .5);
  box-shadow: 0 0 0 7px rgba(255, 255, 255, .10), 0 10px 24px rgba(6, 20, 45, .34);
  object-fit: cover;
  flex: 0 0 82px;
  position: relative; z-index: 1;
  background: var(--brand-dark);
}
.profile-head .ph-main { position: relative; z-index: 1; min-width: 0; flex: 1; }
.profile-head h1 { color: #fff; font-size: 21px; display: flex; align-items: center; gap: 9px; flex-wrap: wrap; }

/* ── 职务区（需求 #5：一人多职务） ─────────────────────────────────
   以前是「职务」+「兼任 A / B / C」两行纯文本，同一条职务被列两遍，
   手机上能占到 60px 以上；改成一颗颗徽标后：职务名加粗、组织名弱化，
   扫读时先看"做什么"再看"在哪个组织"，折行由 flex-wrap 接管。 */
.profile-head .ph-posts {
  position: relative; z-index: 1;
  margin-top: 9px;
  display: flex; flex-wrap: wrap; gap: 6px;
}
.profile-head .ph-post {
  display: inline-flex; align-items: baseline; gap: 5px;
  max-width: 100%;
  padding: 3px 9px;
  border-radius: 999px;
  background: rgba(255, 255, 255, .13);
  border: 1px solid rgba(255, 255, 255, .2);
  font-size: 12.5px; line-height: 1.6;
  color: #EAF2FE;
}
.profile-head .ph-post b { font-weight: 600; color: #fff; }
.profile-head .ph-post i { font-style: normal; color: #A9C3E8; }
/* 权限最高的那条职务反白高亮，一眼能看出"主要身份" */
.profile-head .ph-post.is-main { background: rgba(255, 255, 255, .95); border-color: #fff; }
.profile-head .ph-post.is-main b { color: var(--brand-darker); }
.profile-head .ph-post.is-main i { color: #3A5A8C; }

/* 第 5 条起默认收起：常规的两三个职务一眼看全，只有真正身兼多职才需要展开 */
.profile-head .ph-posts:not(.is-open) .ph-post.is-extra { display: none; }
.profile-head .ph-post-more {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 3px 9px; border-radius: 999px;
  background: transparent;
  border: 1px dashed rgba(255, 255, 255, .42);
  color: #CFE0F8;
  font-size: 12.5px; line-height: 1.6;
  font-family: inherit; cursor: pointer;
}
.profile-head .ph-post-more:hover { background: rgba(255, 255, 255, .14); color: #fff; }
.profile-head .ph-post-more svg { transition: transform .2s; }
.profile-head .ph-posts.is-open .ph-post-more svg { transform: rotate(180deg); }

/* 基础信息改网格：原来是 flex 一行铺开，手机上按内容宽度断行、长短不齐；
   auto-fit 网格让手机稳定两列、桌面一行排完，行数可预期。 */
/* 需求 #11：档案信息区重新排版。
   原来是「标签 值」横排、每项最小 124px 的 auto-fit 网格 —— 六项信息
   在宽屏排成一行、在窄屏碎成好几行，标签和值的字重又接近，
   一眼扫过去分不清哪是标签哪是值，这就是用户说的"内容太拥挤"。

   现在改成**标签在上、值在下**的小格子，格子之间用一根 1px 的发丝线分开
   （做法：容器铺一层半透明白当"线色"，格子里再铺一层深蓝当"底色"，
    1px 的 gap 露出来的就是线）。好处是：
     · 标签与值的层级一眼分明，不用加粗也读得出来；
     · 标签都短（"入队""昵称"），列宽可以收到 108px，同样宽度能多放一项；
     · 六项在宽屏排成 3~6 格、窄屏自动折行，每一格都是完整可读的单元，
       不会出现"标签留在上一行、值掉到下一行"的割裂。

   注意：这里**没有改任何 HTML** —— 靠 flex-direction: column 让标签
   （匿名文本节点）和 <b> 各占一行。三个使用了 .profile-head 的页面
   （个人中心 / 公开档案 / 后台档案）自动一起受益。 */
.profile-head .ph-meta {
  position: relative; z-index: 1;
  margin-top: 13px;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(108px, 1fr));
  gap: 1px;
  padding: 1px;
  border-radius: var(--radius-sm);
  background: rgba(255, 255, 255, .16);   /* 露出来的就是分隔线 */
  overflow: hidden;
  font-size: 11.5px;
  color: #A9C3E8;
  letter-spacing: .2px;
}
.profile-head .ph-meta > span {
  display: flex;
  flex-direction: column;   /* 标签一行、值一行 */
  align-items: flex-start;
  gap: 1px;
  min-width: 0;
  padding: 8px 11px;
  background: rgba(9, 31, 68, .46);   /* 深蓝底，压住渐变的亮部 */
}
.profile-head .ph-meta > span > b {
  font-size: 14px;
  line-height: 1.35;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.profile-head .ph-meta b { color: #fff; font-weight: 600; }
.profile-head .ph-meta-note { grid-column: 1 / -1; font-size: 11.5px; opacity: .8; padding: 7px 11px; }

/* 只在某个组织里当组员、没有职务的组织名；
   原先靠下方分组 chip 承载，一个人加六个组就是六个 chip，那是手机端最长的一行。 */
.profile-head .ph-orgs {
  position: relative; z-index: 1;
  margin-top: 7px; line-height: 1.75;
  font-size: 12.5px; color: #C6DAF5;
}
.profile-head .ph-orgs-t {
  display: inline-block; margin-right: 8px; padding: 1px 7px;
  border-radius: 4px; background: rgba(255, 255, 255, .12);
  color: #A9C3E8; font-size: 11.5px;
}

.profile-head .ph-tags { margin-top: 10px; display: flex; gap: 6px; flex-wrap: wrap; }
.profile-head .ph-tags .chip {
  background: rgba(255, 255, 255, .16);
  border-color: rgba(255, 255, 255, .22);
  color: #EAF2FE;
}
.profile-head .ph-actions { position: relative; z-index: 1; display: flex; gap: 8px; flex-wrap: wrap; }
.profile-head .ph-actions .btn-ghost {
  background: rgba(255, 255, 255, .14);
  border-color: rgba(255, 255, 255, .26);
  color: #fff;
}
.profile-head .ph-actions .btn-ghost:hover { background: rgba(255, 255, 255, .24); color: #fff; }

/* 需求 #78-11：公开主页的「主页短链」区块已整块移除。
   原先这里有一整套 .ph-shortlink / .psl-tag / .psl-url 规则（桌面 9 条 +
   手机端 5 条），现在全站已无任何元素带这些类名 —— 留着只会让后来人
   以为"页面上还有个短链框被藏起来了"，所以一并删掉。
   短链能力本身没删：member_url() / member_share_url() 照旧可用，
   后台「人员档案」详情页与人员列表里仍能看到并复制短链。 */

@media (max-width: 760px) {
  /* 头像改为顶部对齐：main 内容多时（多职务）它会掉到卡片中腰，
     看着像"另起一块"，与右侧文字失去关联。 */
  .profile-head { align-items: flex-start; padding: 16px; gap: 13px; }
  .profile-head .ph-avatar { margin-top: 1px; }
  .profile-head .ph-meta { grid-template-columns: repeat(auto-fit, minmax(108px, 1fr)); gap: 4px 12px; }
  .profile-head .ph-post,
  .profile-head .ph-post-more { font-size: 12px; padding: 4px 10px; }
}
/* 触屏设备：职务折叠按钮的视觉高度只有 20 多像素，
   手指点不准。这里垫一层 44px 高的透明命中区，外观不变、
   命中面积达到可点标准（走查脚本会 elementFromPoint 实测）。 */
@media (hover: none) {
  .profile-head .ph-post-more { position: relative; }
  .profile-head .ph-post-more::after {
    content: ""; position: absolute; left: 0; right: 0; top: 50%;
    transform: translateY(-50%); height: 44px;
  }
}

/* --------------------------------------------------------------------------
   18b. 公开资质档案 · 档案头（需求 2 · v2.12 重做）
   --------------------------------------------------------------------------
   用户原话：「个人主页改成抖音/TikTok 式展示（上头像、下几行标签/呼号）；
             党员身份要往上放（懂政治规矩）；
             顶部头像与标签身份区整体换风格（参考苹果、小米）」。

   上一版是「深蓝渐变海报 + 左头像 + 右侧 12 列信息网格」。
   这一版要的是**产品**感而不是海报感，所以：

     · 版式竖排居中：头像 → 姓名 → 徽标行 → 职务 → 标签行 → 队龄 → 编号。
       这就是抖音个人主页的骨架，也是手机端唯一不需要额外断点就成立的排法
       （上一版得靠 @media 把 12 列压到 2 列、还得逐个调 --sp 才勉强不乱）。
     · 底色：白 + 顶部一层极淡的品牌蓝柔光，22px 大圆角、发丝分隔线、
       柔和外投影。苹果 / 小米共同的东西从来不是某一个颜色，而是
       **大留白 + 单一强调色 + 克制的字重层级 + 用留白而不是边框分组**。
     · 动效只留一个：头像外一圈极淡的品牌光晕缓慢呼吸（6s）。
       旋转虚线环与横向扫光都去掉 —— 那是"活动页"的做法，
       挂在"凭证页"上会削弱可信度。
   动效守规矩：prefers-reduced-motion 下全部关掉，信息一点不少。
   -------------------------------------------------------------------------- */
.pub-hero {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0;
  padding: 28px 22px 0;
  border-radius: 22px;
  margin-bottom: 16px;
  overflow: hidden;
  text-align: center;
  color: var(--text);
  background-color: var(--surface);
  background-image:
    radial-gradient(118% 76% at 50% -18%, rgba(29, 95, 208, .12), transparent 62%),
    linear-gradient(180deg, #FFFFFF 0%, #F9FBFF 100%);
  border: 1px solid var(--border);
  box-shadow: 0 18px 44px -20px rgba(18, 60, 134, .26), 0 1px 3px rgba(22, 35, 58, .04);
  animation: pubHeroIn .46s cubic-bezier(.22, .7, .3, 1) both;
}
@keyframes pubHeroIn {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}

/* 顶部一条品牌色发丝光带。苹果官网的"顶部高光"是同一类手法：
   一条细线就能把一张白卡和页面的浅灰底分开，还能顺手点出品牌色，
   比给整块加深色渐变克制得多。 */
.pub-hero-glow {
  position: absolute; left: 0; right: 0; top: 0; height: 3px;
  pointer-events: none;
  background: linear-gradient(90deg, transparent, var(--brand) 20%, var(--brand-mid) 50%, var(--brand) 80%, transparent);
  opacity: .9;
}

/* ── ★ v2.14 需求 3：档案头背景的线条层 ──────────────────────────────
   用户原话：「顶部背景区域（头像、介绍）太空了，可以适当加一些线条背景……
             总之目前只有顶部一条蓝线有点空」。

   两层细线，都用极低不透明度，靠 mask 向下淡出：
     ① .pub-hero::before —— 30px 品牌蓝方格网（横线 + 竖线各一条 gradient）。
        方格比斜纹"安静"：斜纹会把整块变成一个方向性的动势，
        而这里需要一个能托住头像与文字、但不抢戏的底纹。
     ② .pub-hero-av::before —— 头像后面的三道同心细圆环。
        它们是"证件照光圈"的静态版：v2.12 删掉的旋转虚线光环太跳，
        静态同心圆只提供一点秩序感，不产生任何动效。

   ⚠️ 三条不能改的地方：
     · 两层都 pointer-events:none —— 头像的 data-zoom 点击必须照常穿透，
       否则"点头像看大图"会突然失灵，而且看不出是这层装饰挡的；
     · 不透明度上限 6%：再高一点，手机端在阳光下看就会变成"背景有条纹"；
     · 只有 mask 到 78% 就完全透明 —— 下方是姓名/徽标/职务等文字，
       底纹必须在文字区之前消失，否则会降低对比度、也会被误判成"文字被裁"。
   动效一层都不加：.pub-av-ring 已经是这张卡唯一允许的持续动效，
   再加一处就从"精致"变成"廉价"。 */
.pub-hero::before {
  content: "";
  position: absolute; inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image:
    repeating-linear-gradient(0deg,  rgba(29, 95, 208, .055) 0 1px, transparent 1px 30px),
    repeating-linear-gradient(90deg, rgba(29, 95, 208, .055) 0 1px, transparent 1px 30px);
  -webkit-mask-image: linear-gradient(180deg, #000 0%, rgba(0, 0, 0, .5) 44%, transparent 78%);
          mask-image: linear-gradient(180deg, #000 0%, rgba(0, 0, 0, .5) 44%, transparent 78%);
}
.pub-hero-av::before {
  content: "";
  position: absolute; left: 50%; top: 50%;
  width: 320px; height: 320px; margin: -160px 0 0 -160px;
  z-index: 0;
  pointer-events: none;
  border-radius: 50%;
  background:
    radial-gradient(closest-side, transparent 0 96px,  rgba(29, 95, 208, .10) 96px  97px,  transparent 97px),
    radial-gradient(closest-side, transparent 0 120px, rgba(29, 95, 208, .075) 120px 121px, transparent 121px),
    radial-gradient(closest-side, transparent 0 146px, rgba(29, 95, 208, .05) 146px 147px, transparent 147px);
}
/* 头像本身必须压在圆环之上；.pub-hero-av 已建层，这里只确保顺序 */
.pub-hero-av > .pub-avatar { position: relative; z-index: 1; }

/* ── 头像 ─────────────────────────────────────────────────────────── */
.pub-hero-av {
  position: relative; z-index: 1;
  flex: 0 0 auto;
  display: flex; flex-direction: column; align-items: center; gap: 10px;
}
/* 缓慢呼吸的品牌光晕。整张卡只有这一处持续动效，而它落在证件照的位置上 ——
   一眼就知道这张卡的重点是人。 */
.pub-av-ring {
  position: absolute; top: -12px; left: 50%; margin-left: -72px;
  width: 144px; height: 144px;
  border-radius: 50%;
  pointer-events: none;
  background: radial-gradient(closest-side, rgba(29, 95, 208, .18), rgba(29, 95, 208, .04) 70%, transparent);
  animation: pubRingBreathe 6s ease-in-out infinite;
}
@keyframes pubRingBreathe {
  0%, 100% { transform: scale(.94); opacity: .62; }
  50%      { transform: scale(1.06); opacity: 1; }
}

.pub-avatar {
  position: relative; z-index: 1;
  width: 116px; height: 116px;
  border-radius: 50%;
  object-fit: cover;
  background: var(--surface-2);
  border: 4px solid #fff;
  box-shadow: 0 0 0 1px var(--border), 0 14px 30px -12px rgba(18, 60, 134, .34);
  cursor: zoom-in;
  transition: transform .26s ease, box-shadow .26s ease;
}
.pub-avatar:hover,
.pub-avatar:focus-visible {
  transform: scale(1.045);
  box-shadow: 0 0 0 1px var(--brand-border), 0 18px 34px -12px rgba(18, 60, 134, .44), 0 0 0 9px rgba(29, 95, 208, .09);
  outline: none;
}
/* v2.13 需求 2 删掉了 .pub-av-hint —— 头像下方那块「点击放大」的牌子。
   放大能力一点没动：data-zoom / role="button" / tabindex / aria-label
   全都在，鼠标指针仍是 zoom-in。少一个常驻提示，头像区就干净一分。 */

/* ── 在岗状态（v2.13 需求 2：从徽标行移到姓名下面，点击展开说明） ────
   为什么做成 <button> 而不是给 <span> 挂 click：
     · 键盘 Tab 键必须能到（span 默认不可聚焦）；
     · 读屏要能播报"可展开、当前收起"，靠 aria-expanded 表达。
   小圆点沿用 .pb-online 那套呼吸动画（pubDotPulse），
   换了个外壳但语义不变：一眼看出"这个人现在在不在"。 */
.pub-online {
  margin-top: 9px;
  display: flex; flex-direction: column; align-items: center;
  gap: 7px; max-width: 100%;
}
.pub-online-btn {
  display: inline-flex; align-items: center; gap: 6px;
  height: 28px; padding: 0 12px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border);
  background: var(--surface-2);
  font: inherit; font-size: 12.5px; font-weight: 600;
  color: var(--text-2);
  cursor: pointer;
  transition: background .16s, border-color .16s, color .16s;
}
.pub-online-btn:hover { background: var(--surface-hover); border-color: var(--border-strong); }
.pub-online-btn:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.pub-online-btn > i {
  width: 7px; height: 7px; border-radius: 50%;
  background: currentColor;
  animation: pubDotPulse 2.1s ease-out infinite;
}
.pub-online-btn .pob-caret { display: inline-flex; opacity: .55; transition: transform .18s ease; }
.pub-online-btn[aria-expanded="true"] .pob-caret { transform: rotate(180deg); }
.pub-online-btn.is-on      { color: var(--ok-ink); }
.pub-online-btn.is-standby { color: var(--brand); }
.pub-online-btn.is-busy    { color: var(--warn-ink); }
.pub-online-btn.is-off     { color: var(--text-3); }

/* 说明段：常驻 DOM、靠 hidden 切换（走查能直接断言文案，脚本也不会漏插） */
.pub-online-note {
  margin: 0; max-width: 460px;
  padding: 8px 12px;
  border-radius: var(--radius-sm);
  background: var(--brand-lighter);
  border: 1px solid var(--brand-border);
  color: var(--brand-ink);
  font-size: 12.5px; line-height: 1.6; text-align: left;
}
.pub-online-note[hidden] { display: none; }

/* ── 身份信息：竖排居中（姓名 → 昵称 → 徽标 → 职务 → 标签 → 队龄 → 编号） ──
   上一版的 12 列底格 + gap 露白做分隔线是"表格思维"：
   宽屏上七个格子并排，眼睛要横向来回扫。竖排居中把每一层压成一行，
   阅读动线只有一条（从上往下），这才是个人主页该有的读法。 */
.pub-hero-main {
  position: relative; z-index: 1;
  display: flex; flex-direction: column; align-items: center;
  width: 100%; min-width: 0;
  margin-top: 14px;
}
.pub-name {
  margin: 0;
  /* 姓名 +（昵称）同一行（v2.13 需求 2）。
     用 flex + align-items:baseline 而不是 vertical-align：
     中文字体的基线在 26px 与 14px 两个字号下差得明显，
     vertical-align: middle 会让小字浮在半空、看着像没对齐。 */
  display: flex; align-items: baseline; justify-content: center;
  flex-wrap: wrap; gap: 0 6px;
  font-size: 26px; font-weight: 700; letter-spacing: .6px;
  color: var(--text); line-height: 1.25;
  max-width: 100%;
}
/* 昵称：比名字小一号、颜色浅一档（需求原话「小一点、浅一点」）。
   颜色用 --text-3 而不是 --text-2：姓名才是主标识，
   昵称是注解，两者必须拉开一档才看得出层级。 */
.pub-nick {
  font-size: 14.5px; font-weight: 500; letter-spacing: 0;
  color: var(--text-3);
  overflow-wrap: anywhere;
}

/* ── 身份行（v2.13 需求 2 第四行）：徽标 ＋ 职务 ────────────────────
   桌面端两者同一行：`.pub-badges` 在左、职务接着排；
   手机端整块职务下沉到下一行（需求原话：
   「手机版职务统一放在政治面貌和队内身份下一行」）—— 见下面的媒体查询。
   顺序即权重：政治面貌 → 党内职务 → 退伍军人 → 队内身份。 */
.pub-identity {
  margin-top: 13px;
  width: 100%; max-width: 100%;
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: 8px 14px;
}
.pub-badges {
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: 7px; max-width: 100%;
}
.pb {
  display: inline-flex; align-items: center; gap: 4px;
  height: 26px; padding: 0 11px; border-radius: 999px;
  font-size: 12.5px; font-weight: 600; line-height: 1;
  color: var(--text-2);
  background: var(--surface-2); border: 1px solid var(--border);
  white-space: nowrap;
}
.pb svg { flex: 0 0 auto; }
/* 政治面貌用红：这是整套 UI 里唯一允许"非故障用红"的地方 ——
   党员身份是政治规矩，必须第一眼看到，不能和灰色小标签混在一起。 */
.pb-pol.is-party  { background: var(--danger-light); border-color: var(--danger-border); color: var(--danger-ink); }
.pb-pol.is-league { background: var(--brand-light);  border-color: var(--brand-border);  color: var(--brand-dark); }
/* 党内职务是对政治面貌的补充说明，做成虚线描边 + 常规字重，不与主徽标抢眼 */
.pb-pol.is-soft { background: #fff; border-style: dashed; font-weight: 500; }
.pb-veteran { background: var(--ok-light); border-color: var(--ok-border); color: var(--ok-ink); }
.pb-role    { color: var(--text-2); }
/* 队内身份（v2.13 需求 2：「队内身份要用矩形标注」）
   ────────────────────────────────────────────────────────────────────
   这一行里其它徽标都是 999px 药丸 + 浅底；队内身份是唯一的
   **小圆角矩形 + 品牌蓝实底**。形状本身就是视觉语言：
   形状不同 = 类别不同，比再加一个颜色更容易记住，
   也在黑白打印时仍然区分得开（实心块 vs 空心块）。 */
.pb-role.is-rect {
  border-radius: var(--radius-xs);
  background: var(--brand);
  border-color: var(--brand);
  color: #fff;
  padding: 0 10px;
}
.pb-role.is-rect svg { opacity: .92; }
/* 在岗状态：会呼吸的小圆点。调度视角下这是最先要看的一个字，
   静态小徽标会被 26px 的姓名压住。 */
.pb-online { color: var(--ok-ink); }
.pb-online i {
  width: 7px; height: 7px; border-radius: 50%;
  background: currentColor;
  animation: pubDotPulse 2.1s ease-out infinite;
}
.pb-online.is-standby { color: var(--brand); }
.pb-online.is-busy    { color: var(--warn-ink); }
.pb-online.is-off     { color: var(--text-3); }
/* 呼吸圈用**中性灰**而不是状态色：状态色有四种，
   写死一种颜色的光圈会在别的状态上显脏（浅底上尤其明显）。 */
@keyframes pubDotPulse {
  0%   { box-shadow: 0 0 0 0 rgba(22, 35, 58, .22); }
  70%  { box-shadow: 0 0 0 6px rgba(22, 35, 58, 0); }
  100% { box-shadow: 0 0 0 0 rgba(22, 35, 58, 0); }
}

/* 职务徽标（一人多职）。主职务用品牌蓝实底 —— 一眼看出"主要身份"。
   第 5 条起默认收起，只有真正身兼多职才需要展开（按钮上写着还有几个）。 */
.pub-posts-wrap {
  /* margin-top 交给 .pub-identity 的 gap 管 —— 两者都设会出现
     「gap + margin」叠加的双倍空隙，桌面端看着还行、手机端竖排就很空。 */
  margin-top: 0;
  display: flex; justify-content: center;
  max-width: 100%;
}
.pub-hero .ph-posts { display: flex; flex-wrap: wrap; justify-content: center; gap: 6px; }
.pub-hero .ph-post {
  display: inline-flex; align-items: baseline; gap: 5px;
  max-width: 100%;
  padding: 4px 11px;
  border-radius: 999px;
  background: var(--brand-lighter);
  border: 1px solid var(--brand-border);
  font-size: 12.5px; line-height: 1.6;
  color: var(--brand-dark);
}
.pub-hero .ph-post b { font-weight: 600; }
.pub-hero .ph-post i { font-style: normal; color: var(--text-3); font-size: 11.5px; }
.pub-hero .ph-post.is-main { background: var(--brand); border-color: var(--brand); }
.pub-hero .ph-post.is-main b { color: #fff; }
.pub-hero .ph-post.is-main i { color: rgba(255, 255, 255, .82); }
.pub-hero .ph-posts:not(.is-open) .ph-post.is-extra { display: none; }
.pub-hero .ph-post-more {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 4px 11px; border-radius: 999px;
  background: transparent;
  border: 1px dashed var(--border-strong);
  color: var(--text-3);
  font-size: 12.5px; line-height: 1.6;
  font-family: inherit; cursor: pointer;
}
.pub-hero .ph-post-more:hover { background: var(--surface-2); color: var(--brand); border-color: var(--brand); }
.pub-hero .ph-post-more svg { transition: transform .2s; }
.pub-hero .ph-posts.is-open .ph-post-more svg { transform: rotate(180deg); }

/* ── 标签行：呼号 / 性别 / 年龄 / 所属组织 ───────────────────────── */
/* ── 第三行：性别 ＋ 年龄 ＋ ID ＋ 编号（v2.13 需求 2）──────────────
   ID / 编号用等宽字体 —— 这两个值是要被逐字抄进表格里的，
   比例字体下 0 与 O、1 与 l 分不清，抄错一个字符就找不到人。 */
.pub-meta {
  margin-top: 12px;
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: 7px 16px;
  font-size: 13px; color: var(--text-2);
}
.pm-item { display: inline-flex; align-items: center; gap: 5px; }
.pm-item svg { width: 13px; height: 13px; opacity: .78; }
.pm-id { gap: 6px; }
.pm-id-k { color: var(--text-3); font-size: 12px; }
.pm-id b { color: var(--text); font-weight: 600; }

/* ── 点赞：右上角「红心 ＋ 数字」（v2.23 需求 3）──────────────────────
   需求原话：「个人主页点赞移动到右上角区域，只保留一个红色爱心标志和
              点赞数，点击点赞数字时，会显示队员点赞和游客点赞数据」。

   v2.22 的版本是档案头中段两枚并排胶囊（「♡ 点赞」「12 人点赞」），
   占掉正文一整行，且"点赞"两个字在右上角这种窄区里必然换行。
   现在收成一个**固定尺寸的小控件**贴在 hero 右上角：
     · 只有两个可点目标：红心（点赞）、数字（展开明细）；
     · 数字与图标挤在同一个圆角胶囊里，视觉上是一件事，
       交互上是两件事 —— 想赞点左边，想看看谁赞了点右边；
     · 尺寸写死（高 30px），不随数字位数变形；99+ 用 CSS 收窄字距。

   ⚠️ hero 有 overflow: hidden（顶部光晕要靠它裁），所以明细气泡
      只能往**下**开、且必须落在 hero 内部 —— 这正好是我们要的：
      气泡贴着右上角向下展开，不会溢出卡片。 */
.pub-like {
  position: absolute;
  top: 14px;
  right: 16px;
  z-index: 6;
  display: inline-flex;
  align-items: center;
  height: 30px;
  padding: 0 4px 0 2px;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--surface);
  box-shadow: 0 2px 8px -2px rgba(18, 60, 134, .18);
}
.pl-heart,
.pl-num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 0;
  background: transparent;
  color: var(--text-2);
  cursor: pointer;
  padding: 0;
  transition: color .12s, background .12s, transform .08s;
}
.pl-heart {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  color: #C0C8D6;           /* 没赞过：灰心 */
}
.pl-heart svg { width: 17px; height: 17px; }
.pl-heart:hover { color: var(--danger); background: var(--danger-light); }
.pl-heart:active { transform: scale(.92); }
/* 今天已赞：实心红心。红不是"危险"，是全站对"喜欢"的统一色；
   与「未读」的实心红不同 —— 那个是状态，这个是选择。 */
.pl-heart.is-on { color: var(--danger); }
.pl-heart.is-on svg { fill: currentColor; }
/* 请求在路上：压一点透明度并禁掉连点。不加转圈 —— 这个动作只有几十毫秒。 */
.pl-heart.is-busy { opacity: .55; pointer-events: none; }

.pl-num {
  min-width: 24px;
  height: 26px;
  padding: 0 7px 0 4px;
  border-radius: 999px;
  font-variant-numeric: tabular-nums;
}
.pl-num b { font-size: 13.5px; font-weight: 800; color: var(--text); line-height: 1; }
.pl-num:hover { background: var(--surface-2); }
.pl-num:hover b { color: var(--danger); }
.pl-num:focus-visible,
.pl-heart:focus-visible { outline: 2px solid var(--ring, #4B8CF5); outline-offset: 1px; }

/* 明细气泡：从右上角向下展开，右对齐（贴右边缘才不会溢出 hero） */
.pl-pop {
  position: absolute;
  right: 0;
  top: calc(100% + 8px);
  z-index: 30;
  min-width: 156px;
  display: none;
  flex-direction: column;
  gap: 6px;
  padding: 9px 12px;
  border-radius: var(--radius);
  background: #1B2A44;
  color: #E8EFFA;
  font-size: 12.5px;
  text-align: left;
  box-shadow: 0 10px 26px rgba(10, 20, 40, .26);
}
.pub-like.is-open .pl-pop { display: flex; }
.pl-pop::before {
  content: '';
  position: absolute;
  top: -5px;
  right: 16px;
  width: 9px;
  height: 9px;
  background: inherit;
  transform: rotate(45deg);
}
.plt-row { display: flex; align-items: center; gap: 6px; white-space: nowrap; }
.plt-row b { font-weight: 800; color: #fff; font-variant-numeric: tabular-nums; }
.plt-dot { width: 7px; height: 7px; border-radius: 50%; flex: 0 0 7px; }
.plt-dot.is-m { background: #5AA0FF; }
.plt-dot.is-g { background: #C3CFE2; }
.plt-note {
  margin-top: 2px;
  padding-top: 6px;
  border-top: 1px solid rgba(255, 255, 255, .12);
  font-size: 11.5px;
  color: rgba(232, 239, 250, .62);
}

/* 窄屏：右上角这块要和头像错开 —— 头像居中，胶囊贴右，
   320~420px 的屏上两者会打架，所以给 hero 顶部多留 40px 出来。 */
@media (max-width: 520px) {
  .pub-hero { padding-top: 56px; }
  .pub-like { top: 10px; right: 10px; }
  .pl-pop { min-width: 148px; }
}

/* ── 第五行：呼号 / 入队 / 累计服务 / 参与任务 / 最近参与（需求 2）──
   统一三段式「图标 ＋ 名目 ＋ 值」：名目灰、值黑。
   上一版有的项带前缀有的不带（「入队 2019-03-12」与「227.8 小时」并排），
   读的人得自己猜哪个数字对哪个名目。
   上面一条发丝线把它和「我是谁」那段分开 —— 这段回答的是「我干了多久」。
   （原 .pub-hero-foot / .pub-ids 两套已在 v2.13 合并到这里，不再使用。） */
.pub-facts {
  margin-top: 15px;
  width: 100%;
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: 8px 22px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
  font-size: 13px;
}
.pf-item { display: inline-flex; align-items: center; gap: 5px; max-width: 100%; }
.pf-item svg { flex: 0 0 auto; width: 13px; height: 13px; opacity: .78; }
.pf-k { color: var(--text-3); }
.pf-item b { color: var(--text); font-weight: 600; }

@media (max-width: 640px) {
  /* v2.13 需求 2 原话：「手机版职务统一放在政治面貌和队内身份下一行」——
     桌面端两者并排，手机端竖排。 */
  .pub-identity { flex-direction: column; gap: 9px; }
  .pub-name { font-size: 23px; }
  .pub-nick { font-size: 13.5px; }
  .pub-facts { gap: 7px 16px; }
}

/* 触屏：折叠按钮的视觉高度只有 20 多像素，手指点不准 ——
   垫一层 44px 的透明命中区，外观不变、可点面积达标。 */
@media (hover: none) {
  .pub-hero .ph-post-more { position: relative; }
  .pub-hero .ph-post-more::after {
    content: ""; position: absolute; left: 0; right: 0; top: 50%;
    transform: translateY(-50%); height: 44px;
  }
}

/* 手机：竖排版式本来就自适应，只需把字号 / 间距 / 头像收一档，
   并把「ID ｜ 编号」拆成两行（390px 下一行放得下，但拆开更好读）。 */
@media (max-width: 620px) {
  .pub-hero { padding: 22px 15px 0; border-radius: 18px; }
  .pub-avatar { width: 96px; height: 96px; border-width: 3px; }
  .pub-av-ring { width: 120px; height: 120px; margin-left: -60px; top: -12px; }
  .pub-name { font-size: 22px; }
  .pb { height: 24px; padding: 0 9px; font-size: 12px; }
  .pub-meta { gap: 5px 12px; }
  .pub-hero-foot { gap: 5px 14px; }
  .pub-ids { flex-direction: column; gap: 4px; padding: 11px 0 14px; }
  .pub-id-sep { display: none; }
}

/* 动效全关：信息一点不少，只是不动。 */
@media (prefers-reduced-motion: reduce) {
  .pub-hero { animation: none; }
  .pub-av-ring { animation: none; }
  .pub-avatar { transition: none; }
  .pb-online i { animation: none; }
}

/* --------------------------------------------------------------------------
   19. 前台（对外站点）
   -------------------------------------------------------------------------- */
.public-body { background: var(--surface); }

.site-head {
  position: sticky;
  top: 0;
  z-index: 50;
  background: rgba(255, 255, 255, .94);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--border);
}
.site-head-inner {
  max-width: 1180px;
  margin: 0 auto;
  padding: 12px 20px;
  display: flex;
  align-items: center;
  gap: 14px;
}
.site-logo { display: flex; align-items: center; gap: 10px; }
.site-logo .logo-badge {
  width: 40px; height: 40px; border-radius: 12px;
  background: var(--brand-grad);
  display: grid; place-items: center;
  box-shadow: 0 4px 14px rgba(29, 95, 208, .35);
  flex: 0 0 40px;
}
.site-logo .logo-badge svg { width: 24px; height: 24px; }
.site-logo strong { display: block; font-size: 16px; letter-spacing: .4px; line-height: 1.25; }
.site-logo span { display: block; font-size: 11px; color: var(--text-3); letter-spacing: 1.6px; }

.site-nav { margin-left: auto; display: flex; align-items: center; gap: 4px; }
.site-nav a {
  padding: 8px 13px;
  border-radius: var(--radius-sm);
  color: var(--text-2);
  font-size: 14px;
  font-weight: 500;
}
.site-nav a:hover { background: var(--brand-lighter); color: var(--brand); }

/* 首屏 */
.hero {
  position: relative;
  background:
    radial-gradient(1100px 460px at 82% -12%, rgba(93, 156, 255, .34), transparent 62%),
    radial-gradient(720px 380px at 8% 108%, rgba(255, 106, 43, .18), transparent 66%),
    var(--brand-grad-deep);
  color: #fff;
  overflow: hidden;
}
.hero::after {
  content: "";
  position: absolute; inset: 0;
  background-image:
    linear-gradient(rgba(255,255,255,.045) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255,255,255,.045) 1px, transparent 1px);
  background-size: 46px 46px;
  mask-image: radial-gradient(ellipse at 50% 0%, #000 20%, transparent 76%);
  pointer-events: none;
}
.hero-inner {
  max-width: 1180px;
  margin: 0 auto;
  padding: 62px 20px 74px;
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: 1.15fr .85fr;
  gap: 44px;
  align-items: center;
}
.hero-eyebrow {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 5px 13px;
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, .12);
  border: 1px solid rgba(255, 255, 255, .2);
  font-size: 12.5px;
  letter-spacing: .6px;
  margin-bottom: 18px;
}
.hero-eyebrow i { width: 7px; height: 7px; border-radius: 50%; background: #6BE39A; box-shadow: 0 0 0 3px rgba(107, 227, 154, .25); }
.hero h1 {
  font-size: 42px;
  line-height: 1.2;
  color: #fff;
  letter-spacing: -.5px;
  margin-bottom: 16px;
}
.hero h1 em { font-style: normal; color: #7FB6FF; }
.hero-lead { font-size: 15.5px; color: #C6DAF7; line-height: 1.85; max-width: 560px; margin-bottom: 26px; }
.hero-actions { display: flex; gap: 12px; flex-wrap: wrap; }
.hero-actions .btn { padding: 12px 24px; font-size: 15px; border-radius: var(--radius-sm); }
.btn-glass {
  background: rgba(255, 255, 255, .13);
  border: 1px solid rgba(255, 255, 255, .28);
  color: #fff;
}
.btn-glass:hover { background: rgba(255, 255, 255, .22); color: #fff; filter: none; }
.btn-white { background: var(--surface); color: var(--brand-dark); }
.btn-white:hover { background: #F0F5FF; color: var(--brand-dark); filter: none; }

.hero-stats {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 12px;
}
.hero-stat {
  background: rgba(255, 255, 255, .1);
  border: 1px solid rgba(255, 255, 255, .17);
  border-radius: var(--radius);
  padding: 16px 18px;
  backdrop-filter: blur(6px);
}
.hero-stat .hs-val { font-size: 28px; font-weight: 800; color: #fff; line-height: 1.2; font-variant-numeric: tabular-nums; }
.hero-stat .hs-val small { font-size: 13px; font-weight: 600; color: var(--side-text-dim); margin-left: 3px; }
.hero-stat .hs-label { font-size: 12.5px; color: var(--side-text-dim); margin-top: 2px; }

/* 区块 */
.section { max-width: 1180px; margin: 0 auto; padding: 58px 20px; }
.section-head { text-align: center; margin-bottom: 34px; }
.section-head .kicker {
  display: inline-block;
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: 1.6px;
  color: var(--brand);
  text-transform: uppercase;
  margin-bottom: 8px;
}
.section-head h2 { font-size: 27px; margin-bottom: 10px; }
.section-head p { color: var(--text-3); font-size: 14.5px; max-width: 620px; margin: 0 auto; }

.feature-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(258px, 1fr));
  gap: 16px;
}
.feature {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 22px;
  transition: transform .2s, box-shadow .2s, border-color .2s;
}
.feature:hover { transform: translateY(-3px); box-shadow: var(--shadow); border-color: var(--brand-border); }
.feature .f-icon {
  width: 44px; height: 44px; border-radius: 13px;
  display: grid; place-items: center;
  background: var(--brand-light); color: var(--brand);
  margin-bottom: 14px;
}
.feature .f-icon svg { width: 22px; height: 22px; }
.feature h3 { font-size: 15.5px; margin-bottom: 7px; }
.feature p { font-size: 13.5px; color: var(--text-3); margin: 0; line-height: 1.75; }

.section-alt { background: var(--surface-2); border-top: 1px solid var(--border); border-bottom: 1px solid var(--border); }

/* ==========================================================================
   首页视频板块（v2.13 需求 7）
   ==========================================================================
   整个板块由 includes/homevideo.php 的 home_video_render() 输出，
   **没配置时那一段 HTML 根本不存在** —— 所以这里不需要任何
   ".is-off { display:none }" 之类的"空态样式"，样式表里也就不可能出现
   "管理员没配、但页面上留了一条灰线"这类问题。

   播放器用**固定比例盒子**而不是直接给 <video> 定高：
     <video> 的高度在拿到元数据之前是 0（preload="metadata" 也是异步的），
     直接定高会让封面图被拉伸、不同片子出现不同的黑边。
     这里用经典的 padding-top 比例盒子（16:9 → 56.25%），
     内容绝对定位铺满 —— 老 Safari 没有 aspect-ratio 属性，padding 法通吃。
   ========================================================================== */
.hv-wrap { max-width: 900px; margin: 0 auto; }
.hv-frame {
  position: relative;
  padding-top: 56.25%;              /* 16 : 9 */
  border-radius: var(--radius);
  overflow: hidden;
  background: #0B1B33;              /* 视频没加载出来时是深蓝，不是白闪 */
  box-shadow: var(--shadow-lg);
}
.hv-frame > video,
.hv-frame > iframe,
.hv-frame > a {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  border: 0;
}
.hv-frame > iframe { background: #000; }

/* 认不出来的外链：封面当背景 + 播放按钮，整块可点。
   两层结构：::after 压一层深蓝渐变保证白字在任何底图上都读得清，
   真正的内容（按钮、文字）再抬到渐变之上。 */
.hv-link {
  display: grid;
  place-items: center;
  align-content: center;
  gap: 12px;
  background: #0B1B33 center/cover no-repeat;
  text-decoration: none;
  color: #fff;
}
.hv-link::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(160deg, rgba(10, 37, 80, .78), rgba(18, 60, 134, .66));
}
.hv-link > span { position: relative; z-index: 1; }
.hv-play {
  display: grid;
  place-items: center;
  width: 76px;
  height: 76px;
  border-radius: 50%;
  background: rgba(255, 255, 255, .14);
  border: 1px solid rgba(255, 255, 255, .38);
  color: #fff;
  transition: transform .16s ease, background .16s ease;
}
.hv-link:hover .hv-play { transform: scale(1.06); background: rgba(255, 255, 255, .22); }
.hv-note { font-size: 13.5px; color: #C6DCFB; }
.hv-go {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 13px;
  font-weight: 600;
  padding: 6px 14px;
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, .13);
  border: 1px solid rgba(255, 255, 255, .24);
}

/* 公开查询面板 */
.query-panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 22px;
}
.query-row { display: flex; gap: 10px; flex-wrap: wrap; }
.query-row .input { flex: 1 1 240px; }
.query-row .btn { flex: 0 0 auto; padding: 9.5px 26px; }

.result-card {
  display: flex;
  gap: 16px;
  align-items: center;
  padding: 16px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  margin-bottom: 10px;
  transition: border-color .16s, box-shadow .16s;
  flex-wrap: wrap;
}
.result-card:hover { border-color: var(--brand); box-shadow: var(--shadow); }
.result-card .rc-avatar { width: 58px; height: 58px; border-radius: 50%; object-fit: cover; flex: 0 0 58px; }
.result-card .rc-body { flex: 1; min-width: 180px; }
.result-card .rc-name { font-size: 16px; font-weight: 700; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.result-card .rc-sub { font-size: 12.5px; color: var(--text-3); margin-top: 3px; }
.result-card .rc-tags { margin-top: 8px; display: flex; gap: 6px; flex-wrap: wrap; }

.site-foot {
  background: #0C2A61;
  color: #93ADD6;
  padding: 26px 20px 22px;
  font-size: 13px;
}
.site-foot-inner { max-width: 1180px; margin: 0 auto; display: flex; gap: 14px 22px; flex-wrap: wrap; align-items: baseline; }
.site-foot a { color: var(--head-text-dim); }
.site-foot .foot-right { margin-left: auto; }

/* --------------------------------------------------------------------------
   19b. 页脚重排（需求 #77）
   --------------------------------------------------------------------------
   两行：品牌行（队伍名称 + 系统名称）／法务行（Copyright · 备案号 · 隐私政策 ·
   技术支持）。法务行用 flex + 中点分隔，不写成一条 <div> 拼字符串 ——
   备案号与隐私政策都可能是"没有"，拼字符串就得在 PHP 里到处判空。
   窄屏由下面的媒体查询折成两行，不靠换行符。 */
.site-foot .sf-brand { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.site-foot .sf-brand strong { color: #fff; font-size: 15px; letter-spacing: .3px; }
.site-foot .sf-sys { font-size: 12px; color: #7B9BD0; }
.site-foot .sf-legal { margin-left: auto; display: flex; align-items: center; gap: 6px 14px; flex-wrap: wrap; font-size: 12.5px; }
.site-foot .sf-legal > * { position: relative; }
/* 分隔点用 ::before 画，而不是往文案里塞「·」——
   塞进去的话，最后一项后面会跟着一个孤零零的点（因为每一项都带）。 */
.site-foot .sf-legal > * + *::before {
  content: '·';
  position: absolute;
  left: -9px;
  color: #4E6F9E;
}
.site-foot .sf-tech a { display: inline-flex; align-items: center; gap: 3px; }
.site-foot .sf-tech svg { opacity: .75; }

/* 备案号链接（需求 3）：页脚底色是深蓝，默认的链接色在上面偏暗，
   这里提亮到与品牌行相近的亮度，否则「能点」这件事看不出来。
   下划线只在悬停时出现 —— 页脚是法务信息行，常驻下划线会显得脏。 */
.site-foot .sf-icp { display: inline-flex; align-items: center; gap: 3px; color: #B9CEEB; }
.site-foot .sf-icp:hover { color: #fff; text-decoration: underline; }
.site-foot .sf-icp svg { opacity: .7; }

/* 页脚底部导航（需求 4）：与版权 / 备案号 / 隐私政策同一行的一条平铺链接组。
   为什么自己是一层 inline-flex 而不是直接把 <a> 散在 .sf-legal 里：
   .sf-legal 用 `> * + *::before` 给**每个相邻元素**画分隔点，
   散着放的话「底部导航第一项」和「版权」之间、以及组内每一项之间
   都会出现分隔点，读起来像法务条款而不是导航。
   成组之后整组只与前后各有一个分隔点，组内用自己的 gap 拉开。 */
.site-foot .sf-nav { display: inline-flex; align-items: center; gap: 4px 14px; flex-wrap: wrap; }

/* --------------------------------------------------------------------------
   19c. 页脚访问次数（需求 8）
   --------------------------------------------------------------------------
   需求原话：「整个系统页面底部展示，访问次数，刷新一次增加一次」。

   两套骨架共用同一段 .vf-* 标记，外壳不同、配色必须分别给：
     · .site-foot .sf-stat —— 对外页面，页脚是深蓝底，字要往亮里提
     · .sys-foot           —— 后台 / 个人中心，页脚是浅底，字要往暗里压
   颜色不能写在 .vf-item 上再由外壳继承「一半」—— 那样必有一套出现
   深色字压深色底、看不见的情况。所以这里只定结构，配色一律带外壳前缀。

   深色皮肤下 .sys-foot 用的是语义变量（--surface-2 / --text-3 / --border），
   会自己跟着皮肤走，不需要在这里为 dark 单独写一份。 */
.site-foot .sf-stat,
.sys-foot {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px 12px;
  flex-wrap: wrap;
}
.vf-item { display: inline-flex; align-items: center; gap: 4px; }
.vf-item svg { opacity: .7; }
.vf-item b { letter-spacing: .4px; font-weight: 600; }
/* 分隔竖线用 currentColor：上面那套配色改一次，这里自动跟着变 */
.vf-sep { width: 1px; height: 11px; background: currentColor; opacity: .28; }

/* 对外页脚：单独一行居中，比上面两行更小。
   并进 .sf-legal 那一行的话，一行里会同时挤着版权 / 备案号 / 底部导航 /
   隐私政策 / 技术支持 / 访问次数六样，小屏一折行就散成三行读不通。 */
.site-foot .sf-stat {
  max-width: 1180px;
  margin: 16px auto 0;
  padding-top: 12px;
  border-top: 1px solid rgba(147, 173, 214, .18);
  font-size: 12px;
  color: #7B9BD0;
}
.site-foot .sf-stat b { color: #fff; }

/* 后台 / 个人中心页脚：一条浅色窄条，与内容区用一道细线分开。
   左边是队伍版权、右边是访问次数 —— .sf-l 的 margin-right:auto 把
   右边那一组顶到最右，不需要再加一个空的占位元素。 */
.sys-foot {
  padding: 14px 20px 16px;
  border-top: 1px solid var(--border);
  background: var(--surface-2);
  font-size: 12px;
  color: var(--text-3);
}
.sys-foot .sf-l { margin-right: auto; }
.sys-foot b { color: var(--text); }

/* v2.22 需求 10：技术支持联系方式（写死在 layout.php 的 tech_support()）。
   视觉上比版权略"重"一点（号码是深色 + 加粗），因为这一行是给
   "出事了要找人"的那一刻用的 —— 不能和版权一样淡到看不见。
   号码做成 tel: 链接，手机上点一下就拨号。 */
.sys-foot .sf-tech {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  color: var(--text-3);
}
.sys-foot .sft-k { opacity: .75; }
.sys-foot .sf-tech a,
.sys-foot .sf-tech .sft-wx { display: inline-flex; align-items: center; gap: 4px; }
.sys-foot .sf-tech a { color: var(--brand); font-weight: 600; }
.sys-foot .sf-tech a:hover { text-decoration: underline; }
.sys-foot .sf-tech svg { opacity: .75; }
.sys-foot .sft-sep { width: 1px; height: 11px; background: var(--border); }

/* 登录页 / 注册页这两页自负骨架（不走 public_start / page_start），
   就地给一个同一口径的浅色横条 —— 少了它，「整个系统底部都有访问次数」
   会在"没登进来先看到的两页"上恰好断掉，而那两页往往是访客看到的第一屏。 */
.page-visit {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px 12px;
  flex-wrap: wrap;
  margin: 18px 0 0;
  font-size: 12px;
  color: var(--text-3);
}
.page-visit b { color: var(--text); }

/* 手机端底部导航（.bottom-nav）是 position:fixed、高 58px 的常驻条，
   会正好盖住页脚最后一行 —— 内容区 .content 早就为它留了 78px 下边距，
   但页脚在 .content 之外，得自己让开这 58px，否则访问次数在手机上
   永远被压在导航条底下（看不见的数字等于没做）。 */
@media (max-width: 860px) {
  /* v2.13 需求 1：后台 / 个人中心页脚手机端也改成两行居中 ——
     原来 .sf-l 带 margin-right:auto，窄屏折行后版权贴左、次数贴右，
     中间一大段空，与「一条窄条」的观感对不上。 */
  .sys-foot {
    flex-direction: column;
    gap: 6px;
    padding-bottom: calc(18px + 58px + env(safe-area-inset-bottom, 0px));
  }
  .sys-foot .sf-l { margin-right: 0; }
  .site-foot .sf-stat { margin-top: 14px; }
}

/* --------------------------------------------------------------------------
   20. 登录 / 注册
   -------------------------------------------------------------------------- */
.auth-wrap {
  min-height: 100vh;
  display: grid;
  grid-template-columns: 1.05fr .95fr;
  background: var(--surface);
}

/* --------------------------------------------------------------------------
   42.6 登录页背景面（v2.12 需求 6）—— .auth-side 与后台预览 .ls-shot 共用
   --------------------------------------------------------------------------
   为什么抽成 .ls-surface 而不是各写一份：
     后台「外观皮肤」页有一块登录页左侧的等比预览。预览和真身必须**完全同源**，
     否则必然漂 —— 改了渐变改了遮罩，只改了一处，就出现"后台调好了、
     登录页不是那样"，而这类问题没人会去点，等到用户截图反馈才发现。
     所以背景、照片铺法、遮罩、模糊全部收在这里，两边只叠自己的排版。
   遮罩为什么用品牌蓝而不是近黑：
     第一版用的是 rgba(6,20,45)（近黑），压在一张亮底团队照（雪地/白墙）上
     结果是一片灰 —— 保住了可读性，但把品牌调子丢了，看着像没做完。
     换成 #123C86 → #1D5FD0 这一路品牌蓝之后，亮底照片会变成"深蓝底上的照片"，
     和默认渐变（未传图时）是同一族颜色，传不传图都还是这支队伍的样子。
   -------------------------------------------------------------------------- */
.ls-surface {
  position: relative;
  overflow: hidden;
  background:
    radial-gradient(760px 420px at 78% 6%, rgba(93, 156, 255, .34), transparent 62%),
    radial-gradient(560px 380px at 6% 96%, rgba(29, 95, 208, .34), transparent 66%),
    linear-gradient(158deg, #0A2550 0%, #123C86 52%, #1D5FD0 100%);
}
.ls-surface.has-photo {
  background-image: var(--ls-photo, none);
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}
.ls-surface.has-photo::before {
  content: "";
  /* 负 inset：模糊会在边缘露出底色，往外撑一点盖住那道白边 */
  position: absolute; inset: -2px;
  background: linear-gradient(158deg,
    rgba(10, 37, 80, var(--ls-ov, .62)) 0%,
    rgba(18, 60, 134, calc(var(--ls-ov, .62) * .94)) 52%,
    rgba(29, 95, 208, calc(var(--ls-ov, .62) * .80)) 100%);
  backdrop-filter: blur(var(--ls-blur, 0px));
  -webkit-backdrop-filter: blur(var(--ls-blur, 0px));
}

/* --------------------------------------------------------------------------
   42.7 登录页左侧品牌区（v2.12 需求 6 重做）
   --------------------------------------------------------------------------
   需求原话：「登录页重新设计（太丑）；左侧背景默认支持后台管理员上传一张照片；
             左侧 logo/标题/内容太偏下要调整」。
   三处改动对应三段：
     ① 「太偏下」：原来是 justify-content:center，内容整体沉在屏幕中部偏下，
        上半屏空一大片。改成 flex-start 从顶部起排，底部数据带用 margin-top:auto
        顶住 —— 空间自然分层，不再有一坨悬在中间。
     ② 层级：品牌行 → 主张 → 要点 → 数据带。原来 logo、标题、四个要点
        全挤在一起，没有主次。
     ③ 背景照片：--ls-photo / --ls-ov（遮罩浓度）/ --ls-blur 三个变量由
        后台「外观皮肤」写入 login.php 的 style 属性；背景与遮罩在 .ls-surface
        里（与后台预览共用，见上）。
    字色一律写死浅色而不用主题变量：这一区无论站点皮肤是什么都是深色底，
    跟着皮肤走反而会出现"浅底白字"。
   -------------------------------------------------------------------------- */
.auth-side {
  color: #fff;
  padding: clamp(28px, 4.8vh, 54px) clamp(26px, 3.4vw, 56px) clamp(24px, 3.6vh, 40px);
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
}
/* v2.13 需求 6：删掉原先那层 42px 网格纹理（::after）。
   「页面整体要简约一些」—— 左栏已经有一张（可选）背景照片 + 右下角水印，
   再叠一层网格就是三套纹理打架，在纯色底上尤其像"设计稿没做完"。
   子元素的 z-index 仍然保留（下面那条），因为背景照片层要继续被压住。 */
.auth-side > *:not(.auth-mark) { position: relative; z-index: 1; }

/* 低位水印：把"顶部起排 + 底部数据带"中间那段空白收住。
   右下角出血（right/bottom 为负）+ 6% 不透明度 —— 看得见轮廓、读不出内容，
   不会跟正文抢注意力。pointer-events:none 免得挡住将来的可点元素。 */
.auth-mark {
  position: absolute; right: -52px; bottom: -34px;
  width: clamp(210px, 25vw, 360px); height: auto;
  color: #fff; opacity: .065;
  pointer-events: none; z-index: 0;
}

/* ---- 品牌行 ---- */
.auth-brand { display: flex; align-items: center; gap: 11px; flex: 0 0 auto; }
.auth-logo { display: flex; color: #fff; }
.auth-brand-txt { display: flex; flex-direction: column; line-height: 1.25; min-width: 0; }
.auth-brand-txt strong { font-size: 15.5px; color: #fff; letter-spacing: .2px; }
.auth-brand-txt em {
  font-style: normal; font-size: 9.5px; letter-spacing: 1.5px;
  color: #8FAEDC; text-transform: uppercase; margin-top: 2px;
}

/* ---- 主张 ---- */
.auth-hero { margin-top: clamp(26px, 5.4vh, 60px); }
.auth-eyebrow {
  display: inline-flex; align-items: center; gap: 6px;
  margin-bottom: 13px; padding: 3px 11px 3px 9px;
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, .13);
  border: 1px solid rgba(255, 255, 255, .17);
  font-size: 11.5px; letter-spacing: .4px; color: #DCEAFB;
}
.auth-side h2 {
  color: #fff; font-size: clamp(25px, 2.15vw, 31px);
  line-height: 1.32; margin-bottom: 13px; letter-spacing: -.2px;
}
.auth-side .auth-hero p {
  color: #C1D8F7; font-size: 14px; line-height: 1.85; margin: 0;
  max-width: 34em;
}

/* ---- v2.13 需求 6：整体压缩留白 ----
   需求原话「页面整体要简约一些」。左栏的真正任务只是"告诉访客这是谁家的系统"，
   原来主张区上下留了 clamp(26px,5.4vh,60px)，一屏 900px 时顶上会空一大块。
   压到 3.6vh 上限 40px，同时把主张文案本身缩短（见 login.php），
   四点要点只剩一行一条的短句 —— 三处一起改，观感才会整体变"轻"。 */
.auth-hero { margin-top: clamp(20px, 3.8vh, 42px); }

/* ---- 左侧栏：四个要点就位（v2.13 起为一行一条的短句）---- */
.auth-side .auth-points {
  margin: clamp(18px, 3.2vh, 32px) 0 0; padding: 0; list-style: none;
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 11px 22px;
}

/* ==========================================================================
   20.1 登录页两套骨架（v2.13 需求 6）
   ==========================================================================
   需求原话：「后台要支持管理员设置为左侧图片+右侧登录按钮，也可以设置成
             整个页面由一张背景图片+登录框组成，两种风格」。

   两套骨架的差异被压到**只有外壳**：
     split —— .auth-wrap 两栏；左栏 .auth-side（背景用 .ls-surface）；右栏表单
     full  —— 一个 fixed 背景层 + 一层居中容器；表单卡加 .auth-card-glass

   表单卡本身是同一段 HTML（login.php 里的 $cardHtml），
   所以"改了样式忘了改另一套"这件事在结构上就不可能发生。
   ========================================================================== */

/* 登录页 <body>。两套风格共用一个类，差异全在子元素上。 */
.auth-page { background: var(--bg); }
.auth-page.is-full { background: #0A2550; }

/* 整页背景层。
   为什么是独立 fixed 层而不是给 <body> 加背景：
     .ls-surface（背景 + 遮罩 + 模糊的唯一实现处）自带 overflow:hidden。
     挂到 <body> 上，内容比视口高时页面就滚不动了 —— 手机上开软键盘、
     或者屏幕特别矮的时候，登录按钮会被推到够不着的地方，
     而且这种 bug 只在特定设备上复现，本机测不出来。
   z-index:-1 让它落到 body 背景之上、所有内容之下：
     body 的 #0A2550 是"图没传/图裂了"时的兜底底色，不会露出白屏。 */
.auth-bg {
  position: fixed;
  inset: 0;
  z-index: -1;
  /* ★ pointer-events:none 不能省。
     这一层铺满整个视口且 z-index 为负 —— 看起来"在最底下"，
     但**负 z-index 不影响命中测试**：它仍然是一个盖住全屏的盒子，
     默认 pointer-events:auto，所有落在空白处的点击（点输入框外的区域、
     点卡片之间的缝）都被它接走。表现是"输入框能打字，但点别处没反应"，
     而且只在整页风格下出现，split 风格永远正常 —— 很难联想到是背景层。
     装饰层一律不吃事件，这是本文件里 .auth-mark 也在用的同一条规矩。 */
  pointer-events: none;
}

/* 居中容器。
   min-height: 100vh + place-items:center —— 内容比一屏高时（手机横屏、
   软键盘弹出）自动变成"从顶部滚"，不会出现居中元素上半截被裁掉。 */
.auth-full {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: clamp(22px, 4vh, 48px) 18px;
}

/* 浮在照片上的登录卡。
   白底不透明 + 一层很轻的外发光，而不是"半透明毛玻璃"：
   半透明卡在浅色照片上会让输入框的边框消失、在深色照片上又会让
   灰字变脏 —— 同一个设置在不同照片上表现相反，管理员没法调。
   固定 94% 白 + 8px 圆角阴影，在任何底图上都读得清。 */
.auth-card-glass {
  background: rgba(255, 255, 255, .96);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  border: 1px solid rgba(255, 255, 255, .5);
  border-radius: 16px;
  padding: clamp(22px, 3.4vh, 34px) clamp(20px, 3vw, 30px);
  /* 两道投影，缺一不可：
       ① 大范围投影 —— 深色照片上让卡片"浮"起来，这是主要的分离手段；
       ② 1px 的深色内环 —— 浅色照片（雪地、白墙、浅色队服）上，
          白底卡压白底图时 ① 几乎看不到，卡片会糊在背景里，
          而那正是"最容易出问题的一种照片"。给一条极淡的深色描边兜底。
     只留 ① 会在浅底照片上出问题，只留 ② 会在深底照片上显得扁平。 */
  box-shadow:
    0 18px 48px rgba(4, 18, 44, .34),
    0 0 0 1px rgba(12, 42, 97, .10);
}
/* 宽度用 .auth-full .auth-card 而不是 .auth-card-glass 自己写 max-width：
   .auth-card 的 max-width:396px 在文件里排在更后面，同为单类选择器时
   后写的赢 —— 玻璃卡写自己的 max-width 会被它悄悄盖掉。
   加一层父选择器把优先级抬到 0,2,0，与出现顺序无关。 */
.auth-full .auth-card { max-width: 408px; }

/* ---- 登录页风格示意卡（v2.13 需求 6）----
   与「站点默认皮肤」复用 .skin-card 的观感，但把左侧那个 46px 的色块换成
   一张 74×46 的排版示意：这是**版式**选择（左右分栏 vs 整屏居中），
   一个纯色方块传达不了两者的区别，而"左侧图片+右侧登录框"这句话
   要读两遍才想得明白。
   .is-cur 的角标文案也必须换掉 —— 皮肤那套写的是"当前默认"，
   用在这里会被读成"这是系统默认值"，而不是"现在启用的是这个"。 */
.skin-card.lg-card.is-cur::after { content: '当前启用'; }
.lg-mock {
  flex: 0 0 74px; height: 46px; border-radius: 9px; overflow: hidden;
  position: relative; border: 1px solid var(--border);
}
.lg-mock-split { display: flex; background: var(--surface); }
.lg-mock-split .lgm-a { flex: 0 0 54%; background: linear-gradient(158deg, #0A2550, #1D5FD0); }
.lg-mock-split .lgm-b { flex: 1 1 auto; }
.lg-mock-split .lgm-b::after {
  content: ''; position: absolute; right: 11%; top: 28%;
  width: 25%; height: 44%; border-radius: 3px;
  background: var(--surface-3); border: 1px solid var(--border);
}
.lg-mock-full { background: linear-gradient(158deg, #0A2550 0%, #123C86 55%, #1D5FD0 100%); }
.lg-mock-full .lgm-a, .lg-mock-full .lgm-b { display: none; }
.lg-mock-full::after {
  content: ''; position: absolute; left: 50%; top: 50%;
  width: 46%; height: 58%; transform: translate(-50%, -50%);
  border-radius: 3px; background: rgba(255, 255, 255, .93);
  box-shadow: 0 2px 8px rgba(0, 0, 0, .3);
}
/* 整页背景预览里"登录框会落在哪"的示意块。
   选择器必须是 .ls-shot > .lg-shot-card 而不是 .lg-shot-card：
   上面 .ls-shot > * { position: relative } 是同优先级的单类选择器，
   靠文件顺序决定胜负，而我这段排在 .ls-shot 之前 —— 写单类会被它压回
   position:relative，那个示意块就变成文档流里的一行，整个预览散掉。
   加一层父选择器把优先级抬到 0,2,0，与顺序无关。 */
.ls-shot > .lg-shot-card {
  position: absolute; left: 50%; top: 50%;
  width: 44%; height: 58%; transform: translate(-50%, -50%);
  border-radius: 6px; background: rgba(255, 255, 255, .93);
  box-shadow: 0 6px 20px rgba(0, 0, 0, .3);
}
/* 深色皮肤下也要保证卡内文字清楚：卡是白底，文字必须回到浅底那一套，
   不能跟着皮肤走 —— 否则暗色皮肤会出现"白卡上写白字"。 */
.auth-card-glass,
.auth-card-glass h1,
.auth-card-glass .field > label { color: #16233A; }
.auth-card-glass .auth-sub { color: #5A6B85; }
.auth-card-glass .hint { color: #7A879C; }
.auth-card-glass .remember-row { color: #3B4A63; }
.auth-card-glass .auth-foot { color: #5A6B85; }
.auth-card-glass .auth-foot a { color: var(--brand); }
.auth-card-glass .page-visit { color: #7A879C; }
.auth-card-glass .page-visit b { color: #3B4A63; }

/* 整页风格下卡片顶部的队伍标识（左栏没了，身份要进卡片） */
.auth-card-brand {
  display: flex; align-items: center; gap: 11px;
  padding-bottom: 14px; margin-bottom: 16px;
  border-bottom: 1px solid #E4E9F2;
}
.auth-card-brand .auth-logo { display: flex; color: var(--brand-dark); }
.auth-card-brand .auth-brand-txt { display: flex; flex-direction: column; line-height: 1.25; min-width: 0; }
.auth-card-brand .auth-brand-txt strong { font-size: 15.5px; color: #16233A; letter-spacing: .2px; }
.auth-card-brand .auth-brand-txt em {
  font-style: normal; font-size: 9.5px; letter-spacing: 1.5px;
  color: #8496B3; text-transform: uppercase; margin-top: 2px;
}

.auth-point {
  display: flex; gap: 10px; align-items: flex-start;
  font-size: 13px; line-height: 1.65; color: #DCEAFB;
}
.auth-point .ap-ico {
  width: 25px; height: 25px; border-radius: 8px; flex: 0 0 25px;
  background: rgba(255, 255, 255, .15);
  display: grid; place-items: center;
}
.auth-point .ap-ico svg { width: 15px; height: 15px; }

/* ---- 底部数据带 ---- */
/* margin-top:auto 把它顶到底；上边一条发丝线把它和正文分开。
   数字取真库（includes/functions.php 的 public_team_stats()），
   口径与数据看板 board.php 完全一致 —— 两处数字不一样，用户一眼就看出假。 */
.auth-stats {
  margin-top: auto; padding-top: clamp(20px, 3.6vh, 38px);
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 10px 16px;
  border-top: 1px solid rgba(255, 255, 255, .14);
}
.auth-stats .as-item { min-width: 0; }
.auth-stats .as-item b {
  display: block; font-size: clamp(19px, 1.65vw, 24px); line-height: 1.15;
  color: #fff; font-variant-numeric: tabular-nums; letter-spacing: -.4px;
}
.auth-stats .as-item span {
  display: block; margin-top: 3px;
  font-size: 11.5px; line-height: 1.45; color: #93B4E2;
}

.auth-form-side { display: flex; align-items: center; justify-content: center; padding: 40px 20px; }
.auth-card { width: 100%; max-width: 396px; }
.auth-card h1 { font-size: 25px; margin-bottom: 6px; }
.auth-card .auth-sub { color: var(--text-3); font-size: 13.5px; margin-bottom: 26px; }
.auth-card .field { margin-bottom: 15px; }
.auth-tip {
  margin-top: 18px;
  padding: 12px 14px;
  border-radius: var(--radius-sm);
  background: var(--brand-lighter);
  border: 1px dashed #C9DCFB;
  font-size: 12.5px;
  color: var(--text-2);
  line-height: 1.75;
}
.auth-tip code {
  background: var(--surface);
  padding: 1px 6px;
  border-radius: 5px;
  font-family: var(--mono);
  font-size: 12px;
  color: var(--brand-dark);
  border: 1px solid var(--border);
}

/* --------------------------------------------------------------------------
   42.8 登录页 · 演示账号清单（需求 #19）
   --------------------------------------------------------------------------
   原先是一行「管理员：admin　管理层：manager　正式队员：zhaomin」的纯文字，
   六个账号挤成两行、中英文混排、"哪个是人名哪个是账号"要读两遍。
   现在改成一张小表：身份 / 手机号 / 姓名各占一列，手机号可点即填。
   一列一列对齐之后，扫一眼就知道该用哪个号进哪个权限。
   -------------------------------------------------------------------------- */
.auth-tip .demo-rows {
  display: grid;
  gap: 4px;
  margin: 8px 0 7px;
}
.auth-tip .demo-row {
  display: grid;
  grid-template-columns: 84px minmax(0, 1fr) auto;
  align-items: center;
  gap: 8px;
  font-size: 12px;
}
.auth-tip .dr-role { color: var(--text-3); }
.auth-tip .dr-phone {
  cursor: pointer;
  font-weight: 600;
  letter-spacing: .4px;
  text-align: left;
  transition: background .14s, border-color .14s, color .14s;
}
.auth-tip .dr-phone:hover {
  background: var(--brand);
  border-color: var(--brand);
  color: #fff;
}
.auth-tip .dr-name { color: var(--text-3); white-space: nowrap; }
.auth-tip .dr-note { font-size: 11.5px; line-height: 1.75; color: var(--text-3); }
@media (max-width: 420px) {
  .auth-tip .demo-row { grid-template-columns: 76px minmax(0, 1fr); }
  .auth-tip .dr-name { display: none; }
}
.auth-foot { text-align: center; margin-top: 24px; font-size: 13.5px; color: var(--text-3); }

/* 需求 #62：30 天免登录的勾选框与说明。
   说明用浅底小字块而不是弹窗 —— 登录页弹「是否同意协议」会平白多一次点击。 */
.remember-row {
  display: flex; align-items: center; gap: 8px;
  margin: 2px 0 8px; font-size: 13.5px; color: var(--text-2);
  cursor: pointer; user-select: none;
}
.remember-row input[type="checkbox"] {
  width: 15px; height: 15px; flex: none;
  accent-color: var(--brand); cursor: pointer;
}
.remember-row strong { color: var(--brand-dark); }
.remember-tip {
  display: flex; gap: 7px; align-items: flex-start;
  margin: 0 0 4px; padding: 9px 11px;
  border-radius: var(--radius-sm);
  background: var(--brand-lighter);
  border: 1px solid var(--border);
  font-size: 12px; line-height: 1.7; color: var(--text-3);
}
.remember-tip svg { flex: none; margin-top: 2px; color: var(--brand); }

/* --------------------------------------------------------------------------
   21. 工具类
   -------------------------------------------------------------------------- */
.grid-2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 16px; align-items: start; }
.grid-3 { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 14px; }
.flex { display: flex; }
.flex-wrap { flex-wrap: wrap; }
.items-center { align-items: center; }
.items-end { align-items: flex-end; }
.justify-between { justify-content: space-between; }
.gap-4 { gap: 4px; } .gap-6 { gap: 6px; } .gap-8 { gap: 8px; }
.gap-10 { gap: 10px; } .gap-12 { gap: 12px; } .gap-16 { gap: 16px; }
.ml-auto { margin-left: auto; }
.mt-0 { margin-top: 0; } .mt-8 { margin-top: 8px; } .mt-12 { margin-top: 12px; }
.mt-16 { margin-top: 16px; } .mt-24 { margin-top: 24px; } .mt-8 { margin-top: 8px; }
/* mt-10 / mt-14 在页面里已多处使用，之前漏了定义，补上以免间距失效 */
.mt-10 { margin-top: 10px; } .mt-14 { margin-top: 14px; }
.mb-0 { margin-bottom: 0; } .mb-8 { margin-bottom: 8px; } .mb-12 { margin-bottom: 12px; }
.mb-16 { margin-bottom: 16px; }
.text-2 { color: var(--text-2); } .text-3 { color: var(--text-3); }
.text-sm { font-size: 12.5px; } .text-xs { font-size: 11.5px; }
.text-ok { color: var(--ok); } .text-warn { color: var(--warn); } .text-danger { color: var(--danger); }
.text-brand { color: var(--brand); }
.fw-600 { font-weight: 600; } .fw-700 { font-weight: 700; }
.nowrap { white-space: nowrap; }
.mono { font-family: var(--mono); }
.hidden { display: none !important; }
.divider { height: 1px; background: var(--border); margin: 16px 0; }
.scroll-x { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.hide-mobile { display: initial; }
.show-mobile { display: none; }

/* --------------------------------------------------------------------------
   22. 响应式 —— 手机端适配
   -------------------------------------------------------------------------- */

/* 平板 */
@media (max-width: 1024px) {
  .hero-inner { grid-template-columns: 1fr; gap: 34px; padding: 48px 20px 56px; }
  .hero h1 { font-size: 34px; }
  .auth-wrap { grid-template-columns: 1fr; }
  /* ★ v2.12 需求 6：原来是 display:none —— 手机上登录页只剩一个光秃秃的表单，
     连"这是谁家的系统"都看不出来。改成一条横幅：留品牌行 + 一句主张，
     要点列表与数据带收起（手机上一屏要留给表单本身）。
     管理员传的背景照片在横幅上继续生效 —— 传了图却只在大屏可见，
     是很容易忽略的一类"功能没坏但没人看到"。
     注意这一条放在 1024 断点里，得覆盖掉上面 .auth-side 的 flex 布局。 */
  .auth-side {
    justify-content: flex-start;
    gap: 0;
    padding: 18px 20px 20px;
    min-height: 0;
  }
  .auth-hero { margin-top: 14px; }
  .auth-eyebrow { margin-bottom: 10px; font-size: 11px; }
  .auth-side h2 { font-size: 21px; margin-bottom: 8px; }
  .auth-side h2 br { display: none; }        /* 手机上折成一行更利落 */
  .auth-side .auth-hero p { font-size: 12.5px; line-height: 1.7; }
  .auth-side .auth-points, .auth-stats { display: none; }
  .form-grid .span-3 { grid-column: span 2; }
}

/* 手机 / 窄屏 */
@media (max-width: 860px) {
  /* 侧边栏转为抽屉 */
  .sidebar {
    position: fixed;
    left: 0; top: 0; bottom: 0;
    height: 100%;
    transform: translateX(-102%);
    transition: transform .26s cubic-bezier(.4, 0, .2, 1);
    box-shadow: var(--shadow-lg);
    width: 264px;
    flex-basis: 264px;
  }
  .sidebar.open { transform: translateX(0); }
  .drawer-mask { display: block; pointer-events: none; }
  .drawer-mask.show { pointer-events: auto; }

  .hamburger { display: inline-flex; }

  /* 顶部栏紧凑 */
  .topbar { padding: 0 12px; gap: 9px; }
  .topbar-title h1 { font-size: 15.5px; }
  .topbar-title .crumb { display: none; }
  .user-chip .uc-text { display: none; }
  .user-chip { padding: 4px; }
  .topbar-actions .hide-mobile { display: none; }

  .content { padding: 14px 12px 78px; }

  /* 底部导航 */
  .bottom-nav { display: flex; }

  /* 统计卡两列 */
  .stat-grid { grid-template-columns: repeat(2, 1fr); gap: 10px; }
  .stat { padding: 13px 14px; }
  .stat .stat-value { font-size: 22px; }
  .stat .stat-label { font-size: 11.5px; }
  .stat-icon { width: 28px; height: 28px; border-radius: 8px; margin-bottom: 7px; }
  .stat-icon svg { width: 15px; height: 15px; }

  .card-head { padding: 12px 14px; }
  .card-body { padding: 14px; }
  .card-foot { padding: 10px 14px; }
  .pager { padding: 12px 14px; }
  /* 标签栏：移动端换行铺开，避免横向滚动把后面的分类藏起来 */
  .tabs {
    padding: 0 10px;
    flex-wrap: wrap;
    overflow-x: visible;
    row-gap: 0;
  }
  .tabs a { padding: 10px 10px; font-size: 12.5px; }

  /* 筛选栏：多选芯片整行铺开，按钮铺满 */
  .filter-bar { gap: 12px 8px; }
  .filter-bar .field,
  .filter-bar .field.wide { flex: 1 1 100%; min-width: 0; }
  .filter-bar .fb-actions { flex: 1 1 100%; }
  .filter-bar .fb-actions .btn { flex: 1; justify-content: center; }
  .msel { max-height: 148px; }
  .msel-item > span { padding: 7px 13px; min-height: 34px; font-size: 13px; }

  /* 表单：单列 */
  .form-grid { grid-template-columns: 1fr; gap: 12px; }
  .form-grid .span-2, .form-grid .span-3 { grid-column: span 1; }
  .field > label { font-size: 12.5px; }
  .input, .select, .textarea { font-size: 16px; } /* ≥16px 避免 iOS 自动放大 */

  /* 筛选栏整行 */
  .filter-bar { gap: 8px; }
  .filter-bar .field { flex: 1 1 100%; }
  .filter-bar .field.half { flex: 1 1 calc(50% - 4px); min-width: 0; }
  .filter-bar .fb-actions { flex: 1 1 100%; }
  .filter-bar .fb-actions .btn { flex: 1 1 auto; }

  /* 表格：横向滚动 + 更小内边距 */
  .table { font-size: 13px; min-width: 560px; }
  .table thead th { padding: 9px 11px; font-size: 12px; }
  .table tbody td { padding: 9px 11px; }

  /* 卡片化表格（用于关键列表） */
  .table-stack { min-width: 0; }
  .table-stack thead { display: none; }
  .table-stack tbody tr {
    display: block;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    margin-bottom: 10px;
    padding: 6px 4px;
    background: var(--surface);
  }
  .table-stack tbody tr:hover { background: var(--surface); }
  .table-stack tbody td {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 7px 10px;
    border-bottom: 1px dashed #EEF2F8;
    text-align: left !important;
  }
  .table-stack tbody tr td:last-child { border-bottom: none; }
  .table-stack tbody td::before {
    content: attr(data-label);
    flex: 0 0 82px;
    color: var(--text-3);
    font-size: 12px;
    font-weight: 600;
  }
  .table-stack tbody td.col-actions { justify-content: flex-start; }
  .table-stack tbody td.col-actions::before { content: attr(data-label); }

  /* 档案头部 */
  .profile-head { padding: 16px; gap: 14px; }
  .profile-head .ph-avatar { width: 62px; height: 62px; flex-basis: 62px; }
  .profile-head h1 { font-size: 18px; }
  .profile-head .ph-actions { width: 100%; }
  .profile-head .ph-actions .btn { flex: 1 1 auto; }

  .dl { grid-template-columns: 1fr; }
  .dl-item { flex-direction: column; gap: 2px; }
  .dl-item dt { flex: none; }

  /* 前台 */
  .site-head-inner { padding: 10px 14px; }
  .site-logo strong { font-size: 14.5px; }
  .site-nav { display: none; }
  .site-nav.open {
    display: flex;
    position: absolute;
    /* 相对 .site-head 底部展开（此前误用了后台顶栏变量 --topbar-h，
       前台并未定义该变量，导致 top 失效、菜单在页头内部错位展开） */
    top: 100%;
    left: 0; right: 0;
    flex-direction: column;
    background: var(--surface);
    border-bottom: 1px solid var(--border);
    box-shadow: var(--shadow);
    padding: 8px 12px 14px;
    gap: 2px;
    z-index: 60;
    max-height: calc(100vh - 64px);
    overflow-y: auto;
  }
  .site-nav.open a { padding: 11px 12px; }
  .site-head .hamburger { display: inline-flex; margin-left: auto; }

  .hero-inner { padding: 38px 16px 44px; gap: 26px; }
  .hero h1 { font-size: 26px; line-height: 1.3; }
  .hero-lead { font-size: 14px; line-height: 1.8; }
  .hero-actions .btn { flex: 1 1 100%; }
  .hero-stats { grid-template-columns: repeat(2, 1fr); gap: 9px; }
  .hero-stat { padding: 13px 14px; }
  .hero-stat .hs-val { font-size: 22px; }

  .section { padding: 40px 16px; }
  .section-head h2 { font-size: 21px; }
  .section-head p { font-size: 13.5px; }
  .feature-grid { grid-template-columns: 1fr; gap: 12px; }
  .feature { padding: 18px; }

  .query-panel { padding: 16px; }
  .query-row .btn { flex: 1 1 100%; }

  .result-card { padding: 14px; gap: 12px; }
  .result-card .rc-avatar { width: 48px; height: 48px; flex-basis: 48px; }

  /* ── 页脚（v2.13 需求 1：手机版重排）────────────────────────────────
     旧版窄屏把 .sf-legal 折成「每项一行、左对齐」。实测 390px 宽下
     三行小字只占 136px，右边 220px 全是空 —— 整块看着像没排完。
     新版三层居中：
       ① 品牌行（队伍名 + 系统名）
       ② 法务行（居中平铺、自然折行）
       ③ 访问次数（更小更暗，单独一行）
     ★ 法务行为什么**去掉中点分隔符**：折行位置不可控，点掉到行首或行尾
       就像排版坏了（旧版正是为这个才退化成每项一行）。改用 14px 列间距分组，
       宁可少一个符号，也不要随机出现一个孤零零的点。 */
  .site-foot { padding: 24px 16px calc(18px + env(safe-area-inset-bottom, 0px)); }
  .site-foot-inner {
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 10px;
  }
  .site-foot .foot-right { margin-left: 0; }
  .site-foot .sf-brand { justify-content: center; gap: 6px 10px; }
  .site-foot .sf-legal {
    margin-left: 0;
    flex-direction: row;
    flex-wrap: wrap;
    justify-content: center;
    align-items: center;
    gap: 6px 14px;
    font-size: 12px;
    max-width: 340px;
  }
  .site-foot .sf-legal > * + *::before { content: none; }
  .site-foot .sf-nav { justify-content: center; }

  .auth-form-side { padding: 26px 18px; }
  .auth-card h1 { font-size: 22px; }

  /* v2.13 需求 6：整页风格的手机适配。
     窄屏上把卡的四角收一点、内边距压小，让表单本身占满可用宽度 ——
     玻璃卡在 390px 宽的屏上左右各留 18px 后只剩 354px，
     再扣掉 30px 内边距就只剩 294px 给输入框，手机上偏挤。 */
  .auth-full { padding: 20px 14px; }
  .auth-card-glass {
    border-radius: 14px;
    padding: 20px 16px;
    box-shadow: 0 12px 30px rgba(4, 18, 44, .28);
  }

  .hide-mobile { display: none !important; }
  .show-mobile { display: initial; }

  .modal-box { width: calc(100% - 28px); margin: 0 auto; }
}

@media (max-width: 420px) {
  .stat-grid { grid-template-columns: repeat(2, 1fr); }
  .stat .stat-value { font-size: 20px; }
  .hero h1 { font-size: 23px; }
  h1 { font-size: 20px; }
  .btn-group .btn { flex: 1 1 auto; }
}

/* --------------------------------------------------------------------------
   23. 弹窗
   -------------------------------------------------------------------------- */
.modal-mask {
  position: fixed; inset: 0;
  background: rgba(12, 28, 56, .5);
  z-index: 90;
  display: flex; align-items: center; justify-content: center;
  padding: 16px;
  opacity: 0; pointer-events: none;
  transition: opacity .2s;
}
.modal-mask.show { opacity: 1; pointer-events: auto; }
.modal-box {
  background: var(--surface);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  width: 100%;
  max-width: 560px;
  max-height: 88vh;
  overflow-y: auto;
  transform: translateY(10px) scale(.98);
  transition: transform .2s;
}
.modal-mask.show .modal-box { transform: none; }
.modal-head {
  display: flex; align-items: center; gap: 10px;
  padding: 15px 18px;
  border-bottom: 1px solid var(--border);
}
.modal-head h3 { font-size: 16px; }
.modal-head .modal-close {
  margin-left: auto; background: none; border: none;
  font-size: 22px; line-height: 1; color: var(--text-3); padding: 0 4px;
}
.modal-body { padding: 18px; }
.modal-foot {
  display: flex; gap: 10px; justify-content: flex-end;
  padding: 13px 18px;
  border-top: 1px solid var(--border);
  background: var(--surface-2);
}

/* --------------------------------------------------------------------------
   24. 上传控件
   -------------------------------------------------------------------------- */
/* 上传区也一并收紧（需求 #6：「上传图片占用太大空间」）：
   虚线框内边距 18 → 12、图标 26 → 20、预览图 74 → 60。
   上传区在页面上往往成对出现（头像 + 证书 + 附件），
   每处省 20px 就是一屏的差别。 */
.upload-zone {
  border: 1.5px dashed var(--border-strong);
  border-radius: var(--radius-sm);
  padding: 12px;
  text-align: center;
  background: var(--surface-2);
  transition: border-color .16s, background .16s;
  cursor: pointer;
}
.upload-zone:hover { border-color: var(--brand); background: var(--brand-lighter); }
.upload-zone svg { width: 20px; height: 20px; color: var(--brand); margin: 0 auto 5px; }
.upload-zone .uz-txt { font-size: 12.5px; color: var(--text-2); }
.upload-zone .uz-hint { font-size: 11px; color: var(--text-3); margin-top: 2px; }
.upload-zone input[type=file] { display: none; }
/* 紧凑档上传框（需求 1：档案页的头像区）。
   默认档是"一整行高的拖拽区"，放在 88px 头像旁边时会把那一行顶到 140px。
   紧凑档把内边距与图标一起收小，与头像并排刚好齐高。 */
.upload-zone.uz-sm { padding: 10px 12px; border-radius: var(--radius-sm); }
.upload-zone.uz-sm svg { width: 16px; height: 16px; margin-bottom: 3px; }
.upload-zone.uz-sm .uz-txt { font-size: 12px; }
.upload-zone.uz-sm .uz-hint { font-size: 10.5px; }
.upload-preview { display: flex; gap: 10px; align-items: center; margin-top: 10px; }
.upload-preview img { width: 60px; height: 60px; border-radius: var(--radius-xs); object-fit: cover; border: 1px solid var(--border); }

/* --- 25.0 需求 1 · 「我的档案」的信息分块（user/profile.php） ---
   头像 + 只读身份标识合成一块：左边头像与换头像入口，右边 4 格只读信息。
   只读信息用「发丝线网格」——容器铺一层边框色当线、格子铺底色，
   1px 的 gap 露出来就是分隔线。比给每格画边框少一半规则，
   也不会出现"第一格左边多一条线"这种拼缝。 */
.me-head {
  display: flex; gap: 18px; align-items: flex-start; flex-wrap: wrap;
}
.me-av {
  flex: 0 0 auto;
  display: flex; flex-direction: column; align-items: center; gap: 9px;
}
.me-av-img,
/* 选完图之后 app.js 的 initUpload() 会把 [data-preview] 的内容整块换成
   `<img src=blob:…>`（不带任何 class），旧的 88px 方形预览就是在这儿退化的。
   所以圆形样式挂到结构选择器上，别只挂在 .me-av-img 上。 */
.me-av [data-preview] > img {
  width: 84px; height: 84px; border-radius: 50%;
  object-fit: cover;
  border: 2px solid var(--surface);
  box-shadow: 0 0 0 1px var(--border), var(--shadow-sm);
  background: var(--surface-2);
  display: block;
}
.me-av .upload-zone { width: 138px; }

.me-ro {
  flex: 1 1 340px;
  margin: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 1px; padding: 1px;
  border-radius: var(--radius-sm);
  background: var(--border);
  overflow: hidden;
}
.me-ro-item {
  display: flex; flex-direction: column; gap: 2px;
  min-width: 0;
  padding: 9px 12px;
  background: var(--surface-2);
}
.me-ro-item dt { font-size: 11.5px; color: var(--text-3); }
.me-ro-item dd {
  margin: 0;
  font-size: 14.5px; font-weight: 600; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* 长说明折叠（原生 <details>，不依赖 JS）。
   收起时只有一行小字，点开才有正文 —— 编号怎么申请这类话
   是"要知道但不必每次看一遍"的知识，摆在表单里只会把行高撑到 110px。 */
.me-note {
  margin-top: 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
}
.me-note > summary {
  padding: 8px 12px;
  font-size: 12.5px;
  color: var(--text-2);
  cursor: pointer;
  list-style: none;
  display: flex; align-items: center; gap: 6px;
}
.me-note > summary::-webkit-details-marker { display: none; }
.me-note > summary::before {
  content: "";
  width: 0; height: 0;
  border-left: 5px solid currentColor;
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
  transition: transform .18s;
  flex: 0 0 auto;
}
.me-note[open] > summary::before { transform: rotate(90deg) translateX(-1px); }
.me-note > summary:hover { color: var(--brand); }
.me-note-body { padding: 0 12px 11px 25px; }
.me-note-body p { font-size: 12.5px; line-height: 1.75; color: var(--text-2); margin: 0 0 7px; }
.me-note-body p:last-child { margin-bottom: 0; }
/* 打印时说明一律展开输出 —— 纸上没有"点开"这回事，
   而编号/ID 的来历恰恰是纸质档案最常被问到的地方。 */
@media print {
  .me-note > .me-note-body { display: block !important; }
  .me-note > summary::before { display: none; }
}
/* 证书缩略图：点击即开灯箱放大（PC 悬停有反馈，手机点按全屏） */
.cert-thumb {
  width: 44px; height: 44px; border-radius: 7px; object-fit: cover;
  border: 1px solid var(--border); cursor: zoom-in; background: var(--surface-2);
  transition: border-color .15s, transform .15s;
}
.cert-thumb:hover { border-color: var(--brand); transform: scale(1.04); }

/* --------------------------------------------------------------------------
   25. 新增模块组件
   覆盖：通知公告 / 任务发布报名 / 专业分组通讯录 / 荣誉审核 / 内容与捐赠
   -------------------------------------------------------------------------- */

/* --- 25.1 通用小工具 --- */
.grid-main { display: grid; grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr); gap: 16px; align-items: start; }
.items-start { align-items: flex-start; }
/* 表格里的主标题链接：保持正文字重与颜色，悬停才出现品牌色，避免整列变蓝 */
a.strong { color: var(--text); text-decoration: none; }
a.strong:hover { color: var(--brand); }
.input-sm { padding: 7px 10px; font-size: 13px; border-radius: var(--radius-xs); }
.inline-form { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }

/* --- 需求 #64：人员列表里就地切换在岗状态 ---
   表格单元格很窄，下拉压到最小尺寸；休假日期只在选了「休假」时才出现，
   平时 hidden，避免每行都多一个输入框把表格撑开。 */
.inline-online { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.inline-online .select-xs {
  padding: 4px 8px; font-size: 12.5px; border-radius: var(--radius-xs);
  min-width: 78px; height: auto; line-height: 1.5;
}
.inline-online .vac-pick { width: 138px; max-width: 100%; }
.inline-online .input-sm { padding: 4px 8px; font-size: 12.5px; }
.form-grid-1 { grid-template-columns: 1fr; }
.thumb-link { display: inline-block; margin-top: 6px; }
.thumb-link img { width: 88px; height: 88px; border-radius: var(--radius-xs); object-fit: cover; border: 1px solid var(--border); }

/* --- 25.2 图片缩略图组 + 点击放大 --- */
.thumb-grid { display: flex; flex-wrap: wrap; gap: 8px; }
.thumb-item { position: relative; width: 96px; }
.thumb-item img {
  width: 96px;
  height: 72px;
  object-fit: cover;
  border-radius: var(--radius-xs);
  border: 1px solid var(--border);
  background: var(--bg);
  display: block;
  cursor: zoom-in;
  transition: border-color .15s, transform .15s;
}
.thumb-item img:hover { border-color: var(--brand); transform: translateY(-1px); }
.thumb-cap {
  margin-top: 3px;
  font-size: 11px;
  color: var(--text-3);
  text-align: center;
}
/* 空位提示，避免「四格缺一格」时版式塌陷 */
.thumb-empty {
  width: 96px;
  height: 72px;
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-xs);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  color: var(--text-3);
  background: var(--surface-2);
}

/* 图片放大层 */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 2000;
  background: rgba(12, 20, 36, .82);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 24px;
  cursor: zoom-out;
}
.lightbox img {
  max-width: min(92vw, 1100px);
  max-height: 82vh;
  border-radius: var(--radius-sm);
  background: var(--fixed-white);
  box-shadow: 0 20px 60px rgba(0, 0, 0, .45);
}
.lightbox-cap { color: #EAF0FA; font-size: 13px; }
.lightbox-close {
  position: absolute;
  top: 18px;
  right: 20px;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  border: none;
  background: rgba(255, 255, 255, .16);
  color: #fff;
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
}
.lightbox-close:hover { background: rgba(255, 255, 255, .28); }

/* 车辆四视图上传格 */
.photo-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(148px, 1fr)); gap: 12px; }
.photo-cell {
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 10px;
  background: var(--surface-2);
}
.photo-cell > img {
  width: 100%;
  height: 96px;
  object-fit: cover;
  border-radius: var(--radius-xs);
  border: 1px solid var(--border);
  background: var(--bg);
  display: block;
  cursor: zoom-in;
}
.photo-cell .thumb-empty { width: 100%; height: 96px; }
.photo-cell .input-sm { margin-top: 8px; font-size: 11.5px; padding: 5px 7px; }
.photo-del {
  display: flex;
  align-items: center;
  gap: 5px;
  margin-top: 7px;
  font-size: 12px;
  color: var(--text-3);
  cursor: pointer;
}

/* 列表内的照片小图条 */
.mini-thumbs { display: flex; gap: 4px; flex-wrap: wrap; align-items: center; }
.mini-thumbs img {
  width: 38px;
  height: 30px;
  object-fit: cover;
  border-radius: 4px;
  border: 1px solid var(--border);
  background: var(--bg);
  cursor: zoom-in;
  display: block;
}
.mini-thumbs img:hover { border-color: var(--brand); }
.mt-4 { margin-top: 4px; }
.mb-4 { margin-bottom: 4px; }
.ml-4 { margin-left: 4px; }

/* 通用文本/数值（表格外同样可用） */
.strong { font-weight: 600; color: var(--text); }
.num { font-variant-numeric: tabular-nums; }
.kv { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 9px 14px; margin: 0; font-size: 13.5px; }
.kv dt { color: var(--text-3); white-space: nowrap; }
.kv dd { margin: 0; color: var(--text); word-break: break-word; }

/* 步骤说明列表 */
.step-list { margin: 0; padding-left: 20px; display: flex; flex-direction: column; gap: 10px; font-size: 13.5px; color: var(--text-2); line-height: 1.7; }
.step-list li::marker { color: var(--brand); font-weight: 700; }
.step-list li b { color: var(--text); }

/* --- 25.17 救援贡献量化（履历） --- */
.contrib-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(126px, 1fr)); gap: 12px; }
.contrib-item {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: 16px 10px; border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface-2); text-align: center;
}
.contrib-item b { font-size: 30px; font-weight: 700; line-height: 1.05; color: var(--brand); font-variant-numeric: tabular-nums; }
.contrib-item span { font-size: 12.5px; color: var(--text-2); }
.contrib-item small { font-size: 11px; color: var(--text-3); }
.contrib-item.is-total { background: var(--brand-light); border-color: var(--brand-border); }
.contrib-item.is-total b { color: var(--brand-dark); }
.contrib-item.is-total span { color: var(--brand-dark); }

.honor-list { display: flex; flex-direction: column; gap: 10px; }
.honor-item { display: flex; align-items: center; gap: 10px; padding: 9px 11px; border: 1px solid var(--border); border-radius: var(--radius-xs); background: var(--surface-2); }
.honor-item img { width: 34px; height: 34px; border-radius: 50%; object-fit: cover; flex: 0 0 34px; border: 1px solid var(--border); }

/* --- 25.18 前台单页 / 快捷入口 / 捐赠公示 --- */
.btn-primary { background: var(--brand); color: #fff; }
.btn-primary:hover { background: var(--brand-dark); }

.page-body { font-size: 14.5px; }
.page-body h1, .page-body h2 { font-size: 19px; }
.page-body h3 { font-size: 16px; }

.quick-links { display: grid; grid-template-columns: repeat(auto-fit, minmax(206px, 1fr)); gap: 12px; }
.quick-link {
  display: flex; align-items: center; gap: 12px;
  padding: 14px 16px; border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface); text-decoration: none; color: inherit;
  transition: border-color .16s, box-shadow .16s, transform .16s;
}
.quick-link:hover { border-color: var(--brand); box-shadow: var(--shadow); transform: translateY(-1px); }
.quick-link svg { color: var(--brand); flex: 0 0 auto; }
.quick-link span { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.quick-link b { font-size: 14px; }
.quick-link small { font-size: 11.5px; color: var(--text-3); }

.donor-mark { width: 38px; height: 38px; border-radius: 50%; flex: 0 0 38px; display: flex; align-items: center; justify-content: center; background: var(--danger-light); color: var(--danger); }
.donor-mark svg { width: 18px; height: 18px; }

/* 富文本（任务说明 / 通知正文）——后台录入，无需防注入处理 */
.rich-text { font-size: 14px; line-height: 1.78; color: var(--text-2); word-break: break-word; }
.rich-text p { margin: 0 0 10px; }
.rich-text p:last-child { margin-bottom: 0; }
.rich-text img { max-width: 100%; border-radius: var(--radius-sm); margin: 6px 0; }
.rich-text ul, .rich-text ol { padding-left: 20px; margin: 0 0 10px; }
.rich-text li { margin-bottom: 4px; }
.rich-text h1, .rich-text h2, .rich-text h3 { color: var(--text); font-size: 15px; margin: 14px 0 8px; }
.rich-text a { color: var(--brand); text-decoration: underline; }
.rich-text table { width: 100%; border-collapse: collapse; margin: 8px 0; }
.rich-text th, .rich-text td { border: 1px solid var(--border); padding: 6px 9px; font-size: 13px; }
.rich-text blockquote { margin: 8px 0; padding: 8px 12px; background: var(--surface-2); border-left: 3px solid var(--brand); border-radius: 0 var(--radius-xs) var(--radius-xs) 0; color: var(--text-2); }

/* --- 25.2 头像 --- */
.avatar { width: 38px; height: 38px; border-radius: 50%; object-fit: cover; flex: 0 0 38px; border: 1px solid var(--border); background: var(--brand-lighter); }
.avatar-sm { width: 34px; height: 34px; flex-basis: 34px; }
.avatar-lg { width: 56px; height: 56px; flex-basis: 56px; }

/* --- 25.3 可点击统计卡 --- */
.stat-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(158px, 1fr)); gap: 12px; margin-bottom: 16px; }
.stat-link { display: block; text-decoration: none; color: inherit; transition: border-color .16s, box-shadow .16s; }
.stat-link:hover { border-color: var(--border-strong); box-shadow: var(--shadow); }
.stat.is-active { border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-light); }
.stat.is-active::after { background: var(--brand-light); }
.stat.is-active .stat-value { color: var(--brand); }

/* --- 25.4 列表栈（通知 / 荣誉 / 通用条目） --- */
.list-stack { display: flex; flex-direction: column; }
.list-row { display: flex; align-items: flex-start; gap: 12px; padding: 14px 18px; border-bottom: 1px solid var(--border); }
.list-row:last-child { border-bottom: none; }
.list-row:hover { background: var(--surface-2); }
.list-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 5px; }
.list-title { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 14px; }
.list-title a { color: var(--brand); }
.list-sub { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 13.5px; }
.list-meta { display: flex; align-items: center; gap: 6px 14px; flex-wrap: wrap; font-size: 12px; color: var(--text-3); }
.list-note { font-size: 12.5px; color: var(--text-2); background: var(--surface-2); border-left: 3px solid var(--border-strong); border-radius: 0 var(--radius-xs) var(--radius-xs) 0; padding: 7px 10px; }
.list-actions { flex: 0 0 auto; display: flex; flex-direction: column; gap: 8px; align-items: stretch; min-width: 132px; }
.list-actions .btn { white-space: nowrap; }

/* --- 25.5 荣誉勋章 --- */
.honor-cell { display: flex; align-items: center; gap: 10px; }
.honor-cell img { width: 34px; height: 34px; border-radius: 50%; object-fit: cover; border: 1px solid var(--border); flex: 0 0 34px; }
.honor-mark { width: 34px; height: 34px; border-radius: 50%; display: flex; align-items: center; justify-content: center; background: var(--warn-light); color: var(--warn); flex: 0 0 34px; }
.honor-mark svg { width: 17px; height: 17px; }

/* --- 25.6 专业分组 --- */
.group-list { display: flex; flex-direction: column; gap: 10px; }
.group-item { display: flex; align-items: flex-start; gap: 12px; padding: 13px 14px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface); transition: border-color .16s, background .16s; }
.group-item:hover { border-color: var(--border-strong); background: var(--surface-2); }
.group-item.is-off { opacity: .62; }
.g-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 5px; }
.g-name { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; font-size: 14px; font-weight: 600; }
.g-desc { font-size: 12.5px; color: var(--text-2); }
.g-actions { flex: 0 0 auto; display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }

/* --- 25.7 成员胶囊（通讯录 / 任务名单） --- */
.member-chips { display: flex; flex-wrap: wrap; gap: 10px; }
.member-chip { display: flex; align-items: center; gap: 10px; padding: 9px 12px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface); min-width: 210px; text-decoration: none; color: inherit; transition: border-color .16s, box-shadow .16s; }
a.member-chip:hover { border-color: var(--brand); box-shadow: var(--shadow-sm); }
.member-chip.is-static { background: var(--surface-2); }
.member-chip img { width: 38px; height: 38px; border-radius: 50%; object-fit: cover; flex: 0 0 38px; border: 1px solid var(--border); }
.member-chip > span { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1 1 auto; }
.mc-name { font-size: 13.5px; font-weight: 600; }
.mc-sub { font-size: 11.5px; color: var(--text-3); }
.org-dot { width: 9px; height: 9px; border-radius: 50%; display: inline-block; flex: 0 0 9px; }

/* --- 25.8 通知列表（详情折叠） --- */
.notice-list { display: flex; flex-direction: column; }
.notice-item { border-bottom: 1px solid var(--border); }
.notice-item:last-child { border-bottom: none; }
.notice-item > summary { display: flex; align-items: center; gap: 11px; padding: 14px 18px; cursor: pointer; list-style: none; transition: background .16s; }
.notice-item > summary::-webkit-details-marker { display: none; }
.notice-item > summary:hover { background: var(--surface-2); }
.notice-item.is-unread > summary { background: var(--brand-lighter); }
.notice-item[open] > summary { background: var(--surface-2); }
.ni-dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 8px; background: var(--border-strong); }
.notice-item.is-unread .ni-dot { background: var(--danger); box-shadow: 0 0 0 3px var(--danger-light); }
.ni-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.ni-title { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; font-size: 14.5px; font-weight: 600; }
.ni-meta { font-size: 12px; color: var(--text-3); }
.ni-caret { flex: 0 0 auto; color: var(--text-3); transition: transform .18s; }
.ni-caret svg { width: 16px; height: 16px; display: block; }
.notice-item[open] .ni-caret { transform: rotate(180deg); }
.ni-body { padding: 0 18px 18px 37px; display: flex; flex-direction: column; gap: 12px; }
.ni-content { font-size: 13.5px; line-height: 1.75; color: var(--text-2); }
.ni-content p { margin: 0 0 8px; }
.ni-content img { max-width: 100%; border-radius: var(--radius-sm); }
.ni-img { max-width: 100%; border-radius: var(--radius-sm); border: 1px solid var(--border); }
.ni-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.must-title { font-size: 15.5px; }
.must-badge { display: inline-flex; align-items: center; gap: 6px; padding: 5px 12px; border-radius: var(--radius-pill); background: var(--danger-light); color: var(--danger); font-size: 12.5px; font-weight: 600; }
.must-badge svg { width: 15px; height: 15px; }

/* --- 25.9 通知阅读统计 --- */
.read-bar { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; padding: 14px 18px; background: var(--surface-2); border-bottom: 1px solid var(--border); }
.read-bar-num { display: flex; flex-direction: column; gap: 2px; }
.read-bar-num b { font-size: 22px; font-weight: 700; font-variant-numeric: tabular-nums; line-height: 1.1; }
.read-bar-num span { font-size: 11.5px; color: var(--text-3); }
.read-bar-num.is-ok b { color: var(--ok); }
.read-bar-num.is-warn b { color: var(--warn); }
.read-bar-track { flex: 1 1 160px; height: 8px; border-radius: var(--radius-pill); background: var(--border); overflow: hidden; min-width: 120px; }
.read-bar-track i { display: block; height: 100%; border-radius: var(--radius-pill); background: linear-gradient(90deg, var(--brand), var(--info)); transition: width .3s; }
.read-bar-pct { font-size: 14px; font-weight: 700; color: var(--brand); font-variant-numeric: tabular-nums; }
.read-mini { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text-3); }
.read-mini b { font-variant-numeric: tabular-nums; color: var(--text-2); }

/* --- 25.10 任务卡片（前台任务大厅） --- */
.mission-list { display: flex; flex-direction: column; gap: 12px; }
.mission-item { display: flex; align-items: flex-start; gap: 14px; padding: 14px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface); transition: border-color .16s, box-shadow .16s; }
.mission-item:hover { border-color: var(--border-strong); box-shadow: var(--shadow-sm); }
.mission-item.is-mine { border-color: var(--brand); background: var(--brand-lighter); }
/* 已完成的任务：整体压暗一档，让它不与"正在进行"抢注意力。
   不要用 opacity —— 那会把里面的徽标颜色一起洗淡，看起来像被禁用了。 */
.mission-item.is-done { background: var(--surface-2); }
.mission-item.is-done .mi-title { color: var(--text-2); font-weight: 500; }

/* 任务大厅的分组分隔线：上面「可报名」，下面「已出发、报名已截止」。
   做成一条带小图标的中缝标题（右侧余量走一条细线），而不是加粗大字 ——
   它是分区提示，不该和任务标题抢视觉层级。 */
.mission-divider {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 2px 0 -2px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text-3);
}
.mission-divider::after {
  content: "";
  flex: 1 1 auto;
  height: 1px;
  background: var(--border);
}
.mission-divider svg { flex: 0 0 auto; opacity: .72; }
.mi-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 7px; }
.mi-title { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-size: 15px; font-weight: 600; }
.mi-meta { display: flex; align-items: center; gap: 6px 14px; flex-wrap: wrap; font-size: 12.5px; color: var(--text-3); }
.mi-note { font-size: 12.5px; color: var(--warn); background: var(--warn-light); border-radius: var(--radius-xs); padding: 7px 10px; }
.mi-thumb { flex: 0 0 92px; width: 92px; height: 68px; border-radius: var(--radius-xs); overflow: hidden; border: 1px solid var(--border); }
.mi-thumb img { width: 100%; height: 100%; object-fit: cover; }
.mi-actions { flex: 0 0 auto; display: flex; flex-direction: column; gap: 8px; align-items: stretch; min-width: 118px; }
.mi-actions .btn { white-space: nowrap; }
.mission-cover { width: 100%; max-height: 300px; object-fit: cover; border-radius: var(--radius-sm); border: 1px solid var(--border); }

/* --- 25.11 任务素材 --- */
.media-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(132px, 1fr)); gap: 10px; }
.media-item { position: relative; margin: 0; border-radius: var(--radius-xs); overflow: hidden; border: 1px solid var(--border); background: var(--surface-2); }
.media-item img { display: block; width: 100%; height: 104px; object-fit: cover; }
.media-del { position: absolute; top: 6px; right: 6px; }

/* --- 25.12 任务模板 --- */
.tpl-list { display: flex; flex-direction: column; gap: 10px; }
.tpl-item { display: flex; align-items: flex-start; gap: 12px; padding: 13px 14px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface); }
.tpl-item.is-off { opacity: .62; }
.tpl-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 5px; }
.tpl-name { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; font-size: 14px; font-weight: 600; }
.tpl-desc { font-size: 12.5px; color: var(--text-2); max-height: 44px; overflow: hidden; }
.tpl-actions { flex: 0 0 auto; display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }

/* --- 25.13 操作栏 / 表单行 --- */
.action-bar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 16px; padding: 12px 18px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow-sm); }
.attend-row td { vertical-align: middle; }
.attend-row .select, .attend-row .input { min-width: 150px; }

/* --- 25.14 登录方式切换 / 前台导航按钮 --- */
.tabs-inline { justify-content: center; }
.site-nav-cta { background: var(--brand) !important; color: #fff !important; padding: 7px 14px !important; border-radius: var(--radius-pill); font-weight: 600; }
.site-nav-cta:hover { background: var(--brand-dark) !important; }
/* 紧急求助入口：前台导航里用红色突出，遇险时能一眼找到 */
.site-nav-emergency {
  background: var(--danger) !important; color: #fff !important;
  padding: 7px 14px !important; border-radius: var(--radius-pill); font-weight: 700;
}
.site-nav-emergency:hover { background: var(--danger-dark) !important; }

/* --- 25.15 通用弹窗（mustReadModal 等直接使用 .modal） --- */
.modal { background: var(--surface); border-radius: var(--radius); width: 100%; max-width: 560px; max-height: calc(100vh - 60px); overflow: auto; box-shadow: var(--shadow-lg); }

/* --- 25.16 数据大盘 --- */
.todo-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(128px, 1fr)); gap: 10px; }
.todo-item {
  display: flex; flex-direction: column; gap: 3px; align-items: center;
  padding: 14px 10px; text-align: center;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface-2); text-decoration: none; color: inherit;
  transition: border-color .16s, box-shadow .16s, background .16s;
}
.todo-item:hover { border-color: var(--brand); box-shadow: var(--shadow-sm); background: var(--surface); }
.todo-item b { font-size: 24px; font-weight: 700; line-height: 1.1; font-variant-numeric: tabular-nums; color: var(--text-3); }
.todo-item span { font-size: 12px; color: var(--text-3); }
.todo-item.is-active { border-color: var(--warn-border); background: var(--warn-light); }
.todo-item.is-active b { color: var(--warn); }
.todo-item.is-active span { color: var(--warn-ink); }

/* --- 需求 6：据点信息（队内信息大盘） ---
   一行一处的紧凑清单，刻意不做成卡片网格：据点通常只有个位数，
   网格会把 3 处据点铺成一屏，而这一页已经有二十多张卡了，
   能省一屏就省一屏。地址 / 电话 / 容纳人数都是弱化的灰字，
   一眼先看到的是「哪个据点、什么类型、还没停用」。 */
.fac-list { display: flex; flex-direction: column; gap: 8px; }
.fac-row {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 10px;
  padding: 8px 10px; border: 1px solid var(--border);
  border-radius: var(--radius-sm); background: var(--surface-2);
}
.fac-row.is-off { opacity: .6; }
.fac-row .fac-name { font-weight: 600; font-size: 13px; color: var(--text); }
.fac-row .fac-meta { display: inline-flex; align-items: center; gap: 3px; font-size: 12px; color: var(--text-3); }
.fac-row .fac-meta svg { opacity: .7; }

.chart-list { display: flex; flex-direction: column; gap: 11px; }
.chart-line { display: flex; align-items: center; gap: 10px; }
.cl-label { flex: 0 0 108px; font-size: 12.5px; color: var(--text-2); display: flex; align-items: center; gap: 6px; min-width: 0; }
.cl-label > span:not(.tone-dot):not(.rank-no) { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chart-line .bar { flex: 1 1 auto; }
.cl-val { flex: 0 0 auto; font-size: 12.5px; color: var(--text-2); font-variant-numeric: tabular-nums; text-align: right; min-width: 76px; }

/* --- 需求 #64：状态分布行整行可点，点进去看该状态的人员名单 ---
   行本身是 <a>（工作台用块级、看板用 chart-line），统一样式并给悬停反馈。 */
.status-row {
  display: block; text-decoration: none; color: inherit;
  padding: 6px 8px; margin: -6px -8px;
  border-radius: var(--radius-xs);
  transition: background .15s ease;
}
a.status-row:hover { background: var(--brand-lighter); }
a.status-row:hover .sr-go { opacity: 1; transform: translateX(0); }
.sr-go {
  display: inline-flex; align-items: center; color: var(--brand);
  opacity: 0; transform: translateX(-3px); transition: opacity .15s ease, transform .15s ease;
}
/* chart-line 版的 .status-row 仍要保持横向排列 */
.chart-line.status-row { display: flex; align-items: center; gap: 10px; }
/* 触屏设备没有 hover，箭头常显，否则看不出这一行可以点 */
@media (hover: none) {
  .sr-go { opacity: .5; transform: none; }
}

.tone-dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 8px; background: var(--border-strong); }
.tone-dot.tone-brand  { background: var(--brand); }
.tone-dot.tone-ok     { background: var(--ok); }
.tone-dot.tone-warn   { background: var(--warn); }
.tone-dot.tone-danger { background: var(--danger); }
.tone-dot.tone-info   { background: var(--info); }
.tone-dot.tone-muted  { background: var(--border-strong); }

.rank-no { width: 17px; height: 17px; border-radius: 50%; flex: 0 0 17px; display: inline-flex; align-items: center; justify-content: center; font-size: 10.5px; font-weight: 700; background: var(--surface-3); color: var(--text-3); }
.rank-no.is-top { background: var(--warn-light); color: var(--warn); }

.col-chart { display: flex; align-items: flex-end; gap: 12px; height: 168px; padding: 6px 2px 0; }
.col-col { flex: 1 1 0; display: flex; flex-direction: column; align-items: center; gap: 6px; height: 100%; justify-content: flex-end; }
.col-bars { display: flex; align-items: flex-end; justify-content: center; gap: 5px; width: 100%; height: 100%; }
.col-bar { display: block; width: 40%; max-width: 26px; min-height: 3px; border-radius: 5px 5px 2px 2px; background: linear-gradient(180deg, var(--brand), var(--brand-dark)); transition: height .5s; }
.col-bar.is-alt { background: linear-gradient(180deg, var(--brand-mid), var(--brand)); }
.col-val { font-size: 11.5px; color: var(--text-2); font-variant-numeric: tabular-nums; }
.col-label { font-size: 11.5px; color: var(--text-3); }

.legend { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; margin-top: 12px; }
.legend-item { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text-3); }
.legend-dot { width: 10px; height: 10px; border-radius: 3px; background: linear-gradient(180deg, var(--brand), var(--brand-dark)); }
.legend-dot.is-alt { background: linear-gradient(180deg, var(--brand-mid), var(--brand)); }

.mini-list { display: flex; flex-direction: column; gap: 8px; }
.mini-item { display: flex; align-items: center; gap: 10px; padding: 9px 11px; border: 1px solid var(--border); border-radius: var(--radius-xs); text-decoration: none; color: inherit; transition: border-color .16s, background .16s; }
.mini-item:hover { border-color: var(--brand); background: var(--brand-lighter); }
.mini-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.mini-main .strong { font-size: 13.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

@media (max-width: 900px) {
  .todo-grid { grid-template-columns: repeat(auto-fit, minmax(104px, 1fr)); }
  .cl-label { flex-basis: 88px; }
  .cl-val { min-width: 62px; }
  .col-chart { height: 140px; gap: 6px; }
}

@media (max-width: 900px) {
  .grid-main { grid-template-columns: 1fr; }
  .list-row { flex-wrap: wrap; }
  .list-actions { flex-direction: row; flex-wrap: wrap; min-width: 0; width: 100%; }
  .g-actions { width: 100%; }
  /* ★ 需求 #2：上面这一条单独写，恰好踩中下面「任务卡片」那段注释描述的坑 ——
     `.g-actions` 被设成 width:100% 之后，`.group-item` 若不换行，
     flex 会把 `.g-main`（flex:1 1 auto + min-width:0）一路压到 **0 宽**：
     实测 user/groups.php（390 视口）「联勤中心」整段逐字换行成竖排
     （组名 4 行 + 「现 0 人」3 行 + 简介 31 行），卡片被拉到 876px 高，
     只有「申请加入」按钮正常。修法照抄下面那三行 —— 同一族问题，
     同一个文件里早就写过解法，只是漏了这一处。 */
  .group-item { flex-wrap: wrap; gap: 10px; }
  .g-main { flex: 1 1 150px; }
  .g-actions { flex: 1 1 100%; width: 100%; min-width: 0; }

  .tpl-actions { width: 100%; }
  /* ★ 需求 #2：`.tpl-item / .tpl-main / .tpl-actions`（2772/2774/2777 行）与上面
     分组卡片（2666/2669/2672 行）是逐条对应的同一套结构，也同样漏了"容器换行"。
     实测 admin/task_templates.php（390 视口）：10 个模板的 `.tpl-main` 全是 **0 宽**，
     模板名 + 标签（应急预案 / 水域救援 / 紧急出动…）逐字换行成竖排，
     卡片被拉到 552 / 714 / 742px 高，只有右侧「一键发起 / 编辑 / 删除」正常 ——
     这正是用户报的「任务模板底部的一键发起页面错位」。 */
  .tpl-item { flex-wrap: wrap; gap: 10px; }
  .tpl-main { flex: 1 1 150px; }
  .tpl-actions { flex: 1 1 100%; width: 100%; min-width: 0; }
  .member-chip { min-width: 100%; }
  .ni-body { padding-left: 18px; }

  /* ------------------------------------------------------------------
     任务卡片（任务大厅 / 我参与中的任务）
     行内 `.mi-actions` 被设成 width:100% 后，容器若不换行，flex 会把
     `.mi-main`（flex:1 1 auto 且 min-width:0）压缩到 0 宽，
     中文就会逐字换行——看起来像竖排、整张卡片被拉得很长。
     修法：容器允许换行 + 正文给一个最小基准宽，操作区独占一行。
     ------------------------------------------------------------------ */
  .mission-item { flex-wrap: wrap; gap: 12px; }
  .mi-main { flex: 1 1 150px; }
  .mi-thumb { flex: 0 0 76px; width: 76px; height: 56px; }
  .mi-actions { flex: 1 1 100%; width: 100%; min-width: 0; flex-direction: row; flex-wrap: wrap; }
  .mi-actions > form { flex: 1 1 auto; }
  .mi-actions .btn { flex: 1 1 auto; justify-content: center; }

  /* 指标卡里的长数值/长文案不再顶破栅格 */
  .stat { min-width: 0; }
  .stat .stat-label,
  .stat .stat-foot { overflow-wrap: anywhere; }

  /* 手机端把过小的按钮抬到可点面积（≥36px） */
  .btn-sm, .btn.btn-sm { min-height: 36px; }
}

/* --------------------------------------------------------------------------
   26. 打印样式（迎检报表 A4 输出）
   -------------------------------------------------------------------------- */
@media print {
  .sidebar, .topbar, .bottom-nav, .drawer-mask, .no-print,
  .site-head, .site-foot, .hero { display: none !important; }
  body { background: var(--fixed-white); font-size: 12px; }
  .content { padding: 0; }
  .card { border: none; box-shadow: none; margin: 0; }
  .card-head { border-bottom: 2px solid #000; padding: 0 0 8px; }
  .table { min-width: 0; font-size: 11px; }
  .table thead th { background: #F2F2F2 !important; position: static; }
  .print-header { display: block !important; margin-bottom: 14px; }
  .print-header h1 { font-size: 18px; text-align: center; }
  .print-header .print-meta { text-align: center; font-size: 11px; color: #444; }
  table, tr, td, th { page-break-inside: avoid; }
  @page { size: A4; margin: 14mm 12mm; }
}
.print-header { display: none; }
.print-only { display: none; }
@media print { .print-only { display: block; } }

/* --------------------------------------------------------------------------
   26. 下拉菜单（导出格式选择等）
   -------------------------------------------------------------------------- */
.dd { position: relative; display: inline-block; }
.dd > summary {
  list-style: none; cursor: pointer; user-select: none;
  display: inline-flex; align-items: center; gap: 6px;
}
.dd > summary::-webkit-details-marker { display: none; }
.dd > summary::marker { content: ''; }
.dd-menu {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 70;
  min-width: 178px; padding: 6px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-sm); box-shadow: var(--shadow-lg);
  display: flex; flex-direction: column; gap: 2px;
}
.dd-menu a, .dd-menu button {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 8px 10px; border: 0; background: none; border-radius: var(--radius-xs);
  font-size: 13px; color: var(--text); text-align: left; cursor: pointer; font-family: inherit;
}
.dd-menu a:hover, .dd-menu button:hover { background: var(--surface-2); color: var(--brand); }
.dd-menu .dd-tip { padding: 3px 10px 5px; font-size: 11.5px; color: var(--text-3); }
/* 「这条筛选导出时不生效」的提醒。放在菜单最下方，用分隔线跟三个格式按钮分开 ——
   它说的是「别点」而不是「点这个」，混在按钮里容易被顺手点掉。 */
.dd-menu .dd-warn {
  margin-top: 4px; padding: 7px 10px;
  border-top: 1px solid var(--border);
  font-size: 11.5px; line-height: 1.6;
  color: var(--warn-ink, var(--text-2));
  background: var(--warn-light);
  border-radius: var(--radius-xs);
}
.dd-menu hr { border: 0; border-top: 1px solid var(--border); margin: 5px 2px; }

/* 卡片里如果有下拉菜单（「导出为」这类），卡片**不能裁剪**。
   .card 的 overflow:hidden 是为了让内容贴合圆角，但它同样会裁掉绝对定位的
   .dd-menu —— 菜单只露出「导出为」一行，真正要点的三个按钮被整段吃掉，
   界面上就是「导出为」下面一大片空白、按钮点不到。
   只放开含下拉的卡片，其余卡片照旧裁剪，圆角不受影响。
   现代浏览器走 :has()，老浏览器由 app.js 补 has-dd 类兜底。 */
.card:has(.dd),
.card.has-dd { overflow: visible; }

/* 下方空间不够时向上展开（由 app.js 判断后加类） */
.dd-menu.dd-up { top: auto; bottom: calc(100% + 6px); }

/* 移动端：操作按钮区可横向滚动，避免挤压换行 */
@media (max-width: 640px) {
  .topbar-actions { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .topbar-actions::-webkit-scrollbar { display: none; }
}

/* --------------------------------------------------------------------------
   27. 组织通讯录（四级分组树 + 成员资料卡）
   企业微信式体验：分组可逐级展开，成员默认只露姓名，点开才是联系方式
   -------------------------------------------------------------------------- */
.org-toolbar {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin: 2px 0 10px;
}
.org-tree, .org-extra {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
  margin-bottom: 16px;
}
.org-extra { margin-top: -6px; }

.org-node > .org-row { border-top: 1px solid var(--border); }
.org-tree > .org-node:first-child > .org-row,
.org-extra > .org-node:first-child > .org-row { border-top: 0; }

.org-row {
  display: flex; align-items: center; gap: 9px;
  width: 100%; padding: 11px 14px; min-height: 46px;
  border: 0; background: none; text-align: left; cursor: pointer;
  font-family: inherit; font-size: 14px; font-weight: 600; color: var(--text);
  transition: background .15s;
}
.org-row:hover { background: var(--surface-2); }
.org-caret {
  display: inline-grid; place-items: center;
  width: 18px; height: 18px; flex: 0 0 18px;
  color: var(--text-3); transition: transform .18s ease;
}
.org-node:not(.is-open) > .org-row .org-caret { transform: rotate(-90deg); }
.org-dot { width: 9px; height: 9px; flex: 0 0 9px; border-radius: 50%; }
.org-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.org-cat { font-weight: 500; flex: 0 0 auto; }
.org-count {
  margin-left: auto; flex: 0 0 auto;
  font-size: 12px; font-weight: 500; color: var(--text-3);
}

.org-body { display: none; padding-bottom: 12px; }
.org-node.is-open > .org-body { display: block; }
.org-desc { padding: 0 14px 8px 41px; font-size: 12px; color: var(--text-3); }
.org-nobody { padding: 0 14px 8px 41px; font-size: 12px; color: var(--text-3); }

/* 下级分组：左侧虚线导轨 + 缩进，层级一目了然 */
.org-kids { margin: 0 12px 8px 27px; border-left: 1px dashed var(--border-strong); }
.org-kids .org-row { padding: 9px 12px; min-height: 42px; font-size: 13.5px; }
.org-kids .org-body { padding-bottom: 8px; }
.org-kids .org-desc, .org-kids .org-nobody { padding-left: 39px; }
.org-kids .org-people { padding-left: 39px; }

.org-people { display: flex; flex-wrap: wrap; gap: 8px; padding: 4px 14px 2px 41px; }
/* 搜索结果等非树形场景：不要左侧缩进 */
.org-people.is-plain { padding-left: 0; padding-right: 0; padding-top: 0; }

/* ---------- 三大中心 / 预备申请组（v2.0 组织架构） ---------- */
.org-node.is-center > .org-row {
  background: linear-gradient(90deg, var(--brand-lighter), transparent 62%);
}
.org-node.is-center > .org-row .org-name { font-size: 14.5px; letter-spacing: .2px; }
.org-node.is-reserve > .org-row { background: var(--warn-light); }
.org-node.is-reserve > .org-row .org-name { color: var(--warn); }
.org-node.is-reserve .org-count { color: var(--warn); font-weight: 600; }

.org-applicants {
  margin: 2px 14px 6px 41px;
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--warn-light);
  padding: 8px 10px 10px;
}
.org-kids .org-applicants { margin-left: 39px; }
.oa-head {
  display: flex; align-items: center; gap: 6px;
  font-size: 12px; font-weight: 600; color: var(--warn);
  margin-bottom: 7px;
}
.oa-head svg { flex: 0 0 auto; }
.oa-head .btn { margin-left: auto; }
.oa-row {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 6px 0;
}
.oa-row + .oa-row { border-top: 1px dashed var(--border); }
.oa-row img {
  width: 24px; height: 24px; flex: 0 0 24px;
  border-radius: 50%; object-fit: cover; border: 1px solid var(--border);
}
.oa-name { font-size: 13px; font-weight: 600; color: var(--text); }
.oa-time { font-size: 11.5px; color: var(--text-3); }
.oa-count {
  font-size: 11.5px; font-weight: 600; color: var(--brand);
  padding: 1px 7px; border-radius: var(--radius-pill);
  background: var(--brand-light);
}
.oa-note {
  flex: 1 1 100%; font-size: 11.5px; color: var(--text-2);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ---------- 迷你指标条（后台概况卡） ---------- */
.mini-stat { display: flex; flex-wrap: wrap; gap: 10px; }
.ms-item {
  flex: 1 1 110px; min-width: 0;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  text-align: center;
}
.ms-item.is-warn { border-color: var(--warn); background: var(--warn-light); }
.ms-item.is-warn .ms-num { color: var(--warn); }
.ms-num { display: block; font-size: 19px; font-weight: 700; color: var(--text); line-height: 1.2; }
.ms-label { display: block; margin-top: 3px; font-size: 11.5px; color: var(--text-3); }

/* ---------- 时长系数示例 ---------- */
.coef-demo { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 10px; }
.cd-item {
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-left: 3px solid var(--brand);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
}
.cd-label { display: block; font-size: 12px; font-weight: 600; color: var(--text-3); }
.cd-formula { display: block; margin: 6px 0 4px; font-size: 14px; color: var(--text-2); }
.cd-formula b { color: var(--text); }
.cd-out { color: var(--brand) !important; font-size: 16px; }
.cd-note { display: block; font-size: 11.5px; color: var(--text-3); }

/* 系数页：主系数 / 非一线角色系数 分组小标题 */
.coef-divider {
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  margin: 16px 0 12px;
  padding-top: 14px;
  border-top: 1px dashed var(--border);
}
.coef-divider > span { font-size: 13px; font-weight: 600; color: var(--text); }
.coef-divider > em { font-size: 11.5px; font-style: normal; color: var(--text-3); }
.tag-mini {
  display: inline-block; margin-left: 2px;
  padding: 0 5px;
  font-size: 10.5px; font-weight: 500;
  line-height: 15px;
  color: var(--text-3);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 4px;
  vertical-align: 1px;
}

/* ---------- 表头 / 表尾强调（年度合计行等） ---------- */
.table tfoot td {
  background: var(--surface-2);
  font-weight: 600;
  border-top: 2px solid var(--border-strong);
}

/* ---------- 行内审核表单 ---------- */
.inline-review { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.inline-review .input-sm { max-width: 150px; }

/* ---------- 折叠的原始数据（删除快照等） ---------- */
.raw-details { display: inline-block; text-align: left; }
.raw-details > summary {
  cursor: pointer; font-size: 12px; font-weight: 600; color: var(--brand);
  padding: 3px 8px; border: 1px solid var(--border); border-radius: var(--radius-xs);
  background: var(--surface); list-style: none;
}
.raw-details > summary::-webkit-details-marker { display: none; }
.raw-details[open] > summary { background: var(--brand-light); border-color: var(--brand); }
.raw-details > pre {
  margin: 6px 0 0; max-width: 460px; max-height: 260px; overflow: auto;
  padding: 10px 12px; border: 1px solid var(--border); border-radius: var(--radius-xs);
  background: var(--surface-2); color: var(--text-2);
  font-family: var(--mono); font-size: 11.5px; line-height: 1.55;
  white-space: pre-wrap; word-break: break-all;
}
.org-person {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 5px 12px 5px 5px; min-height: 38px;
  border: 1px solid var(--border); border-radius: var(--radius-pill);
  background: var(--surface); color: var(--text);
  font-family: inherit; font-size: 13px; cursor: pointer;
  transition: border-color .15s, background .15s, box-shadow .15s, transform .15s;
}
.org-person:hover {
  border-color: var(--brand); background: var(--brand-lighter);
  box-shadow: var(--shadow-sm); transform: translateY(-1px);
}
.org-person img {
  width: 26px; height: 26px; flex: 0 0 26px;
  border-radius: 50%; object-fit: cover; border: 1px solid var(--border);
}
.op-name { font-weight: 600; white-space: nowrap; }
.op-tag { font-size: 11px; color: var(--text-3); white-space: nowrap; }

/* --- 成员资料卡（桌面右侧抽屉 / 手机底部半屏） --- */
.sheet-mask {
  position: fixed; inset: 0; z-index: 120;
  background: rgba(12, 42, 97, .42);
  -webkit-backdrop-filter: blur(1px);
  backdrop-filter: blur(1px);
}
.sheet-mask[hidden] { display: none; }

.contact-sheet {
  position: fixed; top: 0; right: 0; z-index: 130;
  width: min(404px, 94vw); height: 100%;
  display: flex; flex-direction: column;
  background: var(--surface); box-shadow: var(--shadow-lg);
  transform: translateX(103%);
  transition: transform .24s ease;
}
.contact-sheet.is-open { transform: translateX(0); }

.cs-head {
  position: relative; padding: 26px 20px 18px;
  background: linear-gradient(158deg, var(--brand-darker), var(--brand-dark) 55%, var(--brand));
  color: #fff;
}
.cs-avatar {
  width: 64px; height: 64px; border-radius: 50%; object-fit: cover;
  border: 2px solid rgba(255, 255, 255, .72); background: rgba(255, 255, 255, .2);
}
.cs-name { margin-top: 10px; font-size: 19px; font-weight: 700; color: #fff; }
.cs-sub { font-size: 12.5px; color: #CFE0F8; }
.cs-tags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.cs-tag {
  padding: 2px 9px; border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, .18); font-size: 11.5px; color: #fff;
}
.cs-close {
  position: absolute; top: 10px; right: 12px;
  width: 32px; height: 32px; border: 0; border-radius: 50%;
  background: rgba(255, 255, 255, .16); color: #fff;
  font-size: 20px; line-height: 1;
}
.cs-close:hover { background: rgba(255, 255, 255, .3); }

.cs-body { flex: 1 1 auto; overflow-y: auto; -webkit-overflow-scrolling: touch; padding-bottom: 12px; }
.cs-row {
  display: flex; align-items: center; gap: 10px;
  padding: 11px 18px; border-bottom: 1px solid var(--border);
}
.cs-k { flex: 0 0 72px; font-size: 12.5px; color: var(--text-3); }
.cs-v { flex: 1 1 auto; min-width: 0; font-size: 13.5px; word-break: break-word; }
.cs-v a { color: var(--brand); font-weight: 600; }
.cs-v .cs-blank { color: var(--text-3); font-size: 12.5px; }
.cs-act { flex: 0 0 auto; }

/* 联系方式无权限时的说明块（v2.19 需求 8）
   刻意做成"一块提示"而不是三行「未填写」：三行空白会让人以为队友没填，
   而事实是"你看不到"—— 这两件事必须区分，否则用户会去找组长补资料。 */
.cs-lock {
  display: grid; gap: 3px; padding: 11px 18px;
  background: var(--warn-light); border-bottom: 1px solid var(--border);
}
.cs-lock b { font-size: 12.5px; color: var(--warn-ink); }
.cs-lock span { font-size: 12px; line-height: 1.65; color: var(--text-2); }

.cs-sec {
  display: flex; align-items: center; gap: 8px;
  padding: 14px 18px 6px; font-size: 12px; color: var(--text-3);
}
.cs-group { display: flex; align-items: flex-start; gap: 9px; padding: 9px 18px; }
.cs-group .cg-dot { width: 8px; height: 8px; margin-top: 7px; flex: 0 0 8px; border-radius: 50%; background: var(--brand); }
.cs-group .cg-txt { min-width: 0; display: block; }
.cs-group .cg-name { display: block; font-size: 13.5px; font-weight: 600; }
.cs-group .cg-path { display: block; font-size: 11.5px; color: var(--text-3); }
.cs-stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1px; margin: 6px 18px 0; background: var(--border); border-radius: var(--radius-sm); overflow: hidden; }
.cs-stat { padding: 10px 6px; background: var(--surface); text-align: center; }
.cs-stat b { display: block; font-size: 17px; color: var(--brand-dark); }
.cs-stat span { font-size: 11.5px; color: var(--text-3); }
.cs-foot { flex: 0 0 auto; display: flex; gap: 8px; padding: 12px 18px; border-top: 1px solid var(--border); }
.cs-foot .btn { flex: 1 1 auto; justify-content: center; }

/* --------------------------------------------------------------------------
   28. 任务简报 / 现场进展 / 指挥大盘
   -------------------------------------------------------------------------- */

/* 两列信息网格（沿用 .kv 的 dt/dd 结构） */
.kv.kv-2 { grid-template-columns: auto minmax(0, 1fr) auto minmax(0, 1fr); }

/* 复选框行：整行可点，触控区域足够大 */
.switch-line {
  display: flex; align-items: center; gap: 9px;
  font-size: 13.5px; color: var(--text); cursor: pointer;
  min-height: 34px;
}
.switch-line input[type=checkbox] { width: 17px; height: 17px; flex: 0 0 17px; accent-color: var(--brand); margin: 0; cursor: pointer; }
.switch-line span { line-height: 1.5; }
/* 把勾选行做成按钮外观，用在表单页脚 */
.switch-line.btn-like {
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 0 12px; background: var(--surface); color: var(--text-2);
}
.switch-line.btn-like:hover { border-color: var(--brand); color: var(--brand); }

.is-hidden { display: none !important; }

/* 内联折叠（details 包裹正文，不用浮层，手机端也稳） */
.dd-inline > summary { list-style: none; cursor: pointer; display: inline-flex; align-items: center; gap: 5px; }
.dd-inline > summary::-webkit-details-marker { display: none; }
.dd-inline[open] > summary { margin-bottom: 4px; }

/* 现场进展时间线：竖线 + 圆点，最新在最上 */
.log-timeline { position: relative; padding-left: 4px; }
.log-item { display: flex; gap: 12px; padding-bottom: 16px; position: relative; }
.log-item:last-child { padding-bottom: 0; }
.log-item::before {
  content: ''; position: absolute; left: 5px; top: 16px; bottom: 0;
  width: 2px; background: var(--border);
}
.log-item:last-child::before { display: none; }
.log-item .li-dot {
  flex: 0 0 12px; width: 12px; height: 12px; margin-top: 4px;
  border-radius: 50%; background: var(--brand); border: 2px solid var(--surface);
  box-shadow: 0 0 0 2px var(--brand-lighter); position: relative; z-index: 1;
}
.log-item.is-notice .li-dot { background: var(--warn, #D97706); box-shadow: 0 0 0 2px #FEF3C7; }
.log-item .li-body { min-width: 0; flex: 1 1 auto; }
.log-item .li-head { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.log-item .li-txt { font-size: 13.5px; color: var(--text); line-height: 1.65; margin-top: 5px; word-break: break-word; }
.log-item .li-shot { margin-top: 8px; }
.log-item .li-shot img {
  max-width: 100%; width: auto; max-height: 240px; border-radius: var(--radius-sm);
  border: 1px solid var(--border); cursor: zoom-in; display: block;
}

/* 指挥大盘：左侧任务轨 + 右侧信息栏 */
.cmd-layout { display: grid; grid-template-columns: minmax(0, 1fr) 320px; gap: 16px; align-items: start; }
.cmd-side { display: flex; flex-direction: column; gap: 16px; position: sticky; top: calc(var(--topbar-h, 60px) + 14px); }
.cmd-pick { display: flex; gap: 8px; overflow-x: auto; padding: 2px 0 6px; }
.cmd-pick a {
  flex: 0 0 auto; border: 1px solid var(--border); border-radius: 20px;
  padding: 5px 13px; font-size: 13px; color: var(--text-2); background: var(--surface); white-space: nowrap;
}
.cmd-pick a.active { border-color: var(--brand); background: var(--brand); color: #fff; font-weight: 600; }
/* 需求 #78-12h：进行中任务默认只列 3 个，余下的由这个"展开"按钮放出来。
   做成和任务胶囊同高同形的样子，展开后整条读起来还是一排任务，
   而不是"一排任务 + 一个突兀的按钮"。 */
.cmd-pick .cmd-pick-more {
  flex: 0 0 auto; display: inline-flex; align-items: center; gap: 3px;
  border: 1px dashed var(--border); border-radius: 20px;
  padding: 5px 12px; font-size: 12.5px; color: var(--text-3);
  background: transparent; cursor: pointer; white-space: nowrap;
}
.cmd-pick .cmd-pick-more:hover { border-color: var(--brand); color: var(--brand); }
.cmd-pick .cmd-pick-more[data-open="1"] svg { transform: rotate(180deg); }
.cmd-live { font-size: 12.5px; color: var(--text-3); }
.cmd-live .dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: var(--ok, #16A34A); margin-right: 5px; }

/* 任务操作条 */
.cmd-actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-top: 10px; }
.cmd-actions form { margin: 0; }

/* 迷你指标卡（人力分布） */
.stat-mini-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }
.stat-mini {
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 9px 4px; text-align: center; background: var(--surface-2);
}
.stat-mini b { display: block; font-size: 19px; font-weight: 700; color: var(--text); line-height: 1.2; }
.stat-mini span { font-size: 11.5px; color: var(--text-3); }
.stat-mini.is-ok b { color: var(--ok); }
.stat-mini.is-brand b { color: var(--brand); }
.stat-mini.is-warn b { color: var(--warn); }

/* 人员小标签：可叫应名单 / 报名名单 */
.mini-people { display: flex; flex-wrap: wrap; gap: 6px; }
.mini-person {
  display: inline-flex; align-items: baseline; gap: 5px;
  border: 1px solid var(--border); border-radius: 20px;
  padding: 4px 11px; font-size: 13px; color: var(--text); background: var(--surface);
  text-decoration: none; min-height: 30px; line-height: 22px;
}
.mini-person span { font-size: 11px; color: var(--text-3); }
a.mini-person:hover { border-color: var(--brand); color: var(--brand); }
.mini-person.is-cmd { border-color: var(--brand); background: var(--brand-lighter); font-weight: 600; }

/* 垂直按钮组（侧栏快捷操作） */
.btn-stack { display: flex; flex-direction: column; gap: 8px; }
.btn-stack .btn { justify-content: center; }

/* 带按钮的提示条 */
.alert-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }

@media (max-width: 1100px) {
  .cmd-layout { grid-template-columns: minmax(0, 1fr); }
  .cmd-side { position: static; }
}

@media (max-width: 640px) {
  .kv.kv-2 { grid-template-columns: auto minmax(0, 1fr); }
  .log-item { gap: 9px; }
  .log-item .li-shot img { max-height: 200px; }
  /* 任务胶囊条：手机上换行铺开，避免横向滚动把后面的任务藏起来 */
  .cmd-pick { flex-wrap: wrap; overflow-x: visible; }
  .cmd-pick a { flex: 1 1 auto; text-align: center; white-space: normal; min-width: 0; }
  .cmd-pick .cmd-pick-more { flex: 1 1 auto; justify-content: center; }
}

/* --------------------------------------------------------------------------
   29. 对外公告与声明（前台公开页）
   -------------------------------------------------------------------------- */

/* 窄栏容器：公告正文阅读宽度更舒适 */
.wrap-narrow { max-width: 880px; margin: 0 auto; padding: 0 16px; }

.crumb-bar {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  font-size: 12.5px; color: var(--text-3); padding: 18px 0 14px;
}
.crumb-bar a { color: var(--text-3); }
.crumb-bar a:hover { color: var(--brand); }

.announce-detail .card-body { padding: 28px clamp(18px, 4vw, 40px) 24px; }
.an-head { display: flex; gap: 8px; flex-wrap: wrap; }
.an-title {
  font-size: clamp(20px, 2.6vw, 27px); line-height: 1.4; margin: 12px 0 10px;
  color: var(--text); letter-spacing: .3px;
}
.an-meta {
  display: flex; flex-wrap: wrap; gap: 6px 20px;
  font-size: 12.5px; color: var(--text-3);
  padding-bottom: 16px; border-bottom: 1px dashed var(--border);
}
.an-meta span { display: inline-flex; align-items: center; gap: 5px; }
.an-meta svg { width: 14px; height: 14px; }
.an-cover { margin: 20px 0 4px; }
.an-cover img { width: 100%; border-radius: var(--radius-sm); border: 1px solid var(--border); display: block; }
.an-body { margin-top: 18px; font-size: 15px; line-height: 2; color: var(--text-2); }
.an-body p { margin: 0 0 1em; }
.an-body img { max-width: 100%; border-radius: var(--radius-sm); }
.an-body strong { color: var(--text); }
.an-link { margin-top: 22px; }
.an-foot { margin-top: 26px; padding-top: 14px; border-top: 1px dashed var(--border); }

.an-others { list-style: none; margin: 0; padding: 0; }
.an-others li {
  display: flex; align-items: baseline; gap: 12px; justify-content: space-between;
  padding: 9px 0; border-bottom: 1px dashed var(--border);
}
.an-others li:last-child { border-bottom: none; }
.an-others a { color: var(--text); font-size: 14px; }
.an-others a:hover { color: var(--brand); }
.an-back { padding: 18px 0 40px; }

/* ── 详情页：作者署名 / 分享动作 / 落款（需求 7）──────────────────────────── */

/* 作者署名。用品牌色描出来而不是和日期一样的灰 —— 对外公告是"谁在说话"，
   署「队部」和署某个人的名字，对外公信力完全不同，这个信息值得显眼。 */
.an-author { color: var(--brand); font-weight: 600; }
.an-author svg { width: 14px; height: 14px; }

/* 复制链接 / 打印。公告最常被做的事是"转给需要的人"，
   原来只能让用户自己复制地址栏。 */
.an-actions {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-top: 22px; padding-top: 16px; border-top: 1px dashed var(--border);
}
.an-copied {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 12.5px; font-weight: 600; color: var(--ok-ink);
}
.an-copied svg { width: 14px; height: 14px; }

/* 落款：队伍名 + 日期右对齐。公告是要担责任的东西，落款是它的"公章位"。 */
.an-sign {
  display: flex; align-items: baseline; justify-content: flex-end;
  gap: 14px; flex-wrap: wrap; margin-bottom: 8px;
}
.an-sign-team { font-size: 16px; font-weight: 700; color: var(--text); letter-spacing: .8px; }
.an-sign-date { font-size: 13px; color: var(--text-2); }

/* ── 列表页（需求 7 改版）────────────────────────────────────────────────
   版心从 880px 放到 1120px：这是全站唯一会被搜索引擎收录、会被转发到
   朋友圈的页面，信息密度该向"对外门户"看齐，而不是内部留言板的宽度。 */
.wrap-list { max-width: 1120px; margin: 0 auto; padding: 0 16px; }

/* 页头：左标题 + 右三个数字。
   为什么要三个数字 —— 公告页最怕"看起来很久没人管"，
   最近更新时间 + 累计条数是最廉价、也最有效的可信度信号。 */
.an-hero {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 20px 32px; justify-content: space-between;
  padding: 26px 28px; margin-bottom: 16px;
  border-radius: var(--radius);
  border: 1px solid var(--brand-border);
  background: linear-gradient(135deg, var(--brand-light), var(--surface) 62%);
}
.an-hero-main { flex: 1 1 320px; min-width: 0; }
.an-hero-kicker {
  font-size: 11px; font-weight: 700; letter-spacing: 2px;
  color: var(--brand); opacity: .8;
}
.an-hero-main h1 {
  margin: 6px 0 8px; letter-spacing: .4px; color: var(--text);
  font-size: clamp(21px, 2.6vw, 28px);
}
.an-hero-main p { margin: 0; font-size: 13.5px; line-height: 1.75; color: var(--text-2); }
.an-hero-stats { display: flex; gap: 10px; flex: 0 0 auto; }
.an-hero-stat {
  min-width: 92px; padding: 12px 16px; text-align: center;
  border-radius: var(--radius-sm); border: 1px solid var(--border);
  background: var(--surface); box-shadow: var(--shadow-sm);
}
.an-hero-stat b {
  display: block; font-size: 19px; line-height: 1.3; color: var(--brand);
  white-space: nowrap;   /* 「2026-10-10」这种日期在窄格里不能被折成两行 */
}
.an-hero-stat span { display: block; margin-top: 2px; font-size: 11.5px; color: var(--text-3); }

.an-toolbar { margin-bottom: 18px; }
.an-search { margin-bottom: 0; }

/* 置顶头条：封面大图 + 摘要，用与下面网格卡片**完全不同**的版式。
   置顶这个动作的语义就是"这条最重要"；如果排版一样，读者只会觉得
   "它怎么排第一个"，而不是"它更重要"——语义被抹掉了。 */
.an-featured {
  display: block; margin-bottom: 22px; overflow: hidden;
  border-radius: var(--radius); border: 1px solid var(--brand-border);
  background: var(--surface); box-shadow: var(--shadow);
}
.an-featured-link {
  display: flex; align-items: stretch;
  text-decoration: none; color: inherit; transition: background .15s;
}
.an-featured-link:hover { background: var(--surface-hover); }
.an-featured-cover {
  display: block; flex: 0 0 46%; max-width: 46%; min-height: 220px;
  background: var(--brand-lighter); overflow: hidden;
}
.an-featured-cover img { display: block; width: 100%; height: 100%; object-fit: cover; }
/* 没配封面时的占位：做成一块**品牌色海报**而不是留一块浅色空框。
   留空框的观感是"图挂了"，而这是列表里最显眼的一格 ——
   宁可它看起来是设计的一部分。 */
.an-featured-cover.is-plain {
  display: flex; align-items: center; justify-content: center;
  color: var(--fixed-white); background: var(--brand-grad-deep);
}
.an-featured-cover.is-plain svg { width: 56px; height: 56px; opacity: .55; }
.an-featured-body {
  display: block; flex: 1 1 auto; min-width: 0;
  padding: 24px clamp(18px, 3vw, 30px);
}
.an-featured-flags { display: flex; gap: 8px; flex-wrap: wrap; }
.an-featured-title {
  display: block; margin: 10px 0; color: var(--text);
  font-size: clamp(18px, 2.2vw, 24px); font-weight: 700; line-height: 1.45;
}
.an-featured-link:hover .an-featured-title { color: var(--brand); }
.an-featured-sum { display: block; font-size: 14px; line-height: 1.85; color: var(--text-2); }
.an-featured-meta {
  display: flex; align-items: center; flex-wrap: wrap; gap: 6px 14px;
  margin-top: 12px; font-size: 12px; color: var(--text-3);
}
.an-featured-meta span { display: inline-flex; align-items: center; gap: 5px; }
.an-featured-meta svg { width: 13px; height: 13px; }
.an-featured-go {
  display: inline-flex; align-items: center; gap: 5px; margin-top: 16px;
  font-size: 13px; font-weight: 600; color: var(--brand);
}

.an-sec-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; margin: 0 0 12px;
}
.an-sec-head h3 { margin: 0; font-size: 16px; color: var(--text); }

/* 两列网格：同样一屏里的信息量接近翻倍，翻页次数随之减少。
   没有配图时卡片自然矮一截，不会留出空洞（.an-card-cover 是可选节点）。 */
.an-grid {
  display: grid; gap: 14px;
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
.an-card {
  display: flex; flex-direction: column; overflow: hidden;
  border-radius: var(--radius-sm); border: 1px solid var(--border);
  background: var(--surface);
  text-decoration: none; color: inherit;
  transition: border-color .15s, box-shadow .15s, transform .15s;
}
.an-card:hover {
  border-color: var(--brand-border); box-shadow: var(--shadow);
  transform: translateY(-2px);
}
.an-card-cover { display: block; aspect-ratio: 16 / 9; background: var(--surface-3); overflow: hidden; }
.an-card-cover img { display: block; width: 100%; height: 100%; object-fit: cover; }
.an-card-body { display: block; padding: 16px 18px; }
.an-card-flags { display: flex; gap: 6px; flex-wrap: wrap; }
.an-card-title {
  display: block; margin: 8px 0 6px; color: var(--text);
  font-size: 15.5px; font-weight: 600; line-height: 1.5;
}
.an-card:hover .an-card-title { color: var(--brand); }
.an-card-sum {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
  font-size: 13px; line-height: 1.7; color: var(--text-3);
}
.an-card-meta {
  display: flex; flex-wrap: wrap; gap: 6px 14px; margin-top: 12px;
  padding-top: 10px; border-top: 1px dashed var(--border);
  font-size: 12px; color: var(--text-3);
}
.an-pager { margin-top: 22px; }

/* 后台公告列表的排序按钮组（↑ ↓ ⇈ ⇊ 四个小按钮挤在同一格里） */
.ann-order { display: inline-flex; gap: 3px; }
.ann-order .btn-xs { padding: 2px 6px; line-height: 1.5; }

@media (max-width: 900px) {
  /* 头条卡从"左图右文"折成"上文下图"：46% 的窄封面在平板上只剩一条缝 */
  .an-featured-link { flex-direction: column; }
  .an-featured-cover {
    flex: none; max-width: none; width: 100%; min-height: 0;
    aspect-ratio: 16 / 9;
  }
  .an-hero { padding: 22px; }
}

@media (max-width: 760px) {
  .an-grid { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 640px) {
  .wrap-list { padding: 0 12px; }
  .an-hero { padding: 20px; gap: 16px; }
  .an-hero-stats { width: 100%; }
  .an-hero-stat { flex: 1 1 0; min-width: 0; padding: 10px 8px; }
  .an-hero-stat b { font-size: 15.5px; }
  .an-card-body { padding: 14px; }
  .an-body { font-size: 14.5px; line-height: 1.9; }
}


@media (max-width: 640px) {
  .org-people { padding-left: 20px; }
  .org-kids .org-people { padding-left: 22px; }
  .org-kids { margin-left: 14px; }
  .org-desc, .org-nobody { padding-left: 30px; }
  .contact-sheet {
    top: auto; bottom: 0; right: 0; left: 0;
    width: 100%; height: min(86vh, 620px);
    border-radius: var(--radius) var(--radius) 0 0;
    transform: translateY(103%);
  }
  .contact-sheet.is-open { transform: translateY(0); }
}

/* --------------------------------------------------------------------------
   31. 移动端触控可用性兜底
   手机上 .btn-sm / .btn-xs 原始高度约 26~29px，低于「可点区域 ≥32px」的通行下限，
   在列表行里密集排列时容易点错。这里统一抬到 32px（只影响触屏宽度，桌面保持紧凑）。
   注意：用 min-height 而不是加大 padding，避免把已有布局撑变形。
   -------------------------------------------------------------------------- */
@media (max-width: 640px) {
  .btn-sm, .btn-xs { min-height: 32px; padding-top: 5px; padding-bottom: 5px; }
  .btn-xs { font-size: 12px; }
  /* 表内操作按钮组：允许换行，避免挤压成极窄的竖条 */
  .btn-group { flex-wrap: wrap; }
  /* 纯文字链接做成可点区域（通讯录、公告列表里的 a） */
  .an-others li { padding: 11px 0; }
  /* 筛选胶囊：默认 28px，抬到 32px */
  .chip { min-height: 32px; padding-top: 5px; padding-bottom: 5px; }
  /* 电话链接（求救记录、通讯录里可直接拨号的号码）：16px 高的行内文字太难点中 */
  .kv dd > a[href^="tel:"], .sai-txt a[href^="tel:"] {
    display: inline-flex; align-items: center; min-height: 32px; padding: 0 2px;
  }
  /* 弹窗关闭按钮 */
  .sa-close, .alert-close { min-width: 34px; min-height: 34px; display: inline-flex; align-items: center; justify-content: center; }
}

/* --------------------------------------------------------------------------
   32. 队员工作台（志愿者登录后的首页 · 需求 #6）
   -------------------------------------------------------------------------- */

/* ── 工作台任务区 ──────────────────────────────────────────────────────
   摆在页面最顶部：志愿者登录后第一眼要看到"现在有什么事要我处理"。
   配色刻意与下面的白色卡片区分开（浅品牌底 + 品牌边框），
   让它在手机上"一眼就是另一块"，不用读标题也知道这里是重点。 */
.workbench {
  border: 1px solid var(--brand-border);
  border-radius: var(--radius);
  background: var(--brand-lighter);
  padding: 12px;
  margin-bottom: 16px;
}
.wb-head {
  display: flex; align-items: center; gap: 8px;
  margin-bottom: 10px; padding: 0 2px;
}
.wb-title {
  display: inline-flex; align-items: center; gap: 6px;
  font-weight: 700; font-size: 14.5px; color: var(--brand-dark);
}
.wb-more {
  margin-left: auto; font-size: 12.5px; color: var(--brand-dark);
  text-decoration: none; display: inline-flex; align-items: center; gap: 2px;
}
.wb-more:hover { text-decoration: underline; }

/* 分组小标题：把「我参与中的任务」和「可以报名的任务」两摞卡片分开。
   原先只靠卡片左侧色条区分，新人看不出这两摞到底差在哪，所以补一行明确文字。
   `.wb-card + .wb-sec` 给"上一摞的最后一张卡"与下一组标题之间留出呼吸。 */
.wb-sec { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; margin: 0 2px 7px; }
.wb-card + .wb-sec { margin-top: 12px; }
.wb-sec-t { font-size: 13px; font-weight: 600; color: var(--brand-dark); }
.wb-sec-h { font-size: 11.5px; color: var(--text-3); }

.wb-card {
  display: block; text-decoration: none; color: inherit;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 11px 12px; margin-bottom: 9px;
}
.wb-card:last-child { margin-bottom: 0; }
/* 我参与中的任务用左侧色条标出来 —— 这类有"我的责任"在里面，优先看 */
.wb-card.is-mine { border-left: 3px solid var(--brand); }
.wb-card.is-open { border-left: 3px solid var(--border); }
a.wb-card:hover { border-color: var(--brand); box-shadow: var(--shadow-sm); }
.wb-card.is-empty {
  display: flex; align-items: flex-start; gap: 10px;
  color: var(--text-3); font-size: 13px;
}
.wb-card.is-empty b { display: block; color: var(--text-2); font-size: 13.5px; margin-bottom: 3px; }
.wb-card.is-empty span { line-height: 1.6; }
.wb-empty-ico {
  flex: 0 0 auto; width: 34px; height: 34px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--ok-light); color: var(--ok);
}
.wb-card.is-warn { border-left: 3px solid var(--warn); }

.wb-row1 { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.wb-name { font-weight: 600; font-size: 14.5px; }
.wb-meta {
  display: flex; flex-wrap: wrap; gap: 4px 14px;
  margin-top: 6px; font-size: 12px; color: var(--text-3);
}
.wb-meta > span { display: inline-flex; align-items: center; gap: 4px; }

/* 最新一条现场进展：工作台要能"看到任务在动"，而不只是一个标题 */
.wb-progress {
  margin-top: 9px; padding: 7px 9px;
  background: var(--surface-3); border-radius: var(--radius-xs);
  font-size: 12.5px; line-height: 1.6;
}
.wb-progress.is-none { color: var(--text-3); font-size: 12px; }
.wb-log { display: block; color: var(--text-2); }
.wb-log i {
  font-style: normal; margin-right: 6px; padding: 0 5px;
  border-radius: 3px; background: var(--brand-lighter);
  color: var(--brand-dark); font-size: 11.5px;
}
.wb-log em { font-style: normal; color: var(--text-3); }
.wb-log time { color: var(--text-3); margin-left: 6px; font-size: 11.5px; }
.wb-log-more { display: inline-block; margin-top: 3px; font-size: 11.5px; color: var(--text-3); }
.wb-acts { display: flex; gap: 8px; margin-top: 10px; flex-wrap: wrap; }
.wb-warn {
  display: flex; align-items: flex-start; gap: 9px;
  color: var(--warn-ink); font-size: 13px; line-height: 1.6;
}

@media (max-width: 760px) {
  .workbench { padding: 10px; margin-bottom: 13px; }
  .wb-card { padding: 10px 11px; }
  /* 触屏：报名/详情按钮的视觉高度只有 32px 左右，手指点不准。
     垫一层 44px 高的透明命中区（外观不变、命中达标）。 */
  .wb-acts .btn { position: relative; }
  .wb-acts .btn::after {
    content: ""; position: absolute; left: 0; right: 0; top: 50%;
    transform: translateY(-50%); height: 44px;
  }
  .wb-more { min-height: 32px; display: inline-flex; align-items: center; }
}

.panel-hero {
  background: linear-gradient(135deg, var(--brand-darker), var(--brand));
  color: #fff; border-radius: var(--radius); padding: 20px;
  margin-bottom: 16px; box-shadow: var(--shadow);
}
.pnl-top { display: flex; align-items: center; gap: 13px; }
.pnl-avatar {
  width: 56px; height: 56px; border-radius: 50%; object-fit: cover;
  border: 2px solid rgba(255,255,255,.45); flex: 0 0 56px; background: var(--fixed-white);
}
.pnl-me { min-width: 0; flex: 1 1 auto; }
.pnl-name { font-size: 19px; font-weight: 700; }
.pnl-sub { font-size: 12.5px; opacity: .85; margin-top: 3px; }
.pnl-status { flex: 0 0 auto; }
.pnl-status.badge { background: rgba(255,255,255,.2); color: #fff; }

.pnl-meta {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px;
  margin-top: 16px; padding-top: 15px; border-top: 1px solid rgba(255,255,255,.2);
}
.pnl-meta-item { text-align: center; }
.pnl-meta-item b { display: block; font-size: 20px; font-weight: 800; font-variant-numeric: tabular-nums; }
.pnl-meta-item span { font-size: 11.5px; opacity: .82; }
.pnl-vac {
  margin-top: 13px; padding: 8px 12px; border-radius: var(--radius-sm);
  background: rgba(255,255,255,.16); font-size: 12.5px;
}

/* 状态切换按钮（.status-picker / .status-btn）的样式已删除 ——
   需求 #1 / #20c 把打卡改成「点一下就提交」的一键式之后，
   个人工作台不再输出这套「先选、再点确认」的两步式控件，
   相关 HTML 与 JS 一并移除，样式留着就是死代码。
   新组件（.sq-*）的样式见本文件末尾「快捷在岗打卡」一节。 */

/* 出勤预览 */
.attend-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
.attend-cell {
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 16px 8px; text-align: center; background: var(--surface-2);
}
.attend-cell .ac-val { font-size: 27px; font-weight: 800; color: var(--brand-dark); line-height: 1.15; font-variant-numeric: tabular-nums; }
.attend-cell .ac-val small { font-size: 13px; font-weight: 600; margin-left: 2px; }
.attend-cell .ac-lbl { font-size: 13px; color: var(--text-2); margin-top: 4px; }
.attend-cell .ac-hours { font-size: 11.5px; color: var(--text-3); margin-top: 2px; }

/* 待办列表（工作台专属）
   为什么用 wb-todo-* 而不是沿用 .todo-item：
   `.todo-item` 在第 25 节「数据大盘」里已被用作**竖排数字卡**
   （flex-direction: column; align-items: center; background: var(--surface-2)）。
   两处语义完全不同，共用类名会让 column / 居中 / 灰底全部继承过来 ——
   待办行被撑成 140px 高、箭头掉到右下角、文字居中。
   与其在每一条新规则里逐个显式覆盖，不如换成专属类名：以后谁改数据大盘都不会误伤这里。 */
.wb-todo { display: flex; flex-direction: column; gap: 10px; }
.wb-todo-item {
  display: flex; align-items: center; gap: 12px; padding: 12px 14px;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface);
  text-decoration: none; color: inherit; text-align: left;
  min-height: 56px; transition: all .15s;
}
.wb-todo-item:hover { border-color: var(--brand); background: var(--brand-lighter); }
.wb-todo-item > svg:last-child { flex: 0 0 auto; color: var(--text-3); }
.wb-td-ico {
  flex: 0 0 36px; width: 36px; height: 36px; border-radius: 10px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--surface-2); color: var(--brand);
}
.wb-td-ico.is-ok { background: var(--ok-light); color: var(--ok); }
.wb-td-ico.is-warn { background: var(--warn-light); color: var(--warn); }
.wb-td-ico.is-brand { background: var(--brand-light); color: var(--brand); }
.wb-td-txt { flex: 1 1 auto; min-width: 0; }
.wb-td-txt b { display: block; font-size: 14.5px; }
.wb-td-txt span { display: block; font-size: 12px; color: var(--text-3); margin-top: 2px; }

/* 功能宫格 */
.tile-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 12px; }
.tile {
  display: flex; flex-direction: column; gap: 3px; padding: 15px 14px;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface); text-decoration: none; color: inherit;
  min-height: 96px; transition: all .15s;
}
.tile:hover { border-color: var(--brand); background: var(--brand-lighter); transform: translateY(-1px); }
.tile-ico {
  width: 38px; height: 38px; border-radius: 11px; margin-bottom: 4px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--brand-light); color: var(--brand);
}
.tile-lbl { font-size: 14.5px; font-weight: 600; }
.tile-desc { font-size: 11.5px; color: var(--text-3); }

@media (max-width: 640px) {
  .panel-hero { padding: 17px 15px; }
  .pnl-meta { grid-template-columns: repeat(2, 1fr); gap: 12px 8px; }
  .attend-grid { gap: 9px; }
  .attend-cell { padding: 13px 5px; }
  .attend-cell .ac-val { font-size: 23px; }
  .tile-grid { grid-template-columns: repeat(2, 1fr); gap: 10px; }
  .tile { min-height: 92px; padding: 13px 12px; }
}

/* 被禁用的单选卡（如「任务中」由系统自动标记，不允许手动切换） */
.radio-card.is-locked { opacity: .6; cursor: not-allowed; }
.radio-card.is-locked:hover { border-color: var(--border); transform: none; }

/* 个人主页头像：点一下原地放大，再点恢复 */
.ph-avatar.ph-zoomable { cursor: zoom-in; transition: transform .25s ease, box-shadow .25s ease; }
.ph-avatar.ph-zoomable:hover { box-shadow: 0 0 0 3px rgba(29,95,208,.18); }
.ph-avatar.ph-zoomable.is-zoomed {
  transform: scale(2.35); cursor: zoom-out; z-index: 60; position: relative;
  box-shadow: 0 14px 40px rgba(12,20,34,.35);
}
@media (max-width: 640px) {
  .ph-avatar.ph-zoomable.is-zoomed { transform: scale(1.9); }
}

/* --------------------------------------------------------------------------
   34. 前台数据看板（榜单 / 分布条）
   -------------------------------------------------------------------------- */

.board-overview {
  /* 7 个指标。固定 repeat(5) 会排成 5+2，最后一行空出三格很难看；
     固定 repeat(7) 在窄屏又会把数字挤到换行。auto-fit 让列数跟着宽度走：
     宽屏一行 7 个，收敛到 4 个 / 2 个时每行都填满。 */
  display: grid; grid-template-columns: repeat(auto-fit, minmax(148px, 1fr));
  gap: 12px; margin-bottom: 16px;
}
.bo-item {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-sm); padding: 18px 10px; text-align: center;
}
.bo-item b {
  display: block; font-size: 26px; font-weight: 800; color: var(--brand-dark);
  line-height: 1.15; font-variant-numeric: tabular-nums;
}
.bo-item span { font-size: 12.5px; color: var(--text-3); }

.board-2col { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; margin-bottom: 16px; align-items: start; }

/* 活跃度对比 */
.active-cmp { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
.ac-col {
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 16px 8px; text-align: center; background: var(--surface-2);
}
.ac-col .acv { font-size: 26px; font-weight: 800; color: var(--brand-dark); line-height: 1.15; font-variant-numeric: tabular-nums; }
.ac-col .acv small { font-size: 12px; font-weight: 600; margin-left: 3px; }
.ac-col .acv.up { color: var(--ok); }
.ac-col .acv.down { color: var(--danger); }
.ac-col .acl { font-size: 13px; color: var(--text-2); margin-top: 4px; }
.ac-col .ach { font-size: 11.5px; color: var(--text-3); margin-top: 2px; }

/* 排行榜 */
.rank-list { display: flex; flex-direction: column; }
.rank-row {
  display: flex; align-items: center; gap: 11px; padding: 10px 16px;
  text-decoration: none; color: inherit; border-bottom: 1px solid var(--border);
  min-height: 54px; transition: background .15s;
}
.rank-row:last-child { border-bottom: none; }
.rank-row:hover { background: var(--brand-lighter); }
.rk-no {
  flex: 0 0 24px; width: 24px; height: 24px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 12.5px; font-weight: 700; color: var(--text-3);
  background: var(--surface-2); border: 1px solid var(--border);
}
.rk-no.is-top1 { background: #FFD34D; color: #6B4A00; border-color: #F0C33C; }
.rk-no.is-top2 { background: #D8E0EC; color: #46536B; border-color: #C6D0DE; }
.rk-no.is-top3 { background: #F0C79B; color: #6E4416; border-color: #E2B784; }
.rk-avatar { flex: 0 0 34px; width: 34px; height: 34px; border-radius: 50%; object-fit: cover; background: var(--surface-2); }
.rk-main { flex: 1 1 auto; min-width: 0; }
.rk-name { display: flex; align-items: baseline; flex-wrap: wrap; gap: 7px 7px; font-size: 14px; font-weight: 600; }
.rk-role { font-size: 11.5px; color: var(--text-3); font-weight: 400; }
.rk-bar { display: block; height: 5px; border-radius: 4px; background: var(--border); margin-top: 6px; overflow: hidden; }
.rk-bar i { display: block; height: 100%; border-radius: 4px; background: linear-gradient(90deg, var(--brand), var(--brand-mid)); }
.rk-val { flex: 0 0 auto; font-size: 16px; font-weight: 800; color: var(--brand-dark); font-variant-numeric: tabular-nums; }
.rk-val small { font-size: 11px; font-weight: 600; color: var(--text-3); margin-left: 2px; }

/* 分布条 */
.bar-list { display: flex; flex-direction: column; gap: 12px; }
.bar-row { display: flex; align-items: center; gap: 11px; font-size: 13px; }
.br-lbl { flex: 0 0 88px; color: var(--text-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.br-track { flex: 1 1 auto; height: 9px; border-radius: 5px; background: var(--border); overflow: hidden; }
.br-track i { display: block; height: 100%; border-radius: 5px; background: linear-gradient(90deg, var(--brand), var(--brand-mid)); }
.br-track.is-ok i { background: linear-gradient(90deg, var(--ok), #4ED08A); }
.br-val { flex: 0 0 54px; text-align: right; color: var(--text-3); font-variant-numeric: tabular-nums; }

.board-foot { display: flex; gap: 10px; flex-wrap: wrap; justify-content: center; padding: 8px 0 10px; }

/* 看板筛选条（年份 / TOP 人数） */
.board-filter {
  display: flex; flex-wrap: wrap; gap: 10px 22px; align-items: center;
  padding: 12px 16px; margin-bottom: 14px;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm);
}
.bf-group { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.bf-label { font-size: 12.5px; color: var(--text-3); margin-right: 2px; }
.bf-chip {
  display: inline-flex; align-items: center; height: 30px; padding: 0 13px;
  border-radius: 15px; border: 1px solid var(--border); background: var(--surface-2);
  font-size: 13px; color: var(--text-2); text-decoration: none; white-space: nowrap;
  transition: all .15s;
}
.bf-chip:hover { border-color: var(--brand); color: var(--brand); }
.bf-chip.is-on {
  background: var(--brand); border-color: var(--brand); color: #fff; font-weight: 600;
  box-shadow: 0 3px 10px rgba(29,95,208,.28);
}
/* 原先按年度查看时的「N 年任务数 / N 年任务时长(h)」两张卡额外套了一层品牌色
   强调（.bo-item.is-brand：蓝边框 + 淡蓝底，数字用 --brand-dark）。用户在看板上
   一眼数出 7 张卡里只有这两张颜色不一样，问「为啥加了蓝色边框，用不到就删掉」——
   于是去掉：7 项指标口径不同但地位相同，人为挑两项高亮反而让人误以为
   只有这两项是重点。数字本身的字号与颜色（.bo-item b）已经足够醒目。
   HTML 里的 is-brand 类名也一并删了，避免留一个没有样式的死类。 */

.board-note {
  display: flex; gap: 8px; align-items: flex-start;
  font-size: 12.5px; line-height: 1.7; color: var(--text-3);
  background: var(--brand-lighter); border: 1px solid var(--brand-light, var(--border));
  border-radius: var(--radius-sm); padding: 11px 14px; margin: 0 0 14px;
}
.board-note svg { flex: 0 0 auto; margin-top: 3px; color: var(--brand); }

/* 任务类型标记 */
.tt-dot {
  display: inline-block; width: 8px; height: 8px; border-radius: 50%;
  margin-right: 6px; vertical-align: middle;
}
.tt-tag {
  display: inline-block; margin-left: 6px; padding: 1px 6px; border-radius: 8px;
  font-size: 10.5px; font-style: normal; font-weight: 700; vertical-align: middle;
}
.tt-tag.is-rescue { background: rgba(214,60,60,.12); color: var(--danger); }
.br-track.is-danger i { background: linear-gradient(90deg, var(--danger), #F07A7A); }
/* TOP100 长榜：压紧行高，避免页面过长 */
.rank-list.is-long .rank-row { padding: 7px 16px; min-height: 44px; }
.rank-list.is-long .rk-avatar { flex-basis: 28px; width: 28px; height: 28px; }
.rank-list.is-long .rk-name { font-size: 13px; }
.rank-list.is-long .rk-val { font-size: 14px; }
.rank-list.is-long .rk-bar { height: 4px; }

/* --------------------------------------------------------------------------
   34b. 需求 2（v2.11）· 数据看板改造：TOP 大头像榜 + 环形图/饼图
   --------------------------------------------------------------------------
   改之前这一页的排行榜头像只有 34px（TOP100 档更是缩到 28px）——
   一排小圆点，谁是谁根本认不出来，"看榜"变成了"看数字"。
   这一版把前三名拎出来做成领奖台（104px / 88px 头像），
   第 4 名往后也放大到 48px；名字、职务、条形、数值一列一列对齐。

   为什么用 flex `order` 而不是先把第 2 名写进 DOM：
   领奖台的视觉顺序是「亚军 冠军 季军」，但 DOM 必须是 1/2/3 ——
   读屏器顺序、Tab 顺序、"没有样式时"的顺序都该是按名次。
   用 order 只改视觉不改语义，两边都对。
   -------------------------------------------------------------------------- */

/* ── 领奖台（前三名） ─────────────────────────────────────────────── */
.rank-podium {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px; padding: 22px 20px 18px;
  background:
    radial-gradient(120% 150% at 50% -20%, rgba(93,156,255,.16), transparent 62%),
    var(--surface-2);
  border-bottom: 1px solid var(--border);
}
.pod {
  position: relative;
  display: flex; flex-direction: column; align-items: center; gap: 7px;
  padding: 18px 12px 16px;
  border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--surface);
  text-decoration: none; color: inherit;
  transition: transform .22s ease, box-shadow .22s ease, border-color .22s ease;
}
.pod:hover { transform: translateY(-3px); border-color: var(--brand); box-shadow: var(--shadow-md); }
.pod-2 { order: 1; }
.pod-1 { order: 2; }
.pod-3 { order: 3; }
/* 领奖台「台阶」（需求 1，v2.12）：冠亚季三张卡等宽等高，但亚军/季军要
   比冠军**低一档**，看上去才是台子而不是三个并排的框。
   做法是给 2/3 名加 margin-top：三张卡同处一个 stretch 行，
   顶部让出 18px，底边仍对齐 —— 冠军自然高出半个头。
   不用 transform: translateY() 是因为 .pod:hover 已经在用 transform，
   两处会互相覆盖（悬停位移会"吃掉"台阶）。 */
.pod-2, .pod-3 { margin-top: 18px; }
/* 冠军高一档：整体上浮、描金边、底色更亮 —— 一眼就知道谁是第一 */
.pod-1 {
  border-color: #F0C33C;
  box-shadow: 0 0 0 1px rgba(240,195,60,.45), 0 12px 26px rgba(12,20,34,.16);
  background: linear-gradient(180deg, rgba(255,211,77,.16), var(--surface) 46%);
}
.pod-medal {
  position: absolute; top: -13px; left: 50%; transform: translateX(-50%);
  width: 27px; height: 27px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 13px; font-weight: 800; font-variant-numeric: tabular-nums;
  background: #D8E0EC; color: #46536B; border: 2px solid var(--surface);
}
.pod-1 .pod-medal { background: #FFD34D; color: #6B4A00; }
.pod-3 .pod-medal { background: #F0C79B; color: #6E4416; }

.pod-av { position: relative; display: block; flex: 0 0 auto; }
.pod-av img {
  display: block; width: 88px; height: 88px; border-radius: 50%;
  object-fit: cover; background: var(--surface-3);
  border: 3px solid var(--surface); box-shadow: 0 0 0 1px var(--border), var(--shadow-sm);
}
.pod-1 .pod-av img { width: 104px; height: 104px; border-color: #F0C33C; }
.pod-name {
  font-size: 15px; font-weight: 700; color: var(--text);
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pod-1 .pod-name { font-size: 17px; }
.pod-role { font-size: 11.5px; color: var(--text-3); }
.pod-val {
  /* margin-top:auto：数值行钉在卡片底部 —— 带身份标记的卡会多出一行，
     不钉底的话三张卡的时长/次数就各在各的高度上，横向扫过去读不出比较。 */
  margin-top: auto;
  font-size: 20px; font-weight: 800; color: var(--brand-dark);
  font-variant-numeric: tabular-nums; line-height: 1.1;
}
.pod-1 .pod-val { font-size: 23px; }
.pod-val small { font-size: 11.5px; font-weight: 600; color: var(--text-3); margin-left: 2px; }

/* ── 第 4 名往后：同样是大头像，只是比领奖台小一档 ───────────────── */
.rank-list.is-big .rank-row { padding: 11px 16px; min-height: 68px; }
.rank-list.is-big .rk-avatar {
  flex: 0 0 48px; width: 48px; height: 48px;
  border: 2px solid var(--surface); box-shadow: 0 0 0 1px var(--border);
}
.rank-list.is-big .rk-name { font-size: 15px; }
.rank-list.is-big .rk-val { font-size: 19px; }
.rank-list.is-big .rk-bar { height: 6px; }
.rank-list.is-big .rk-no { width: 22px; height: 22px; flex-basis: 22px; font-size: 11.5px; }

/* ── TOP100 头像墙（需求 2） ──────────────────────────────────────
   100 个人排成一列 68px 的名单要滚 6600px。改成 6 列头像格只要约 2900px，
   而且"找人"从逐行扫变成了扫一眼 —— 头像本身就是最好的检索锚点。 */
.rank-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(126px, 1fr));
  gap: 10px; padding: 14px 16px 4px;
}
.rg-item {
  display: flex; flex-direction: column; align-items: center; gap: 5px;
  padding: 11px 8px 10px;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface-2);
  text-decoration: none; color: inherit;
  transition: transform .18s ease, border-color .18s ease, box-shadow .18s ease;
}
.rg-item:hover { transform: translateY(-2px); border-color: var(--brand); box-shadow: var(--shadow-sm); }
.rg-no {
  font-size: 11px; font-weight: 800; color: var(--text-3);
  font-variant-numeric: tabular-nums;
}
.rg-av img {
  display: block; width: 72px; height: 72px; border-radius: 50%;
  object-fit: cover; background: var(--surface-3);
  border: 2px solid var(--surface); box-shadow: 0 0 0 1px var(--border);
}
.rg-name {
  max-width: 100%; font-size: 13px; font-weight: 600; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rg-val {
  font-size: 14.5px; font-weight: 800; color: var(--brand-dark);
  font-variant-numeric: tabular-nums; line-height: 1.1;
}
.rg-val small { font-size: 10.5px; font-weight: 600; color: var(--text-3); margin-left: 1px; }

/* ── 身份标记：党员 / 退伍军人（需求 1，v2.12） ─────────────────────
   榜单上光有名字和数字，看不出"这人是党员""这人是退伍军人"——
   而这两类身份在救援队的调度语境里是实打实的参考信息
   （党员突击队、退役军人应急分队都要按名册点人）。
   标记做成小药丸：图标 + 两个字，颜色走语义色（党员用危险红、
   退伍用军绿），不抢名字的字重、也不改卡片高度。

   为什么标记不是纯文字而是「图标 + 文字」：
   色盲用户分不出红和绿，图标（旗帜 / 盾牌）是冗余的第二编码。 */
.mk {
  display: inline-flex; align-items: center; gap: 3px;
  height: 18px; padding: 0 7px; border-radius: 9px;
  font-size: 10.5px; font-weight: 700; line-height: 1;
  white-space: nowrap; vertical-align: middle;
}
.mk svg { flex: 0 0 auto; }
.mk i { font-style: normal; }
.mk-party {
  background: var(--danger-light); color: var(--danger-ink);
  box-shadow: inset 0 0 0 1px var(--danger-border);
}
.mk-veteran {
  background: var(--ok-light); color: var(--ok-ink);
  box-shadow: inset 0 0 0 1px var(--ok-border);
}
/* 领奖台：标记排在名字下面、职务上面，横向居中；两张以上自动换行。 */
.pod-marks {
  display: flex; flex-wrap: wrap; justify-content: center; gap: 4px;
}
/* 名单行（第 4 名起）：标记跟在名字后面，与职务同一行。 */
.rank-row .mk { height: 17px; padding: 0 6px; font-size: 10px; }
/* 头像墙（TOP100）：格子只有 72px 宽，两个字放不下 —— 只留图标，
   完整说明交给 title 提示；hover 头像时浏览器原生 tooltip 会补上文字。 */
.rg-av { position: relative; display: block; }
.rg-marks {
  position: absolute; top: -3px; right: -6px;
  display: flex; gap: 2px;
}
.rg-marks .mk { height: 17px; padding: 0 4px; gap: 0; border-radius: 9px; }
.rg-marks .mk i { display: none; }

/* 总览指标的图标（需求 2：7 个纯数字并排太素，各配一个语义图标） */
.bo-item { display: flex; flex-direction: column; align-items: center; gap: 6px; }
.bo-ico {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; border-radius: 9px;
  background: var(--brand-lighter); color: var(--brand);
}

/* ── 环形图 / 饼图（includes/chart.php 产出） ─────────────────────── */
.ct {
  display: flex; align-items: center; gap: 20px; flex-wrap: wrap;
  /* v2.13 需求 4：悬停浮层的定位基准。
     设在 .ct 上而不是 .ct-svg 上 —— 浮层要能盖到图例那一侧
     （鼠标在扇区上时，浮层往右飘出去不会被 .ct-svg 的边界裁掉）。 */
  position: relative;
}
.ct-figure { margin: 0; flex: 0 0 auto; text-align: center; }
.ct-svg { display: block; width: 168px; height: 168px; }
.ct-seg { transition: stroke-width .18s ease, opacity .18s ease; }
.ct-svg:hover .ct-seg { opacity: .45; }
/* 悬停哪一段哪一段亮起来 —— 图例与扇区互相印证，比单独一个 tooltip 好认 */
.ct-svg .ct-seg:hover { opacity: 1; }
.ct-donut .ct-svg .ct-seg { stroke-width: 20; }
.ct-donut .ct-svg .ct-seg:hover { stroke-width: 26; }
.ct-pie .ct-svg .ct-seg { stroke-width: 108; }
.ct-total {
  font-size: 26px; font-weight: 800; fill: var(--text);
  font-variant-numeric: tabular-nums;
}
.ct-unit { font-size: 11.5px; fill: var(--text-3); }
.ct-cap { margin: 8px 0 0; font-size: 12.5px; color: var(--text-3); }
.ct-cap b { color: var(--text); font-size: 14px; font-weight: 700; }

/* ── 环图悬停浮层（v2.13 需求 4）─────────────────────────────────────
   需求原话：「鼠标放到饼状图具体块上要显示对应标题和数据」。
   为什么不用 SVG 自带的 <title>：Chrome 里要停住约 1 秒才弹、样式无法控制、
   手机上完全不弹。<title> 保留着当降级（JS 没跑起来时仍能看数据），
   真正显示的是这一层 —— 由 app.js 的 initChartTip() 填内容并跟随鼠标。
   position:absolute 让它脱离 .ct 的 flex 流，不影响图与图例的排布。 */
.ct-tip {
  position: absolute; z-index: 8; pointer-events: none;
  max-width: 260px; padding: 7px 11px;
  border-radius: var(--radius-xs);
  background: rgba(12, 42, 97, .96);
  color: #fff; font-size: 12.5px; line-height: 1.45;
  box-shadow: var(--shadow-lg);
  white-space: nowrap;
  transform: translate(-50%, -100%);   /* 默认浮在光标正上方 */
  transition: opacity .12s ease;
}
.ct-tip[hidden] { display: none; }
/* 上方空间不够时翻到光标下方（JS 加 .is-below）*/
.ct-tip.is-below { transform: translate(-50%, 0); }
.ct-tip .ctt-sw {
  display: inline-block; width: 8px; height: 8px; border-radius: 2px;
  margin-right: 6px; vertical-align: 1px;
}
.ct-tip .ctt-lb { color: rgba(255, 255, 255, .76); }
.ct-tip b { font-weight: 700; font-variant-numeric: tabular-nums; }

/* ── 任务类别明细表（v2.13 需求 5，index.php「任务态势」）──────────────
   为什么是表格不是环图：每行六个字段，环图装不下；而且类别是**有固定顺序**
   的字典项，"第三行是队内培训"比"某个颜色的扇区是队内培训"好找得多。
   四个网格列：类别 / 归类 / 一组数值 / 占比。
   三个数值刻意包成一组（.tc-c-vals）—— 拆成三个独立网格项的话，
   窄屏要么横向溢出，要么被压成三行高。 */
.tc-table { display: flex; flex-direction: column; }
.tc-row {
  display: grid;
  grid-template-columns: minmax(0, 1.5fr) 86px minmax(210px, 1fr) minmax(132px, 1fr);
  align-items: center;
  gap: 8px 14px;
  padding: 9px 10px;
  border-radius: var(--radius-sm);
  font-size: 13px;
}
.tc-row + .tc-row { border-top: 1px solid var(--border); }
.tc-row.is-head {
  padding: 0 10px 8px;
  border: 0;
  font-size: 12px;
  font-weight: 600;
  color: var(--text-3);
}
.tc-row:not(.is-head):hover { background: var(--surface-hover); }
.tc-row.is-zero { color: var(--text-3); }
.tc-row.is-zero .tc-c-name i { opacity: .35; }

.tc-c-name { display: flex; align-items: center; gap: 8px; min-width: 0; }
.tc-c-name > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tc-sw { flex: 0 0 10px; width: 10px; height: 10px; border-radius: 3px; }

.tc-kind {
  display: inline-flex; align-items: center; justify-content: center;
  padding: 2px 8px; border-radius: var(--radius-xs);
  font-size: 11.5px; font-weight: 600;
  background: var(--brand-light); color: var(--brand-dark);
}
.tc-kind.is-rescue { background: var(--danger-light); color: var(--danger-ink); }

.tc-c-vals {
  display: grid;
  /* 三列定宽，数据行的数字才是"一列一列对齐"的 ——
     用 flex 的话列宽随内容漂移（「17 起」比「318 人次」窄，后面的列就跟着挪），
     一眼看上去像表格没排齐。定宽值按最长内容留：5 位数字 + 单位。 */
  grid-template-columns: 74px 84px 86px;
  gap: 0 10px;
  align-items: baseline;
  min-width: 0;
}
.tc-c-vals > span { color: var(--text-3); font-size: 12.5px; white-space: nowrap; }
.tc-c-vals b {
  color: var(--text); font-size: 14px; font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.tc-row.is-zero .tc-c-vals b { color: var(--text-3); }

.tc-c-share { display: flex; align-items: center; gap: 8px; }
.tc-bar {
  flex: 1; min-width: 40px; height: 8px;
  border-radius: var(--radius-pill);
  background: var(--surface-3);
  overflow: hidden;
}
.tc-bar i { display: block; height: 100%; border-radius: var(--radius-pill); background: var(--brand-grad); }
.tc-c-share em {
  font-style: normal; min-width: 42px; text-align: right;
  font-size: 12px; color: var(--text-2); font-variant-numeric: tabular-nums;
}

@media (max-width: 720px) {
  /* 表头在窄屏没有意义（每行自己带标签的语境已经由数值后缀「起 / h / 人次」承担） */
  .tc-row.is-head { display: none; }
  .tc-row {
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 8px 10px;
    padding: 12px 2px;
    align-items: center;
  }
  .tc-c-name { grid-column: 1; }
  .tc-c-kind { grid-column: 2; justify-self: end; }
  .tc-c-vals {
    grid-column: 1 / -1;
    /* 列宽沿用桌面端那份（三列共 264px + 左缩进 18px < 358px 可用宽），
       不重定义，免得两处数字对不上。 */
    padding-left: 18px;   /* 与类别名的色块对齐，读起来是一条竖线 */
  }
  .tc-c-share { grid-column: 1 / -1; padding-left: 18px; }
}

.ct-legend {
  flex: 1 1 240px; min-width: 0;
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: 2px;
}
.ct-li {
  display: flex; align-items: center; gap: 9px;
  padding: 7px 9px; border-radius: var(--radius-xs);
  font-size: 13px;
}
.ct-li:hover { background: var(--surface-2); }
/* 值为 0 的类别（只可能在 keep_zero 时出现）：留一行说明"这一类确实没有"，
   但不抢眼 —— 它没有扇区，做成正常字重会让人在图上找半天。 */
.ct-li.is-zero { opacity: .62; }
.ct-li.is-zero .ct-vl { font-weight: 600; }
.ct-sw {
  flex: 0 0 11px; width: 11px; height: 11px; border-radius: 3px;
  box-shadow: inset 0 0 0 1px rgba(0,0,0,.12);
}
.ct-lb {
  flex: 1 1 auto; min-width: 0; color: var(--text-2);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ct-vl {
  flex: 0 0 auto; font-size: 14px; font-weight: 700; color: var(--text);
  font-variant-numeric: tabular-nums;
}
.ct-vl small { font-size: 11px; font-weight: 600; color: var(--text-3); margin-left: 1px; }
.ct-pc {
  flex: 0 0 52px; text-align: right; font-style: normal;
  font-size: 12.5px; color: var(--text-3); font-variant-numeric: tabular-nums;
}

@media (max-width: 900px) {
  .rank-podium { gap: 10px; padding: 20px 12px 16px; }
  .pod { padding: 16px 8px 13px; }
  .pod-av img { width: 72px; height: 72px; }
  .pod-1 .pod-av img { width: 84px; height: 84px; }
  .pod-name { font-size: 13.5px; }
  .pod-1 .pod-name { font-size: 15px; }
  .pod-val { font-size: 17px; }
  .pod-1 .pod-val { font-size: 19px; }
  .pod-2, .pod-3 { margin-top: 12px; }
  .ct { gap: 14px; }
  .ct-svg { width: 152px; height: 152px; }
}
@media (max-width: 640px) {
  .rank-podium { gap: 8px; padding: 18px 9px 14px; }
  .pod { padding: 15px 6px 12px; gap: 5px; }
  .pod-av img { width: 58px; height: 58px; border-width: 2px; }
  .pod-1 .pod-av img { width: 68px; height: 68px; }
  .pod-name { font-size: 12.5px; max-width: 84px; }
  .pod-1 .pod-name { font-size: 13.5px; }
  .pod-role { font-size: 10.5px; }
  .pod-val { font-size: 15px; }
  .pod-1 .pod-val { font-size: 17px; }
  .pod-val small { font-size: 10px; }
  .pod-2, .pod-3 { margin-top: 9px; }
  /* 窄屏药丸收一档。390px 下领奖台一张卡内宽约 93px，
     两个"党员 + 退伍"药丸横排（约 90px）刚好贴边，所以还要允许换行
     （.pod-marks 已 flex-wrap）——宁可分成两行，也不缩到看不清字。 */
  .mk { height: 16px; padding: 0 5px; font-size: 9.5px; }
  .rank-list.is-big .rank-row { padding: 10px 12px; min-height: 62px; }
  .rank-list.is-big .rk-avatar { flex-basis: 42px; width: 42px; height: 42px; }
  .rank-list.is-big .rk-name { font-size: 14px; }
  .rank-list.is-big .rk-val { font-size: 17px; }
  /* 头像墙窄屏 3 列：126px 的最小格宽在 390 视口下只能放 2 列，
     2 列时头像 72px 显得空，3 列反而更紧凑也更像"墙"。 */
  .rank-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; padding: 12px 12px 2px; }
  .rg-item { padding: 9px 5px 8px; }
  .rg-av img { width: 58px; height: 58px; }
  .rg-name { font-size: 12.5px; }
  .rg-val { font-size: 13.5px; }
  /* 窄屏：图在上、图例在下。横排放不下 —— 168px 的图 + 240px 最小的图例
     已经超过 390 - 2*16 的可用宽度，硬撑只会横向溢出。 */
  .ct { flex-direction: column; align-items: stretch; }
  .ct-figure { align-self: center; }
  .ct-svg { width: 150px; height: 150px; }
  .ct-legend { flex: 0 0 auto; }
  .ct-pc { flex-basis: 46px; }
}
/* 键盘/读屏：图例项本身不可聚焦，扇区的 <title> 由浏览器原生提示承担；
   动效全关时保留 hover 的高亮（那不是动画，是状态反馈）。 */
@media (prefers-reduced-motion: reduce) {
  .pod, .ct-seg { transition: none; }
  .pod:hover { transform: none; }
}

/* 核验页提示数据 */
.verify-hint {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin-top: 18px;
}
.vh-item {
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 15px 8px; text-align: center; background: var(--surface-2);
}
.vh-item b { display: block; font-size: 23px; font-weight: 800; color: var(--brand-dark); font-variant-numeric: tabular-nums; }
.vh-item span { font-size: 12.5px; color: var(--text-3); }

@media (max-width: 900px) {
  .board-2col { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 640px) {
  .board-overview { gap: 9px; }
  .bo-item { padding: 14px 8px; }
  .bo-item b { font-size: 21px; }
  .active-cmp { gap: 9px; }
  .ac-col { padding: 13px 5px; }
  .ac-col .acv { font-size: 21px; }
  .rank-row { padding: 9px 13px; gap: 9px; min-height: 50px; }
  .rk-avatar { flex-basis: 30px; width: 30px; height: 30px; }
  .rk-role { display: none; }
  .br-lbl { flex-basis: 70px; font-size: 12.5px; }
  .br-val { flex-basis: 50px; font-size: 12px; }
  .verify-hint { gap: 9px; }
  .vh-item { padding: 12px 6px; }
  .vh-item b { font-size: 20px; }
}

/* --------------------------------------------------------------------------
   35. 顶栏快捷区 / 返回首页 / Logo 原样展示 / 前官网入口
   -------------------------------------------------------------------------- */

/* 顶栏回到后台首页的按钮 */
.topbar-home {
  flex: 0 0 auto; width: 34px; height: 34px; border-radius: 9px;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--text-2); background: var(--surface-2); border: 1px solid var(--border);
  transition: all .15s;
}
.topbar-home:hover { color: var(--brand); border-color: var(--brand); background: var(--brand-lighter); }
.topbar-title { display: flex; align-items: center; gap: 10px; min-width: 0; }
.tt-txt { min-width: 0; }

/* 右上角快捷区：任务 / 通知 / 求救 一步直达 */
.top-quick { display: flex; align-items: center; gap: 6px; }
.tq-item {
  position: relative;
  display: inline-flex; align-items: center; gap: 6px;
  height: 34px; padding: 0 11px; border-radius: 9px;
  border: 1px solid var(--border); background: var(--surface);
  color: var(--text-2); font-size: 13px; text-decoration: none;
  transition: all .15s; white-space: nowrap;
}
.tq-item:hover { border-color: var(--brand); color: var(--brand); background: var(--brand-lighter); }
.tq-item.is-active { border-color: var(--brand); background: var(--brand-lighter); color: var(--brand-dark); font-weight: 600; }
.tq-item.is-danger { color: var(--danger); border-color: var(--danger-border); background: var(--danger-light); }
.tq-item.is-danger:hover { background: #FBDDDD; color: var(--danger); }
.tq-badge {
  min-width: 17px; height: 17px; padding: 0 5px; border-radius: 9px;
  background: var(--danger); color: #fff; font-size: 10.5px; font-weight: 700;
  display: inline-flex; align-items: center; justify-content: center; line-height: 1;
}
.tq-item.is-danger .tq-badge { background: var(--fixed-white); color: var(--danger); }

/* 自定义 Logo：原样展示，不加蓝色圆角底（需求⑤） */
.sidebar-logo.is-custom,
.logo-badge.is-custom {
  background: none !important;
  border-radius: 0;
  box-shadow: none;
  padding: 0;
}
.logo-badge { width: 40px; height: 40px; flex: 0 0 40px; border-radius: 11px; display: grid; place-items: center; }

/* 需求 #78-3：前台导航的「蓝天官网」外链项已换成站内的「对外公告」，
   原来那两条 .site-nav-out 规则一并删掉 —— 留着就是"样式类没有对应元素"，
   下次有人搜到它还会以为那个入口只是被藏起来了。 */

/* --------------------------------------------------------------------------
   35.1 提示音：铃铛按钮 + 设置气泡（需求 #65）
   -------------------------------------------------------------------------- */
.sound-wrap { position: relative; display: inline-flex; }

.sound-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; padding: 0;
  border: 1px solid var(--border); border-radius: 9px;
  background: var(--surface); color: var(--text-2);
  cursor: pointer; transition: all .15s;
}
.sound-btn:hover { border-color: var(--brand); color: var(--brand); background: var(--brand-lighter); }
.sound-btn.is-muted { color: var(--text-3); background: var(--surface-2); }
.sound-btn .sb-icon,
.sound-btn .sb-off { display: grid; place-items: center; }
/* [hidden] 的默认 display:none 会被上面的 grid 覆盖，必须显式压回去 */
.sound-btn [hidden] { display: none !important; }

/* 前台深色头部里的变体：不抢视觉重心，但要能看清 */
.sound-btn.is-dark {
  width: 32px; height: 32px;
  border-color: rgba(255, 255, 255, .24);
  background: rgba(255, 255, 255, .08);
  color: rgba(255, 255, 255, .92);
}
.sound-btn.is-dark:hover {
  border-color: rgba(255, 255, 255, .45);
  background: rgba(255, 255, 255, .18);
  color: #fff;
}

.sound-pop {
  position: absolute; top: calc(100% + 10px); right: 0; z-index: 70;
  width: 284px; padding: 14px;
  background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  color: var(--text); text-align: left;
}
.sound-pop[hidden] { display: none; }

.sp-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.sp-head b { font-size: 13.5px; }
.sp-close {
  border: 0; background: none; padding: 0 2px; cursor: pointer;
  font-size: 20px; line-height: 1; color: var(--text-3);
}
.sp-close:hover { color: var(--text); }

.sp-switch { display: flex; align-items: center; gap: 8px; padding: 7px 0; font-size: 13px; cursor: pointer; }

.sp-vol {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 0 10px; font-size: 12.5px; color: var(--text-2);
  border-bottom: 1px solid var(--border);
}
.sp-vol input[type="range"] { flex: 1; min-width: 0; accent-color: var(--brand); }
.sp-vol b { width: 26px; text-align: right; font-family: var(--mono); font-size: 12px; color: var(--text); }

.sp-demo { padding: 10px 0 2px; }
.sp-demo-label { font-size: 12.5px; color: var(--text-2); }
.sp-demo-btns { display: flex; gap: 6px; margin-top: 7px; }
.sp-demo-btn {
  flex: 1; height: 30px; border-radius: 8px; cursor: pointer;
  border: 1px solid var(--border); background: var(--surface-2); color: var(--text-2);
  font-size: 12.5px; font-family: inherit; transition: all .15s;
}
.sp-demo-btn:hover:not(:disabled) { border-color: var(--brand); color: var(--brand); background: var(--brand-lighter); }
.sp-demo-btn:disabled { opacity: .45; cursor: not-allowed; }

.sp-tip {
  display: flex; align-items: flex-start; gap: 6px;
  margin: 9px 0 0; font-size: 11.5px; line-height: 1.55; color: var(--text-3);
}
.sp-tip svg { flex: 0 0 auto; margin-top: 2px; }

/* 35.1.1 未处理时的重复提醒（需求 #1）
   还在重复鸣响时：① 气泡里出现「停止提醒」；② 顶栏铃铛加呼吸光晕，
   让"声音还在响"这件事在视觉上也成立 —— 静音环境下不至于完全没线索。 */
.sp-stop {
  display: flex; align-items: center; justify-content: center; gap: 6px;
  width: 100%; margin: 10px 0 2px; height: 34px;
  border: 0; border-radius: 9px; cursor: pointer; font-family: inherit;
  background: var(--danger); color: #fff; font-size: 13px; font-weight: 600;
}
.sp-stop:hover { background: var(--danger-dark); }
.sp-stop[hidden] { display: none !important; }

.sound-wrap.is-ringing .sound-btn {
  color: var(--danger);
  border-color: var(--danger);
  animation: sb-pulse 1.1s ease-in-out infinite;
}
@keyframes sb-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(217, 58, 58, .45); }
  50%      { box-shadow: 0 0 0 7px rgba(217, 58, 58, 0); }
}
/* 关闭系统动效时只保留变色，不做脉冲 */
@media (prefers-reduced-motion: reduce) {
  .sound-wrap.is-ringing .sound-btn { animation: none; }
}

/* --------------------------------------------------------------------------
   35.2 皮肤选择面板（需求 #66）
   -------------------------------------------------------------------------- */
.skin-wrap { position: relative; display: inline-flex; }

.skin-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; padding: 0;
  border: 1px solid var(--border); border-radius: 9px;
  background: var(--surface); color: var(--text-2);
  cursor: pointer; transition: all .15s;
}
.skin-btn:hover { border-color: var(--brand); color: var(--brand); background: var(--brand-lighter); }
.skin-btn.is-dark {
  width: 32px; height: 32px;
  border-color: rgba(255, 255, 255, .24);
  background: rgba(255, 255, 255, .08);
  color: rgba(255, 255, 255, .92);
}
.skin-btn.is-dark:hover {
  border-color: rgba(255, 255, 255, .45);
  background: rgba(255, 255, 255, .18);
  color: #fff;
}

.skin-pop {
  position: absolute; top: calc(100% + 10px); right: 0; z-index: 70;
  width: 288px; padding: 14px;
  background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  color: var(--text); text-align: left;
}
.skin-pop[hidden] { display: none; }

.skp-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 4px; }
.skp-head b { font-size: 13.5px; }
.skp-close {
  border: 0; background: none; padding: 0 2px; cursor: pointer;
  font-size: 20px; line-height: 1; color: var(--text-3);
}
.skp-close:hover { color: var(--text); }
.skp-tip { margin: 0 0 10px; font-size: 11.5px; line-height: 1.55; color: var(--text-3); }

.skp-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; }

.skp-item {
  position: relative;
  display: flex; align-items: center; gap: 8px;
  padding: 7px 9px; cursor: pointer; text-align: left;
  border: 1px solid var(--border); border-radius: var(--radius-xs);
  background: var(--surface-2); color: var(--text-2);
  font-family: inherit; font-size: 12.5px; transition: all .15s;
}
.skp-item:hover { border-color: var(--brand); background: var(--brand-lighter); color: var(--text); }
.skp-item.is-cur {
  border-color: var(--brand); background: var(--brand-lighter); color: var(--text);
  font-weight: 600; box-shadow: inset 0 0 0 1px var(--brand);
}
.skp-item.is-cur::after {
  content: '✓'; position: absolute; top: 2px; right: 5px;
  font-size: 11px; color: var(--brand); font-weight: 700;
}

.skp-chip { flex: 0 0 22px; height: 22px; border-radius: 6px; box-shadow: inset 0 0 0 1px rgba(0,0,0,.06); }
.skp-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.skp-def {
  flex: 0 0 auto; font-size: 10px; font-weight: 500;
  padding: 0 5px; border-radius: 20px; line-height: 15px;
  background: var(--brand-light); color: var(--brand-ink);
}

.skp-reset { width: 100%; margin-top: 11px; justify-content: center; }

/* 色卡：直接展示每套皮肤的品牌渐变，点之前就能看出气质。
   这里的色值刻意写死 —— 它是「色卡」，本来就该恒等于那套皮肤的颜色，
   跟着当前皮肤变就没有意义了。 */
[data-skin-chip="blue"]     { background: linear-gradient(135deg, #3D8BFF, #1D5FD0 60%, #123C86); }
[data-skin-chip="dark"]     { background: linear-gradient(135deg, #2C4E86, #1B3A66 60%, #101C33); }
[data-skin-chip="orange"]   { background: linear-gradient(135deg, #F5943F, #E2650F 60%, #B84C08); }
[data-skin-chip="green"]    { background: linear-gradient(135deg, #4FAE7E, #1B7F53 60%, #12603D); }
[data-skin-chip="teal"]     { background: linear-gradient(135deg, #3FA9B8, #0B7C8C 60%, #075D6A); }
[data-skin-chip="red"]      { background: linear-gradient(135deg, #E05F5B, #C0302C 60%, #982220); }
[data-skin-chip="paper"]    { background: linear-gradient(135deg, #FFFFFF, #EEF0F4 55%, #3F4A5A); }
[data-skin-chip="gold"]     { background: linear-gradient(135deg, #D3A83F, #A87C1A 60%, #84600F); }
[data-skin-chip="purple"]   { background: linear-gradient(135deg, #9273DE, #6B45C8 60%, #5232A0); }
[data-skin-chip="graphite"] { background: linear-gradient(135deg, #718096, #4A5568 60%, #374151); }

/* 后台「外观皮肤」页的皮肤卡片：两列大卡，卡片本身就是一张色卡 */
.skin-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 12px; }

.skin-card {
  position: relative; display: flex; align-items: stretch; gap: 12px;
  padding: 12px; cursor: pointer;
  border: 1.5px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface); transition: all .15s;
}
.skin-card:hover { border-color: var(--brand); background: var(--surface-hover); }
.skin-card.is-cur { border-color: var(--brand); background: var(--brand-lighter); }
.skin-card.is-cur::after {
  content: '当前默认'; position: absolute; top: -9px; right: 10px;
  font-size: 10.5px; font-weight: 600; line-height: 18px; padding: 0 8px;
  border-radius: 20px; background: var(--brand); color: #fff;
}
/* 原生 radio 保留在 DOM 里（键盘可达、表单可提交），视觉上藏起来 */
.skin-card input[type="radio"] {
  position: absolute; opacity: 0; width: 0; height: 0; pointer-events: none;
}
.skin-card input[type="radio"]:focus-visible ~ .sc-chip { outline: 2px solid var(--brand); outline-offset: 2px; }

.sc-chip {
  flex: 0 0 46px; height: 46px; border-radius: 10px;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .06);
}
.sc-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.sc-name { font-size: 13.5px; font-weight: 600; color: var(--text); }
.sc-name em {
  font-style: normal; font-weight: 400; font-size: 11px;
  margin-left: 6px; color: var(--text-3); letter-spacing: .3px;
}
.sc-desc { font-size: 11.5px; line-height: 1.55; color: var(--text-3); }

/* --------------------------------------------------------------------------
   36. 装备管理 · 板块化与批量操作（需求 #67）
   -------------------------------------------------------------------------- */

/* 板块导航：比默认 tabs 大一点，因为这一页的板块是「换一个工作场景」，
   不是同一批数据的小切换 */
.tabs-lg a { padding: 10px 16px; font-size: 13.5px; }
.tabs-lg a svg { margin-right: 5px; vertical-align: -2px; }
.tab-n {
  display: inline-block; margin-left: 6px; padding: 0 6px;
  font-size: 11px; line-height: 17px; border-radius: 20px;
  background: var(--brand-light); color: var(--brand-ink);
}
.tabs a.active .tab-n { background: var(--brand); color: #fff; }

/* 勾选列：窄，且不参与移动端的堆叠展示 */
.eq-tabs { margin-bottom: 0; }
.th-check, .td-check { width: 34px; text-align: center; }
.th-check input, .td-check input { width: 15px; height: 15px; accent-color: var(--brand); cursor: pointer; }
.td-check input:disabled { cursor: not-allowed; opacity: .4; }

/* 批量维护条：横向排布，窄屏自动折行 */
.eq-batch-bar {
  display: flex; align-items: center; flex-wrap: wrap; gap: 10px;
  padding: 12px 14px; margin-bottom: 16px;
  border: 1px solid var(--brand-border); border-radius: var(--radius-sm);
  background: var(--brand-lighter);
}
.ebb-title { display: inline-flex; align-items: center; gap: 5px; font-size: 13.5px; font-weight: 600; color: var(--brand-ink); }
.ebb-hint { font-size: 12px; color: var(--text-3); }
.ebb-field { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--text-2); }
.ebb-field.grow { flex: 1; min-width: 180px; }
.ebb-field.grow .input { flex: 1; min-width: 0; }
.ebb-count {
  margin-left: auto; font-size: 12.5px; font-weight: 600;
  color: var(--brand-ink); white-space: nowrap;
}

/* 一键完成检修条 */
.eq-fix-bar {
  display: flex; flex-direction: column; gap: 8px;
  padding: 12px 14px; border-bottom: 1px solid var(--border);
  background: var(--warn-light);
}
.efb-title { display: inline-flex; align-items: center; gap: 5px; font-size: 13.5px; font-weight: 600; color: var(--warn-ink); }
.eq-fix-item { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; }
.efi-name { display: inline-flex; align-items: baseline; gap: 8px; min-width: 190px; }
.efi-name b { font-size: 13px; }

/* 装备照片：入库/编辑时最多 5 张 */
.eq-photos { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 10px; }
.eq-photo {
  position: relative; display: flex; align-items: center; justify-content: center;
  width: 104px; height: 104px; overflow: hidden;
  border: 1.5px dashed var(--border-strong); border-radius: var(--radius-sm);
  background: var(--surface-2); cursor: pointer; transition: all .15s;
}
.eq-photo.is-existing { border-style: solid; border-color: var(--border); background: var(--surface); }
.eq-photo img { width: 100%; height: 100%; object-fit: cover; }
.eq-photo.is-add:hover { border-color: var(--brand); background: var(--brand-lighter); }
.eq-photo.is-add input[type="file"] { position: absolute; inset: 0; opacity: 0; cursor: pointer; }
.epa-ico { color: var(--text-3); }
.epa-txt {
  position: absolute; left: 0; right: 0; bottom: 8px;
  text-align: center; font-size: 11.5px; line-height: 1.4; color: var(--text-3);
}
.eq-photo.is-add { flex-direction: column; }
.ep-del {
  position: absolute; left: 0; right: 0; bottom: 0;
  display: flex; align-items: center; justify-content: center; gap: 4px;
  padding: 3px 0; font-size: 11.5px; cursor: pointer;
  background: rgba(15, 23, 42, .68); color: #fff;
}
.ep-del input { width: 13px; height: 13px; accent-color: var(--danger); }
/* 勾了「删除」的照片立刻变灰划掉，保存前就能看出将失去哪张 */
.eq-photo:has(.ep-del input:checked) img { opacity: .35; filter: grayscale(1); }
.eq-photo:has(.ep-del input:checked) .ep-del { background: var(--danger); }

/* 台账里的照片缩略图 */
.eq-thumb-row { display: inline-flex; gap: 3px; margin-top: 4px; }
.eq-thumb-row img {
  width: 26px; height: 26px; object-fit: cover;
  border-radius: 5px; border: 1px solid var(--border);
}
.eq-thumb-more {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border-radius: 5px;
  background: var(--surface-3); color: var(--text-3); font-size: 10.5px;
}

@media (max-width: 1100px) {
  /* 空间不够时快捷区只留图标 */
  .tq-txt { display: none; }
  .tq-item { padding: 0 9px; }
}
@media (max-width: 640px) {
  .top-quick { gap: 5px; }
  .tq-item { height: 32px; }
  .topbar-home { width: 32px; height: 32px; }
  /* 气泡在窄屏上贴右边会被切掉，改成按屏宽收窄并往左让一点 */
  .sound-pop { right: -6px; width: calc(100vw - 32px); max-width: 300px; }
  .skin-pop  { right: -6px; width: calc(100vw - 32px); max-width: 300px; }
}

/* --------------------------------------------------------------------------
   36. 任务信息大盘（实时播报）
   -------------------------------------------------------------------------- */

.live-board { border-top: 3px solid var(--brand); }
.live-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 12px; }
.live-card {
  display: block; text-decoration: none; color: inherit;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 13px 15px; background: var(--surface);
  border-left: 3px solid var(--border-strong); transition: all .15s;
}
.live-card:hover { border-color: var(--brand); background: var(--brand-lighter); }
.live-card.is-ongoing { border-left-color: var(--warn); }
.lc-top { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }
.lc-title { font-size: 15px; font-weight: 700; line-height: 1.45; }
.lc-meta { font-size: 12px; color: var(--text-3); margin-top: 4px; }
.lc-nums { display: flex; flex-wrap: wrap; gap: 6px 16px; margin-top: 9px; font-size: 12.5px; color: var(--text-2); }
.lc-nums b { color: var(--brand-dark); font-size: 15px; font-weight: 800; }
.lc-foot { font-size: 11.5px; color: var(--text-3); margin-top: 8px; padding-top: 7px; border-top: 1px dashed var(--border); }

/* 现场进展滚动播报 */
.live-feed { margin-top: 16px; border: 1px solid var(--border); border-radius: var(--radius-sm); overflow: hidden; }
.lf-head {
  display: flex; align-items: center; gap: 7px;
  padding: 9px 14px; background: var(--surface-2);
  border-bottom: 1px solid var(--border);
  font-size: 13px; font-weight: 700; color: var(--text-2);
}
.lf-tip { margin-left: auto; font-size: 11.5px; font-weight: 400; color: var(--text-3); }
.lf-track { max-height: 240px; overflow: hidden; }
.lf-inner { padding: 6px 0; }
.lf-item {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 9px;
  padding: 8px 14px; font-size: 13px; border-bottom: 1px dashed var(--border);
}
.lf-item:last-child { border-bottom: none; }
.lf-item.is-notice { background: var(--warn-light); }
.lf-time { flex: 0 0 auto; font-size: 12px; color: var(--text-3); }
.lf-task { flex: 0 0 auto; font-weight: 600; color: var(--brand-dark); }
.lf-txt { flex: 1 1 200px; color: var(--text-2); }
.lf-who { flex: 0 0 auto; font-size: 12px; color: var(--text-3); }

/* 小型排行榜（序号 + 姓名 + 次数） */
.mini-rank { list-style: none; margin: 0; padding: 0; counter-reset: mr; }
.mini-rank li {
  display: flex; align-items: center; gap: 10px; justify-content: space-between;
  padding: 7px 0; border-bottom: 1px dashed var(--border); font-size: 13.5px;
  counter-increment: mr;
}
.mini-rank li:last-child { border-bottom: none; }
.mini-rank li::before {
  content: counter(mr); flex: 0 0 20px; width: 20px; height: 20px; border-radius: 50%;
  background: var(--surface-2); border: 1px solid var(--border); color: var(--text-3);
  font-size: 11.5px; font-weight: 700; display: inline-flex; align-items: center; justify-content: center;
}
.mini-rank li a { flex: 1 1 auto; color: var(--text); }
.mini-rank li a:hover { color: var(--brand); }
.mini-rank li b { color: var(--brand-dark); font-weight: 700; }

@media (max-width: 640px) {
  .live-grid { grid-template-columns: minmax(0, 1fr); }
  .lf-track { max-height: 200px; }
  .lf-item { gap: 6px; padding: 7px 12px; }
  .lf-txt { flex-basis: 100%; }
}

/* 手机端顶栏：底部导航已覆盖任务/消息/我的，这里只留最紧急的「求救」，
   避免顶栏变成横向滚动条（滚动内容不易被发现，等于把入口藏起来）。
   注意这里必须保持 overflow-x: visible —— 顶栏的「导出」是个 <details> 下拉，
   父层一旦 overflow:auto/hidden，弹出的菜单就会被裁掉。 */
@media (max-width: 640px) {
  .top-quick .tq-item:not(.is-danger) { display: none; }
  .topbar-actions { overflow-x: visible; }

  /* 顶栏按钮的文字在窄屏收起，只留图标（含义由 title / aria-label 兜底）。
     不这么做的话，窄屏上只要有「导出下拉 + 发起任务 + 快捷区 + 头像」凑齐，
     .topbar-actions 就会被顶出视口：整页出现横向滚动条，
     而 position:fixed 的底部导航、抽屉遮罩会按溢出后的宽度重排，看起来像"整页错位"。
     一个 87px 的「导出」下拉就足以越过 390px 的视口，所以这里连同内边距一起收紧。 */
  .topbar .dd > summary .dd-lb { display: none; }
  .topbar .dd > summary { padding-left: 9px; padding-right: 9px; gap: 4px; }
  .topbar .dd > summary svg:last-child { display: none; }
}

/* （原「状态切换按钮内的文字 .status-btn .sb-txt」样式已随该控件一并删除） */

/* 看板环比变化的第 3 列用分隔色区分 */
.ac-col.is-delta { background: var(--surface); border-style: dashed; }

/* 短信通道状态与测试条 */
.sms-probe {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  margin-top: 16px; padding: 13px 15px;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface-2);
}
.sp-left { display: flex; align-items: center; gap: 9px; flex: 1 1 260px; flex-wrap: wrap; }
.sp-left code {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 4px; padding: 1px 5px; font-size: 11.5px;
}
.sp-right { display: flex; align-items: center; gap: 8px; }
@media (max-width: 640px) {
  .sp-right { width: 100%; }
  .sp-right .input { max-width: none !important; flex: 1 1 auto; }
}

/* 省 / 市 / 区 三级联动选择：一行三列，小屏自动堆叠 */
.region-select { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
.region-select .select { min-width: 0; }
@media (max-width: 640px) {
  .region-select { grid-template-columns: minmax(0, 1fr); }
}

/* ==========================================================================
   37. 志愿者服务承诺弹窗（注册页）
   ========================================================================== */
.link-btn {
  background: none; border: 0; padding: 0; cursor: pointer;
  color: var(--brand); font: inherit; font-weight: 600;
  text-decoration: underline; text-underline-offset: 2px;
}
.link-btn:hover { color: var(--brand-dark); }

.pledge-mask {
  position: fixed; inset: 0; z-index: 2200;
  background: rgba(12, 42, 97, .48);
  -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
  display: flex; align-items: center; justify-content: center; padding: 18px;
}
.pledge-mask[hidden] { display: none !important; }

.pledge {
  width: 100%; max-width: 620px; max-height: 88vh;
  background: var(--surface); border-radius: var(--radius-lg, 16px);
  box-shadow: 0 24px 60px rgba(12, 20, 34, .28);
  display: flex; flex-direction: column; overflow: hidden;
}
.pledge-head {
  display: flex; align-items: center; gap: 10px;
  padding: 17px 20px; border-bottom: 1px solid var(--border);
  background: var(--surface-2);
}
.pledge-head h3 { margin: 0; font-size: 16px; }
.pledge-x {
  margin-left: auto; background: none; border: 0; cursor: pointer;
  font-size: 24px; line-height: 1; color: var(--text-3); padding: 0 2px;
}
.pledge-x:hover { color: var(--text-1); }
.pledge-body { padding: 18px 20px; overflow-y: auto; }
.pledge-list { margin: 0; padding-left: 20px; }
.pledge-list li { margin-bottom: 10px; font-size: 13.5px; line-height: 1.72; color: var(--text-2); }
.pledge-list li b { color: var(--text-1); }
.pledge-note {
  margin: 16px 0 0; padding: 12px 14px; font-size: 12.5px; line-height: 1.7;
  color: var(--text-2); background: var(--surface-2);
  border-left: 3px solid var(--brand); border-radius: 0 8px 8px 0;
}
/* v2.12 需求 4：结语从「一整段纯文本」升级成「后台可编辑的富文本」之后，
   盒子里面装的是 <p> / <ul> / <ol> 这些块级元素 ——
   里面第一个块的上下外边距要抹掉，否则浅底盒子会被顶出一段空白、
   看着像"样式错位"。原来外层是 <p> 时不存在这个问题（因为非法嵌套，
   内容根本没被盒子包住），换成 <div> 之后必须补上。 */
.pledge-note > :first-child { margin-top: 0; }
.pledge-note > :last-child  { margin-bottom: 0; }
.pledge-note > p { margin: 0 0 8px; }
.pledge-note > p + p { margin-top: 0; }
.pledge-note > ul, .pledge-note > ol { margin: 0 0 8px; padding-left: 20px; }
.pledge-note > h3, .pledge-note > h4 { margin: 0 0 8px; font-size: 13.5px; color: var(--text-1); }
.pledge-foot {
  display: flex; align-items: center; gap: 12px;
  padding: 14px 20px; border-top: 1px solid var(--border);
  background: var(--surface-2);
}
@media (max-width: 640px) {
  .pledge-mask { padding: 0; align-items: flex-end; }
  .pledge { max-width: none; max-height: 92vh; border-radius: 16px 16px 0 0; }
  .pledge-list li { font-size: 13px; }
}

/* ==========================================================================
   38. 首页「任务态势」三段时段卡片
   ========================================================================== */
.period-grid {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px;
}
/* ★ v2.27 需求 6：任务态势只留「去年 / 今年」两段。
   三列栅格里放两张卡会空出右边一整列，看起来像"少了一张"，
   所以给两段单独一个两列版本（列宽跟着变宽，数字也更醒目）。 */
.period-grid.is-two { grid-template-columns: repeat(2, minmax(0, 1fr)); }
/* 时段卡里只有两个数字（任务数量 / 任务总时长）时用两列均分；
   沿用三列会空出第三格，两个数字挤在左边，重心是歪的。 */
.pc-main.is-two { grid-template-columns: repeat(2, minmax(0, 1fr)); }
/* 一句对比：两段之间的关系。刻意做成安静的一行小字，
   不抢上面两张卡的数字 —— 它是注解，不是主信息。 */
.period-diff {
  display: flex; align-items: flex-start; gap: 7px; flex-wrap: wrap;
  margin: 14px 0 0; padding: 10px 14px;
  border-left: 3px solid var(--brand); border-radius: 0 9px 9px 0;
  background: var(--brand-lighter);
  font-size: 13px; line-height: 1.7; color: var(--text-2);
}
.period-diff svg { flex: 0 0 auto; margin-top: 3px; color: var(--brand); }
.period-diff b { color: var(--text-1); font-variant-numeric: tabular-nums; }
.period-diff .pd-raw { font-size: 11.5px; color: var(--text-3); }
.period-card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 18px 18px 14px;
  display: flex; flex-direction: column; gap: 12px;
  box-shadow: 0 1px 2px rgba(16, 24, 40, .04);
  transition: box-shadow .2s ease, transform .2s ease;
}
.period-card:hover { box-shadow: 0 10px 26px rgba(16, 24, 40, .08); transform: translateY(-2px); }
.period-card.is-feature { border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-lighter); }

.pc-head { display: flex; flex-direction: column; gap: 2px; }
.pc-head h3 { margin: 0; font-size: 15.5px; }
.pc-range { font-size: 11.5px; color: var(--text-3); font-variant-numeric: tabular-nums; }

.pc-main {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px;
  padding: 12px 0; border-top: 1px solid var(--border); border-bottom: 1px solid var(--border);
}
.pcm { display: flex; flex-direction: column; align-items: center; gap: 3px; text-align: center; }
.pcm b { font-size: 21px; line-height: 1.15; font-weight: 700; color: var(--text-1); font-variant-numeric: tabular-nums; }
.pcm b small { font-size: 12px; font-weight: 500; color: var(--text-3); margin-left: 1px; }
.pcm span { font-size: 11.5px; color: var(--text-3); }

.pc-foot { display: flex; flex-wrap: wrap; gap: 6px 14px; font-size: 12px; color: var(--text-2); }
.pc-foot span { display: inline-flex; align-items: center; gap: 4px; }
.pc-foot b { color: var(--text-1); font-variant-numeric: tabular-nums; }

.pc-split { display: flex; flex-direction: column; gap: 6px; }
.pcs-row {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 10px; border-radius: 9px; font-size: 12px;
}
.pcs-row.is-rescue { background: rgba(217, 45, 32, .07); }
.pcs-row.is-train  { background: rgba(29, 95, 208, .07); }

/* ★ 类名是 .pcs-label，**不是** .pcs-tag —— 这层改名是本节的防腐层，别改回去。
   事故经过：这里原本叫 .pcs-tag，而 user/apply.php 的「申请条件流程」组件
   也用同名 .pcs-tag（见本文件下方 .pcs-flow 那一段，那里要的是**实心品牌色药丸**）。
   两份 .pcs-tag 定义特异性相同（都是 0,1,0），后者在文件里更靠后，
   于是它的 `background: var(--brand)` 覆盖到了首页这两个标签上：
   本该是「深红字 / 深蓝字压在极浅底行上」的朴素标签，变成了两块刺眼的实心蓝；
   而文字色因为 `.pcs-row.is-rescue .pcs-tag` 特异性更高（0,3,0）**没有**跟着变，
   结果就是深红字压在饱和蓝底上 —— 对比度低到快读不出来。
   注意「一个类名被两个组件共用、靠源码先后决定长相」这种耦合：
   它不会报错、不会崩，只会默默让 A 组件长得像 B 组件，改 B 的时候 A 跟着变。
   现在这组规则只认自己（.pcs-label），底与字全部走语义变量，
   十套皮肤下都自动跟着变，且不再依赖规则先后。 */
.pcs-label {
  flex: 0 0 auto; font-weight: 600; font-size: 11.5px;
  padding: 2px 8px; border-radius: 7px; line-height: 1.6;
  /* 用「白底 + 同色系细描边」而不是「同色系浅底」：
     下面那行 .pcs-row 的底本来就是同类色的极浅底（rgba .07），
     再叠一块 --danger-light / --brand-light 上去，实测两者亮度几乎相同
     （标签订在行底上的对比只有 1.03），pill 就"消失"在行里了。
     改成白底（--surface，深色皮肤下自动变深）靠描边立轮廓，
     文字用同色系深色 —— 浅、干净，且十套皮肤下都成立。
     box-sizing 全局是 border-box，所以这 1px 描边不会把 pill 撑大。 */
  background: var(--surface);
}
.pcs-row.is-rescue .pcs-label { border: 1px solid var(--danger-border); color: var(--danger-ink); }
/* 文字用 --brand-ink 而**不是** --brand-dark：
   两者在浅色皮肤下看着差不多（#0C2A61 vs #123C86），但十套皮肤扫下来，
   深色皮肤里 --brand-dark 仍是深蓝 #3B76D6，压在深色卡底上对比度只有 3.82，
   低于 WCAG AA 的 4.5 —— 也就是说这个标签在夜间皮肤下会"发闷"。
   --brand-ink 的语义就是「品牌色系的文字色」，深色皮肤下会自动翻成浅蓝 #A9C9FF，
   实测十套皮肤最低对比度 8.4 以上。救援那侧同理用 --danger-ink（它在深色皮肤下
   也会翻成浅红 #FF9C9C），所以两侧都不需要在组件里写皮肤判断。 */
.pcs-row.is-train  .pcs-label { border: 1px solid var(--brand-border);  color: var(--brand-ink); }
.pcs-vals { margin-left: auto; color: var(--text-2); font-variant-numeric: tabular-nums; }
.pcs-vals b { color: var(--text-1); }

.period-note {
  display: flex; align-items: flex-start; gap: 7px;
  margin: 16px 0 0; font-size: 12.5px; line-height: 1.7; color: var(--text-3);
}
/* ★ 这段说明文字在 HTML 里必须包一层 <span>，别直接把文字摊在 .period-note 里。
   本规则是 flex 容器（图标 + 文字要顶部对齐），flex 会把**每个文本节点、
   每个 <b>** 都变成独立 flex 子项，各自被压到最小内容宽 ——
   实测「不计」「is_rescue」这些 <b> 会各占一列，整段排成阶梯状、完全读不出句子。
   包一层 span 之后整段是**一个**子项，换行交给 span 自己处理。
   （.board-note 是同一个坑，见它上面那段注释。） */
.period-note > span { min-width: 0; }
.period-note svg { flex: 0 0 auto; margin-top: 2px; }

@media (max-width: 900px) {
  .period-grid { grid-template-columns: minmax(0, 1fr); }
  .period-card.is-feature { order: -1; }
}

/* 能力栅格：8 个板块时桌面 4 列两行更整齐 */
@media (min-width: 1024px) {
  .feature-grid-8 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

/* 首页公告卡 */
.notice-card { text-decoration: none; color: inherit; transition: box-shadow .2s ease, transform .2s ease; }
.notice-card:hover { box-shadow: 0 10px 26px rgba(16, 24, 40, .08); transform: translateY(-2px); }
.notice-card h3 { font-size: 15px; line-height: 1.45; }

/* 前台导航当前页高亮 */
.site-nav a.is-cur { background: var(--brand-lighter); color: var(--brand); font-weight: 600; }

/* ==========================================================================
   39. 车辆查询结果卡片（前台 · 与核验页 result-card 同基调）
   设计意图：一台车一张卡。车牌是主角（大字号等宽），
   型号/核载/车主做成一条浅底数据带，实拍四角度图另起一条。
   描边一律 1px #ECECEF，圆角 20px，不用阴影强调层级，只在 hover 时抬 1px。
   ========================================================================== */
.vq-item {
  border: 1px solid var(--border); border-radius: 20px; background: var(--surface);
  box-shadow: 0 1px 2px rgba(22, 35, 58, .04);
  overflow: hidden; margin-bottom: 14px;
  transition: box-shadow .2s, border-color .2s, transform .2s;
}
.vq-item:hover {
  border-color: var(--border-strong);
  box-shadow: 0 8px 28px rgba(22, 35, 58, .09);
  transform: translateY(-1px);
}

/* ---------- 车牌行 ---------- */
.vq-head {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  padding: 20px 24px 18px;
}
.vq-plate-no {
  font-size: 23px; font-weight: 700; letter-spacing: .04em;
  color: var(--text); font-variant-numeric: tabular-nums;
  background: var(--surface-3); border: 0; border-radius: 12px;
  padding: 8px 16px;
}
.vq-tags { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; margin-left: auto; }
.vq-tags .badge { border: 0; }

/* ---------- 数据带（值大标签小，苹果式读数顺序） ---------- */
.vq-meta {
  display: grid; grid-template-columns: repeat(3, 1fr);
  margin: 0; padding: 0;
  border-top: 1px solid var(--border); background: var(--surface-2);
}
.vq-meta > div { padding: 15px 24px 16px; display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.vq-meta > div + div { border-left: 1px solid var(--border); }
.vq-meta b {
  font-size: 15.5px; font-weight: 600; color: var(--text);
  letter-spacing: -.2px; overflow-wrap: anywhere;
}
.vq-meta span { font-size: 12px; color: var(--text-3); }

/* ---------- 实拍四角度 ---------- */
.vq-shots {
  display: flex; gap: 10px; flex-wrap: wrap;
  padding: 16px 24px 20px; border-top: 1px solid var(--border);
}
.vq-shot { margin: 0; width: 168px; }
.vq-shot img {
  width: 168px; height: 116px; object-fit: cover; display: block;
  border-radius: 12px; border: 1px solid var(--border); background: var(--surface-3);
  cursor: zoom-in;
}
.vq-shot figcaption { font-size: 11.5px; color: var(--text-3); text-align: center; margin-top: 5px; }

@media (max-width: 760px) {
  .vq-head { padding: 16px 18px 14px; gap: 10px; }
  .vq-plate-no { font-size: 20px; padding: 7px 13px; }
  .vq-tags { margin-left: 0; flex: 1 1 100%; }
  .vq-meta { grid-template-columns: 1fr; }
  .vq-meta > div { padding: 12px 18px 13px; }
  .vq-meta > div + div { border-left: 0; border-top: 1px solid var(--border); }
  .vq-shots { padding: 14px 18px 18px; display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; }
  .vq-shot, .vq-shot img { width: 100%; }
  .vq-shot img { height: 84px; }
}

/* ==========================================================================
   40. 图片放大灯箱（沿用既有 .lightbox，此处做苹果风格收敛）
   ========================================================================== */
.lightbox {
  background: rgba(10, 14, 22, .9);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  padding: 40px 24px 28px;
  animation: lb-in .18s ease;
}
@keyframes lb-in { from { opacity: 0 } to { opacity: 1 } }
.lightbox img {
  max-width: min(94vw, 1180px); max-height: 84vh;
  border-radius: 14px;
  box-shadow: 0 30px 80px rgba(0, 0, 0, .55);
}
.lightbox-cap { color: #D7E1F2; font-size: 13px; letter-spacing: .01em; }
.lightbox-close {
  top: 14px; right: 16px; width: 36px; height: 36px;
  background: rgba(255, 255, 255, .14);
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  transition: background .16s ease;
}
.lightbox-close:hover { background: rgba(255, 255, 255, .26); }

/* 图片画廊的前后翻页（需求 #3）
   同一图组有多张图时才由 JS 插进来，所以这里的样式对单图场景没有影响。
   触摸目标 44px（苹果 HIG 的最小舒适尺寸），键盘聚焦时描边可见。 */
.lightbox-nav {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 46px; height: 46px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  border: none; cursor: pointer;
  background: rgba(255, 255, 255, .16); color: #fff;
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
  transition: background .16s ease, transform .16s ease;
  opacity: .82;
}
.lightbox-nav:hover { background: rgba(255, 255, 255, .3); opacity: 1; }
.lightbox-nav:active { transform: translateY(-50%) scale(.94); }
.lightbox-nav:focus-visible { outline: 2px solid #fff; outline-offset: 2px; opacity: 1; }
/* 位置用「离边缘固定距离」而不是「贴住图片」：
   图片最大宽度 1180px 居中显示，宽屏两侧留白本就有几百像素，
   箭头贴在留白里既不会压住图片，也不用写 calc()/min() 这类
   一旦浏览器不支持就整条声明失效的复杂表达式。 */
.lightbox-nav.is-prev { left: clamp(10px, 2vw, 28px); }
.lightbox-nav.is-next { right: clamp(10px, 2vw, 28px); }
/* 竖屏窄手机：图几乎占满宽度，箭头改成贴边半透明浮层。
   尺寸给到 44px —— 苹果 HIG 的最小舒适触摸目标，40px 实测偏小、
   手指容易点在边缘上而滑走（现场戴手套时更明显）。 */
@media (max-width: 760px) {
  .lightbox-nav { width: 44px; height: 44px; background: rgba(20, 26, 40, .62); opacity: .9; }
  .lightbox-nav.is-prev { left: 6px; }
  .lightbox-nav.is-next { right: 6px; }
}
.lightbox-count {
  position: absolute; left: 50%; transform: translateX(-50%); bottom: 14px;
  padding: 3px 12px; border-radius: 999px;
  background: rgba(255, 255, 255, .16); color: #fff;
  font-size: 12.5px; letter-spacing: .04em; font-variant-numeric: tabular-nums;
}
@media (max-width: 640px) {
  .lightbox { padding: 56px 12px 20px; }
  .lightbox img { max-height: 76vh; border-radius: 12px; }
}

/* --------------------------------------------------------------------------
   41. 移动端收尾：触控目标尺寸
   放在样式表最后是有意的——顶栏相关选择器在 35 节才定义，
   媒体查询必须晚于它们的普通规则，否则同优先级下会被覆盖。
   -------------------------------------------------------------------------- */
@media (max-width: 900px) {
  /* 顶栏「返回后台首页」与右上角快捷入口是高频点击目标，抬到 40px */
  .topbar-home { width: 40px; height: 40px; }
  .tq-item { height: 40px; padding: 0 12px; }
  /* 手机上顶栏只留图标，避免图标 + 文字把标题挤掉；紧急求助保留文字以免误触 */
  .topbar-quick .tq-item .tq-txt,
  .top-quick .tq-item .tq-txt { display: none; }
  .top-quick .tq-item.is-danger .tq-txt { display: inline; }
  .sa-close { width: 40px; height: 40px; }
}

/* ==========================================================================
   42. 核验结果卡（苹果基调重做）
   设计意图：把原来「一团小标签」改成有呼吸感的三段式——
     ① 身份行：头像 / 姓名 / 徽章 / 关键标识，右侧一个主行动按钮
     ② 数据条：四个最该被看到的数字，用大字号直接给结论
     ③ 明细行：分组 / 技能 / 持证，每行左侧一个灰色小节标签
   取色与字号参考 Apple HIG：正文 #1D1D1F、次级 #86868B、浅底 #F2F2F7，
   分隔线一律 1px #ECECEF 的极浅灰，不用描边强调层级。
   ========================================================================== */
.result-card {
  display: block;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 20px;
  background: var(--surface);
  box-shadow: 0 1px 2px rgba(22, 35, 58, .04);
  overflow: hidden;
  margin-bottom: 14px;
  transition: box-shadow .2s, border-color .2s, transform .2s;
}
.result-card:hover {
  border-color: var(--border-strong);
  box-shadow: 0 8px 28px rgba(22, 35, 58, .09);
  transform: translateY(-1px);
}

/* ---------- ① 身份行 ---------- */
.result-card .rc-head {
  display: flex; align-items: center; gap: 18px;
  padding: 22px 24px 20px;
}
.result-card .rc-avatar {
  flex: 0 0 68px; width: 68px; height: 68px;
  border-radius: 50%; object-fit: cover;
  box-shadow: 0 0 0 4px var(--surface-3);
}
.result-card .rc-id { flex: 1 1 auto; min-width: 0; }
.result-card .rc-name {
  font-size: 21px; font-weight: 700; letter-spacing: -.4px; line-height: 1.25;
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
}
.result-card .rc-nick { font-size: 13.5px; font-weight: 500; color: var(--text-3); letter-spacing: 0; }
.result-card .rc-badges { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; margin-top: 9px; }
.result-card .rc-meta {
  display: flex; flex-wrap: wrap; gap: 4px 18px;
  margin-top: 10px; font-size: 12.5px; color: var(--text-3);
}
.result-card .rc-meta b { font-weight: 600; color: var(--text); }
.result-card .rc-go {
  flex: 0 0 auto;
  display: inline-flex; align-items: center; gap: 4px;
  height: 40px; padding: 0 18px; border-radius: 999px;
  background: var(--brand); color: #fff;
  font-size: 13.5px; font-weight: 600; white-space: nowrap;
  transition: background .16s, transform .16s;
}
.result-card .rc-go:hover { background: var(--brand-dark); color: #fff; transform: translateX(2px); }

/* ---------- ② 数据条 ---------- */
.result-card .rc-stats {
  display: grid; grid-template-columns: repeat(4, 1fr);
  border-top: 1px solid var(--border); background: var(--surface-2);
}
.result-card .rc-stat { padding: 16px 20px 17px; text-align: left; }
.result-card .rc-stat + .rc-stat { border-left: 1px solid var(--border); }
.result-card .rc-stat b {
  display: block; font-size: 26px; font-weight: 700; letter-spacing: -.8px;
  line-height: 1.15; color: var(--text); font-variant-numeric: tabular-nums;
}
.result-card .rc-stat span { display: block; margin-top: 3px; font-size: 12px; color: var(--text-3); }

/* ---------- ③ 明细行 ---------- */
.result-card .rc-sec {
  display: flex; align-items: flex-start; gap: 16px;
  padding: 13px 24px; border-top: 1px solid var(--border);
}
.result-card .rc-sec-t {
  flex: 0 0 60px; padding-top: 4px;
  font-size: 12.5px; color: var(--text-3); white-space: nowrap;
}
.result-card .rc-chips { flex: 1 1 auto; min-width: 0; display: flex; flex-wrap: wrap; gap: 7px; }

/* 卡片内标签：浅底胶囊，不用描边（描边在密集标签里会显得很吵） */
.result-card .chip {
  border: 0; border-radius: 999px;
  background: var(--surface-3); color: #3A3A3C;
  padding: 4px 11px; font-size: 12.5px; gap: 6px;
}
.result-card .chip i {
  font-style: normal; font-size: 11.5px; font-weight: 600;
  color: var(--text-3); letter-spacing: 0;
}
.result-card .chip-brand { background: var(--brand-light); color: var(--brand-dark); }
.result-card .chip-quiet { background: transparent; color: var(--text-3); padding-left: 2px; }
.result-card .chip--ok     { background: var(--ok-light);     color: #0B7A3C; }
.result-card .chip--warn   { background: var(--warn-light);   color: #A06300; }
.result-card .chip--danger { background: var(--danger-light); color: var(--danger-ink); }
.result-card .chip--ok i, .result-card .chip--warn i, .result-card .chip--danger i { color: inherit; opacity: .72; }

/* ---------- 核验页整体呼吸感（苹果式大留白） ---------- */
.query-panel {
  border: 1px solid var(--border); border-radius: 20px;
  box-shadow: 0 1px 2px rgba(22, 35, 58, .04);
  padding: 24px;
}
.query-panel .query-row .input { height: 44px; border-radius: 12px; }
.query-panel .query-row .btn { height: 44px; border-radius: 12px; }
.query-panel .query-row .select { height: 44px; border-radius: 12px; }

/* 主查询行下方的次级筛选行（车辆查询用：类型 / 状态 / 重置）
   为什么单独一行：主行已经占了「查询方式 + 关键词 + 查询」，
   次级条件塞进同一行会把关键词输入框压扁，标签也会跟输入框错位。 */
.query-sub {
  display: flex; align-items: flex-end; gap: 12px; flex-wrap: wrap;
  margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--border);
}
.query-sub .qs-field { display: flex; flex-direction: column; gap: 6px; flex: 0 1 188px; min-width: 150px; }
.query-sub .qs-field > label { font-size: 12.5px; color: var(--text-3); }
.query-sub .qs-field .select { height: 40px; border-radius: 12px; }
/* v2.12 需求 3：次级筛选行自己的「确认筛选」主按钮。
   之前这一行只有两个下拉 + 一个"重置"链接、没有任何提交按钮，
   用户选完年份不知道该点哪里（唯一那个「搜索」在上一行、贴着关键词框）。
   按钮与它要提交的控件放在同一行、同一视觉块里，因果才明确。
   align-self: flex-end 让它与下拉底边对齐（.query-sub 是 align-items:flex-end，
   但 qs-field 是纵向 flex，含 label，故显式对齐更稳）。 */
.query-sub .qs-apply {
  height: 40px; border-radius: 12px; padding: 0 20px; align-self: flex-end;
}
.query-sub .qs-reset { height: 40px; border-radius: 12px; padding: 0 18px; }

/* 查询口径提示：单行小字，跟随控件宽度自然换行，不占位挤压输入框 */
.query-note {
  margin: 12px 0 0; font-size: 12px; line-height: 1.6; color: var(--text-3);
}

.verify-hint { gap: 14px; margin-top: 20px; }
/* 四格版本（爱心公示页：金额 / 笔数 / 物资 / 历年合计） */
.verify-hint.is-4 { grid-template-columns: repeat(4, 1fr); }
.verify-hint.is-4 .vh-item b { font-size: 22px; }
.vh-item {
  border: 0; border-radius: 16px; background: var(--surface-3);
  padding: 18px 8px;
}
.vh-item b { font-size: 26px; letter-spacing: -.8px; color: var(--text); }
.vh-item span { font-size: 12.5px; color: var(--text-3); }

@media (max-width: 760px) {
  .query-panel { padding: 18px; }
  .query-panel .query-row .select { flex: 1 1 100% !important; }
  .query-sub { gap: 10px; }
  .query-sub .qs-field { flex: 1 1 calc(50% - 5px); min-width: 0; }
  /* 确认筛选占满一行、置于两个下拉之下：窄屏下"选完点哪里"要一眼可见 */
  .query-sub .qs-apply { flex: 1 1 100%; justify-content: center; }
  .query-sub .qs-reset { flex: 1 1 100%; justify-content: center; }
  .verify-hint.is-4 { grid-template-columns: repeat(2, 1fr); }
  .verify-hint.is-4 .vh-item b { font-size: 19px; }
  .result-card .rc-head { flex-wrap: wrap; gap: 14px; padding: 18px 18px 16px; }
  .result-card .rc-avatar { flex: 0 0 56px; width: 56px; height: 56px; box-shadow: 0 0 0 3px var(--surface-3); }
  .result-card .rc-name { font-size: 18.5px; }
  .result-card .rc-go { flex: 1 1 100%; justify-content: center; }
  .result-card .rc-stats { grid-template-columns: repeat(2, 1fr); }
  .result-card .rc-stat { padding: 14px 16px; }
  .result-card .rc-stat:nth-child(3) { border-left: 0; }
  .result-card .rc-stat:nth-child(n+3) { border-top: 1px solid var(--border); }
  .result-card .rc-stat b { font-size: 23px; }
  .result-card .rc-sec { flex-direction: column; gap: 8px; padding: 13px 18px; }
  .result-card .rc-sec-t { flex: 0 0 auto; padding-top: 0; }
}

/* 核验结果小节标题行：手机上允许换行，避免「核验结果」被挤成两行 */
.verify-result-bar { display: flex; align-items: center; gap: 9px; margin-bottom: 13px; flex-wrap: wrap; }
.verify-result-bar > strong { font-size: 15px; white-space: nowrap; }
.verify-result-bar .vrb-hint { margin-left: auto; font-size: 12px; color: var(--text-3); }
@media (max-width: 640px) {
  .verify-result-bar .vrb-hint { margin-left: 0; flex: 1 1 100%; }
}

/* ==========================================================================
   43. 「保存档案到本地」打印文档（Apple / 小米 基调的公文版式）
   屏幕上完全不出现，只在打印 / 另存为 PDF 时接管整页。
   为什么另起一版：屏幕档案头是深蓝底、表格是手机卡化样式，
   A4 宽度（≈720px）会落进 860px 断点，打印出来表格被压成一列一列的长条，
   一本证书占掉半页纸。这里按公文口径重排，并把证书照片带进表格。
   ========================================================================== */
.archive-doc { display: none; }

@media print {
  /* 打印时整页换成档案文档，屏幕版一律不输出 */
  .archive-screen { display: none !important; }
  .archive-doc { display: block !important; }

  .archive-doc {
    font-family: var(--font);
    color: #111; font-size: 10.5pt; line-height: 1.62;
    -webkit-print-color-adjust: exact; print-color-adjust: exact;
  }

  /* ---------- 抬头 ---------- */
  .archive-doc .ad-head { text-align: center; border-bottom: 1.4pt solid #111; padding-bottom: 8pt; margin-bottom: 12pt; }
  .archive-doc .ad-team { font-size: 10pt; letter-spacing: 4px; color: #333; }
  .archive-doc .ad-head h1 { font-size: 19pt; font-weight: 700; letter-spacing: 6px; margin: 5pt 0 4pt; }
  .archive-doc .ad-sub { font-size: 8.5pt; color: #555; }

  /* ---------- 分节 ---------- */
  .archive-doc .ad-sec { margin-bottom: 11pt; break-inside: avoid; page-break-inside: avoid; }
  .archive-doc .ad-sec-last { break-inside: auto; }
  .archive-doc .ad-h2 {
    font-size: 11pt; font-weight: 700; letter-spacing: 1px;
    display: flex; align-items: center; gap: 6pt;
    border-bottom: .6pt solid #C9C9CE; padding-bottom: 4pt; margin-bottom: 7pt;
  }
  .archive-doc .ad-h2 i {
    font-style: normal; font-size: 9pt; font-weight: 700;
    width: 13pt; height: 13pt; line-height: 13pt; text-align: center;
    background: #111; color: #fff; border-radius: 50%;
  }

  /* ---------- 身份卡 ---------- */
  .archive-doc .ad-idcard {
    display: flex; align-items: center; gap: 12pt;
    border: .6pt solid var(--border); border-radius: 6pt;
    padding: 9pt 11pt; margin-bottom: 7pt; background: var(--surface-2);
  }
  .archive-doc .ad-avatar { width: 20mm; height: 20mm; border-radius: 50%; object-fit: cover; }
  .archive-doc .ad-name { font-size: 15pt; font-weight: 700; letter-spacing: 2px; }
  .archive-doc .ad-nick { font-size: 9.5pt; font-weight: 400; color: #666; letter-spacing: 0; margin-left: 6pt; }
  .archive-doc .ad-idmeta { font-size: 9pt; color: #555; margin-top: 3pt; }
  .archive-doc .ad-idmeta-more { font-size: 8.5pt; color: #666; }

  /* ---------- 键值表 ---------- */
  .archive-doc .ad-kv { width: 100%; border-collapse: collapse; }
  .archive-doc .ad-kv th,
  .archive-doc .ad-kv td { border: .6pt solid var(--border); padding: 4.5pt 7pt; font-size: 9.5pt; text-align: left; }
  .archive-doc .ad-kv th { width: 15%; background: var(--surface-3); color: #444; font-weight: 600; white-space: nowrap; }
  .archive-doc .ad-kv td { width: 18.33%; }
  .archive-doc .ad-kv b { font-weight: 700; }
  .archive-doc .ad-kv-skill th { width: 18%; }

  /* ---------- 数据四宫格 ---------- */
  .archive-doc .ad-figures { display: flex; border: .6pt solid var(--border); border-radius: 6pt; overflow: hidden; }
  .archive-doc .ad-fig { flex: 1 1 25%; padding: 8pt 10pt; background: var(--surface-2); }
  .archive-doc .ad-fig + .ad-fig { border-left: .6pt solid #E1E1E6; }
  .archive-doc .ad-fig b { display: block; font-size: 16pt; font-weight: 700; letter-spacing: -.5pt; font-variant-numeric: tabular-nums; }
  .archive-doc .ad-fig span { display: block; font-size: 8.5pt; color: #666; margin-top: 1pt; }

  .archive-doc .ad-lines { margin-top: 6pt; font-size: 9.5pt; display: flex; gap: 10pt; }
  .archive-doc .ad-lines-t { flex: 0 0 auto; color: #666; }

  /* ---------- 数据表 ---------- */
  .archive-doc .ad-table { width: 100%; border-collapse: collapse; }
  .archive-doc .ad-table th,
  .archive-doc .ad-table td { border: .6pt solid var(--border); padding: 4.5pt 7pt; font-size: 9.5pt; text-align: left; vertical-align: middle; }
  .archive-doc .ad-table thead th { background: var(--surface-3); color: #333; font-weight: 700; }
  .archive-doc .ad-table tbody tr { break-inside: avoid; page-break-inside: avoid; }
  .archive-doc .ad-table .strong { font-weight: 700; }
  .archive-doc .ad-table-cert td { height: 20mm; }

  /* 证书照片：A4 表格里按固定高度收敛，避免长图把行撑爆 */
  .archive-doc .ad-photo-cell { text-align: center !important; padding: 4pt !important; }
  .archive-doc .ad-photo-cell img { max-width: 68px; max-height: 68px; width: auto; height: auto; margin: 0 auto; border-radius: 3pt; }
  .archive-doc .ad-nophoto { font-size: 8pt; color: #999; }

  /* 状态角标：只用极浅底色，省墨且黑白打印也能分辨字重 */
  .archive-doc .ad-state { display: inline-block; padding: 1pt 6pt; border-radius: 999px; font-size: 8.5pt; font-weight: 600; }
  .archive-doc .ad-state--ok     { background: #E8F5EC; color: #0B6B36; }
  .archive-doc .ad-state--warn   { background: #FDF3E0; color: #8A5600; }
  .archive-doc .ad-state--danger { background: #FBEAEA; color: #9B2828; }

  .archive-doc .ad-empty { font-size: 9.5pt; color: #888; margin: 0; }

  /* ---------- 说明与签章 ---------- */
  .archive-doc .ad-notes { margin: 0; padding-left: 16pt; font-size: 9pt; color: #444; }
  .archive-doc .ad-notes li { margin-bottom: 2pt; }
  .archive-doc .ad-sign { display: flex; gap: 24pt; margin-top: 16pt; }
  .archive-doc .ad-sign-box {
    flex: 1 1 50%; border-bottom: .6pt solid #111;
    height: 34pt; display: flex; align-items: flex-end; padding-bottom: 3pt;
  }
  .archive-doc .ad-sign-box span { font-size: 9pt; color: #666; }

  .archive-doc .ad-foot {
    margin-top: 14pt; padding-top: 6pt; border-top: .6pt solid #C9C9CE;
    font-size: 8pt; color: #777; text-align: center;
  }
}

@media print {
  /* 列宽按百分比给，必须 fixed 才会严格生效，否则浏览器按内容重算，
     长中文列（发证机构）会被挤成一字一列 */
  .archive-doc .ad-table-cert { table-layout: fixed; }
  .archive-doc .ad-table-cert th,
  .archive-doc .ad-table-cert td { overflow-wrap: break-word; word-break: normal; }
  .archive-doc .ad-kv { table-layout: fixed; }
}
@media print {
  /* 状态角标不允许断行，「已过期」被折成两行会很难读 */
  .archive-doc .ad-state { white-space: nowrap; }
  /* 打印时隐藏交互态留下的焦点描边与悬浮位移 */
  .archive-doc .ad-table tbody tr:hover { background: transparent; }
}
@media print {
  /* 状态角标不允许断行，「已过期」被折成两行会很难读 */
  .archive-doc .ad-state { white-space: nowrap; }
  /* 打印时隐藏交互态留下的焦点描边与悬浮位移 */
  .archive-doc .ad-table tbody tr:hover { background: transparent; }
}

/* ==========================================================================
   44. 前台整体基调收口（苹果优先、小米其次）
   为什么要单独收一遍：前 43 节是逐需求叠加出来的，各页的留白、字号、
   分隔方式不一致。这一节只做「统一的排版逻辑」，不引入新组件：
     · 层级靠字号 + 留白，不靠描边和阴影堆叠（苹果）
     · 数字一律 tabular-nums，右对齐不跳动（小米的参数表习惯）
     · 分隔线只用 1px 极浅灰，避免多层描边互相打架
   所有规则都放在样式表最末，确保能压过前面的同名规则；
   各断点的覆写紧跟其后，避免被前面的媒体查询反向覆盖。
   ========================================================================== */

/* ---------- 区块节奏：更大的上下留白 ---------- */
.public-body .section { padding: 68px 20px; }
.public-body .section-head { margin-bottom: 40px; }
.public-body .section-head h2 {
  font-size: 31px; font-weight: 750; letter-spacing: -.7px; line-height: 1.25;
}
.public-body .section-head p { font-size: 15px; line-height: 1.75; max-width: 660px; }
.public-body .section-head .kicker { letter-spacing: 1.8px; margin-bottom: 10px; }

/* ---------- 首屏：字号拉大、统计区改成极浅分隔线网格 ---------- */
.hero-inner { padding: 76px 20px 84px; gap: 52px; }
.hero h1 {
  font-size: 46px; font-weight: 750; letter-spacing: -1.2px; line-height: 1.14;
  margin-bottom: 20px;
}
.hero-lead { font-size: 16px; line-height: 1.8; margin-bottom: 30px; }
.hero-actions .btn { padding: 13px 26px; font-size: 15px; border-radius: 999px; }

/* 统计区：去掉半透明方块，改用 1px 极浅分隔线做网格。
   方块叠方块会显廉价，细线网格更接近苹果的「参数区」做法。 */
.hero-stats {
  display: grid; grid-template-columns: repeat(2, 1fr); gap: 0;
  border-top: 1px solid rgba(255, 255, 255, .16);
}
.hero-stat {
  background: transparent; border: 0; border-radius: 0; backdrop-filter: none;
  padding: 20px 24px 22px 0;
  border-bottom: 1px solid rgba(255, 255, 255, .16);
}
.hero-stat:nth-child(odd) {
  padding-right: 24px; border-right: 1px solid rgba(255, 255, 255, .16);
}
.hero-stat:nth-child(even) { padding-left: 24px; }
/* 奇数个统计项时，最后一个独占整行，不留半格空洞 */
.hero-stat:last-child:nth-child(odd) {
  grid-column: 1 / -1; border-right: 0; padding-right: 0;
}
.hero-stat .hs-val {
  font-size: 40px; font-weight: 700; letter-spacing: -1.6px; line-height: 1.1;
  font-variant-numeric: tabular-nums;
}
.hero-stat .hs-val small {
  font-size: 14px; font-weight: 600; color: var(--side-text-dim); margin-left: 5px; letter-spacing: 0;
}
.hero-stat .hs-label { font-size: 13px; color: var(--side-text-dim); margin-top: 5px; }

/* ---------- 系统核心能力：8 张卡改两列，卡片内留白加倍 ---------- */
.feature-grid-8 { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px; }
.feature-grid-8 .feature { padding: 28px 30px 30px; border-radius: 20px; }
.feature-grid-8 .feature .f-icon {
  width: 42px; height: 42px; border-radius: 13px; margin-bottom: 16px;
}
.feature-grid-8 .feature .f-icon svg { width: 23px; height: 23px; }
.feature-grid-8 .feature h3 {
  font-size: 17.5px; font-weight: 700; letter-spacing: -.3px; margin-bottom: 9px;
}
.feature-grid-8 .feature p { font-size: 14px; line-height: 1.85; color: #6B7280; }

/* ---------- 台账统计卡：取消色条与图标底块，只留数字说话 ---------- */
#stats .stat {
  border-radius: 20px; box-shadow: none; background: var(--surface-2);
  border-color: var(--border); padding: 26px 26px 24px;
}
#stats .stat::after { display: none; }
#stats .stat .stat-icon {
  width: 34px; height: 34px; border-radius: 11px; margin-bottom: 14px;
}
#stats .stat .stat-label { font-size: 13px; color: var(--text-3); }
#stats .stat .stat-value {
  font-size: 38px; font-weight: 700; letter-spacing: -1.4px; line-height: 1.12;
  font-variant-numeric: tabular-nums; margin: 6px 0 0; color: var(--text);
}
#stats .stat .stat-value small {
  font-size: 14px; font-weight: 600; color: var(--text-3); letter-spacing: 0; margin-left: 4px;
}
#stats .stat .stat-foot { font-size: 12.5px; color: var(--text-3); margin-top: 6px; }
#stats .stat-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 16px; }

/* 台账内的卡片：统一 20px 圆角 + 极浅描边，取消默认阴影 */
#stats .card { border-radius: 20px; border-color: var(--border); box-shadow: none; }
#stats .card .card-head { padding: 20px 24px 16px; }
#stats .card .card-body { padding: 18px 24px 22px; }
/* 进度条改成更细的胶囊，颜色收敛为单一品牌色 */
#stats .bar { height: 7px; border-radius: 999px; background: var(--surface-3); }
#stats .bar > i { border-radius: 999px; }

/* ---------- 任务态势卡：圆角与描边统一到同一套 ---------- */
.period-card { border-radius: 20px; box-shadow: none; border-color: var(--border); }
.period-card.is-feature { box-shadow: none; border-color: var(--brand); }
.period-card .pcm b { font-size: 24px; letter-spacing: -.6px; }
.period-note { font-size: 12.5px; color: var(--text-3); }

/* ---------- 公告卡：统一圆角 ---------- */
.notice-card { border-radius: 20px; border-color: var(--border); }

@media (max-width: 1100px) {
  .feature-grid-8 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  #stats .stat-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 900px) {
  .public-body .section { padding: 48px 18px; }
  .public-body .section-head { margin-bottom: 28px; }
  .public-body .section-head h2 { font-size: 24px; letter-spacing: -.4px; }
  .public-body .section-head p { font-size: 14px; }
  .hero-inner { padding: 46px 18px 52px; gap: 32px; }
  .hero h1 { font-size: 32px; letter-spacing: -.8px; }
  .hero-lead { font-size: 14.5px; margin-bottom: 24px; }
  .hero-actions .btn { flex: 1 1 100%; justify-content: center; }
  .hero-stat { padding: 16px 18px 18px 0; }
  .hero-stat:nth-child(odd) { padding-right: 18px; }
  .hero-stat:nth-child(even) { padding-left: 18px; }
  .hero-stat .hs-val { font-size: 28px; letter-spacing: -1px; }
  .hero-stat .hs-label { font-size: 12px; }
  .feature-grid-8 { grid-template-columns: minmax(0, 1fr); gap: 12px; }
  .feature-grid-8 .feature { padding: 20px 20px 22px; }
  .feature-grid-8 .feature h3 { font-size: 16px; }
  .feature-grid-8 .feature p { font-size: 13.5px; line-height: 1.8; }
  #stats .stat { padding: 18px 18px 16px; }
  #stats .stat .stat-value { font-size: 28px; letter-spacing: -.8px; }
  #stats .card .card-head { padding: 16px 18px 12px; }
  #stats .card .card-body { padding: 14px 18px 18px; }
}

/* --------------------------------------------------------------------------
   40. 网站功能介绍页（features.php）
   -------------------------------------------------------------------------- */

/* ==========================================================================
   网站功能介绍页（features.php）—— 「产品规格书」版式（v2.15 需求 4）
   --------------------------------------------------------------------------
   为什么要重排：旧版是「13 个交替底色的整幅区块 + 每块一组等宽卡片」。
   整页读下来最大的问题是**没有横向坐标** —— 读到第 8 块时早忘了自己在
   第几块、还有几块、当初想找的是哪一块。所以新版只做一件结构性的事：

       把「13 块纵向长卷」改成「左侧粘性索引 + 右侧规格条目」。

   读的人随时知道自己在哪、能跳哪、还剩多少。条目本身反而做得更素：
   只留「行号 + 标题 + 标签 + 一句说明」，靠字号与 1px 分隔线分层级。

   三条实现约定：
     · 颜色一律走 CSS 变量（--surface / --border / --text / --brand），
       不自带硬编码色 —— 本站有 10 套皮肤 + 浅色/深色主题，写死就翻车；
     · 断点从大到小写。宽屏是「索引 + 内容」两栏，1100px 以下索引变成
       页面顶部一条可横滑的快跳条（**不折叠成下拉菜单**：手机上要多点
       一次才能看到目录，等于没有目录）；
     · 粘性索引的 top 必须避开站点头部（.site-head 是 top:0 的 sticky），
       否则横滑条会钻到导航底下去。
   ========================================================================== */

.feat-hero .hero-inner { align-items: start; gap: 52px; }
.feat-hero .hero-lead { max-width: 620px; }

/* ---- 首屏右侧：模块索引 ---- */
.feat-index { display: grid; gap: 6px; align-content: start; max-height: 396px; overflow-y: auto; padding-right: 2px; }
.feat-index::-webkit-scrollbar { width: 6px; }
.feat-index::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, .22); border-radius: 3px; }
.fx-item {
  display: flex; align-items: center; gap: 10px; padding: 9px 12px; border-radius: 10px;
  background: rgba(255, 255, 255, .06); border: 1px solid rgba(255, 255, 255, .11);
  color: #E3EEFF; text-decoration: none; font-size: 13px;
  transition: background .15s, border-color .15s, transform .15s;
}
.fx-item:hover { background: rgba(255, 255, 255, .15); border-color: rgba(255, 255, 255, .3); transform: translateX(2px); }
.fx-no { flex: 0 0 auto; font-size: 11px; font-weight: 800; color: #9FC0EA; font-variant-numeric: tabular-nums; }
.fx-t { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fx-n { flex: 0 0 auto; font-size: 11px; color: var(--side-text-dim); }
.fx-new {
  flex: 0 0 auto; font-size: 10px; font-weight: 800; letter-spacing: .5px; line-height: 1.5;
  background: linear-gradient(135deg, #FF7A45, #FF4D4F); color: #fff; border-radius: 6px; padding: 0 5px;
}

/* ---- 首屏下方：本次更新条 ---- */
.feat-upd { background: var(--surface); border-bottom: 1px solid var(--border); }
.feat-upd-in { max-width: 1180px; margin: 0 auto; padding: 16px 20px; display: flex; align-items: center; gap: 11px; flex-wrap: wrap; }
.feat-upd-in b { font-size: 12.5px; color: var(--brand); letter-spacing: .4px; white-space: nowrap; }
.fu-chip {
  display: inline-flex; align-items: center; gap: 7px; font-size: 12.5px; text-decoration: none;
  color: var(--text-2); background: var(--brand-lighter); border: 1px solid var(--border);
  border-radius: 999px; padding: 4px 13px; transition: border-color .15s, color .15s;
}
.fu-chip:hover { border-color: var(--brand); color: var(--brand); }
.fu-chip::before { content: ''; width: 5px; height: 5px; border-radius: 50%; background: var(--brand); flex: 0 0 auto; }

/* ---- 主体栅格 ---- */
.feat-wrap {
  max-width: 1180px; margin: 0 auto; padding: 44px 20px 70px;
  display: grid; grid-template-columns: 232px minmax(0, 1fr); gap: 42px; align-items: start;
}

/* ---- 左侧粘性索引 ----
   top 走 --feat-top（由本页脚本按**实际量到的**站点头部高度写入）。
   为什么不写死像素：头部高度取决于队伍名长短与是否换行，本机量到
   桌面 65px / 窄屏 61px，换个队名就会变。写死 54px 的结果是窄屏上
   索引条被头部盖住 7 像素（z-index 头部 50 > 索引 20，头部盖在上面），
   顶部那一排 chip 的上半截看不见 —— 探针里专门留了 railUnderHead 这一项盯它。 */
.feat-rail { position: sticky; top: calc(var(--feat-top, 62px) + 24px); display: grid; gap: 1px; max-height: calc(100vh - 120px); overflow-y: auto; }
.fr-hd { font-size: 11px; font-weight: 700; letter-spacing: 1.4px; text-transform: uppercase; color: var(--text-3); padding: 0 10px 9px; }
.fr-link {
  display: flex; align-items: center; gap: 9px; padding: 7px 10px; border-radius: 9px;
  text-decoration: none; color: var(--text-3); font-size: 13px; line-height: 1.45;
  border-left: 2px solid transparent; transition: background .15s, color .15s;
}
.fr-link:hover { color: var(--text); background: var(--brand-lighter); }
.fr-link.on { color: var(--brand); font-weight: 700; background: var(--brand-lighter); border-left-color: var(--brand); }
.fr-no { flex: 0 0 auto; font-size: 10.5px; font-weight: 800; font-variant-numeric: tabular-nums; opacity: .75; }
.fr-t { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fr-n { flex: 0 0 auto; font-size: 10.5px; opacity: .7; }
.fr-top { margin-top: 11px; padding-top: 11px; border-top: 1px solid var(--border); }

/* ---- 右侧：模块 ---- */
.feat-flow { display: grid; gap: 42px; }
.feat-sec { scroll-margin-top: calc(var(--feat-top, 62px) + 30px); position: relative; }
/* 水印大号编号：0.05 透明度只为给整块一个"编号感"，不参与阅读 */
.fs-num {
  position: absolute; right: -4px; top: -18px; font-size: 88px; font-weight: 800; letter-spacing: -5px;
  line-height: 1; color: var(--text); opacity: .05; font-variant-numeric: tabular-nums;
  pointer-events: none; user-select: none;
}
.fs-head { display: flex; align-items: flex-start; gap: 14px; padding-bottom: 15px; border-bottom: 2px solid var(--border); position: relative; }
.fs-head::after { content: ''; position: absolute; left: 0; bottom: -2px; width: 86px; height: 2px; background: var(--brand); border-radius: 1px; }
.fs-ic {
  flex: 0 0 46px; width: 46px; height: 46px; border-radius: 13px; position: relative; z-index: 1;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--brand); color: #fff; box-shadow: 0 6px 16px rgba(29, 95, 208, .26);
}
.fs-meta { flex: 1 1 auto; min-width: 0; position: relative; z-index: 1; }
.fs-kick { font-size: 10.5px; font-weight: 700; letter-spacing: 1.4px; text-transform: uppercase; color: var(--brand); margin-bottom: 5px; }
.fs-meta h2 { font-size: 22px; letter-spacing: -.4px; margin-bottom: 7px; }
.fs-meta p { font-size: 13.5px; line-height: 1.8; color: var(--text-3); margin: 0; max-width: 640px; }
.fs-bdg { flex: 0 0 auto; display: grid; gap: 6px; justify-items: end; position: relative; z-index: 1; }
.fs-cnt { font-size: 11.5px; color: var(--text-3); background: var(--surface); border: 1px solid var(--border); border-radius: 999px; padding: 3px 11px; white-space: nowrap; }
.fs-new { font-size: 10.5px; font-weight: 800; letter-spacing: .5px; color: #fff; background: linear-gradient(135deg, #FF7A45, #FF4D4F); border-radius: 999px; padding: 2px 9px; }

/* ---- 规格条目：两列，行号 + 标题 + 标签 + 说明 ---- */
.fs-items { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 34px; margin-top: 4px; }
.fs-it { display: grid; grid-template-columns: 25px minmax(0, 1fr); gap: 0 6px; padding: 15px 0 16px; border-bottom: 1px solid var(--border); }
.si-n { font-size: 11px; font-weight: 800; color: var(--text-3); font-variant-numeric: tabular-nums; padding-top: 3px; transition: color .15s; }
.fs-it:hover .si-n { color: var(--brand); }
.si-t { font-size: 14.5px; font-weight: 700; color: var(--text); margin-bottom: 6px; display: flex; align-items: center; flex-wrap: wrap; gap: 6px; }
.si-tag { font-size: 10.5px; font-style: normal; font-weight: 700; background: var(--brand-lighter); color: var(--brand); border: 1px solid var(--border); border-radius: 999px; padding: 1px 8px; }
.si-d { font-size: 13px; line-height: 1.82; color: var(--text-3); }
.si-d b { color: var(--text-2); font-weight: 600; }

.feat-cta { display: flex; gap: 10px; flex-wrap: wrap; justify-content: center; }

/* ---- 1100px 以下：索引改成顶部横滑快跳条 ---- */
@media (max-width: 1100px) {
  .feat-wrap { grid-template-columns: minmax(0, 1fr); gap: 24px; padding-top: 18px; }
  .feat-rail {
    position: sticky; top: var(--feat-top, 62px); z-index: 20; max-height: none; overflow: visible;
    display: flex; gap: 7px; overflow-x: auto; padding: 9px 0;
    margin: 0 -20px; padding-left: 20px; padding-right: 20px;
    background: var(--surface); border-bottom: 1px solid var(--border);
    -webkit-overflow-scrolling: touch; scrollbar-width: none;
  }
  .feat-rail::-webkit-scrollbar { display: none; }
  .fr-hd, .fr-no, .fr-n, .fr-top { display: none; }
  .fr-link {
    flex: 0 0 auto; border: 1px solid var(--border); border-radius: 999px; padding: 5px 13px;
    border-left-width: 1px; white-space: nowrap; background: var(--surface); max-width: 62vw;
  }
  .fr-link.on { border-color: var(--brand); background: var(--brand-lighter); }
  .feat-sec { scroll-margin-top: calc(var(--feat-top, 62px) + 62px); }
  .fs-items { grid-template-columns: minmax(0, 1fr); }
  .fs-num { font-size: 66px; top: -10px; }
}
@media (max-width: 640px) {
  .feat-hero .hero-inner { gap: 30px; }
  .feat-index { max-height: none; }
  .feat-upd-in { padding: 13px 16px; gap: 8px; }
  .feat-wrap { padding-top: 14px; padding-bottom: 52px; }
  .fs-head { padding-bottom: 13px; gap: 11px; }
  .fs-ic { flex-basis: 40px; width: 40px; height: 40px; border-radius: 11px; }
  .fs-meta h2 { font-size: 18.5px; }
  .fs-meta p { font-size: 12.5px; }
  .fs-cnt { display: none; }
  .fs-it { padding: 13px 0 14px; grid-template-columns: 22px minmax(0, 1fr); }
  .si-t { font-size: 13.5px; }
  .si-d { font-size: 12.5px; }
  .feat-flow { gap: 32px; }
}

/* --------------------------------------------------------------------------
   41. 人员内部备注（admin/member_view.php）
   -------------------------------------------------------------------------- */

.note-list { display: flex; flex-direction: column; gap: 10px; }
.note-item {
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 12px 14px; background: var(--surface-2);
}
.note-item.is-pinned { border-color: var(--warn, #E8A33D); background: rgba(232,163,61,.06); }
.ni-head {
  display: flex; align-items: center; gap: 9px; flex-wrap: wrap;
  font-size: 12.5px; color: var(--text-3); margin-bottom: 7px;
}
.ni-who { font-weight: 700; color: var(--text-2); }
.ni-time { font-variant-numeric: tabular-nums; }
.ni-head .btn-group .btn { padding: 3px 9px; font-size: 12px; }
.ni-body { font-size: 13.5px; line-height: 1.85; color: var(--text); word-break: break-word; }
.ni-imgs { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
.ni-imgs a { display: block; border-radius: 8px; overflow: hidden; border: 1px solid var(--border); line-height: 0; }
.ni-imgs img { width: 96px; height: 96px; object-fit: cover; display: block; }
@media (max-width: 640px) {
  .ni-imgs img { width: 74px; height: 74px; }
}

/* --------------------------------------------------------------------------
   42. 晋级条件自查与链路（user/apply.php · admin/promote.php）
   -------------------------------------------------------------------------- */

/* 逐项核对行 */
.cond-row {
  display: flex; align-items: center; gap: 9px; flex-wrap: wrap;
  padding: 8px 11px; border-radius: 9px; font-size: 13px;
  border: 1px solid var(--border); background: var(--surface-2); margin-bottom: 7px;
}
.cond-row.is-ok { border-color: var(--ok-border); background: var(--ok-light); }
.cond-row.is-no { border-color: var(--warn-border); background: var(--warn-light, #FFF7E8); }
.cr-mark {
  flex: 0 0 18px; width: 18px; height: 18px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: 800; color: #fff; background: var(--danger);
}
.cond-row.is-ok .cr-mark { background: var(--ok); }
.cr-label { font-weight: 600; }
.cr-need { color: var(--text-3); font-size: 12.5px; }
.cr-have { margin-left: auto; font-weight: 600; font-variant-numeric: tabular-nums; }
.cond-row.is-no .cr-have { color: var(--danger); }
.cond-row.is-ok .cr-have { color: var(--ok); }

/* 晋级链路 */
.promo-chain { display: flex; flex-direction: column; gap: 9px; }
.pc-step {
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 11px 14px; background: var(--surface-2);
}
.pc-step.is-now { border-color: var(--brand); background: var(--brand-lighter); }
.pc-step.is-off { opacity: .62; }
.pcs-flow {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  font-size: 14px; font-weight: 700;
}
.pcs-arrow { color: var(--text-3); font-weight: 400; }
.pcs-tag {
  font-size: 10.5px; font-style: normal; font-weight: 700;
  background: var(--brand); color: #fff; border-radius: 9px; padding: 1px 8px;
}
.pcs-tag.is-off { background: var(--text-3); }
.pcs-cond { font-size: 12.5px; color: var(--text-3); line-height: 1.7; margin-top: 5px; }

@media (max-width: 640px) {
  .cr-need { display: none; }
  .cond-row { font-size: 12.5px; padding: 7px 10px; }
}

/* --------------------------------------------------------------------------
   43. 多选勾选栅格（任务报名资质限定等）
   -------------------------------------------------------------------------- */
.pick-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 7px; padding: 10px 12px; border: 1px solid var(--border);
  border-radius: var(--radius-sm); background: var(--surface-2);
  max-height: 220px; overflow-y: auto;
}
.pick-grid .check-item { padding: 6px 9px; font-size: 13px; }
@media (max-width: 640px) {
  .pick-grid { grid-template-columns: minmax(0, 1fr); max-height: 260px; }
}

/* --------------------------------------------------------------------------
   44. 顶栏「发起任务」主行动按钮（需求 #26）
   -------------------------------------------------------------------------- */
.topbar-new-mission {
  flex: 0 0 auto; display: inline-flex; align-items: center; gap: 6px;
  height: 34px; padding: 0 14px; border-radius: 9px;
  background: linear-gradient(135deg, var(--brand), #2E7BEA);
  color: #fff; text-decoration: none; font-size: 13.5px; font-weight: 600;
  box-shadow: 0 3px 10px rgba(29,95,208,.3); white-space: nowrap;
  transition: transform .15s, box-shadow .15s;
}
.topbar-new-mission:hover { transform: translateY(-1px); box-shadow: 0 5px 14px rgba(29,95,208,.38); color: #fff; }
@media (max-width: 640px) {
  .topbar-new-mission { padding: 0 10px; }
  .topbar-new-mission span { display: none; }
}

/* 照片选中后的预览态 */
.eq-photo.is-preview { border-style: solid; border-color: var(--brand); }
.eq-photo.is-preview img { width: 100%; height: 100%; object-fit: cover; }
.epa-count {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: 2px 4px; font-size: 10.5px; line-height: 1.4; text-align: center;
  background: rgba(15, 23, 42, .72); color: #fff;
}

/* --------------------------------------------------------------------------
   39. 地图大数据双大盘（需求 #69）
   -------------------------------------------------------------------------- */
.map-layout { display: grid; grid-template-columns: 264px 1fr; gap: 16px; align-items: start; }
@media (max-width: 980px) { .map-layout { grid-template-columns: 1fr; } }

.map-side { display: flex; flex-direction: column; gap: 16px; position: sticky; top: calc(var(--topbar-h) + 12px); }
@media (max-width: 980px) { .map-side { position: static; } }
.map-main { display: flex; flex-direction: column; gap: 16px; min-width: 0; }

.map-filter { display: flex; flex-direction: column; gap: 11px; }
.map-filter .field { margin: 0; }
.map-filter .field label { font-size: 12.5px; }
.map-filter-acts { display: flex; gap: 6px; margin-top: 4px; }

/* 图例：点一下开关一类，比勾选框更符合「看地图时随手调」的习惯 */
.map-legend { display: flex; flex-wrap: wrap; gap: 6px; }
.ml-item {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 4px 10px; cursor: pointer; font-family: inherit;
  font-size: 12px; border-radius: var(--radius-pill);
  border: 1px solid var(--border); background: var(--surface-2); color: var(--text-3);
  transition: all .15s;
}
.ml-item i { width: 9px; height: 9px; border-radius: 50%; flex: 0 0 9px; filter: grayscale(1); opacity: .45; }
.ml-item.is-on { border-color: var(--border-strong); background: var(--surface); color: var(--text); font-weight: 500; }
.ml-item.is-on i { filter: none; opacity: 1; }
.ml-item:hover { border-color: var(--brand); }
.ml-item.is-off { text-decoration: line-through; }
.map-legend-acts { display: flex; gap: 6px; margin-top: 9px; }

.map-points { display: flex; flex-direction: column; gap: 7px; }
.mp-item { display: flex; align-items: center; gap: 7px; font-size: 12.5px; }
.mp-dot { width: 9px; height: 9px; border-radius: 50%; flex: 0 0 9px; }
.mp-facility { background: var(--brand); }
.mp-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text); }
.mp-kind { flex: 0 0 auto; font-size: 11px; color: var(--text-3); }

/* 行政区分布条 */
.area-list { display: flex; flex-direction: column; gap: 12px; }
.area-row { display: flex; flex-direction: column; gap: 5px; }
.area-row.is-unknown { opacity: .68; }
.ar-head { display: flex; align-items: center; gap: 8px; font-size: 13px; }
.ar-name { font-weight: 600; color: var(--text); }
.ar-prov { font-size: 11.5px; color: var(--text-3); }
.ar-n { margin-left: auto; font-weight: 600; color: var(--text-2); font-variant-numeric: tabular-nums; }
.ar-bar {
  display: flex; height: 10px; border-radius: 5px; overflow: hidden;
  background: var(--surface-3); min-width: 2%;
}
.ar-seg { display: block; height: 100%; transition: width .2s; }
.ar-meta { display: flex; flex-wrap: wrap; gap: 12px; font-size: 11.5px; color: var(--text-3); }

.unknown-samples { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.us-item {
  padding: 2px 8px; font-size: 11.5px; border-radius: var(--radius-pill);
  background: var(--surface-3); color: var(--text-2); max-width: 220px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* 真实地图容器 */
.amap-box { width: 100%; height: 560px; border-radius: 0 0 var(--radius-sm) var(--radius-sm); }
.amap-box:fullscreen { height: 100vh !important; border-radius: 0; }
.amap-box:-webkit-full-screen { height: 100vh !important; }

.map-main .table tr.is-off { opacity: .6; }

/* 全屏按钮：贴在板块导航行右侧 */
.map-full-btn { margin-left: auto; align-self: center; }
/* 需求 #72：地图页的标签条与「全屏」按钮同一行。
   标签条能横向滚动（标签多时不换行），全屏按钮固定在右侧不参与滚动。 */
.map-tabs { display: flex; align-items: stretch; gap: 8px; }
.map-tabs .tabs { flex: 1 1 auto; min-width: 0; }

/* --------------------------------------------------------------------------
   40. 首页：时长榜、任务构成、累计口径说明（需求 #70）
   -------------------------------------------------------------------------- */

/* 年份切换条贴在榜单区顶部 */
.home-year-tabs { margin-bottom: 16px; }

/* 救援类任务的 chip：与「常规」在视觉上必须一眼可分，
   因为两者的时长系数不同（×1.5 vs ×1） */
.chip-danger { background: var(--danger-light); border-color: var(--danger-border); color: var(--danger-ink); }
.chip svg { margin-right: 4px; vertical-align: -1px; }

/* 累计口径：平台 + 历史 = 合计，用一个加号一个等号把关系摆明白 */
.ledger-split {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 4px 0 2px;
}
.ls-item {
  flex: 1 1 130px; min-width: 118px;
  padding: 12px 14px; border-radius: var(--radius-sm);
  border: 1px solid var(--border); background: var(--surface-2);
}
.ls-item.is-total {
  border-color: var(--brand-border); background: var(--brand-lighter);
}
.ls-label { font-size: 12px; color: var(--text-3); }
.ls-value {
  margin-top: 3px; font-size: 22px; font-weight: 800; letter-spacing: -.5px;
  color: var(--text); font-variant-numeric: tabular-nums;
}
.ls-item.is-total .ls-value { color: var(--brand-dark); }
.ls-value small { font-size: 11.5px; font-weight: 600; color: var(--text-3); margin-left: 3px; }
.ls-sub { margin-top: 2px; font-size: 11.5px; color: var(--text-3); font-variant-numeric: tabular-nums; }
.ls-plus, .ls-eq {
  flex: 0 0 auto; font-size: 18px; font-weight: 700; color: var(--text-3);
}
@media (max-width: 560px) {
  .ls-plus, .ls-eq { display: none; }
  .ls-item { flex: 1 1 100%; }
}

/* 指挥大盘：出勤人员构成 + 现场照片（需求 #70） */
.demo-bars { display: flex; flex-direction: column; gap: 11px; }
.db-row { display: flex; flex-direction: column; gap: 5px; }
.db-top {
  display: flex; align-items: center; justify-content: space-between;
  font-size: 12.5px; color: var(--text-2);
}
.db-top b { color: var(--text); font-variant-numeric: tabular-nums; }
.db-bar { height: 8px; border-radius: 4px; background: var(--surface-3); overflow: hidden; }
.db-bar i { display: block; height: 100%; border-radius: 4px; transition: width .4s; }

.role-chips { display: flex; flex-wrap: wrap; gap: 7px; }
.rc-item {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 4px 11px; border-radius: var(--radius-pill);
  border: 1px solid var(--border); background: var(--surface-2);
  font-size: 12.5px; color: var(--text-2);
}
.rc-item b { color: var(--brand-dark); font-variant-numeric: tabular-nums; }

.shot-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(132px, 1fr)); gap: 9px; }
.shot-item {
  display: block; position: relative; overflow: hidden;
  border-radius: var(--radius-xs); border: 1px solid var(--border);
  aspect-ratio: 4 / 3; background: var(--surface-3);
}
.shot-item img { width: 100%; height: 100%; object-fit: cover; display: block; }
.shot-item .si-cap {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: 4px 7px; font-size: 11px; line-height: 1.4;
  background: rgba(15, 23, 42, .66); color: #fff;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.shot-item:hover img { transform: scale(1.04); transition: transform .2s; }

/* ==========================================================================
   41. 入队指引（join_us.php）与新人引导入口（需求 #71 / #25）

   这一页是「招募落地页」，来访者多半是第一次接触队伍，
   所以视觉上比站内其它页更重（深色首屏 + 大字号），但**一个组件都没有新造**：
   首屏沿用首页 hero 的渐变与玻璃拟态按钮，内容区沿用 section / card，
   加群区沿用卡片风格。新皮肤上线时这一页会自动跟着变，不需要单独适配。
   ========================================================================== */

/* ---- 首屏 ---- */
.join-hero {
  background: var(--brand-grad-deep);
  color: var(--head-text);
  padding: 56px 20px 50px;
  position: relative;
  overflow: hidden;
}
.join-hero::after {
  /* 右上角一团柔光，避免大面积深色显得死板 */
  content: '';
  position: absolute; top: -160px; right: -120px;
  width: 460px; height: 460px; border-radius: 50%;
  background: radial-gradient(circle, rgba(92, 160, 255, .38), transparent 68%);
  pointer-events: none;
}
.join-hero-inner {
  max-width: 1180px; margin: 0 auto;
  display: grid; grid-template-columns: 1.15fr .85fr;
  gap: 44px; align-items: center;
  position: relative; z-index: 1;
}
.join-eyebrow {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 5px 13px; border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, .12);
  border: 1px solid rgba(255, 255, 255, .2);
  font-size: 12.5px; letter-spacing: .6px; margin-bottom: 18px;
}
.join-eyebrow i {
  width: 7px; height: 7px; border-radius: 50%;
  background: #6BE39A; box-shadow: 0 0 0 3px rgba(107, 227, 154, .25);
}
.join-hero h1 {
  font-size: 42px; line-height: 1.2; letter-spacing: -.5px;
  color: var(--fixed-white); margin-bottom: 16px;
}
.join-hero h1 em { font-style: normal; color: #7FB6FF; }
.join-lead {
  font-size: 15.5px; color: #C6DAF7; line-height: 1.85;
  max-width: 560px; margin-bottom: 26px;
}
.join-hero-acts { display: flex; gap: 12px; flex-wrap: wrap; }
.join-hero-acts .btn { padding: 12px 24px; font-size: 15px; border-radius: var(--radius-sm); }

.jh-facts { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; }
.jh-facts .jf {
  background: rgba(255, 255, 255, .1);
  border: 1px solid rgba(255, 255, 255, .17);
  border-radius: var(--radius);
  padding: 16px 18px;
  backdrop-filter: blur(6px);
}
.jh-facts .jf-val {
  font-size: 28px; font-weight: 800; color: var(--fixed-white);
  line-height: 1.2; font-variant-numeric: tabular-nums;
}
.jh-facts .jf-val small { font-size: 13px; font-weight: 600; color: var(--side-text-dim); margin-left: 3px; }
.jh-facts .jf-label { font-size: 12.5px; color: var(--side-text-dim); margin-top: 2px; }
.jh-note {
  margin-top: 12px; padding: 11px 14px;
  border-radius: var(--radius-sm);
  background: rgba(255, 255, 255, .12);
  border: 1px solid rgba(255, 255, 255, .2);
  font-size: 12.5px; line-height: 1.7; color: #DCE9FB;
  display: flex; gap: 8px; align-items: flex-start;
}
.jh-note svg { flex: 0 0 auto; margin-top: 2px; }

/* 首页引导条与其它 section 之间不需要额外留白，靠 section 自带 padding */
.section-tight { padding-top: 34px; padding-bottom: 34px; }

/* ---- 新人进度卡（仅未认证志愿者可见） ---- */
.join-progress .jp-title { font-size: 16px; margin-bottom: 14px; }
.jp-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 12px; }
.jp-item {
  display: flex; align-items: center; gap: 12px;
  padding: 14px 16px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  text-decoration: none; color: inherit;
  transition: border-color .18s, background .18s, transform .18s;
}
.jp-item:hover { border-color: var(--brand-border); background: var(--brand-lighter); transform: translateY(-2px); }
.jp-item .jp-no {
  flex: 0 0 auto; width: 28px; height: 28px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--brand); color: var(--fixed-white);
  font-size: 13.5px; font-weight: 700;
}
.jp-item b { display: block; font-size: 14.5px; margin-bottom: 3px; }
.jp-item span { font-size: 12.5px; color: var(--text-3); line-height: 1.65; }
.jp-item > svg { margin-left: auto; color: var(--text-3); flex: 0 0 auto; }

/* ---- 入队流程（编号步骤） ---- */
.jsteps {
  list-style: none; margin: 0; padding: 0;
  display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 16px;
  counter-reset: jstep;
}
.jstep {
  display: flex; gap: 14px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 20px 20px 20px 18px;
  position: relative;
  transition: transform .2s, box-shadow .2s, border-color .2s;
}
.jstep:hover { transform: translateY(-3px); box-shadow: var(--shadow); border-color: var(--brand-border); }
.jstep-no {
  flex: 0 0 auto;
  width: 34px; height: 34px; border-radius: 11px;
  display: grid; place-items: center;
  background: var(--brand-light); color: var(--brand-ink);
  font-size: 15px; font-weight: 800;
  font-variant-numeric: tabular-nums;
}
.jstep-body h3 { font-size: 15.5px; margin-bottom: 6px; }
.jstep-body p { font-size: 13.5px; color: var(--text-3); line-height: 1.75; margin: 0; }

/* ---- 需要准备的资料 ---- */
.jdocs { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 12px; }
.jdoc {
  display: flex; gap: 11px; align-items: flex-start;
  padding: 15px 17px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.jdoc .jdoc-ico { color: var(--ok); flex: 0 0 auto; margin-top: 1px; }
.jdoc b { display: block; font-size: 14px; margin-bottom: 3px; }
.jdoc span { font-size: 12.5px; color: var(--text-3); line-height: 1.7; }

.jtip {
  margin-top: 18px;
  display: flex; gap: 8px; align-items: flex-start;
  padding: 12px 15px;
  border-radius: var(--radius-sm);
  background: var(--brand-lighter);
  border: 1px solid var(--brand-border);
  color: var(--text-2);
  font-size: 12.5px; line-height: 1.75;
}
.jtip svg { flex: 0 0 auto; margin-top: 2px; color: var(--brand); }
.jtip-center { justify-content: center; max-width: 720px; margin-left: auto; margin-right: auto; }

/* ---- 培训与认证 ---- */
.jtrains { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 14px; }
.jtrain {
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 4px solid var(--brand);
  border-radius: var(--radius-sm);
  padding: 17px 19px;
}
.jtrain h3 { font-size: 15px; margin-bottom: 7px; }
.jtrain p { font-size: 13.5px; color: var(--text-3); line-height: 1.75; margin: 0; }

/* ---- 常见问题（原生 details，零 JS 也能用） ---- */
.jfaq { max-width: 860px; margin: 0 auto; display: flex; flex-direction: column; gap: 10px; }
.jfaq-item {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  overflow: hidden;
}
.jfaq-item[open] { border-color: var(--brand-border); box-shadow: var(--shadow-sm); }
.jfaq-item summary {
  list-style: none; cursor: pointer;
  display: flex; align-items: center; gap: 12px;
  padding: 15px 18px;
  font-size: 14.5px; font-weight: 600; color: var(--text);
}
.jfaq-item summary::-webkit-details-marker { display: none; }
.jfaq-q { flex: 1 1 auto; }
.jfaq-arrow { flex: 0 0 auto; color: var(--text-3); transition: transform .2s; display: flex; }
.jfaq-item[open] .jfaq-arrow { transform: rotate(180deg); color: var(--brand); }
.jfaq-a {
  padding: 0 18px 16px 18px;
  font-size: 13.5px; color: var(--text-2); line-height: 1.85;
  border-top: 1px dashed var(--border);
  padding-top: 13px;
}

/* ---- 加群与咨询 ---- */
.jqr { display: grid; grid-template-columns: 340px 1fr; gap: 22px; align-items: start; }
.jqr-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 22px;
  text-align: center;
  box-shadow: var(--shadow-sm);
}
.jqr-img {
  width: 100%; max-width: 260px; aspect-ratio: 1 / 1;
  object-fit: contain; display: block; margin: 0 auto;
  border-radius: var(--radius-sm); border: 1px solid var(--border);
  background: var(--fixed-white); cursor: zoom-in;
}
.jqr-hint { font-size: 12.5px; color: var(--text-3); margin-top: 10px; }
.jqr-empty {
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  padding: 28px 12px;
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-sm);
  color: var(--text-3);
  background: var(--surface-2);
}
.jqr-empty svg { color: var(--border-strong); }
.jqr-empty b { font-size: 14px; color: var(--text-2); }
.jqr-empty span { font-size: 12px; line-height: 1.7; }
.jqr-acts { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; margin-top: 16px; }

.jqr-side h3 { font-size: 17px; margin-bottom: 14px; }
.jqr-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 11px; }
.jqr-list li { display: flex; gap: 9px; align-items: flex-start; font-size: 14px; color: var(--text-2); line-height: 1.75; }
.jqr-list li svg { flex: 0 0 auto; margin-top: 4px; color: var(--ok); }
.jqr-list b { color: var(--text); }
.jqr-foot {
  margin-top: 18px; padding-top: 14px;
  border-top: 1px dashed var(--border);
  font-size: 12.5px; color: var(--text-3); line-height: 1.8;
  display: flex; gap: 8px; align-items: flex-start;
}
.jqr-foot svg { flex: 0 0 auto; margin-top: 2px; color: var(--brand); }

/* ---- 新人引导入口（首页 / 个人中心，需求 #71） ---- */
.join-guide-wrap { max-width: 1180px; margin: 0 auto; padding: 26px 20px 0; }
.join-guide {
  display: flex; gap: 16px; align-items: center; flex-wrap: wrap;
  background: var(--brand-lighter);
  border: 1px solid var(--brand-border);
  border-radius: var(--radius);
  padding: 18px 20px;
}
.join-guide-me { background: var(--brand-lighter); border-color: var(--brand-border); }
.join-guide .jg-main { display: flex; gap: 13px; align-items: flex-start; flex: 1 1 460px; min-width: 0; }
.join-guide .jg-ico {
  flex: 0 0 auto;
  width: 42px; height: 42px; border-radius: 13px;
  display: grid; place-items: center;
  background: var(--brand); color: var(--fixed-white);
}
.join-guide .jg-title { font-size: 15.5px; margin-bottom: 5px; color: var(--brand-ink); }
.join-guide .jg-desc { font-size: 13px; color: var(--text-2); line-height: 1.75; margin: 0; }
.join-guide .jg-acts { display: flex; gap: 9px; flex-wrap: wrap; margin-left: auto; }

/* ---- 移动端 ---- */
@media (max-width: 900px) {
  .join-hero-inner { grid-template-columns: 1fr; gap: 28px; }
  .jqr { grid-template-columns: 1fr; }
}
@media (max-width: 640px) {
  .join-hero { padding: 36px 16px 32px; }
  .join-hero h1 { font-size: 30px; }
  .join-lead { font-size: 14.5px; }
  .join-hero-acts .btn { flex: 1 1 auto; justify-content: center; }
  .section { padding: 40px 16px; }
  .section-head h2 { font-size: 22px; }
  .jsteps, .jdocs, .jtrains { grid-template-columns: 1fr; }
  .join-guide { flex-direction: column; align-items: stretch; }
  .join-guide .jg-acts { margin-left: 0; }
  .join-guide .jg-acts .btn { flex: 1 1 auto; justify-content: center; }
  .jqr-card { padding: 18px; }
}

/* 深色皮肤下首屏的柔光略收，避免过曝 */
[data-skin="dark"] .join-hero::after,
[data-skin="graphite"] .join-hero::after { opacity: .45; }

/* ---- 后台「入队指引」设置：二维码编辑区 ---- */
.qr-edit { display: grid; grid-template-columns: 200px 1fr; gap: 20px; align-items: start; }
.qr-edit-view {
  text-align: center;
  padding: 12px; border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface-2);
}
.qr-edit-view img {
  width: 100%; max-width: 170px; aspect-ratio: 1 / 1;
  object-fit: contain; display: block; margin: 0 auto;
  border-radius: var(--radius-xs); border: 1px solid var(--border);
  background: var(--fixed-white); cursor: zoom-in;
}
.qr-edit-empty {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 7px;
  aspect-ratio: 1 / 1; max-width: 170px; margin: 0 auto;
  border: 1px dashed var(--border-strong); border-radius: var(--radius-xs);
  color: var(--text-3); font-size: 12.5px;
  background: var(--surface);
}
.qr-edit-empty svg { color: var(--border-strong); }
.qr-edit-form { min-width: 0; }
@media (max-width: 640px) {
  .qr-edit { grid-template-columns: 1fr; }
  .qr-edit-view img, .qr-edit-empty { max-width: 200px; }
}

/* ---- 后台「外观皮肤」设置：登录页左侧背景（v2.12 需求 6）--------------
   为什么要做预览而不是只给两个滑杆：
   「遮罩 62%」这种数字管理员没法在脑子里换算成观感。团队照片有雪地的、
   有夜训的，同一个 62% 在一张上刚好、在另一张上白字就糊了。给一块
   16:10 的等比缩略，拖滑杆当场看到字还读不读得出来，
   不用反复"保存 → 开登录页 → 回后台再调"。
   预览块**复用**登录页那套变量名（--ls-photo / --ls-ov / --ls-blur），
   所以预览与真身走的是同一套 CSS，不会出现"预览好看、真页不是那样"。 */
.ls-edit { display: grid; grid-template-columns: minmax(240px, 380px) minmax(0, 1fr); gap: 22px; align-items: start; }
.ls-view { min-width: 0; }
/* .ls-shot 只负责"预览块的排版"，背景/遮罩/模糊来自 .ls-surface ——
   与登录页 .auth-side 同源，所以后台看到的比例、色调就是登录页的样子。 */
.ls-shot {
  aspect-ratio: 16 / 10; border-radius: var(--radius-sm);
  border: 1px solid var(--border);
  display: flex; flex-direction: column; gap: 8px;
  padding: 16px 18px;
  color: #fff;
}
.ls-shot > * { position: relative; z-index: 1; }
.ls-shot-brand { display: flex; align-items: center; gap: 7px; font-size: 12.5px; }
.ls-shot-title { font-size: 16px; font-weight: 700; line-height: 1.35; margin-top: auto; }
.ls-shot-bar { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; margin-top: 10px; }
.ls-shot-bar i { display: block; height: 15px; border-radius: 3px; background: rgba(255, 255, 255, .22); }
.ls-view-tip { margin: 8px 0 0; font-size: 11.5px; line-height: 1.6; color: var(--text-3); overflow-wrap: anywhere; }
.ls-form { min-width: 0; }

.ls-range { min-width: 0; }
.ls-range > label {
  display: flex; align-items: baseline; gap: 8px;
  font-size: 13px; font-weight: 600; color: var(--text-1); margin-bottom: 5px;
}
.ls-range > label b {
  margin-left: auto; font-family: var(--mono); font-size: 12.5px;
  font-weight: 700; color: var(--brand);
  background: var(--brand-lighter); border: 1px solid var(--brand-border);
  border-radius: var(--radius-pill); padding: 0 8px;
}
/* range 本身各浏览器默认样式差异很大，这里统一成一条细轨 + 圆形滑块，
   避免在 Chrome/Safari 上一个是蓝条一个是灰条。 */
.ls-range input[type="range"] {
  -webkit-appearance: none; appearance: none;
  width: 100%; height: 22px; margin: 0; background: transparent; cursor: pointer;
}
.ls-range input[type="range"]::-webkit-slider-runnable-track {
  height: 5px; border-radius: 999px; background: var(--surface-3); border: 1px solid var(--border);
}
.ls-range input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 16px; height: 16px; margin-top: -6px; border-radius: 50%;
  background: var(--brand); border: 2px solid var(--surface);
  box-shadow: 0 1px 4px rgba(0, 0, 0, .22);
}
.ls-range input[type="range"]::-moz-range-track {
  height: 5px; border-radius: 999px; background: var(--surface-3); border: 1px solid var(--border);
}
.ls-range input[type="range"]::-moz-range-thumb {
  width: 14px; height: 14px; border-radius: 50%;
  background: var(--brand); border: 2px solid var(--surface);
}
.ls-range .hint { display: block; margin-top: 2px; }

@media (max-width: 860px) {
  .ls-edit { grid-template-columns: 1fr; }
}

/* ==========================================================================
   42. 后台二级标签栏（需求 #72 / #18 #37）

   位置：顶栏之下、内容之上，**吸顶**。
   需求原文是「一级/二级菜单点进去后，右侧顶部也能继续基于内容做菜单管理」，
   所以它必须紧跟顶栏、并且在长页面滚动时一直看得见——
   否则滚到一半想换标签还得先滚回顶部。

   吸顶的 top 用 --topbar-h（62px）：顶栏本身 sticky top:0 且 z-index:40，
   标签条取 39 压在它下面。两个都给 40 会让标签条盖住顶栏的毛玻璃。
   ========================================================================== */
.page-tabs {
  position: sticky;
  top: var(--topbar-h);
  z-index: 39;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  /* 横向滚动而不是换行：换行会让吸顶高度随标签数量变化，
     滚动时下面的内容会跟着跳动，阅读体验很差。 */
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.page-tabs::-webkit-scrollbar { display: none; }
/* 标签条自己带边框，去掉 .tabs 原本的重复下边框 */
.page-tabs .tabs { border-bottom: none; background: transparent; flex-wrap: nowrap; }
/* 每个标签必须保持「一整块、单行文字」：
   默认 flex 项会被压缩，文字一换行这条吸顶栏就长高（390 宽下 7 个标签能顶到 134px），
   sticky 元素高度一变，下面内容会跟着跳 —— 所以宁可横向滚动，不许换行。 */
.page-tabs .tabs a { padding: 12px 15px; white-space: nowrap; flex: 0 0 auto; }
.page-tabs .tabs a svg { margin-right: 5px; vertical-align: -2px; }

@media (max-width: 640px) {
  .page-tabs .tabs { padding: 0 12px; }
  .page-tabs .tabs a { padding: 11px 12px; font-size: 13px; }
}

/* 打印时标签栏是交互控件，不进正式文档 */
@media print {
  .page-tabs { display: none !important; }
}

/* --------------------------------------------------------------------------
   42.1 表单内分组标签（admin_panes()）

   与上面「换 URL」的标签不同：这里是**同一个表单**里的字段分组，
   所有分组始终在表单里，只是同一时间显示一块。

   `.has-panes` 由 JS 在初始化时加到 <form>（或 <html>）上。没有 JS 就没有这个类，
   所有分组全展开 —— 宁可页面长，也不能出现"点不开、看不到内容"的白屏。
   -------------------------------------------------------------------------- */
.has-panes [data-pane] { display: none; }
.has-panes [data-pane].is-on { display: block; }

.pane-tabs {
  /* 表单里的标签条不需要吸顶（它本来就在表单最上方，且下方紧跟内容） */
  position: relative;
  margin-bottom: 14px;
  /* 右侧挂的「保存」不参与横向滚动，别被标签挤走 */
  align-items: center;
}
.pane-tabs .pane-save {
  margin-left: auto;
  padding: 0 14px 0 12px;
  display: flex;
  align-items: center;
  /* 标签多到要横向滚时，保存按钮要一直看得见 */
  position: sticky;
  right: 0;
  background: var(--surface);
}
.pane-tabs .pane-save .btn { white-space: nowrap; }

@media (max-width: 640px) {
  .pane-tabs .pane-save { padding: 0 12px 0 8px; }
  .pane-tabs .pane-save .btn { padding: 6px 10px; }
}

/* --------------------------------------------------------------------------
   45. 任务页 · 快速填写任务信息 + 任务协作（需求 #7）
   --------------------------------------------------------------------------
   只在有权限的人打开任务页时渲染，所以样式不必考虑"志愿者也看到"的情况，
   但**手机端优先**：现场指挥基本都在手机上操作。
   -------------------------------------------------------------------------- */

/* ── 快速填写：默认收起，展开后是一组两列表单 ────────────────────────── */
.mq-toggle {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  width: 100%; padding: 11px 13px; min-height: 46px;
  border: 1px dashed var(--brand-border); border-radius: var(--radius-sm);
  background: var(--brand-lighter); color: var(--brand-dark);
  font-family: inherit; font-size: 13.5px; font-weight: 600;
  cursor: pointer; text-align: left;
}
.mq-toggle:hover { border-style: solid; background: var(--brand-light); }
.mq-toggle svg { flex: 0 0 auto; transition: transform .18s; }
.mq-toggle[aria-expanded="true"] svg { transform: rotate(180deg); }

.mq-form { margin-top: 14px; }
.mq-form .form-grid { gap: 12px 16px; }
/* 任务说明独占整行 —— 它是唯一会长到多行的字段，挤在半列里很难写 */
.mq-form .mq-full { grid-column: 1 / -1; }
.mq-clear {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 12px; color: var(--text-3); font-weight: 400; cursor: pointer;
}
.mq-clear input { margin: 0; }
.mq-foot {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-top: 16px; padding-top: 13px; border-top: 1px solid var(--border);
}
.mq-foot .hint { font-size: 12px; }

/* ── 任务协作：协作者列表 + 添加表单 ──────────────────────────────────── */
.ms-list { display: flex; flex-direction: column; gap: 9px; margin-bottom: 4px; }
.ms-row {
  display: flex; align-items: center; gap: 11px;
  padding: 10px 12px; border: 1px solid var(--border);
  border-radius: var(--radius-sm); background: var(--surface-2);
}
.ms-row img {
  width: 38px; height: 38px; border-radius: 50%; object-fit: cover;
  flex: 0 0 38px; background: var(--surface-3);
}
.ms-info { flex: 1 1 auto; min-width: 0; }
.ms-name { display: flex; align-items: center; gap: 7px; font-size: 14px; font-weight: 600; flex-wrap: wrap; }
.ms-sub { display: block; font-size: 12px; color: var(--text-3); margin-top: 2px; }
.ms-del { flex: 0 0 auto; }

.ms-add { max-width: 460px; }
.ms-add .field { margin-bottom: 13px; }
/* 候选人选择器：多行列表比原生下拉更适合"翻一翻再挑"，
   配合上方过滤框，找人不靠滚动靠打字。 */
.ms-add select[multiple], .ms-add select[size] {
  height: auto; min-height: 132px; padding: 4px;
  line-height: 1.5;
}
.ms-add select option { padding: 5px 7px; border-radius: 4px; }

@media (max-width: 760px) {
  .mq-form .form-grid { grid-template-columns: 1fr; }
  /* 触屏：展开/收起按钮垫 44px 命中区 */
  .mq-toggle { position: relative; min-height: 46px; }
  .ms-add { max-width: none; }
  .ms-row { padding: 9px 10px; }
  .ms-del .btn { position: relative; }
  .ms-del .btn::after {
    content: ""; position: absolute; left: 0; right: 0; top: 50%;
    transform: translateY(-50%); height: 44px;
  }
  .mq-foot .btn { flex: 1 1 auto; justify-content: center; }
}

/* --------------------------------------------------------------------------
   27. 数据导出中心（需求 #10：全站数据导出 + 多条件筛选导出）
   -------------------------------------------------------------------------- */
.xc-jump { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.xc-jump .chip { text-decoration: none; }

/* 一个数据集 = 一行摘要 + 一条可展开的筛选带。
   筛选带做成 <details>，不点不占地方：41 个数据集排在一页上仍然扫得动。 */
.xc-item { border-bottom: 1px solid var(--border); }
.xc-item:last-child { border-bottom: none; }
.xc-item > .list-row { border-bottom: none; }

.xc-more > summary {
  list-style: none; cursor: pointer; user-select: none;
  display: flex; align-items: center; gap: 6px;
  padding: 9px 18px; font-size: 12.5px; font-weight: 600;
  color: var(--brand); background: var(--surface-2);
  border-top: 1px dashed var(--border);
}
.xc-more > summary::-webkit-details-marker { display: none; }
.xc-more > summary::marker { content: ''; }
.xc-more[open] > summary {
  background: var(--brand-lighter);
  border-top-color: var(--brand-border);
}
.xc-more .xc-form {
  padding: 14px 18px 16px;
  background: var(--surface-2);
  border-top: 1px dashed var(--border);
}
/* 没有可筛字段时的说明条，与筛选带同款底色，视觉上仍是「这一数据集的一条附注」 */
.xc-more-flat {
  padding: 9px 18px; font-size: 12.5px; color: var(--text-3);
  background: var(--surface-2); border-top: 1px dashed var(--border);
}

/* 「当前筛选」提示条：把「筛后 N 条 / 全部 M 条」摆出来，
   用户按下导出前就知道自己会拿到多少行。 */
.xc-cur { border-color: var(--brand-border); }
.xc-cur .honor-mark { background: var(--brand-lighter); color: var(--brand); }

@media (max-width: 760px) {
  /* 触屏：展开条垫足命中高度 */
  .xc-more > summary { min-height: 46px; padding: 12px 14px; }
  .xc-more .xc-form { padding: 12px 14px 14px; }
  .xc-item > .list-row { flex-wrap: wrap; }
  .xc-item > .list-row .btn-group { width: 100%; }
  .xc-item > .list-row .btn-group .btn { flex: 1 1 auto; justify-content: center; }
  .xc-cur .list-row { flex-wrap: wrap; }
  .xc-cur .list-row .btn-group { width: 100%; }
  .xc-cur .list-row .btn-group .btn { flex: 1 1 auto; justify-content: center; }
}

/* ==========================================================================
   发起任务页 · 紧凑表单（需求 #2 修订）
   --------------------------------------------------------------------------
   需求原文：「1.要做什么 / 2.什么时候去哪里集合 / 3.谁来指挥、怎么联系
   这几个标题可以删掉」「套用任务模板板块删掉，需要套用模板直接进入
   任务模版页面」「页面简化一点（内容不能少）」。

   所以这里把原来的「三张带编号的卡 + 右侧 296px 模板栏」换成：

     · .me-bar  —— 顶部一条细引导条 + 一个「从模板发起」按钮；
     · .me-grid —— 显式三列栅格，8 个字段压成四行；
     · .me-tip  —— 原「发布须知」卡片压成一行脚注。

   为什么不用 .form-grid（auto-fit）：auto-fit 的列数随容器宽度浮动，
   span-2 / span-3 在窄屏会撑出**隐式列**，直接把卡片顶出横向滚动条。
   显式列数 + 两级媒体查询可控得多。
   ========================================================================== */
.me-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  padding: 8px 12px;
  margin-bottom: 12px;
  background: var(--brand-lighter);
  border: 1px solid var(--brand-border);
  border-radius: var(--radius-sm);
}
.me-bar-txt {
  display: flex;
  align-items: center;
  gap: 7px;
  min-width: 0;
  flex: 1 1 320px;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--text-2);
}
.me-bar-txt svg { flex: 0 0 auto; color: var(--brand); }
.me-bar-code { color: var(--text-3); white-space: nowrap; }
.me-bar-btn { flex: 0 0 auto; }

/* 发起 / 编辑任务页：三列紧凑栅格（8 个字段 → 四行） */
.me-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px 14px;
}
.me-grid > .span-2 { grid-column: span 2; }
.me-grid > .span-3 { grid-column: span 3; }
@media (max-width: 900px) {
  .me-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .me-grid > .span-3 { grid-column: span 2; }
}
@media (max-width: 620px) {
  .me-grid { grid-template-columns: 1fr; }
  .me-grid > .span-2,
  .me-grid > .span-3 { grid-column: span 1; }
}

/* 原「发布须知」侧栏卡片 → 一行脚注（少占地方，内容一条不减） */
.me-tip {
  display: flex;
  align-items: flex-start;
  gap: 6px;
  margin: 12px 0 0;
  font-size: 11.5px;
  line-height: 1.75;
  color: var(--text-3);
}
.me-tip svg { flex: 0 0 auto; margin-top: 3px; }

/* 折叠区：原生 details，零 JS 也能展开（无 JS 时它就是一块可见的说明条） */
.me-adv {
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
  overflow: hidden;
}
.me-adv > summary {
  list-style: none;
  cursor: pointer;
  user-select: none;
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 11px 16px;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-2);
  background: var(--surface-2);
}
.me-adv > summary::-webkit-details-marker { display: none; }
.me-adv > summary::marker { content: ''; }
.me-adv > summary:hover { color: var(--brand); }
.me-adv[open] > summary {
  color: var(--brand);
  border-bottom: 1px solid var(--border);
}
.me-adv-body { padding: 14px 16px 4px; }

/* 模板列表里的「预设了什么」标签 */
.tpl-preset { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 6px; }

/* ── 模板列表分段：应急预案 / 日常（需求 #2 修订）──────────────────────
   需求原文：「模版页面要新增几个应急预案管理模板……主要用于一旦发生重大
   事件，一键发起针对特殊救援的任务」。

   所以列表不能再是一锅粥：应急预案单独一段置顶，段头加图标与说明，
   条目上带红色「应急预案」徽章。紧急时刻管理员是"照着名字找"的，
   分段比排序更可靠。 */
.tpl-sec + .tpl-sec { margin-top: 20px; padding-top: 18px; border-top: 1px solid var(--border); }
.tpl-sec-head {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 10px;
}
.tpl-sec-title {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 13.5px;
  font-weight: 700;
  color: var(--text-1);
}
.tpl-sec-title svg { color: var(--brand); }
.tpl-sec-note { font-size: 11.5px; color: var(--text-3); }
.tpl-sec-empty {
  margin: 0;
  padding: 10px 12px;
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-sm);
  font-size: 12.5px;
  line-height: 1.7;
  color: var(--text-3);
}

@media (max-width: 640px) {
  /* 触屏：折叠摘要垫到 46px 才好点 */
  .me-adv > summary { min-height: 46px; padding: 12px 14px; }
  .me-adv-body { padding: 12px 12px 2px; }
  /* 触屏上引导条与按钮各占一行，按钮撑满更好点 */
  .me-bar { padding: 9px 11px; }
  .me-bar-btn { width: 100%; justify-content: center; }
  /* 折叠区摘要右侧那串"报名截止 · 人数上限 · …"在 390px 下会换行，
     把摘要从 46px 撑到 89px —— 白白多占一行。窄屏直接隐掉，
     点开折叠区照样能看到完整字段名。 */
  .me-adv > summary .badge { display: none; }
}

/* ── 编号 / ID 规则 · 实时示例（需求 #16）──────────────────────────
   设置页里四个输入框下面给一条"签发出的第一个编号长什么样"。
   用 accent 底色 + 等宽字体，一眼能和说明文字区分开。 */
.ltid-preview {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  padding: 11px 14px;
  margin-top: 4px;
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
}
.ltid-preview-label { font-size: 12.5px; color: var(--text-3); }
.ltid-preview-code {
  font-family: var(--mono);
  font-size: 17px;
  font-weight: 600;
  letter-spacing: .3px;
  color: var(--accent-ink);
}

@media (max-width: 640px) {
  .ltid-preview { gap: 6px; }
  .ltid-preview-code { font-size: 16px; }
}

/* --------------------------------------------------------------------------
   快捷在岗打卡（需求 #1 / #20c / **需求 6（本轮）**）
   --------------------------------------------------------------------------
   本轮需求原话：「电脑和手机版首页，在岗状态打卡，点击后要有确认打卡按钮，
   这个板块要 4 个板块，现在就是很小的文字按钮，不显眼」。

   历史沿革值得记一笔：早先这里刻意做成"30px 高的小圆角条、一行排开"，
   依据是 #6 的「不要把小按钮能解决的事把 ui 画很大」。
   本轮用户明确改了口径 —— 打卡这个板块要做大、要显眼、要有确认。
   两条需求并不冲突：#6 针对的是**表单字段**（改一个性别不该占一整行），
   而打卡是**登录后第一件要做的决定**，它本来就该比筛选器更抢眼。
   所以这里按新口径重画，其它表单的紧凑口径不动。

   结构（每块状态一张卡，桌面 4 列 / 手机 2 列）：
     · .sq-tile        一块状态卡：图标 + 状态名 + 一句说明 + 右上角角标
     · .is-cur         当前就是这个状态（角标写「当前」）
     · .is-picked      用户刚点选、还没确认（品牌色描边 + 勾 + 抬起）
     · .sq-confirm-row 点选之后才出现的「确认打卡」，初始 hidden
   配色沿用全局语义变量（ok / brand / accent / text-3 那套），
   所以 10 套皮肤下都会自动跟着变，不会出现某一套皮肤里看不清的情况。
   -------------------------------------------------------------------------- */
.sq-card .sq-body {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 12px 14px 14px;
}

/* --- 4 个板块 --- */
.sq-tiles {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 10px;
}
.sq-tile {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 12px;
  text-align: left;
  color: var(--text-2);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background .16s, border-color .16s, box-shadow .16s, transform .12s;
}
/* 每个状态一套色调，供图标底色 / 选中态共用 */
.sq-tile.is-on      { --sq-tone: var(--ok);      --sq-bg: var(--ok-light);      --sq-bd: var(--ok-border);      --sq-ink: var(--ok-ink); }
.sq-tile.is-standby { --sq-tone: var(--brand);   --sq-bg: var(--brand-light);   --sq-bd: var(--brand-border);   --sq-ink: var(--brand-ink); }
.sq-tile.is-busy    { --sq-tone: var(--accent);  --sq-bg: var(--accent-light);  --sq-bd: var(--accent);         --sq-ink: var(--accent-ink); }
.sq-tile.is-off     { --sq-tone: var(--text-3);  --sq-bg: var(--surface-3);     --sq-bd: var(--border-strong);  --sq-ink: var(--text-2); }

.sq-tile-ico {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  border-radius: 11px;
  color: var(--sq-tone, var(--brand));
  background: var(--sq-bg, var(--brand-light));
}
.sq-tile-txt { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.sq-tile-txt b { font-size: 15px; font-weight: 700; color: var(--text); letter-spacing: .2px; }
.sq-tile-txt em { font-size: 11.5px; font-style: normal; color: var(--text-3); white-space: nowrap; }

/* 右上角角标：初始是勾（提示"可以点这个"），当前状态写「当前」。
   绝对定位而不是放进 flex 流里 —— 放进流里会把状态名挤窄，
   四块卡片的文字基线就不齐了。 */
.sq-tile-flag {
  position: absolute;
  top: 6px;
  right: 7px;
  display: inline-flex;
  align-items: center;
  gap: 2px;
  font-size: 10.5px;
  font-weight: 700;
  line-height: 1;
  padding: 3px 5px;
  border-radius: 999px;
  color: var(--sq-tone, var(--text-3));
  background: var(--sq-bg, var(--surface-3));
  opacity: .55;
  transition: opacity .16s, background .16s, color .16s;
}
.sq-tile-flag svg { width: 11px; height: 11px; }

/* 角标里同时装着「✓」和「当前」两段，默认只露 ✓。
   当前状态那一块改成露「当前」——**但正在被点选的那块例外**：
   那时该显示"我选的就是它"，而不是"它是我现在的状态"，
   两个含义必须分开，否则用户没法确认自己点对了哪一块。 */
.sq-tile-flag .sq-flag-cur { display: none; }
.sq-tile.is-cur:not(.is-picked) .sq-tile-flag .sq-flag-cur { display: inline; }
.sq-tile.is-cur:not(.is-picked) .sq-tile-flag .sq-flag-check { display: none; }

.sq-tile:hover:not(:disabled) {
  background: var(--surface);
  border-color: var(--sq-bd, var(--brand-border));
  box-shadow: var(--shadow-sm);
}
.sq-tile:hover:not(:disabled) .sq-tile-flag { opacity: .9; }
.sq-tile:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }
.sq-tile:disabled { opacity: .5; cursor: not-allowed; }
.sq-tile:disabled .sq-tile-flag { opacity: .25; }

/* 当前状态：只做一圈淡描边 + 角标写「当前」，**不用选中的样式** ——
   两者必须一眼可分，否则用户分不清"我现在是这个状态"和"我刚选了它还没确认"。 */
.sq-tile.is-cur {
  background: var(--sq-bg, var(--surface-2));
  border-color: var(--sq-bd, var(--border));
}
.sq-tile.is-cur .sq-tile-flag { opacity: 1; }

/* 已点选（还没确认）：品牌色描边 + 外发光 + 轻微抬起，是整页最亮的一块 */
.sq-tile.is-picked {
  background: var(--surface);
  border-color: var(--brand);
  box-shadow: 0 0 0 3px var(--brand-light), var(--shadow-sm);
  transform: translateY(-1px);
}
.sq-tile.is-picked .sq-tile-flag {
  opacity: 1;
  color: var(--fixed-white);
  background: var(--brand);
}
.sq-tile.is-picked .sq-tile-ico { background: var(--brand-light); }
/* 当前「在岗」加一圈缓慢呼吸的光晕 —— 只有一个状态值得被强调：
   调度员扫一眼后台就知道这个人现在能不能派。 */
.sq-tile.is-on.is-cur .sq-tile-ico { animation: sqPulse 2.4s ease-in-out infinite; }
@keyframes sqPulse {
  0%, 100% { box-shadow: 0 0 0 0 var(--ok-light); }
  50%      { box-shadow: 0 0 0 5px var(--ok-light); }
}
@media (prefers-reduced-motion: reduce) {
  .sq-tile.is-on.is-cur .sq-tile-ico { animation: none; }
  .sq-tile.is-picked { transform: none; }
}

/* --- 确认打卡 --- */
.sq-confirm-row {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  padding-top: 2px;
}
/* 需求 6 的核心：点选之后出现的确认按钮。做成品牌实心大按钮，
   并给一段 8px 的上滑淡入 —— 它是在用户点完状态之后才出现的，
   没有动效的话会被当成"页面本来就有的东西"而忽略。 */
.sq-confirm {
  height: 40px;
  padding: 0 22px;
  font-size: 14.5px;
  font-weight: 700;
  gap: 7px;
  animation: sqConfirmIn .18s ease-out;
}
@keyframes sqConfirmIn {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .sq-confirm { animation: none; }
}
.sq-confirm-hint { font-size: 12.5px; color: var(--text-3); }

.sq-note { height: 32px; font-size: 13px; }
.sq-vac { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.sq-vac-label { font-size: 12.5px; color: var(--text-3); white-space: nowrap; }
.sq-vac .input { height: 32px; max-width: 190px; font-size: 13px; }
/* 「点了「休假」但没填日期」时的就地提示。做成 flex:1 1 100% 自己占一行，
   而不是挤在日期框右边 —— 窄屏上那点宽度会让日期框被压变形。
   只在真缺日期时才出现，平时不占高度。 */
.sq-vac-msg { flex: 1 1 100%; font-size: 12px; color: var(--danger); }
.sq-hint { font-size: 12px; color: var(--text-3); }

/* 更矮的一档：后台首页用。四块卡仍在（需求 6 要的就是"4 个板块"），
   只把内边距与图标缩小，整块不超过 130px 高。 */
.sq-card.is-compact .sq-body { padding: 10px 12px 12px; gap: 8px; }
.sq-card.is-compact .sq-tiles { gap: 8px; }
.sq-card.is-compact .sq-tile { padding: 8px 10px; gap: 8px; }
.sq-card.is-compact .sq-tile-ico { width: 30px; height: 30px; border-radius: 9px; }
.sq-card.is-compact .sq-tile-txt b { font-size: 13.5px; }
.sq-card.is-compact .sq-tile-txt em { font-size: 10.5px; }
.sq-card.is-compact .sq-confirm { height: 34px; padding: 0 16px; font-size: 13.5px; }

/* 手机端：2 列（4 列在这个宽度下每块只剩 80px 宽，状态名会被折行）。
   顺带把"确认打卡"变成整行宽 —— 拇指够得着的区域。 */
@media (max-width: 900px) {
  .sq-tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 640px) {
  .sq-card .sq-body { padding: 10px 12px 12px; }
  .sq-tile { padding: 10px; gap: 9px; }
  .sq-tile-ico { width: 34px; height: 34px; }
  .sq-tile-txt b { font-size: 14px; }
  .sq-confirm { width: 100%; }
  .sq-confirm-row { gap: 8px; }
}
/* 超窄屏（320-400）：2 列里每块只有 140px 左右，说明文字先让位 ——
   状态名比"随时可调度"重要得多。 */
@media (max-width: 400px) {
  .sq-tile-txt em { display: none; }
}

/* ==========================================================================
   系统设置 · 首页版式选择（需求 #2 追加项）
   --------------------------------------------------------------------------
   五套风格改的是**版面**（首屏骨架 + 数据列数 + 留白），不是配色 ——
   光看名字（"聚焦大屏""数据网格"）管理员分不出差别，所以要给一张版面示意图。

   示意图用纯 CSS 画：一个 10×5 的网格，四个 <i> 按各自的 grid-area 摆位。
   数据区那条用 repeating-linear-gradient 画出竖条纹，表示"这里是一排指标"，
   2 条 vs 5 条一眼可辨 —— 比塞一张 PNG 好在：跟着皮肤换色、不占体积、
   也不会在 Retina 屏上糊。

   这套样式只服务后台设置页，所以与首页的 .home-root 无关，
   是 app.css 的正当内容，不需要挂 [data-home] 前缀。
   ========================================================================== */
.hs-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 12px;
}
.hs-card {
  display: flex;
  align-items: flex-start;
  gap: 13px;
  padding: 13px 14px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
  cursor: pointer;
  transition: border-color .16s, background .16s, box-shadow .16s;
}
.hs-card:hover { border-color: var(--brand-border); background: var(--brand-lighter); }
.hs-card.is-cur { border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-glow, rgba(29,95,208,.14)); }
.hs-card input { position: absolute; opacity: 0; pointer-events: none; }

.hs-body { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.hs-name { font-size: 13.5px; font-weight: 700; color: var(--text-1); }
.hs-name em { font-style: normal; font-weight: 500; font-size: 11.5px; color: var(--text-3); margin-left: 4px; }
.hs-desc { font-size: 11.5px; line-height: 1.7; color: var(--text-3); }

/* 版面示意图：10 列 × 5 行的小网格 */
.hs-bone {
  flex: 0 0 auto;
  display: grid;
  width: 112px;
  height: 68px;
  padding: 7px;
  gap: 4px;
  grid-template-columns: repeat(10, 1fr);
  grid-template-rows: repeat(5, 1fr);
  border: 1px solid var(--border);
  border-radius: 9px;
  background: var(--surface-2);
  color: var(--brand);
}
.hs-bone i { display: block; border-radius: 2.5px; background: currentColor; opacity: .26; }

/* 条纹：把一条指标区画成"一排若干列"，列数即 data-bone 想表达的密度 */
.hs-bone i:nth-child(3) {
  background-image: repeating-linear-gradient(90deg, currentColor 0 46%, transparent 46% 100%);
}

/* ① classic · 左文右数（默认版式） */
.hs-bone[data-bone="split"] i:nth-child(1) { grid-area: 1 / 1 / 3 / 6; opacity: .42; }
.hs-bone[data-bone="split"] i:nth-child(2) { grid-area: 3 / 1 / 4 / 6; opacity: .20; }
.hs-bone[data-bone="split"] i:nth-child(3) { grid-area: 1 / 7 / 4 / 11; opacity: .30; }
.hs-bone[data-bone="split"] i:nth-child(4) { grid-area: 4 / 1 / 6 / 11; opacity: .15; }

/* ② spotlight · 居中聚焦：标题与数据都居中，指标横排一整条 */
.hs-bone[data-bone="centered"] i:nth-child(1) { grid-area: 1 / 3 / 2 / 9; opacity: .45; }
.hs-bone[data-bone="centered"] i:nth-child(2) { grid-area: 2 / 4 / 3 / 8; opacity: .20; }
.hs-bone[data-bone="centered"] i:nth-child(3) {
  grid-area: 3 / 1 / 4 / 11;
  background-image: repeating-linear-gradient(90deg, currentColor 0 17%, transparent 17% 20%);
  opacity: .38;
}
.hs-bone[data-bone="centered"] i:nth-child(4) { grid-area: 4 / 1 / 6 / 11; opacity: .15; }

/* ③ grid · 数据网格：首屏压成一条信息带，指标一行铺开，留白压缩 */
.hs-bone[data-bone="band"] { grid-template-rows: repeat(4, 1fr); }
.hs-bone[data-bone="band"] i:nth-child(1) { grid-area: 1 / 1 / 2 / 4; opacity: .42; }
.hs-bone[data-bone="band"] i:nth-child(2) { grid-area: 1 / 4 / 2 / 11; opacity: .20; }
.hs-bone[data-bone="band"] i:nth-child(3) {
  grid-area: 2 / 1 / 3 / 11;
  background-image: repeating-linear-gradient(90deg, currentColor 0 17%, transparent 17% 20%);
  opacity: .38;
}
.hs-bone[data-bone="band"] i:nth-child(4) { grid-area: 3 / 1 / 5 / 11; opacity: .16; }

/* ④ split · 左右分栏：左边一条窄的速览栏，右边大字 */
.hs-bone[data-bone="rail"] i:nth-child(1) { grid-area: 1 / 4 / 4 / 11; opacity: .42; }
.hs-bone[data-bone="rail"] i:nth-child(2) { grid-area: 4 / 4 / 5 / 11; opacity: .20; }
.hs-bone[data-bone="rail"] i:nth-child(3) {
  grid-area: 1 / 1 / 5 / 3;
  background-image: repeating-linear-gradient(180deg, currentColor 0 20%, transparent 20% 25%);
  opacity: .30;
}
.hs-bone[data-bone="rail"] i:nth-child(4) { grid-area: 5 / 1 / 6 / 11; opacity: .15; }

/* ⑤ editorial · 画报留白：大标题、窄栏、上下留白最多 */
.hs-bone[data-bone="airy"] i:nth-child(1) { grid-area: 2 / 2 / 3 / 10; opacity: .45; }
.hs-bone[data-bone="airy"] i:nth-child(2) { grid-area: 3 / 4 / 4 / 8; opacity: .20; }
.hs-bone[data-bone="airy"] i:nth-child(3) {
  grid-area: 4 / 1 / 5 / 11;
  background-image: repeating-linear-gradient(90deg, currentColor 0 17%, transparent 17% 20%);
  opacity: .34;
}
.hs-bone[data-bone="airy"] i:nth-child(4) { display: none; }

/* ==========================================================================
   39. 档案材料与身体信息（需求 #14 / #15 / #18）
   ========================================================================== */

/* --- 39.1 flex 间距工具类 ---
   .flex / .items-center 早就有了，但一直没有配套的 gap ——
   于是页面里写 class="flex items-center gap-4" 时，gap-4 是个不存在的类，
   实际间距为 0，按钮和输入框贴着长在一起。这种"少块角"靠读代码看不出来
   （class 名字看着很合理），所以补齐这三个最常用的档位。 */
.gap-4 { gap: 4px; }
.gap-6 { gap: 6px; }
.gap-8 { gap: 8px; }

/* --- 39.2 锁定后的只读展示 ---
   身体信息填过之后不再渲染输入框，改用这个块展示值。
   高度刻意跟 .input 对齐（38px），这样"填写前 / 填写后"切换时页面不会跳。 */
.readonly-val {
  min-height: 38px;
  display: flex;
  align-items: center;
  padding: 7px 11px;
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
  background: var(--surface-2);
  color: var(--text-2);
  font-size: 13.5px;
  line-height: 1.5;
  word-break: break-word;
}
.readonly-val.strong { color: var(--text); font-weight: 600; }

/* --- 39.3 材料附件展示 --- */
.doc-file { display: flex; align-items: center; gap: 10px; }
.doc-thumb {
  width: 56px; height: 56px;
  object-fit: cover;
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
  background: var(--surface-2);
}
.doc-pdf {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 8px 11px;
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
  background: var(--surface-2);
  color: var(--text-2);
  font-size: 12.5px;
  text-decoration: none;
  max-width: 100%;
}
.doc-pdf:hover { border-color: var(--brand); color: var(--brand); }
.doc-pdf span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* --- 39.4 「申请变更」折叠入口 ---
   默认收起。理由：绝大多数人一辈子不需要改身体信息，
   把一整套变更表单常驻在页面上，只会把真正要看的信息挤下去。 */
.doc-more > summary {
  cursor: pointer;
  font-size: 12.5px;
  color: var(--brand);
  padding: 6px 0;
  list-style: none;
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.doc-more > summary::-webkit-details-marker { display: none; }
.doc-more > summary::before { content: '▸'; font-size: 10px; transition: transform .15s; }
.doc-more[open] > summary::before { transform: rotate(90deg); }
.doc-more > summary:hover { text-decoration: underline; }

/* --- 39.5 迷你进度条（齐备度矩阵用） --- */
.mini-bar {
  height: 6px;
  border-radius: 3px;
  background: var(--surface-3, var(--surface-2));
  overflow: hidden;
  margin-bottom: 4px;
}
.mini-bar > span {
  display: block;
  height: 100%;
  border-radius: 3px;
  background: var(--brand);
  transition: width .2s;
}
.mini-bar > span.is-ok   { background: var(--ok); }
.mini-bar > span.is-warn { background: var(--warn); }

/* --- 39.6 分段切换（待审队列 / 齐备度 / 全部记录） ---
   用链接实现而不是 JS：这三个视图各自是一次独立的服务端查询，
   做成前端切换就得把三份数据都渲染出来（材料记录可能上千条），
   而且刷新后会跳回第一个标签，用户分享链接也带不上视图。 */
.tabs-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 0 0 14px;
  border-bottom: 1px solid var(--border);
  padding-bottom: 0;
}
.tab {
  display: inline-flex;
  align-items: center;
  padding: 8px 14px;
  font-size: 13px;
  color: var(--text-2);
  text-decoration: none;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
}
.tab:hover { color: var(--text); }
.tab.is-active { color: var(--brand); border-bottom-color: var(--brand); font-weight: 600; }

@media (max-width: 700px) {
  .doc-thumb { width: 46px; height: 46px; }
  .tab { padding: 7px 10px; font-size: 12.5px; }
  .readonly-val { font-size: 13px; }
}

/* ==========================================================================
   40. 任务时长结算表（需求 #5）
   --------------------------------------------------------------------------
   用户对管理页的要求是「不要明明一个很小的按钮、编辑框就可以解决的，
   非要把 UI 画很大，这样会造成填写基础的信息都要大幅度滑动页面」。
   所以这一节只做两件事：
     ① 把列宽压到刚好够用，让 8 列能塞进内容区而不出横向滚动条；
     ② 「修正」的展开表单单独占一整行（colspan），并且用 4 列窄栅格 ——
        每个输入框一行一个的话，改一个人的时长要往下翻 4 屏。
   ========================================================================== */

/* --- 40.1 结算表列宽 --- */
.table-mini th.col-grp, .table-mini td.col-grp { width: 132px; }
.table-mini th.col-st,  .table-mini td.col-st  { width: 74px; }
.table-mini th.col-h,   .table-mini td.col-h   { width: 76px; }
.table-mini th.col-k,   .table-mini td.col-k   { width: 56px; }
/* v2.19 需求 1：装备分组列。78px = 四个汉字（医疗急救）加徽章内边距的实测最小宽；
   这张卡在 grid-3 里只有一列宽，分组列再宽就会把「装备」列压成两行。 */
.table-mini th.col-eqg, .table-mini td.col-eqg { width: 78px; }
/* 需求 #79-5：新增「签到 / 签退」两列。
   92px 是「09-29 08:30」单行放下的最小宽度；
   不给这两列定宽的话，它们会去抢「队员」列的宽度，
   长姓名被挤成两行，整张表的行高跟着被撑高。 */
.table-mini th.col-t,   .table-mini td.col-t   { width: 92px; }
/* 操作列从 60px 放宽到 138px：现在要同时站下「代签到/代签退」与「修正」两个按钮，
   60px 会把它们压成上下两行、把行高拉高一倍。 */
.table-mini th.col-act, .table-mini td.col-act { width: 138px; text-align: right; }

/* --- 40.2 卡片头里并排的小表单（重新结算按钮） --- */
.inline-form { display: inline-flex; align-items: center; margin: 0; }

/* --- 40.3 展开行 --- */
tr.row-detail > td {
  background: var(--surface-2, #f7f9fc);
  padding: 10px 12px;
}
.adjust-form { margin: 0; }
.adjust-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 8px 10px;
}
/* 展开行里的输入框一律压到 32px —— 这里是"补个数字"的场景，不是主表单 */
.adjust-grid .input,
.adjust-grid .select { height: 32px; font-size: 13px; padding: 0 8px; }
.adjust-grid .field { margin: 0; }
.adjust-grid .field > label {
  font-size: 12px;
  color: var(--text-3, #7a8699);
  margin-bottom: 3px;
  display: block;
}
.adjust-grid .hint { font-size: 11.5px; }
.adjust-foot {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 8px;
}
.adjust-foot > .btn, .adjust-foot > button { margin-left: auto; }

/* 展开按钮的"已展开"态：给一点视觉反馈，否则第二次点不知道是收起还是又展开 */
.btn.is-on { background: var(--brand-soft, rgba(29, 95, 208, .1)); color: var(--brand); }

@media (max-width: 900px) {
  .adjust-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
/* 手机上列宽固定会直接撑出横向滚动条，这里放开让内容自己决定 ——
   结算表在手机上本来也看不全，横向能滑比被压成乱码强。 */
@media (max-width: 700px) {
  .table-mini th.col-grp, .table-mini td.col-grp,
  .table-mini th.col-st,  .table-mini td.col-st,
  .table-mini th.col-h,   .table-mini td.col-h,
  .table-mini th.col-k,   .table-mini td.col-k,
  .table-mini th.col-eqg, .table-mini td.col-eqg { width: auto; }
  .adjust-grid { grid-template-columns: minmax(0, 1fr); }
  .adjust-foot { flex-wrap: wrap; }
  .adjust-foot > .btn, .adjust-foot > button { margin-left: 0; width: 100%; }
}

/* ==========================================================================
   41. 简报模板条 / 占位符按钮（需求 #5）
   --------------------------------------------------------------------------
   用户反复强调的一条：「不要明明一个小按钮、编辑框就可以解决的，非要把 ui 画很大，
   这样会造成填写基础的信息都要大幅度滑动页面」。所以这里全部按"一行放得下"来做：
     · 模板条是一排可换行的小胶囊，不占卡片高度，不做成 6 张卡片；
     · 占位符按钮 22px 高，一行能放十来个；
     · 提示只占一行小字，不弹窗、不占块。
   ========================================================================== */

/* --- 41.1 模板条（填写简报页） --- */
.brief-tpl-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  padding: 8px 10px;
  margin-bottom: 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-2, #f7f9fc);
}
.brief-tpl-label {
  font-size: 12px;
  font-weight: 700;
  color: var(--text-3, #7a8699);
  white-space: nowrap;
}
.brief-tpl-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  height: 26px;
  padding: 0 10px;
  font-size: 12.5px;
  font-weight: 600;
  line-height: 1;
  border: 1px solid var(--border-strong, #cfd8e3);
  border-radius: 999px;
  background: var(--surface, #fff);
  color: var(--text-1);
  cursor: pointer;
  white-space: nowrap;
  transition: border-color .15s, background .15s, color .15s;
}
.brief-tpl-chip:hover { border-color: var(--brand); color: var(--brand); }
.brief-tpl-chip .btc-hit {
  font-size: 10.5px;
  font-weight: 700;
  color: var(--brand);
  background: var(--brand-soft, rgba(29, 95, 208, .1));
  border-radius: 999px;
  padding: 1px 5px;
}
.brief-tpl-chip.is-on {
  border-color: var(--brand);
  background: var(--brand-soft, rgba(29, 95, 208, .1));
  color: var(--brand);
}
.brief-tpl-tip {
  margin-top: 8px;
  padding: 6px 10px;
  font-size: 12px;
  line-height: 1.55;
  border-radius: var(--radius-sm);
  background: var(--brand-soft, rgba(29, 95, 208, .08));
  color: var(--brand-dark, #17499f);
}
.brief-tpl-tip.is-hidden { display: none; }

/* --- 41.2 占位符按钮（后台简报模板页） --- */
.form-section-title .fst-note {
  float: right;
  font-weight: 500;
  font-size: 11.5px;
  color: var(--text-3, #7a8699);
}
/* ★ 需求 6（本轮）：窄屏上说明文字**不浮动**。
   390px 宽的卡片里，一行 30 多字的说明浮动后就占满整行并折成两三行，
   标题被挤得只剩一条缝（"通用审批"四个字竖着排）。
   改成块级独占一行后，顺序变成「标题 → 说明」，
   既不用再跟标题抢宽度，读起来也是先结论后解释。 */
@media (max-width: 860px) {
  .form-section-title .fst-note { float: none; display: block; }
}
.ph-bar {
  display: flex;
  align-items: center;
  gap: 5px;
  flex-wrap: wrap;
  margin: 6px 0 8px;
}
.ph-chip {
  height: 22px;
  padding: 0 7px;
  font-size: 11px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  line-height: 1;
  color: var(--brand);
  background: var(--brand-soft, rgba(29, 95, 208, .08));
  border: 1px solid transparent;
  border-radius: 4px;
  cursor: pointer;
}
.ph-chip:hover { border-color: var(--brand); }
.ph-chip:active { background: var(--brand); color: #fff; }

/* 正文模板用等宽字体看结构更清楚（读一、二、三的层级和占位符都更省眼） */
.textarea.mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12.5px; line-height: 1.7; }

/* 手机端：模板条横向可滚，避免 6 个胶囊换 4 行把正文推下去 */
@media (max-width: 700px) {
  .brief-tpl-bar { flex-wrap: nowrap; overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .brief-tpl-chip { flex: 0 0 auto; height: 30px; }
}

/* --- 41.3 保险附件列表（需求 #18）---
   一行一个附件：类型徽章 + 名称 + 预览/查看 + 删除。
   不做成大卡片 —— 一支队一年也就几十张保单，但一屏能看全比"滚着找"重要。 */
.ins-list { display: flex; flex-direction: column; gap: 6px; margin-top: 4px; }
.ins-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
}
.ins-badge {
  flex: 0 0 auto;
  font-size: 10.5px;
  font-weight: 700;
  line-height: 1;
  padding: 4px 6px;
  border-radius: 4px;
  background: var(--brand-soft, rgba(29, 95, 208, .1));
  color: var(--brand);
}
.ins-name {
  font-size: 13px;
  color: var(--text-1);
  text-decoration: none;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ins-name:hover { color: var(--brand); text-decoration: underline; }
.ins-thumb {
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
  object-fit: cover;
  border-radius: 4px;
  border: 1px solid var(--border);
  cursor: zoom-in;
}
@media (max-width: 700px) {
  .ins-item { flex-wrap: wrap; }
  .ins-thumb { width: 48px; height: 48px; }
}

/* --- 41.4 工作台快捷入口的「有待办」态（需求 #13 / #15 / #18）---
   只改文字与底色，**不动尺寸** —— 后台首页的快捷操作是一排小按钮，
   为了提示"有 N 件待审"把按钮撑大，反而把下面真正要看的卡片挤出去。
   待办清空后这个类会被去掉，按钮自动回到普通态。 */
.btn-soft.is-warn {
  background: var(--warn-light);
  color: var(--warn-ink);
}
.btn-soft.is-warn:hover {
  background: #F7E7C6;
  color: var(--warn-ink);
}

/* --- 41.5 可输入下拉选择（smart-select）---
   人员等长列表的选择控件：「输入框 + 就地过滤的候选列表」，替代原生 <select>。
   为什么另起一套而不是复用 .select：
     · 原生下拉在几百号人时只能靠眼睛扫，用户明确反馈"很难选择"；
     · 选中项与候选列表要能同时显示（多选时还要能逐个撤），
       .select 的结构撑不起这两件事。
   视觉上沿用 .input 的描边与圆角，因此在表单里看不出是"另一种控件"。
   原生 <select> 由 .smart-native 藏起来 —— 只在有 JS 时隐藏，
   没 JS 时它就是普通下拉，功能不丢。 */
.smart-native { display: none; }

.smart { position: relative; }

.smart-box {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 5px;
  min-height: 36px;
  padding: 4px 9px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  transition: border-color .15s, box-shadow .15s;
}
.smart.is-open .smart-box {
  border-color: var(--brand);
  box-shadow: 0 0 0 3px var(--brand-light);
}
.smart.is-invalid .smart-box { border-color: var(--danger); }

.smart-input {
  flex: 1 1 90px;
  min-width: 80px;
  height: 26px;
  padding: 0;
  border: 0;
  outline: 0;
  background: transparent;
  color: var(--text);
  font-size: 13px;
  font-family: inherit;
}
.smart-input::placeholder { color: var(--text-3); }

/* 多选的胶囊区：空的时候只有一行灰字，不占高度 */
.smart-chips { display: flex; flex-wrap: wrap; gap: 5px; }
.smart-multi .smart-chips { flex: 1 1 100%; }
.smart-chips:empty { display: none; }
.smart-chips-ph { color: var(--text-3); font-size: 12.5px; }
.smart-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  max-width: 100%;
  padding: 3px 5px 3px 9px;
  border: 1px solid var(--brand-border);
  border-radius: 999px;
  background: var(--brand-light);
  color: var(--brand-dark);
  font-size: 12.5px;
  font-weight: 600;
  line-height: 1.5;
}
.smart-chip-x {
  flex: 0 0 auto;
  width: 15px;
  height: 15px;
  border-radius: 50%;
  background: rgba(29, 95, 208, .14);
  color: var(--brand-dark);
  font-size: 12px;
  font-style: normal;
  line-height: 15px;
  text-align: center;
  cursor: pointer;
}
.smart-chip-x:hover { background: var(--danger); color: #fff; }

/* 候选列表：绝对定位浮层，最多 260px 高后内部滚动 */
.smart-menu {
  position: absolute;
  z-index: 60;
  top: calc(100% + 3px);
  left: 0;
  right: 0;
  max-height: 260px;
  overflow-y: auto;
  padding: 4px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  box-shadow: 0 10px 26px rgba(15, 23, 42, .16);
}
.smart-item {
  display: block;
  width: 100%;
  padding: 7px 9px;
  border: 0;
  border-radius: 5px;
  background: transparent;
  color: var(--text);
  font-size: 13px;
  font-family: inherit;
  text-align: left;
  cursor: pointer;
}
.smart-item:hover,
.smart-item.is-hi { background: var(--brand-lighter); }
.smart-item.is-picked { color: var(--brand); font-weight: 700; }
.smart-item.is-picked::after { content: " ✓"; }
.smart-empty {
  padding: 12px 9px;
  color: var(--text-3);
  font-size: 12.5px;
  text-align: center;
}
.smart-warn {
  margin-top: 5px;
  color: var(--danger);
  font-size: 12px;
}

/* 手机端：候选行加大触控目标，胶囊换行更紧凑 */
@media (max-width: 700px) {
  .smart-item { padding: 10px 10px; font-size: 14px; }
  .smart-menu { max-height: 220px; }
  .smart-box { min-height: 40px; }
}

/* ---------- 41.6 任务人员指派（需求 #4 / #12） ---------- */

/* 经纬度：一格两个小输入框。做成两格会把「经纬度」这件事拆到两个标签上，
   填表的人得自己判断哪个是纬度；并排放在一起、各自带 placeholder 更省一次思考。 */
.coord-pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 6px;
}

/* 非一线岗位指派：表头 + 可增删的行。
   列宽用 fixed 第一列（人员）拿自适应、后三列定宽 —— 岗位名最长 4 个字、
   系数最多 4 字符，「移除」按钮也不需要宽，给它们定宽能把空间让给人员那一格。 */
.rp-wrap { display: block; }
.rp-head,
.rp-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 120px 96px 62px;
  gap: 8px;
  align-items: center;
}
.rp-head {
  padding: 0 2px 6px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 8px;
}
.rp-head > span {
  font-size: 11.5px;
  font-weight: 600;
  color: var(--text-3);
}
.rp-row { margin-bottom: 8px; }
.rp-cell { min-width: 0; }
.rp-cell .select,
.rp-cell .input { width: 100%; }
.rp-cell .btn { width: 100%; }
.rp-foot {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 4px;
}
@media (max-width: 760px) {
  /* 窄屏：岗位与系数并排一行，「移除」独占一行右侧，避免英文按钮文字被压断 */
  .rp-head { display: none; }
  .rp-row {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 8px;
    padding: 10px;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface-2);
  }
  .rp-cell:nth-child(4) { grid-column: span 2; }
  .rp-cell:nth-child(4) .btn { width: auto; }
}

/* 结算表展开区里的第二张表单（岗位与系数）—— 用一条虚线把它与"修正时间/奖励"分清楚，
   两张表单叠在一起时如果不分隔，会让人以为下面那组字段属于上面那个保存按钮。 */
.rp-adjust {
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px dashed var(--border);
}

/* ---------- 41.7 任务列表的状态筛选按钮（需求 #6） ----------
   五个状态一行摆开、可多选。做成胶囊按钮而不是标签页（tabs），
   是因为它允许**叠加**：标签页是"单选"的语义，用它表达多选会让用户
   以为点了新的就取消了旧的。 */
.mf-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 7px;
  align-items: center;
}
.mf-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 11px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface);
  color: var(--text-2);
  font-size: 12.5px;
  font-weight: 500;
  line-height: 1.5;
  text-decoration: none;
  white-space: nowrap;
  transition: background .15s, border-color .15s, color .15s;
}
.mf-btn:hover { border-color: var(--border-strong); color: var(--text); background: var(--surface-2); }
.mf-btn.is-on {
  border-color: var(--brand);
  background: var(--brand-lighter);
  color: var(--brand);
  font-weight: 600;
}
.mf-n {
  display: inline-block;
  min-width: 18px;
  padding: 0 4px;
  border-radius: 999px;
  background: var(--surface-2);
  color: var(--text-3);
  font-size: 11px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  text-align: center;
}
.mf-btn.is-on .mf-n { background: var(--brand); color: #fff; }
/* 选中时按状态色调走，和列表里的状态徽标一眼对得上 */
.mf-btn.is-on.tone-warn   { border-color: var(--warn);   background: var(--warn-light);   color: var(--warn-ink); }
.mf-btn.is-on.tone-warn .mf-n   { background: var(--warn); }
.mf-btn.is-on.tone-danger { border-color: var(--danger); background: var(--danger-light); color: var(--danger-ink); }
.mf-btn.is-on.tone-danger .mf-n { background: var(--danger); }
.mf-btn.is-on.tone-ok     { border-color: var(--ok);     background: var(--ok-light);     color: var(--ok-ink); }
.mf-btn.is-on.tone-ok .mf-n     { background: var(--ok); }
.mf-clear { color: var(--text-3); border-style: dashed; }
@media (max-width: 620px) {
  .mf-btn { padding: 7px 10px; font-size: 12px; }
}

/* ==========================================================================
   42. 样式补漏（需求 #9 · 自检产物）
   --------------------------------------------------------------------------
   怎么来的：写了一个「把 PHP 里所有 class="…" 抽出来，逐个回 CSS 里找规则」
   的审计脚本，把只在标记里出现、CSS 里查无此类的挑出来。
   剩下的都是**真正的漏网之鱼** —— 它们在页面上表现为"这段没有样式"，
   而因为周围元素是好的，肉眼很容易以为是浏览器缓存问题。

   这一类 bug 的共同点是：**加的时候忘了同步写规则**，
   所以每次加完新页面/新组件都要把这个审计再跑一遍。
   脚本：.dev-tools/maintain/css_audit.js（随包发布，运维可自查）。
   ========================================================================== */

/* --- 42.1 缺的间距 / 对齐工具类 --------------------------------------------
   .mb-12 / .mt-12 / .gap-12 这些早就有，但 .mb-10 / .mb-14 / .mt-6 没补，
   于是 <div class="field mb-10"> 之类写上去完全没有效果 —— 表单挤在一起。 */
.mb-10 { margin-bottom: 10px; }
.mb-14 { margin-bottom: 14px; }
.mt-6  { margin-top: 6px; }
.text-center { text-align: center; }
.text-right, .ta-r { text-align: right; }

/* 段落里的行内跳转链接：不带下划线、hover 才出现，避免正文花 */
.link {
  color: var(--brand);
  font-weight: 600;
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: border-color .14s;
}
.link:hover { border-bottom-color: var(--brand); }

/* --- 42.2 档案打印件（member_public.php）身份卡主体 -----------------------
   .ad-idcard 是 display:flex（头像 + 主体）。少了 .ad-idmain 的 flex:1 /
   min-width:0，长名字 + 多个职务时主体不会收缩，会把头像挤到卡片外。
   注意：这些规则原本只在 .archive-doc 打印作用域里，屏幕上也该成立。 */
.archive-doc .ad-idmain,
.ad-idmain {
  flex: 1 1 auto;
  min-width: 0;
}

/* --- 42.3 入队指引首屏两栏（join_us.php）--------------------------------
   .join-hero-inner 是 1.15fr / .85fr 的两列 grid，两个孩子本身不需要规则；
   但缺 min-width:0 时，右栏的四格数据卡在窄屏会把整行撑出横向滚动条。 */
.jh-left,
.jh-right { min-width: 0; }

/* --- 42.4 对外公告页首屏（announcements_public.php）---------------------
   这一页直接用了 .page-hero，但这个类从来没被定义过 ——
   标题与说明以默认字号贴在一起，与站内其他对外页完全不像一套。 */
.page-hero {
  padding: 34px 0 22px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 18px;
}
.page-hero h1 {
  margin: 0 0 8px;
  font-size: 26px;
  font-weight: 800;
  letter-spacing: .5px;
  color: var(--text);
}
.page-hero p {
  margin: 0;
  max-width: 660px;
  font-size: 13.5px;
  line-height: 1.8;
  color: var(--text-3);
}
/* 宽屏时给一条左侧品牌竖线，视觉上把"页头"和"列表"分开 */
@media (min-width: 860px) {
  .page-hero { padding-left: 16px; border-left: 3px solid var(--brand); border-radius: 0 var(--radius-sm) var(--radius-sm) 0; }
}

/* --- 42.5 后台页头（admin/review_flows.php 等）--------------------------
   后台的 .page-head 是老写法：标题在左、操作按钮在右。
   .table-tool / .card-head 用得多，页级标题一直没有对应类。 */
.page-head {
  display: flex;
  align-items: flex-start;
  flex-wrap: wrap;
  gap: 12px;
  margin: 0 0 14px;
}
.page-head > h2,
.page-head > h1 { margin: 0; font-size: 17px; font-weight: 700; color: var(--text); }
.page-head-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-left: auto;
}
.page-desc {
  flex: 1 1 100%;
  margin: 0;
  font-size: 12.5px;
  line-height: 1.75;
  color: var(--text-3);
}


/* --- 42.7 状态快切表单（includes/status_quick.php）---------------------
   表单本身靠内层 .sq-* 组件撑起来，外层不需要边框；
   显式写出来是为了让"审计脚本"不再把它报成漏网之鱼。 */
.sq-form { display: block; }

/* --- 42.8 档案打印件的个人介绍 / 屏幕公开主页的个人介绍（需求 #79-8）------
   .ad-intro 是打印件里那一段自述（member_public.php 的 .archive-doc）。
   .ph-bio 是屏幕上公开主页档案卡下面那一段。
   两者语义一样（都是本人自述），但一个要走打印墨色、一个要走屏幕主题色，
   所以不合并成一个类 —— 合并之后改打印样式会连带把屏幕配色改掉。 */
.ad-intro {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.9;
  color: #333;
  text-align: justify;
  word-break: break-word;
}
.ad-intro .ad-none { color: #999; }
/* 打印时给自述加一条左侧细线，与上下的表格区分开 —— 纯文字段落夹在两张
   表格中间，容易被当成表格的脚注。 */
@media print {
  .ad-intro { padding-left: 10px; border-left: 2px solid #ddd; }
}
.ph-bio {
  margin: 0 0 16px;
  padding: 13px 15px;
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  border-left: 3px solid var(--brand);
  font-size: 13.5px;
  line-height: 1.85;
  color: var(--text);
  white-space: pre-line;
  word-break: break-word;
}
@media (max-width: 560px) {
  .ph-bio { font-size: 13px; padding: 11px 12px; }
}


/* ==========================================================================
   43. 紧凑行内字段（.field-sm / .input-sm）

   为什么单独一档，而不是继续用 .field ——
   需求 #15 的党团妇工页里，一个类别要在**一行**里放 6 个字段
   （组织名称 / 组织关系所在地 / 职务 / 加入时间 / 状态 / 备注）。
   用默认的 .field + .form-grid（minmax 200px + 每个 label 独占一行）
   会把这 6 个字段排成 6 行，四个类别下来就是 24 行，
   光是看完自己的资料就要滚三屏。用户的纪律是：
   「不要明明一个很小的按钮、编辑框就可以解决的，非要把 ui 画很大，
     这样会造成填写基础的信息都要大幅度滑动页面」。

   所以给「字段多、但每个都很短」的场景单独一档：
     · 控件高 36px → 30px、字号 13.5 → 12.5px
     · label 字号 12.5 → 11.5px、与控件的间隙 4 → 3px
   它不是 .input 的替代品：姓名、地址这类要完整输入的仍用默认尺寸。
   窄屏 700px 以下自动退回默认高度（见文末媒体查询），
   桌面省下的空间不拿手机的可点性去换。
   ========================================================================== */
.field-sm { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.field-sm > span { font-size: 11.5px; font-weight: 600; color: var(--text-3); }
.input-sm {
  min-height: 30px;
  padding: 4px 9px;
  font-size: 12.5px;
  border-radius: var(--radius-xs);
}
select.input-sm { padding-right: 26px; background-position: right 8px center; }
textarea.input-sm { min-height: 54px; }

@media (max-width: 700px) {
  /* 手机上 30px 低于可点尺寸，手指点不准会误触相邻字段 —— 还回默认高度 */
  .input-sm { min-height: 40px; padding: 8px 11px; font-size: 14px; }
  .field-sm > span { font-size: 12.5px; }
}

/* ==========================================================================
   44. 党团妇工（user/politics.php · admin/politics.php · 需求 #15）

   前台：四个类别各一块并列表单（一个人同一类别只有一条记录，
         所以没有"新增条目"这一步，见 user/politics.php 顶部注释）。
   后台：全队统计 + 比例条 + 领导班子名单（admin/politics.php）。
   ========================================================================== */

/* --- 44.1 前台：一个类别一块 ------------------------------------------- */
.pol-block {
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  padding: 10px 12px 11px;
  margin-top: 10px;
}
/* 已填写的那一块给一条品牌色左描边：四个块长得一样，
   扫一眼要能立刻分出"哪几个我填过了"，而不是逐个读徽标。 */
.pol-block.is-filled { border-left: 3px solid var(--brand); padding-left: 10px; }

.pol-head {
  display: flex;
  align-items: center;
  gap: 7px;
  flex-wrap: wrap;
  margin-bottom: 8px;
  color: var(--text-2);
}
.pol-head svg { flex: 0 0 auto; color: var(--brand); }
.pol-title { font-size: 13px; font-weight: 700; color: var(--text); }
/* 加入时间推到最右边：它是"注释"，不该和上面的状态徽标挤在一串 */
.pol-since { margin-left: auto; }

/* 6 个短字段一行排开。auto-fit + 148px 的下限：
   内容区约 900px 时正好 6 列；窄到 600px 时退成 3~4 列；
   再窄由 700px 媒体查询接管成单列（那时输入框已恢复 40px）。 */
.pol-form {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(148px, 1fr));
  gap: 8px 10px;
  align-items: end;
}
/* 按钮作为栅格里的普通一格，跟在最后一个字段后面 —— 不另起一行，
   否则 6 个字段 + 一条只有两个小按钮的空行，白占 40px 高度。 */
.pol-actions { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }

/* --- 44.2 后台：比例条 ------------------------------------------------- */
.pol-ratio {
  display: flex;
  height: 14px;
  border-radius: var(--radius-pill);
  overflow: hidden;
  /* 轨道用最浅的一档（surface-2 #FAFBFE）而不是 surface-3（#F2F2F7）：
     「群众」那一段是 border-strong（#D2DAE8），和 surface-3 只差一点点，
     两条灰叠在一起时看不出分界，整条比例尺会像"只填了 56%"。
     轨道压到近白之后，四段的分界才清楚。 */
  background: var(--surface-2);
  border: 1px solid var(--border);
}
.pol-ratio > span { display: block; min-width: 0; transition: flex-basis .3s ease; }
.pol-ratio .is-party  { background: var(--danger); }
.pol-ratio .is-league { background: var(--brand); }
.pol-ratio .is-other  { background: var(--warn); }
.pol-ratio .is-masses { background: var(--border-strong); }
@media (prefers-reduced-motion: reduce) {
  .pol-ratio > span { transition: none; }
}

.pol-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 16px;
  margin-top: 9px;
  font-size: 12px;
  color: var(--text-2);
}
.pol-legend > span { display: inline-flex; align-items: center; gap: 6px; }
.pol-legend .swatch {
  width: 10px; height: 10px; border-radius: 3px;
  flex: 0 0 10px;
  background: var(--surface-3);
}
.pol-legend .swatch.is-party  { background: var(--danger); }
.pol-legend .swatch.is-league { background: var(--brand); }
.pol-legend .swatch.is-other  { background: var(--warn); }
.pol-legend .swatch.is-masses { background: var(--border-strong); }
.pol-legend b { color: var(--text); }

/* --- 44.3 后台：领导班子名单 ------------------------------------------ */
.pol-leads {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(268px, 1fr));
  gap: 10px;
}
.pol-lead {
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
  padding: 10px 12px;
}
.pol-lead-head {
  display: flex;
  align-items: center;
  gap: 9px;
  min-width: 0;
}
.pol-lead-head .pol-ava {
  width: 36px; height: 36px;
  border-radius: 50%;
  flex: 0 0 36px;
  object-fit: cover;
  background: var(--surface-3);
  display: grid;
  place-items: center;
  color: var(--text-3);
  font-size: 13px;
  font-weight: 700;
  overflow: hidden;
}
.pol-lead-name {
  font-size: 13.5px;
  font-weight: 700;
  color: var(--text);
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}
.pol-lead-role { font-size: 11.5px; color: var(--text-3); margin-top: 2px; }
/* 这个人身上挂的党团妇工徽标（可能一个都没有 —— 那是正常的，
   不是所有队干部都必须是党员，空着就不显示这一行）。 */
.pol-badges { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 8px; }
.pol-badges .badge { font-size: 11px; padding: 1px 8px; }
.pol-lead-empty { font-size: 12px; color: var(--text-3); }

/* ==========================================================================
   富文本编辑器（需求 #78-5）
   --------------------------------------------------------------------------
   工具栏刻意做小：字号 12px、按钮高 26px —— 与项目里其它 inline 控件同级。
   用户反复强调过「小按钮 / 编辑框能解决的不要画大」，一个正文输入框
   顶上一排 40px 的大按钮，是把录入区变成"仪表盘"，不是排版。
   编辑区用 max-height + 内滚，而不是让它随内容无限长高：
   否则在手机上发布一条长公告，保存按钮会被推到屏幕外好几屏。
   ========================================================================== */
.rt {
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
  overflow: hidden;
  transition: border-color .16s, box-shadow .16s;
}
.rt:focus-within {
  border-color: var(--brand);
  box-shadow: 0 0 0 3px rgba(29, 95, 208, .12);
}
.rt-bar {
  display: flex;
  align-items: center;
  gap: 3px;
  flex-wrap: wrap;
  padding: 4px 6px;
  background: var(--surface-2);
  border-bottom: 1px solid var(--border);
}
.rt-btn {
  height: 26px;
  min-width: 26px;
  padding: 0 7px;
  font-size: 12px;
  line-height: 1;
  color: var(--text-2);
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-xs);
  cursor: pointer;
  transition: background .14s, color .14s, border-color .14s;
}
.rt-btn:hover { background: var(--surface-hover); color: var(--text); border-color: var(--border); }
.rt-btn:active { background: var(--brand-light); color: var(--brand-dark); border-color: var(--brand-border); }
.rt-tip { margin-left: auto; padding-left: 8px; font-size: 11.5px; color: var(--text-3); }
.rt-area {
  padding: 9px 11px;
  font-size: 13.5px;
  line-height: 1.72;
  color: var(--text);
  outline: none;
  overflow-y: auto;
  max-height: 46vh;
}
/* 占位提示：contenteditable 不能用 placeholder 属性，只能用 :empty 伪元素 */
.rt-area:empty:before { content: attr(data-placeholder); color: var(--text-3); }
.rt-area p { margin: 0 0 9px; }
.rt-area p:last-child { margin-bottom: 0; }
.rt-area h3 { margin: 12px 0 7px; font-size: 15px; font-weight: 700; color: var(--text); }
.rt-area h3:first-child { margin-top: 0; }
.rt-area ul, .rt-area ol { padding-left: 22px; margin: 0 0 9px; }
.rt-area li { margin-bottom: 3px; }
.rt-area blockquote {
  margin: 0 0 9px;
  padding: 6px 11px;
  border-left: 3px solid var(--brand-border);
  background: var(--surface-2);
  color: var(--text-2);
}
.rt-area a { color: var(--brand); text-decoration: underline; }
.rt-area img { max-width: 100%; border-radius: var(--radius-sm); }
@media (max-width: 700px) {
  /* 手机上工具栏横滑而不是换行：8 个按钮换行会占掉 3 行高度，
     正文区被压到只剩两行，发布一条通知要先滚工具栏。 */
  .rt-bar { flex-wrap: nowrap; overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .rt-btn { flex: 0 0 auto; }
  .rt-tip { display: none; }
  .rt-area { max-height: 40vh; }
}

/* 前台 / 详情展示：引用段落与首元素收口
   （.rich-text 原有规则里没有 blockquote，加了排版功能后不补上，
     引用段会变成一段没有任何缩进标记的普通文字，等于白点那个按钮） */
.rich-text blockquote {
  margin: 0 0 10px;
  padding: 7px 12px;
  border-left: 3px solid var(--brand-border);
  background: var(--brand-lighter);
  color: var(--text-2);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}
.rich-text > :first-child { margin-top: 0; }
.rich-text h3 { font-weight: 700; }

/* 老内容的 <div> 段落（需求 #78-5 补）
   升级前用 textarea 写正文的人，有人是用 <div> 分段的（旧 placeholder
   只举了 <p> 的例子，但没有禁止 div）。净化器保留 div（拆掉它会把
   段落语义一起丢掉、两段合成一段），所以这里必须补上段间距 ——
   否则"老公告的段落依旧挤在一起"，正是这条需求要修的现象。 */
.rich-text div, .rt-area div { margin: 0 0 10px; }
.rt-area div { margin-bottom: 9px; }
.rich-text div:last-child, .rt-area div:last-child { margin-bottom: 0; }

/* 说明：寻人启事（需求 #79-3）与红头文件通告（需求 #79-4）两个模块已整体删除，
   本文件里它们用过的 `.mp-*`（四种启事风格 + 列表卡）与 `.rh-*`（发布页风格卡 /
   前台版式 / 具名页 @page 打印）样式已一并移除（需求 #15「删掉寻人启事、
   删掉红头文件」）。
   为什么样式也要删、不能只删 HTML：留着不用的类名，下一个人翻样式表时
   会以为这两块 UI 还在，改版时还会去"修"它们。 */

/* ==========================================================================
   个人主页 · 培训履历（需求 #79-7）与档案头呼号（需求 #79-1）
   ========================================================================== */

/* 必修完成度条。
   为什么不用 .stat 那一套：这是卡片**内部**的一行读数，
   不是并列的统计格；用统计格会在卡片里再嵌一层格子，层级变吵。 */
.tch-req { margin-bottom: 14px; }
.tch-req-h {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  margin-bottom: 7px;
}
.tch-req-h .text-xs { flex: 1; min-width: 0; }
.tch-req-pct {
  font-size: 17px; font-weight: 700; color: var(--brand);
  font-variant-numeric: tabular-nums; letter-spacing: .3px;
}
/* 手机端：百分比换到下一行右对齐，避免把「已完成 3/5 门 · 课时 12/20 h」挤成两行碎字 */
@media (max-width: 560px) {
  .tch-req-h .text-xs { flex: 1 1 100%; order: 3; }
  .tch-req-pct { margin-left: auto; }
}

/* ==========================================================================
   后台 · 个人介绍与我的材料（需求 #79-8）
   ========================================================================== */

/* 个人介绍正文。
   用 pre-line 保留换行：介绍是本人手写的自述，分段是内容的一部分，
   压成一行读起来像流水账。 */
.mi-text {
  white-space: pre-line;
  line-height: 1.75;
  font-size: 13px;
  color: var(--text);
  word-break: break-word;
}

/* 表格里的折叠态：最多 3 行，超出打省略号。
   为什么用行数截断而不是按字数截：中英混排、有没有换行都会让"50 字"
   忽长忽短，行数截断才能保证每一行高度一致 —— 表格才不会被一条长介绍
   撑出 400px 的空白。展开走服务端（?open=ID 在原行渲染全文），
   不依赖 JS，也不会像弹窗那样盖住旁边的对照行。 */
.mi-text.is-clamp {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  overflow: hidden;
}

/* 材料文件的文件名：与图标同行、超长截断，避免一行文件名把「大小 / 时间」
   两列挤到看不见。完整名字在 title 上，鼠标停一下能看到。 */
.mf-name {
  display: flex;
  align-items: flex-start;
  gap: 5px;
  font-size: 12px;
  line-height: 1.5;
}
.mf-name svg { flex: 0 0 auto; margin-top: 2px; }
.mf-name > span {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ==========================================================================
   需求 #1 · 手机端「错位」修复
   ==========================================================================
   用户报「手机版分类搜索错位 / 发布任务·报名指派页错位 / 任务模板底部一键发起
   错位 / 人员处罚错位 / 档案材料审核错位 / 简报模板错位 / 审批与财务错位」。

   这些页面看着各不相同，实际只有**两个**根因，都发生在窄屏：

   ① 顶栏动作区溢出 → 整页视口被撑宽
      实测（390 宽，admin/flows.php 与 admin/finance.php）：
        .topbar       盒子 390px、内容 416px   ← 26px 溢出
        .topbar-actions 里的页面级按钮 118px（「审批类型设置」「爱心公示」）
      顶栏一溢出，`position: fixed` 的底部导航与抽屉遮罩会按**溢出后的宽度**重排，
      于是整页看着像错位 —— 明明只有顶栏超出 26px。
      注意：`.topbar-actions` 在这之前被显式写成 `flex: 0 0 auto`（见文件上方 393 行
      的注释），是"动作区不参与压缩、由标题让位"的设计。该设计对图标类按钮成立，
      但页面级**纯文字链接**能到 118px，标题退到 0 之后仍然放不下。

   ② 裸表格的 560px 最小宽度 → 整页视口被撑宽
      实测（390 宽，admin/penalties.php）：
        table.table.table-mini  min-width: 560px  →  .app 被撑到 615px
      这张表**没有** `.table-wrap` 包着、也不是 `.table-stack`，
      于是 `@media (max-width:860px)` 里那条 `.table{min-width:560px}`
      直接作用在裸表上，把布局视口一路顶到 615px（是 390 的 1.58 倍）。
      手机端浏览器遇到"内容比视口宽"会把**布局视口**放大，
      所以用户看到的不是"只有表格能横向滑"，而是"整个页面被缩小、位置全偏"。

   另外补一条 `.table-stack` 的：卡片化之后桌面态的 `white-space: nowrap`
   仍然生效，单元格里的长文本不肯折行，卡片被撑到 462px
   （实测 admin/doc_review.php，溢出 85px）。
   ========================================================================== */

@media (max-width: 860px) {
  /* ② 窄屏表格：自己变成横向滚动容器，不再用 560px 最小宽度去撑整页。
     `display: block` 之后 thead/tbody 仍保持各自的 table-* 显示值，
     浏览器会用匿名 table 盒包住它们，列宽照常计算 —— 外观不变，只是能横滑。 */
  .table:not(.table-stack) {
    display: block;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    min-width: 0;
    max-width: 100%;
  }
  /* 已经在 .table-wrap 里的表不用再滚一层，交给外层容器 */
  .table-wrap > .table:not(.table-stack) {
    display: table;
    overflow: visible;
  }

  /* 卡片化表格（.table-stack）：卡片是上下排布的，
     桌面态留给表格的 nowrap 在这里只会让卡片撑破，一律还原成可折行。 */
  .table-stack tbody td {
    white-space: normal;
    min-width: 0;
    overflow-wrap: anywhere;
  }
  /* 但 ::before 那个「标签」是短词，保持不折行才对齐 */
  .table-stack tbody td::before {
    white-space: nowrap;
  }
}

@media (max-width: 640px) {
  /* ① 顶栏：收紧留白，并让**页面级动作条**可横滑，而不是把整页视口撑宽。

     这里踩过一个坑，记下来免得后人重犯：
     上一版的做法是「压缩按钮」—— 给 .topbar-actions 写 flex:0 1 auto + min-width:0。
     实测（390 宽，admin/missions.php）证明那是**反效果**：
        .topbar-actions 盒子被压成 122px、内容却有 204px（scrollWidth=204）
     盒子一窄，overflow:visible 的内容就直接画到视口外面 ——
     按钮既点不到、也看不见，比"页面被缩小"更糟。

     正确做法是给动作区一个**真正的滚动容器**（.ta-page），
     滚动容器的 min-content 尺寸为 0，于是 .topbar-actions 收缩时
     被压的是这一条，右侧全局控件（提示音/皮肤/发起任务/头像）位置不动。
     唯一要绕开的坑：条内的「导出」是 <details class="dd">，
     父层一旦 overflow:auto 就会把弹出的菜单裁掉 —— 菜单在 app.js
     initDropdowns() 里展开时临时改成 position:fixed 摆位。 */
  .topbar { padding: 0 10px; gap: 8px; }
  /* ★ 动作区的收缩下限 = 全局控件不可再压的宽度（v2.5.0 · 需求 #1 补修）。
     提示音 34 + 皮肤 34 + 发起任务 36 + 快捷区 0 + 头像 76 = 180，
     再加 5 个 6px 间距 = 210。
     为什么不能用 min-width:0 / min-width:auto 两个"默认写法"，实测都说清了：
       · min-width: 0（原来的写法）
         容器可以被一路压扁，而里面的全局控件是 flex:0 0 auto 不会再小，
         于是它们直接画到视口外 —— 390 宽、admin/mission_report.php?id=255
         实测 .user-menu right=465，**布局视口被撑到 465**，
         position:fixed 的底部导航与抽屉遮罩按 465 重排，整页看着被缩小、
         位置全偏。这正是用户说的「错位」，不是"按钮挤一挤"那么简单。
       · min-width: auto（改了一版，也不行，两个方向都错）
         空页面级按钮条时算小：Chrome 不计 gap，最小值只算 180 而不是 210，
         那 30px 间隙照样把 .user-menu 顶到 right=410；
         满页面级按钮条时又算大：把 .ta-page 的内容全算进去（admin/member_import.php
         是 467px），容器干脆不收缩，顶栏整条溢出，视口被撑到 531 —— 反而更糟。
     显式写下限只表达一个含义：全局控件不许被挤出去；
     页面级按钮条（.ta-page 有 overflow-x:auto）自己横滑，位置不动。 */
  .topbar { --ta-floor: 210px; }
  .topbar-actions {
    gap: 6px;
    flex: 0 1 auto;
    min-width: var(--ta-floor);
  }
  /* 全局控件永不压缩，否则头像会被挤出屏幕 */
  .topbar-actions > .sound-wrap,
  .topbar-actions > .skin-wrap,
  .topbar-actions > .topbar-new-mission,
  .topbar-actions > .top-quick,
  .topbar-actions > .user-menu { flex: 0 0 auto; }

  .ta-page {
    min-width: 0;
    flex: 0 1 auto;
    overflow-x: auto;
    overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  .ta-page::-webkit-scrollbar { display: none; }
  .ta-page > * { flex: 0 0 auto; }
  /* 条内按钮收得更紧一点，少滚几屏 */
  .ta-page > a.btn,
  .ta-page .dd > summary { padding-left: 9px; padding-right: 9px; }
}

/* 页面级动作条：默认就是一段普通的横排按钮（不裁剪，条内 <details> 下拉照常弹出） */
.ta-page { display: flex; align-items: center; gap: 10px; }

/* ★ 顶栏放不下时（app.js 的 syncTopbarBars() 挂上这个类），动作条自己横滑。
   它是整条顶栏里**唯一**可收缩的部分 —— 这样 1101~1280 这类"刚好不够"的宽度下，
   被压的是它，右侧全局控件（提示音/皮肤/发起任务/快捷区/头像）位置不动。
   为什么必须"按需挂类"而不是无条件写：
     overflow: auto/hidden 一律是裁剪容器，无条件挂上就会把条内 <details class="dd">
     的弹出菜单裁掉 —— 菜单还在，但一个选项都点不到。
   挂上这个类之后 initDropdowns() 会改用它判断，把菜单换成 position:fixed 摆位。 */
.ta-page.is-scrollable {
  min-width: 0;
  flex: 0 1 auto;
  overflow-x: auto;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.ta-page.is-scrollable::-webkit-scrollbar { display: none; }
.ta-page.is-scrollable > * { flex: 0 0 auto; }

/* --------------------------------------------------------------------------
   44. 人员批量导入 · 模板下载条（需求 #11）

   放在上传区**上方**而不是只留在顶栏：用户的动作顺序是"先拿模板填表、
   再回来上传"，入口跟着动作走。顶栏那两个按钮仍然保留（老习惯），
   但新用户第一眼看到的是这里 —— 顶栏按钮在窄屏会横向滚动，不一定被看见。
   -------------------------------------------------------------------------- */
.import-tpl-bar {
  display: flex; align-items: center; flex-wrap: wrap; gap: 8px;
  padding: 10px 12px; margin-bottom: 12px;
  background: var(--brand-lighter); border: 1px solid var(--brand-light);
  border-radius: var(--radius-sm);
}
.import-tpl-bar .itb-txt { font-size: 12.5px; font-weight: 600; color: var(--brand-ink); }
.import-tpl-bar .btn { background: var(--surface); }
.import-tpl-bar .text-xs a { color: var(--brand); text-decoration: underline; }

/* --------------------------------------------------------------------------
   45. 发起任务页 · 顶部模板快捷区 + 可折叠任务地点（需求 #2 / #3 / #13）

   需求原话：
    「2. 发布任务页面经纬度、所在城市移动到进阶信息里；把集结地点下拉改成
        仓库、现场、仓库和现场、等通知……如果集结地点是仓库或者等通知，
        下方的任务地点新折叠一下，除非用户点击展开再填写，免得输入框太多
        影响观看」；
    「3. 发布任务时顶部，支持一键展开任务模板，快速选择任务模板」；
    「13. 发起任务页面顶部的从模板发起页面，打开后默认展示的就是最近使用的
         模板，支持一键发起，新增模板在底部」。

   两块都遵循同一条原则：**平时不占地方，要用时一下就到**。
   所以都用原生 <details>：无 JS 也能开合（渐进增强），
   JS 只做「按钮 → 展开并滚到可见」和「集结地点 → 任务地点收放」这两件事。
   -------------------------------------------------------------------------- */

/* ── 45.1 顶部模板快捷区 ─────────────────────────────────────────────── */
.me-tpl {
  border: 1px solid var(--brand-light);
  border-radius: var(--radius-sm);
  background: var(--surface);
  margin-bottom: 12px;
  overflow: hidden;
}
.me-tpl > summary {
  list-style: none;
  cursor: pointer;
  user-select: none;
  display: flex;
  align-items: center;
  gap: 7px;
  flex-wrap: wrap;
  padding: 10px 14px;
  font-size: 13px;
  font-weight: 600;
  color: var(--brand-ink);
  background: var(--brand-lighter);
}
.me-tpl > summary::-webkit-details-marker { display: none; }
.me-tpl > summary::marker { content: ''; }
.me-tpl > summary:hover { color: var(--brand); }
.me-tpl[open] > summary { border-bottom: 1px solid var(--brand-light); }
.me-tpl > summary svg { flex: 0 0 auto; color: var(--brand); }
.me-tpl-note {
  margin-left: auto;
  font-size: 11.5px;
  font-weight: 400;
  color: var(--text-3);
}
.me-tpl-body { padding: 12px 14px 10px; }

/* 卡片栅格：宽屏 3 列、中屏 2 列、窄屏 1 列。
   auto-fit 在这里是安全的 —— 卡片没有跨列需求，不会被撑出隐式列。 */
.me-tpl-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 10px;
}
.me-tpl-card {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 10px 12px;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
  min-width: 0;
}
.me-tpl-card:hover { border-color: var(--brand-light); background: var(--brand-lighter); }
.me-tpl-name {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-1);
  line-height: 1.45;
}
.me-tpl-meta { font-size: 11.5px; color: var(--text-3); }
.me-tpl-loc {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 11.5px;
  color: var(--text-3);
  min-width: 0;
}
.me-tpl-loc svg { flex: 0 0 auto; }
.me-tpl-act {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 6px;
  padding-top: 8px;
  border-top: 1px dashed var(--border);
}
.me-tpl-used { font-size: 11px; color: var(--text-3); }
.me-tpl-act .btn { margin-left: auto; }
.me-tpl-foot {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px solid var(--border);
}
.me-tpl-foot .link-btn { margin-left: auto; font-size: 12.5px; }

/* ── 45.2 可折叠的任务地点（跟随集结地点收放）────────────────────────── */
.me-loc {
  grid-column: 1 / -1;      /* 跨满整行：任务地点本来就是个 span-3 的宽框 */
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
  overflow: hidden;
}
.me-loc > summary {
  list-style: none;
  cursor: pointer;
  user-select: none;
  display: flex;
  align-items: center;
  gap: 7px;
  flex-wrap: wrap;
  padding: 9px 12px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text-2);
}
.me-loc > summary::-webkit-details-marker { display: none; }
.me-loc > summary::marker { content: ''; }
.me-loc > summary:hover { color: var(--brand); }
.me-loc > summary svg { flex: 0 0 auto; color: var(--brand); }
.me-loc-hint { font-weight: 400; font-size: 11.5px; color: var(--text-3); }
.me-loc-now {
  font-weight: 400;
  font-size: 11.5px;
  color: var(--text-3);
  max-width: 46%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* 展开时摘要是多余的 —— 输入框就在下面，答案已经在那儿了 */
.me-loc[open] .me-loc-hint,
.me-loc[open] .me-loc-now { display: none; }
/* 收起时只显示"有内容的那个"：有地点显示地点，没地点才解释为什么不用填 */
.me-loc:not([open]) .me-loc-hint:empty,
.me-loc:not([open]) .me-loc-now:empty { display: none; }
/* 右侧的展开指示箭头：纯 CSS 三角，不额外引图标字体 */
.me-loc-toggle {
  margin-left: auto;
  width: 0; height: 0;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 5px solid currentColor;
  opacity: .55;
  transition: transform .15s ease;
}
.me-loc[open] .me-loc-toggle { transform: rotate(180deg); }
.me-loc-body { padding: 12px 12px 2px; }
/* 折起来时下方不留空隙：空 body 仍会带 padding，这里一并清掉 */
.me-loc:not([open]) .me-loc-body { display: none; }

/* ---------- 46. 板块一句话说明（装备拆分后的 eq-hint） ----------
   拆成「总库存 / 入库 / 出库 / 台账」四个入口后，用户点的第一个问题
   往往不是"怎么操作"，而是"这一页到底管什么"。所以每个板块给一行说明。
   刻意做小做淡：它是路标不是一个板块，不能把标签栏挤下去。 */
.eq-hint {
  display: flex;
  align-items: flex-start;
  gap: 6px;
  margin: -2px 0 12px;
  padding: 0 2px;
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--text-3);
}
.eq-hint svg { flex: 0 0 auto; margin-top: 2px; color: var(--brand); opacity: .8; }
.eq-hint > span { min-width: 0; }

/* ---------- 47. 任务类别页的两段分组（救援类 / 常规类） ----------
   需求 #14：两种类别都是正式任务，只是系数不同，所以要一眼看出
   "这是两组并列的类别"，而不是"一组类别 + 一组别的什么东西"。
   做法尽量轻：一条细分隔线 + 一个小标题，不再给每组套一层卡片
   （套卡片会让本就不宽的分类表再窄一圈，还会多出一圈边框噪音）。 */
.tt-sec + .tt-sec { border-top: 1px solid var(--border); }
.tt-sec-head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 6px 10px;
  padding: 12px 16px 0;
}
.tt-sec-head h4 {
  margin: 0;
  font-size: 14px;
  font-weight: 600;
  display: inline-flex;
  align-items: center;
  gap: 8px;
}
/* 系数是本页最该被记住的数字，单独给它一点存在感 */
.tt-sec-coef {
  font-size: 12px;
  font-weight: 600;
  color: var(--brand);
  background: var(--brand-light);
  border-radius: 999px;
  padding: 1px 8px;
}
.tt-sec-sub { font-size: 12px; color: var(--text-3); }
.tt-sec-note {
  margin: 4px 16px 0;
  font-size: 12px;
  line-height: 1.55;
  color: var(--text-3);
}
.tt-sec .table-wrap { margin-top: 8px; }

/* ---------- 48. 通知列表的排序说明（notice-order-hint） ----------
   需求 #15b：前台通知改成「未读优先 → 必读优先 → 时间倒序」后，
   这个顺序**不再是时间序**。用户滑到一条三天前的必读通知、而昨天发的
   普通通知在它下面时，第一反应是"系统把我刚发的弄丢了"。
   所以列表上方必须常驻一行说明 —— 但它是一句注解，不是内容，
   跟 46 的 eq-hint 同样处理：小字、弱色、单行，不占高度。 */
.notice-order-hint {
  display: flex;
  align-items: flex-start;
  gap: 6px;
  margin-top: 12px;
  padding: 7px 10px;
  border-radius: 8px;
  background: var(--surface-2);
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--text-3);
}
.notice-order-hint svg { flex: 0 0 auto; margin-top: 2px; color: var(--brand); opacity: .8; }
.notice-order-hint > span { min-width: 0; }
.notice-order-hint b { color: var(--text-2); font-weight: 600; }
.notice-order-hint + * { margin-top: 12px; }

/* ---------- 49. 通知级别的可点胶囊（v2.23 需求 5） ----------
   用户原话：「我的通知页面，默认可以直接点击切换普通、重要、紧急；
              包括管理员的后台通知管理页面，也要支持直接点击切换，
              而非下拉选择再筛选」。

   一条细栏 + 四枚胶囊（全部 / 普通 / 重要 / 紧急）。三条约束：
     · 高度必须小：它不是内容，是"换个角度看同一份列表"的开关，
       做成大按钮会把第一条通知挤出首屏；
     · 选中态要一眼看出来：胶囊是链接，不点下去没有任何反馈，
       所以选中项用**实心底 + 白字**，未选中是描边；
     · 颜色跟着级别走（普通=中性、重要=琥珀、紧急=红），
       选中时加深，未选中时只染字与描边 —— 这样即使不点，
       三档轻重也已经在视觉上分开了。 */
.nt-pills-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  padding: 0 18px 12px;
}
/* 后台「筛选」卡里的那一条：卡片本身有内边距，这里只补上下的呼吸 */
.card > .nt-pills-bar { padding: 12px 18px 0; }
.nt-pills-k {
  font-size: 12px;
  color: var(--text-3);
  flex: 0 0 auto;
}
.nt-pills { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }

.nt-pill {
  display: inline-flex;
  align-items: center;
  height: 26px;
  padding: 0 11px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text-2);
  font-size: 12.5px;
  line-height: 1;
  text-decoration: none;
  white-space: nowrap;
  transition: background .15s, border-color .15s, color .15s;
}
.nt-pill:hover { background: var(--surface-2); border-color: var(--border-strong, var(--border)); color: var(--text); }
.nt-pill:focus-visible { outline: 2px solid var(--ring, #4B8CF5); outline-offset: 1px; }

/* 未选中时按级别染色（只染字与描边，不抢视线） */
.nt-pill-important { color: #B26A00; border-color: rgba(178, 106, 0, .38); }
.nt-pill-urgent    { color: #C62828; border-color: rgba(198, 40, 40, .38); }

/* 选中：实心底白字。三档各用自己的深色，避免"选中了却看不出是哪一档" */
.nt-pill.is-on {
  background: var(--brand);
  border-color: var(--brand);
  color: #fff;
  font-weight: 600;
}
.nt-pill-important.is-on { background: #B26A00; border-color: #B26A00; }
.nt-pill-urgent.is-on    { background: #C62828; border-color: #C62828; }

@media (max-width: 640px) {
  .nt-pills-bar { padding-left: 12px; padding-right: 12px; gap: 8px; }
  .card > .nt-pills-bar { padding: 12px 12px 0; }
  .nt-pill { height: 25px; padding: 0 9px; font-size: 12px; }
}

@media (max-width: 640px) {
  .me-tpl-note { margin-left: 0; flex: 1 1 100%; }
  .me-tpl-grid { grid-template-columns: 1fr; }
  .me-loc-now { max-width: 100%; flex: 1 1 100%; }
}


/* ==========================================================================
   需求 #2 · 手机端「表格 / 容器被压成竖条」修复
   ==========================================================================
   用户报「手机版分类搜索错位 / 发布任务·报名指派错位 / 任务模板一键发起错位 /
   人员处罚错位 ……」。在 390 视口逐页真实渲染量下来是三类根因：

   ① 栅格轨道的"最小宽度"被卡片内容顶住 —— admin/map_facility.php
      窄屏下 `.grid-main` 是单列 `1fr`，而 `1fr` = `minmax(auto, 1fr)`，
      那个 `auto` 取的是栅格项的**最小内容宽度**。卡片里 7 列的据点表
      最小内容宽 501px，于是轨道被顶到 531px，整页布局视口撑到 556px
      （390 的 1.43 倍），用户看到的是"整页被缩小、位置全偏"。
      实测（390）：`.grid-main` 轨道 531.109px、`nav.bottom-nav@556`、OVERX=166。
      解法：把栅格项的最小宽度从轨道下限里摘出去，表格回到"自己横向滚动"。

   ② `.table-wrap` 里的表被压到容器宽度以下 —— flow_types / skill_review …
      上方"需求 #1"为了让**裸表**不撑宽整页，给 `.table:not(.table-stack)`
      统一写了 `min-width: 0`；连已经包在 `.table-wrap` 里（外层本身就是
      横向滚动容器、根本不会撑宽整页）的表也一起改小了，列宽被压到容器宽度
      以下，日期 / 数字列变成"一个字一行"的竖条。
      实测（390）：flow_types 的 `table-mini` = 384px 宽、`td.num` 内容宽 24px
      排 11 行；skill_review 的 `td` 内容宽 36px 排 6 行。
      解法：`.table-wrap` 里的表按内容宽度排，横滑交给外层滚动容器。

   ③ 窄屏下 flex 容器的宽度被算成 0 —— admin/search.php 的分类芯片、
      admin/mission_edit.php「报名与人员指派」页的人员选择框
      实测（390）：父级块盒内容宽 336px，而 `.sr-chips` / `.field` 的
      used width = 0（同一个元素改成 `display: block` 立刻是 336，
      改回 `display: flex` 又是 0；桌面 1440 下则是正常的 922px）。
      容器宽 0 → 子项被压到最小内容宽 → 「队员档案」竖成一列、
      人员选择框的占位文案排成 10 行。
      解法：显式给宽度，不再依赖 `auto`。
   ========================================================================== */

@media (max-width: 900px) {
  /* ① 栅格轨道不再被卡片的最小内容宽顶住（表格改为自身横滑） */
  .grid-main > * { min-width: 0; }
}

@media (max-width: 860px) {
  /* ② 已在 .table-wrap 里的表：按内容宽度排，外层负责横滑 */
  .table-wrap > .table:not(.table-stack) { min-width: max-content; }

  /* ②b 但横滑壳自己必须**能被压窄**。`.table-wrap` 一旦成为 flex/grid 子项
        （例如 admin/penalties.php 的档位表挂在 `.alert` 这个 flex 容器里），
        它的 `min-width: auto` 会取表的最小内容宽（520px），于是外壳跟着撑到 520，
        整页被顶到 575 视口、185px 溢出 —— 比修之前的"竖条"更糟。
        `min-width: 0` 把下限摘掉，溢出交回给 overflow-x:auto 自己滚动。 */
  .table-wrap { min-width: 0; }

  /* ②c `.table-wrap` 修好了还不够 —— 挡住它的往往是**外面那一层**。
       实测 admin/penalties.php：结构是
         .alert(display:flex) > div ── 这个 div 才是 flex 子项，
       它的 `min-width: auto` 同样取"里面那张 520px 的表"的最小内容宽，
       于是 div 撑到 520、`.alert` 跟着 520、整页视口 575。
         （`.table-wrap` 自己也写了 min-width:0，但它在外层 div 里面，
           外层不松口，里面再松也没用 —— 这就是第一次只改 .table-wrap
           却"毫无效果"的原因，记下来免得下次再试一遍。）
       所以凡是**提示条 / 看板注记**这类 flex 容器，直接子项一律允许被压窄。 */
  .alert > *:not(svg), .board-note > *:not(svg) { min-width: 0; }

  /* ③ 窄屏下某些 flex/grid 子项会被压到 0 宽（父级过约束 + 子项 min-width:0 时，
        浏览器**真的**会把宽度算成 0，不是取近似），显式把宽度给回去。
        实测：admin/search.php 的 `.sr-chips`、admin/mission_edit.php
        报名指派页的 `.card-body > .field`。 */
  .card-body > .field { width: 100%; }

  /* ④ 卡片化表格（.table-stack）的单元格：把「标签 + 内容」从 flex 行
        改成「块流 + 浮动标签」。
        卡片模式原本是 `td{display:flex}` + `::before{flex:0 0 82px}` 的**一行两列**：
        左 82px 标签、右内容。问题是内容里的**每个子节点都会变成独立的 flex 列**
        并排铺开 —— 裸文字 + 若干 <div> 就成了并排的多列，每列被挤到 3.2 字宽以下，
        形成「一字一行」的竖条。实测（390 视口，修前）：
          · admin/roles.php      td.strong（宽 334）里「副队长」最宽 13px / 3 行
          · admin/trainings.php  td.strong（宽 334）里「救援安全与自我防护」最宽 39px
          · admin/doc_review.php 「过敏史：无」最宽 23px / 3 行
          · admin/search.php?q=  「孙磊 · LT000005」最宽 40px / 3 行
        改成块流后：文字恢复整行宽度；多个 <div> 上下堆叠（本来就该这样）；
        而 `.btn` 是 `display:inline-flex`，同一格里多个按钮照旧并排。
        标签摆回原处（左边距 10px、宽 82px），内容左边界仍是 102px = 10 + 82 + 10，
        与原来的 flex 布局**像素级一致**。

        标签必须**参与单元格的高度计算**，不能用绝对定位。
        绝对定位的第一次尝试（left:10px / top:7px）视觉上一样，但标签脱离文档流、
        不再撑高单元格，于是**值为空的格子**会被它溢出到下一条：
        空格子只有上下内边距，实测高 15px（7 + 1 边框 + 7）；标签本身 19.8px，
        从 7px 起画，底部落在 26.8px —— 越出格子 11.8px，压到下一条的标签上。
        实测 admin/reports.php?type=roster：850 个格子里 1 个（标签「ID」）溢出 18.8px。

        顺便把"标签会不会折行"量清楚了，免得下次再猜：
        · 全站静态 `data-label` 去重 30 个，最宽的是 `身份 / 职务`（admin/members.php），
          11 个半角宽 → 12px 字号下 66px；
        · 报表列标签（includes/reports.php，`<td data-label="<?= e($c['label']) ?>"`）
          去重 74 个，最宽的是「个人出勤时长」等 6 个汉字 → 72px。
        82px 的标签位都装得下，**不会折行**，所以只需要处理空格子这一种情形。
        （`includes/export_registry.php` 里还有 14 字的标签，但那是导出用的列名，
          不参与这张表，别混进来。）

        改法：标签用 `float`（仍在左侧，且会撑高单元格），
        再用 `::after { display: table; clear: both }` 这个 clearfix 把浮动收进高度里。
        clearfix 只用 `display:table` + `clear`，所有浏览器都认 —— 不依赖
        `display:flow-root`（那要 Chrome 58+，X5 / 老 WebView 上不保险），
        也不像 `overflow:hidden` 那样会裁掉格子里的下拉菜单 / 提示。
        浮动标签用 `margin-left:-92px` 落进左侧内边距区：
        内容盒左边界 102px − 92px = 10px 起，margin-right:10px 再把内容顶回 102px；
        浮动的 margin 盒正好填满左侧内边距（10 + 82 + 10 = 102），
        内容盒整体在浮动右侧，块级子元素也不会和标签重叠。 */
  .table-stack tbody td {
    display: block;
    padding-left: 102px;
    min-width: 0;
  }
  .table-stack tbody td::before {
    float: left;
    width: 82px;
    margin-left: -92px;
    margin-right: 10px;
    flex: none;
  }
  /* clearfix：把浮动标签的高度收进单元格，空格子（高 15px）也不会再溢到下一条 */
  .table-stack tbody td::after {
    content: '';
    display: table;
    clear: both;
  }
}

/* --------------------------------------------------------------------------
   46. 前台顶部导航的二级下拉（需求 3：后台可自定义一级 / 二级菜单）
   --------------------------------------------------------------------------
   需求原话「支持发布到一级菜单，也要支持二级菜单，下拉选择，包括支持
   自定义第三方链接，支持在新页面打开」。

   为什么用纯 CSS 而不是 JS：
     · :hover 管鼠标，:focus-within 管键盘 —— Tab 到父项就能展开，
       这是纯 hover 做不到的（键盘用户会完全打不开下拉）；
     · app.js 里那条 data-site-nav-toggle 只管**手机端整条导航的展开**，
       两套逻辑互不干扰，少一处会失效的地方。

   手机端（≤860px）没有 hover，导航本身已是竖排展开，所以子项直接静态
   列出来（缩进 + 左侧竖线），不做下拉 —— 竖屏下点出来的浮层会盖住内容，
   而且悬停下拉在触屏上根本没有触发时机。 */
.site-drop { position: relative; }
.site-drop > a { display: inline-flex; align-items: center; gap: 4px; }
/* --------------------------------------------------------------------------
   悬停桥（需求：二级菜单"消失太快、选不中"）

   根因是**几何上的断点**：下拉层用 top: calc(100% + 6px) 与父项拉开 6px
   视觉间隙，鼠标从父项移向菜单时必须穿过这段空白 —— 指针一旦落进空白，
   .site-drop:hover 立刻失效、菜单在指针到达前就收起，于是"怎么点都点不中"。
   这不是动画太快，是**可命中区域断成了两段**。

   修法：给父项铺一层透明的 ::after 覆盖住这段间隙。伪元素属于 .site-drop，
   指针落在它上面仍然算 hover 父项 —— 悬停区域因此连成一片，
   既保留了 6px 的视觉呼吸感，又不用把菜单改成"贴着父项"。
   高度给到 12px（> 6px 间隙）：间隙值将来若微调，桥也不会突然失效。
   层级：菜单自带 z-index:70，桥是 z-index:auto，菜单仍在桥之上，
   不会挡住二级项的点击。
   -------------------------------------------------------------------------- */
.site-drop::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 100%;
  height: 12px;
}
.site-drop-caret { transition: transform .16s; opacity: .62; }
.site-drop:hover .site-drop-caret,
.site-drop:focus-within .site-drop-caret { transform: rotate(180deg); }
.site-drop-menu {
  position: absolute; top: calc(100% + 6px); left: 0; min-width: 172px;
  display: none; flex-direction: column; gap: 2px;
  padding: 6px; background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  box-shadow: var(--shadow-lg); z-index: 70;
}
.site-drop:hover > .site-drop-menu,
.site-drop:focus-within > .site-drop-menu { display: flex; }
.site-drop-menu a {
  padding: 8px 11px; font-size: 13.5px; font-weight: 500;
  white-space: nowrap; border-radius: var(--radius-sm);
}
/* 父项自身也命中（例如点进 board.php）时的统一观感 */
.site-nav a.is-cur-parent { background: var(--brand-lighter); color: var(--brand); font-weight: 600; }

@media (max-width: 860px) {
  .site-drop { position: static; }
  .site-drop-caret { display: none; }
  .site-drop-menu {
    position: static; display: flex; box-shadow: none; border: none;
    background: transparent; padding: 0 0 0 13px; margin: 2px 0 5px;
    border-left: 2px solid var(--border); min-width: 0;
  }
  .site-drop-menu a { padding: 9px 11px; }
}

/* ==========================================================================
   36. 跨页板块按钮（核验页 ↔ 车辆查询，需求本轮）
   一块浅底卡片：左说明、右按钮，手机端按钮整行铺开。
   与 .quick-links 的区别是它**只有一个去向**，所以做成一条更醒目的大按钮，
   而不是排成一格一格的入口墙。
   ========================================================================== */
.xlink-card { margin-top: 16px; }
.xlink-body { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.xlink-txt { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.xlink-txt b { font-size: 15.5px; font-weight: 600; color: var(--text); letter-spacing: -.2px; }
.xlink-txt span { font-size: 12.5px; color: var(--text-3); }
.xlink-btn { margin-left: auto; }
@media (max-width: 640px) {
  .xlink-body { gap: 12px; }
  .xlink-btn { margin-left: 0; width: 100%; justify-content: center; }
}

/* ==========================================================================
   37. 「未查询到结果」加强提示（需求本轮：红字加大加粗 + 队部电话一键拨号）
   .nf-box 挂在 .empty 上，所以空态原有的图标/居中/留白都继承，
   这里只负责标题的强调与电话条。颜色全部走皮肤变量（--danger / --danger-light
   / --danger-border）—— 深色皮肤下会自动换成浅红字 + 深红底，写死 #D93A3A
   会在这个项目里"浅色好看、夜间发闷"。
   ========================================================================== */
.nf-box .nf-title {
  font-size: 21px;
  font-weight: 800;
  color: var(--danger);
  letter-spacing: -.3px;
  line-height: 1.35;
  margin: 12px 0 6px;
}
.nf-box .nf-sub { font-size: 14px; color: var(--text-2); margin: 0 0 5px; line-height: 1.7; }
.nf-box .nf-note { font-size: 12px; color: var(--text-3); margin: 0; line-height: 1.7; }
.nf-phone {
  margin-top: 16px;
  padding: 13px 18px;
  border-radius: var(--radius-sm);
  background: var(--danger-light);
  border: 1px solid var(--danger-border);
  display: flex; flex-direction: column; gap: 5px; align-items: center;
}
.nf-phone > span { font-size: 12px; color: var(--text-3); }
.nf-tel {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 22px; font-weight: 800;
  color: var(--danger);
  text-decoration: none;
  font-variant-numeric: tabular-nums;
  letter-spacing: .01em;
}
.nf-tel:hover, .nf-tel:focus-visible { text-decoration: underline; }
@media (max-width: 640px) {
  .nf-box .nf-title { font-size: 19px; }
  .nf-tel { font-size: 20px; }
}

/* ==========================================================================
   38. 爱心捐赠 · 公示照片（多图 + 逐张说明）—— 后台编辑区（需求 6）
   与装备照片（.eq-photos）同一套观感，区别是每张照片下面多一个说明输入框：
   需求原文点名了「现场合影」「捐赠物品照片」，而这两类照片的意义只有
   说明文字能区分，所以卡片从「104px 方图」改成「图 + 说明」的两段式。
   颜色全部走皮肤变量，深色皮肤下自动适配。
   ========================================================================== */
.don-photos { display: flex; flex-wrap: wrap; gap: 12px; margin-bottom: 12px; }
.don-photo { position: relative; display: flex; flex-direction: column; gap: 6px; width: 136px; }
.don-photo .dp-fig {
  position: relative; display: flex; align-items: center; justify-content: center;
  width: 136px; height: 100px; overflow: hidden;
  border: 1.5px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface-2);
}
.don-photo .dp-fig img { width: 100%; height: 100%; object-fit: cover; display: block; }
.don-photo.is-add .dp-fig {
  border-style: dashed; border-color: var(--border-strong);
  flex-direction: column; gap: 4px; cursor: pointer; transition: all .15s;
}
.don-photo.is-add .dp-fig:hover { border-color: var(--brand); background: var(--brand-lighter); }
.don-photo.is-add .dp-fig input[type="file"] { position: absolute; inset: 0; opacity: 0; cursor: pointer; }
.dpa-ico { color: var(--text-3); }
.dpa-txt { font-size: 11.5px; color: var(--text-3); text-align: center; line-height: 1.4; }
.don-photo .dp-cap { width: 100%; }

/* ── v2.12 bug 修复：选完图立刻反馈（原话「上传公示照片不显示上传成功」）
   改之前：选完文件那一格毫无变化，同页的「捐赠凭证」却会立刻出预览 ——
   同一套流程两种反馈，管理员当然认为照片那栏没上传成功。
   现在：缩略图铺格 + 左上「待保存」角标 + 右上「×」移除 + 整组汇总提示。 */
.dpa-pv { display: none; }
.don-photo.is-picked .dp-fig {
  display: block; border-style: solid; border-color: var(--ok-border); background: #0B1220;
}
.don-photo.is-picked .dpa-ico,
.don-photo.is-picked .dpa-txt { display: none; }
.don-photo.is-picked .dpa-pv { display: block; width: 100%; height: 100%; object-fit: cover; }

.dp-badge {
  position: absolute; left: 0; top: 0; z-index: 2;
  display: none; align-items: center; gap: 3px;
  padding: 2px 7px; font-size: 10.5px; line-height: 1.6;
  border-radius: 0 0 var(--radius-xs) 0;
  background: var(--ok); color: #fff;
  pointer-events: none;              /* 角标不拦点击，点它等于点选图格 */
}
.don-photo.is-picked .dp-badge { display: inline-flex; }
/* 超限：当场标红，不必等提交被驳回 */
.don-photo.is-over .dp-fig { border-color: var(--danger-border); }
.don-photo.is-over .dp-badge { background: var(--danger); }
.don-photo.is-over .dpa-pv { opacity: .5; }

.dpa-del {
  position: absolute; right: -7px; top: -7px; z-index: 3;
  display: none; align-items: center; justify-content: center;
  width: 21px; height: 21px; padding: 0; border: 2px solid var(--surface);
  border-radius: 50%; background: var(--danger); color: #fff;
  font-size: 13px; line-height: 1; cursor: pointer;
  box-shadow: 0 1px 4px rgba(15, 23, 42, .25);
}
.don-photo.is-picked .dpa-del { display: inline-flex; }
.dpa-del:hover { background: var(--danger-ink); }

.don-photos-tip {
  display: flex; align-items: center; gap: 6px;
  margin: 0 0 10px; padding: 7px 11px; font-size: 12.5px; line-height: 1.55;
  border: 1px solid var(--ok-border); border-radius: var(--radius-xs);
  background: var(--ok-light); color: var(--ok-ink);
}
.don-photos-tip.is-bad {
  border-color: var(--danger-border); background: var(--danger-light); color: var(--danger-ink);
}

/* 删除遮罩：勾选后照片立刻变灰，保存前就能看出将失去哪一张 */
.dp-del {
  position: absolute; left: 0; right: 0; bottom: 0;
  display: flex; align-items: center; justify-content: center; gap: 4px;
  padding: 3px 0; font-size: 11.5px; cursor: pointer;
  background: rgba(15, 23, 42, .68); color: #fff;
}
.dp-del input { width: 13px; height: 13px; accent-color: var(--danger); }
.don-photo:has(.dp-del input:checked) .dp-fig img { opacity: .35; filter: grayscale(1); }
.don-photo:has(.dp-del input:checked) .dp-del { background: var(--danger); }
@media (max-width: 640px) {
  .don-photo, .don-photo .dp-fig { width: calc(50% - 6px); }
}

/* 捐赠台账里的照片缩略图（与装备台账同款） */
.don-thumb-row { display: inline-flex; gap: 3px; margin-top: 4px; }
.don-thumb-row img {
  width: 26px; height: 26px; object-fit: cover;
  border-radius: 5px; border: 1px solid var(--border);
}
.don-thumb-more {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border-radius: 5px;
  background: var(--surface-3); color: var(--text-3); font-size: 10.5px;
}

/* ==========================================================================
   39. 捐赠明细页（前台 donation_view.php，需求 6）
   整页结构：抬头（捐赠人 + 金额）→ 明细键值格 → 照片墙 → 隐私说明。
   照片墙用 data-zoom-group，点开可左右翻页看大图（与车辆四视图同一套 JS）。
   ========================================================================== */
.dv-hero {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  padding: 20px 24px; border-bottom: 1px solid var(--border);
}
.dv-hero-mark {
  display: inline-flex; align-items: center; justify-content: center;
  width: 52px; height: 52px; flex: none; border-radius: 14px;
  background: var(--brand-lighter); color: var(--brand);
}
.dv-hero-main { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.dv-hero-name { font-size: 21px; font-weight: 700; color: var(--text); letter-spacing: -.3px; }
.dv-hero-tags { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.dv-hero-amt { margin-left: auto; text-align: right; }
.dv-hero-amt b {
  display: block; font-size: 26px; font-weight: 800; color: var(--danger);
  letter-spacing: -.6px; font-variant-numeric: tabular-nums;
}
.dv-hero-amt span { font-size: 12px; color: var(--text-3); }

/* ── v2.12 需求 3：操作行置顶 + 证书就地展开 ────────────────────────────
   原话「查看/下载捐赠证书 按钮移到页面顶部」「证书可展开→点击放大→再点恢复」。
   折叠用的是 aria-expanded + hidden 面板（不是 <details>）：
   <details> 展开时会撑满整行，把同桌的按钮挤到下一行 —— 按钮位置会跳。
   用一个普通按钮控制，按钮永远钉在原地，aria-expanded 还能被走查断言。 */
.dv-actions {
  display: flex; align-items: center; gap: 9px; flex-wrap: wrap;
  margin: 0 0 14px;
}
.dv-actions .btn { min-height: 34px; padding: 0 13px; font-size: 13px; }
.ds-chev { transition: transform .22s ease; }
.dv-cert-toggle[aria-expanded="true"] .ds-chev { transform: rotate(180deg); }
.dv-cert-toggle[aria-expanded="true"] {
  background: var(--brand-light); color: var(--brand-ink);
}

.dv-cert-panel {
  margin: 0 0 16px; padding: 16px;
  border: 1px solid var(--brand-border); border-radius: var(--radius);
  background: linear-gradient(180deg, var(--brand-lighter), var(--surface));
  animation: dvCertIn .26s ease both;
}
@keyframes dvCertIn {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: none; }
}
.dv-cert-tip {
  display: flex; align-items: center; gap: 6px;
  font-size: 12.5px; color: var(--text-3); margin: 0 0 11px;
}
/* 证书本体：1600×1131 的横版，窄屏下等比缩，别拉出横向滚动条 */
.dv-cert { max-width: 900px; margin: 0 auto; }
.dv-cert img {
  display: block; width: 100%; height: auto; cursor: zoom-in;
  border-radius: var(--radius-xs); background: #fff;
  border: 1px solid var(--border);
  box-shadow: 0 1px 2px rgba(16, 35, 74, .05), 0 14px 30px -16px rgba(16, 35, 74, .3);
}
.dv-cert img:hover { border-color: var(--brand-border); }

/* 页尾只留一个轻量出口（操作行已上移到页首） */
.dv-foot { margin-top: 16px; text-align: center; }

@media (max-width: 640px) {
  .dv-actions { gap: 8px; }
  .dv-actions .btn { min-height: 32px; padding: 0 11px; font-size: 12.5px; }
  .dv-cert-panel { padding: 11px; }
}
@media (prefers-reduced-motion: reduce) {
  .dv-cert-panel { animation: none; }
  .ds-chev { transition: none; }
}

.dv-kv {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 0;
}
.dv-kv > div { padding: 14px 24px; border-bottom: 1px solid var(--border); }
.dv-kv > div + div { border-left: 1px solid var(--border); }
.dv-kv dt { font-size: 12px; color: var(--text-3); margin: 0 0 5px; }
.dv-kv dd { margin: 0; font-size: 14.5px; color: var(--text); line-height: 1.65; word-break: break-word; }

.dv-gallery {
  display: grid; gap: 12px;
  grid-template-columns: repeat(auto-fill, minmax(196px, 1fr));
}
.dv-shot { margin: 0; border: 1px solid var(--border); border-radius: 12px; overflow: hidden; background: var(--surface-2); }
.dv-shot img {
  width: 100%; aspect-ratio: 4 / 3; object-fit: cover; display: block; cursor: zoom-in;
}
.dv-shot figcaption {
  padding: 8px 11px; font-size: 12.5px; color: var(--text-2); line-height: 1.5;
  background: var(--surface); border-top: 1px solid var(--border);
  display: flex; align-items: center; gap: 6px;
}
.dv-shot figcaption i {
  font-style: normal; font-size: 11px; color: var(--text-3);
  flex: none; font-variant-numeric: tabular-nums;
}
.dv-empty-shot {
  padding: 30px 20px; text-align: center; color: var(--text-3); font-size: 13px;
  border: 1px dashed var(--border-strong); border-radius: 12px; background: var(--surface-2);
}
@media (max-width: 640px) {
  .dv-hero { padding: 16px 18px; gap: 12px; }
  .dv-hero-amt { margin-left: 0; text-align: left; width: 100%; }
  .dv-hero-amt b { font-size: 23px; }
  .dv-kv > div { padding: 12px 18px; }
  .dv-kv > div + div { border-left: 0; }
  .dv-gallery { grid-template-columns: repeat(2, 1fr); gap: 10px; }
  .dv-shot figcaption { font-size: 12px; padding: 7px 9px; }
}

/* 爱心公示清单里，捐赠人姓名本身就是进明细的链接（.list-title a 已有品牌色） */
.list-title a.strong { font-weight: 700; text-decoration: none; }
.list-title a.strong:hover,
.list-title a.strong:focus-visible { text-decoration: underline; }

/* ==========================================================================
   40. 捐赠证书页（前台 donation_cert.php，需求 3）
   页面结构：操作栏（下载图片 / 下载 SVG / 打印）→ 证书画布 → 说明 + 出口。
   证书本身是服务端渲染的 SVG（见 includes/cert.php），这里只负责"怎么摆"。
   ★ 打印是本页的主要出口，所以 .no-print / @page 这两段要跟证书一起看 ——
     打印时整页只留证书，且必须是 A4 横向（证书按 297:210 排版）。
   ========================================================================== */
.cert-page { max-width: 1240px; margin: 0 auto; }

.cert-bar {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 10px 14px; margin-bottom: 14px;
  border: 1px solid var(--border); border-radius: 14px;
  background: var(--surface); box-shadow: var(--shadow-sm);
}
.cert-bar-l { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.cert-bar-r { margin-left: auto; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

.cert-sheet {
  position: relative;
  border-radius: 10px;
  overflow: hidden;
  background: #FFFDF7;
  /* 证书是 1600 × 1131（A4 横向比例）。用 aspect-ratio 把高度算出来，
     这样 <img> 还在解码时就不会出现 0 高度的跳动（CLS）。 */
  aspect-ratio: 1600 / 1131;
  box-shadow: 0 18px 46px -18px rgba(15, 23, 42, .38), 0 2px 8px rgba(15, 23, 42, .08);
}
.cert-img { display: block; width: 100%; height: 100%; }
/* SVG 里的字号是按 1600px 宽定的；缩到手机上时整体等比缩小，不用另写一套 */
@media (max-width: 760px) {
  .cert-sheet { border-radius: 8px; }
}

.cert-foot { margin-top: 16px; display: grid; gap: 12px; }
.cert-note {
  display: flex; gap: 11px; align-items: flex-start;
  padding: 14px 16px; border-radius: 12px;
  background: var(--brand-lighter); border: 1px solid var(--border);
  font-size: 13px; color: var(--text-2); line-height: 1.7;
}
.cert-note svg { flex: none; color: var(--brand); margin-top: 1px; }
.cert-note strong { color: var(--text); }
.cert-links { display: flex; gap: 10px; flex-wrap: wrap; }

@media (max-width: 640px) {
  .cert-bar { padding: 10px 12px; }
  .cert-bar-r { margin-left: 0; width: 100%; }
  .cert-bar-r .btn { flex: 1 1 auto; justify-content: center; }
}

/* ---- 打印：只留证书，铺满 A4 横向 ----
   证书服务端输出的就是矢量 SVG，所以"打印成 PDF"得到的也是矢量，
   放大到 A3 甚至更大幅面都不会糊 —— 这是位图方案给不了的。 */
@media print {
  @page { size: A4 landscape; margin: 0; }

  .no-print,
  .site-head, .site-foot,
  .public-body > .site-head,
  .public-body > .site-foot { display: none !important; }

  html, body, .public-body, .section, .cert-page {
    background: #fff !important;
    margin: 0 !important;
    padding: 0 !important;
    max-width: none !important;
  }
  .cert-sheet {
    width: 100vw; height: 100vh;
    aspect-ratio: auto;
    border-radius: 0;
    box-shadow: none;
    page-break-inside: avoid;
  }
  .cert-img { width: 100%; height: 100%; }
}

/* ---- 需求 3：后台「捐赠证书」标签里的几块 ----
   .cert-no     台账里的证书编号（可点开证书）
   .cert-tpl-now 当前模版的预览条
   .cert-preview 证书预览（等比缩放，不撑破卡片）
   .cert-rules  编号规则说明 */
.cert-no {
  display: inline-block; font-size: 12.5px; font-weight: 700;
  color: var(--brand); text-decoration: none; letter-spacing: .2px;
  padding: 3px 8px; border-radius: 6px; background: var(--brand-lighter);
  border: 1px solid var(--border);
}
.cert-no:hover, .cert-no:focus-visible { background: var(--brand-light); text-decoration: underline; }

.cert-tpl-now {
  display: flex; gap: 14px; align-items: flex-start;
  padding: 12px; margin-bottom: 10px;
  border: 1px solid var(--border); border-radius: 12px; background: var(--surface-2);
}
.cert-tpl-now img {
  width: 190px; height: 134px; flex: none; object-fit: cover;
  border-radius: 8px; border: 1px solid var(--border); background: #fff;
}
.cert-tpl-now .check-item { display: inline-flex; align-items: center; gap: 7px; padding: 6px 10px; }
@media (max-width: 640px) {
  .cert-tpl-now { flex-direction: column; }
  .cert-tpl-now img { width: 100%; height: auto; aspect-ratio: 297 / 210; }
}

.cert-preview {
  border: 1px solid var(--border); border-radius: 10px; overflow: hidden;
  background: #FFFDF7; aspect-ratio: 1600 / 1131;
}
.cert-preview img { display: block; width: 100%; height: 100%; }

.cert-rules { font-size: 13.5px; color: var(--text-2); line-height: 1.75; }
.cert-rules p { margin: 0 0 8px; }
.cert-rules-list { margin: 0; padding-left: 20px; }
.cert-rules-list li { margin-bottom: 4px; }

/* ==========================================================================
   42. 注册页 · 志愿者服务承诺「读完才能同意」（需求 4）
   结构：进度条 → 可滚动正文 → 底部两个勾选。三块都参与"读完"这件事，
   所以样式不能只当装饰：
     · 进度条要真的能看出"还差多少"，否则用户以为卡住了；
     · .pledge-body 必须有固定高度上限，否则内容长时它不滚动、
       判定永远达不到底（勾选框就永远解不开）；
     · .is-locked 要一眼看出"现在还点不动"。
   ========================================================================== */

/* 正文容器：把「富文本正文 + 结语」一起放进这个可滚区。
   高度上限给的是百分比 + vh 的组合：桌面端靠 62vh，矮屏靠 min-height 兜住。 */
.pledge-body {
  max-height: 62vh;
  scroll-behavior: smooth;
  overscroll-behavior: contain;
}
.pledge-content { font-size: 13.5px; line-height: 1.78; color: var(--text-2); }
.pledge-content p { margin: 0 0 10px; }
.pledge-content ol, .pledge-content ul { margin: 0 0 10px; padding-left: 20px; }
.pledge-content li { margin-bottom: 8px; }
.pledge-content b, .pledge-content strong { color: var(--text-1); }
.pledge-content h3 { margin: 0 0 10px; font-size: 14.5px; color: var(--text-1); }

/* ── 阅读进度 ───────────────────────────────────────────────────────────── */
.pledge-progress {
  position: relative; height: 26px;
  background: var(--surface-2); border-bottom: 1px solid var(--border);
  display: flex; align-items: center; overflow: hidden;
}
.pledge-progress-bar {
  position: absolute; left: 0; top: 0; bottom: 0; width: 0;
  background: linear-gradient(90deg, var(--brand), var(--brand-2, #2563EB));
  transition: width .18s linear; opacity: .18;
}
.pledge-progress-txt {
  position: relative; padding: 0 20px; font-size: 12px;
  color: var(--text-3); font-weight: 600; letter-spacing: .2px;
}
.pledge.is-read .pledge-progress { background: var(--ok-bg, #F0FDF4); }
.pledge.is-read .pledge-progress-bar { background: var(--ok, #15803D); opacity: .26; }
.pledge.is-read .pledge-progress-txt { color: var(--ok, #15803D); }

/* ── 「还没读完」的上锁态 ───────────────────────────────────────────────────
   用 opacity + cursor 而不是直接隐藏：用户得看得见"这里有个勾选框，
   只是现在还点不动"，否则会以为页面上根本没有同意这一项。 */
.pledge-agree.is-locked { opacity: .62; }
.pledge-agree.is-locked .check-item { cursor: not-allowed; }
.pledge-agree.is-locked #agreeBox { cursor: not-allowed; }
.pledge-agree.is-locked #pledgeHint { color: var(--warn, #B45309); }
.pledge-agree { transition: opacity .2s ease; }

/* 弹窗里那个内层勾选也一样 */
.pledge .check-item input:disabled { cursor: not-allowed; }
.pledge .check-item:has(input:disabled) span { color: var(--text-3); }
#pledgeRead:disabled { opacity: .55; cursor: not-allowed; }

/* 无 JS 时摊在页面上的承诺全文 */
.plenscript { margin-top: 12px; }
.plenscript .pledge-note { margin-top: 12px; }

@media (max-width: 640px) {
  .pledge-body { max-height: 54vh; }
  .pledge-content { font-size: 13px; }
}

/* ==========================================================================
   43. 富文本里的媒体（需求 7：公告正文可插图片 / 视频）
   净化器不放行 style / class，所以"图不能撑破页面"这件事只能靠这里兜住 ——
   插入时写行内样式是没用的，保存时会被剥掉，表现成"当时好的、存完错位"。
   选择器挂在 .rich-text 上：编辑器内的实时预览与前台正文共用同一套规则，
   WYSIWYG 才真的是 WYSIWYG。
   ========================================================================== */

.rich-text img {
  display: block;
  max-width: 100%;
  height: auto;
  margin: 12px auto;
  border-radius: 10px;
}
.rich-text figure { margin: 14px 0; }
.rich-text figure img { margin: 0 auto; }
.rich-text figcaption {
  margin-top: 6px; text-align: center;
  font-size: 12.5px; color: var(--text-3);
}
.rich-text video {
  display: block;
  max-width: 100%;
  height: auto;
  margin: 14px auto;
  border-radius: 10px;
  background: #0B1220;
}

/* 编辑器内的媒体略小一点：编辑区比前台正文栏窄，插一张原尺寸大图会把人顶到
   看不见工具栏的位置，改一个字就得来回滚。 */
.rt-area img,
.rt-area video { max-width: 100%; height: auto; margin: 10px auto; display: block; border-radius: 8px; }
.rt-area video { background: #0B1220; }

/* ── 工具栏上的媒体按钮 ─────────────────────────────────────────────────── */
.rt-sep {
  width: 1px; height: 18px; margin: 0 6px;
  background: var(--border); display: inline-block; vertical-align: middle;
}
.rt-btn-media { display: inline-flex; align-items: center; gap: 5px; }
.rt-btn-media svg { width: 14px; height: 14px; }

/* 上传状态提示：**必须**有。没有它，用户点了按钮、选了几十兆的视频，
   然后要等十几秒 —— 期间页面上一点反应都没有，他只会以为坏了并再点一次。 */
.rt-state {
  margin-left: 8px; padding: 3px 9px; border-radius: 99px;
  font-size: 12px; font-weight: 600;
  background: var(--surface-2); color: var(--text-2);
  align-self: center;
}
.rt-state.is-busy { background: var(--warn-bg, #FFFBEB); color: var(--warn, #B45309); }
.rt-state.is-ok   { background: var(--ok-bg, #F0FDF4);   color: var(--ok, #15803D); }
.rt-state.is-err  { background: var(--danger-bg, #FEF2F2); color: var(--danger, #B91C1C); }

/* 承诺编辑页的预览框（后台 register_pledge.php 右侧） */
.pledge-preview {
  border: 1px solid var(--border); border-radius: 12px;
  overflow: hidden; background: var(--surface);
}
.pledge-preview-head {
  padding: 12px 16px; font-weight: 700; font-size: 14px;
  background: var(--surface-2); border-bottom: 1px solid var(--border);
}
.pledge-preview-body {
  padding: 14px 16px; max-height: 320px; overflow-y: auto;
}
.pledge-preview-foot {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 16px; border-top: 1px solid var(--border);
  background: var(--surface-2);
}
.pledge-preview-foot .btn { margin-left: auto; }

/* ==========================================================================
   47. 我的档案 · 合并引导页（user/onboard.php · 需求 5）
   --------------------------------------------------------------------------
   这一套样式只服务一个页面：把「完善资料 + 我的材料 + 党团妇工」三页摞成
   一页之后，需要一个"九块进度 + 一键跳转"的导航条，以及技能证书的条目卡。

   两条设计约束（都不是随手定的）：
     ① **只用变量，不写死颜色**。本站有皮肤切换（[data-skin]），
        写死 #fff / #333 的组件在"素纸白"皮肤下会变成一块突兀的白板。
     ② **箭头/序号用文字而不是图标字体**。↑↓ 是纯文本，任何字体下都在，
        不会因为缺字变成方框。
   ========================================================================== */

/* ---- 47.1 顶部粘性进度条 ------------------------------------------------ */
.ob-rail {
  position: sticky;
  top: calc(var(--topbar-h) + 8px);
  z-index: 30;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  padding: 10px 14px;
  margin-bottom: 14px;
}
.ob-rail-main { display: flex; align-items: center; gap: 12px; }
.ob-rail-left { display: flex; align-items: center; gap: 10px; flex: 1 1 auto; min-width: 0; }
.ob-rail-title { display: inline-flex; align-items: center; gap: 5px; font-weight: 700; white-space: nowrap; }
.ob-rail-pct { font-size: 12.5px; color: var(--text-3); white-space: nowrap; }
/* 进度条：flex 里必须允许收缩（min-width:0），否则它会顶掉右侧的按钮 */
.ob-rail-bar {
  flex: 1 1 auto; min-width: 40px; height: 7px;
  background: var(--surface-3); border-radius: var(--radius-pill); overflow: hidden;
}
.ob-rail-bar > i { display: block; height: 100%; background: var(--brand-grad); border-radius: inherit; transition: width .3s; }
.ob-rail-go { flex: 0 0 auto; }

.ob-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 9px; }
.ob-chip {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 9px; font-size: 12.5px; line-height: 1.5;
  border: 1px solid var(--border); border-radius: var(--radius-pill);
  background: var(--surface-2); color: var(--text-2);
  /* ★ nowrap 放在**基础规则**里，不是只放窄屏媒体查询 ——
     "允许换行"这件事对整排（.ob-chips，桌面折成多行没问题）成立，
     对单个药丸从来不成立：中文标签没有空格可断，四个字被压进窄药丸
     就是一字一行、药丸窜到 150px 高（v2.12 需求 9 的原始现象）。
     flex:0 0 auto 同理 —— 药丸是标签不是弹性列，不该参与收缩。 */
  flex: 0 0 auto; white-space: nowrap;
}
.ob-chip:hover { background: var(--surface-hover); border-color: var(--brand-border); color: var(--brand); }
.ob-chip > b {
  display: inline-flex; align-items: center; justify-content: center;
  width: 16px; height: 16px; border-radius: 50%;
  background: var(--surface-3); color: var(--text-3); font-size: 10.5px;
}
/* 已完成：绿点 + 浅绿底。**不是**把整条涂成品牌蓝 ——
   九个蓝色药丸并排会让人分不清"哪些还没做"。 */
.ob-chip.is-done { background: var(--ok-light); border-color: var(--ok-border); color: var(--ok-ink); }
.ob-chip.is-done > b { background: var(--ok); color: #fff; }
/* 必填且未完成：橙色描边 —— 这是唯一需要"催"的状态 */
.ob-chip.is-req { border-color: var(--warn-border); background: var(--warn-light); color: var(--warn-ink); }
.ob-chip.is-req > b { background: var(--warn); color: #fff; }

.ob-lead {
  padding: 10px 14px; margin-bottom: 14px;
  border-left: 3px solid var(--brand-border);
  background: var(--brand-lighter);
  border-radius: var(--radius-xs);
  font-size: 13px; color: var(--text-2);
}

/* ---- 47.2 板块序号与锚点偏移 -------------------------------------------- */
/* 所有可跳转的目标都要留出顶栏 + 进度条的高度，否则"跳到第 4 块"之后
   标题正好被进度条盖住，用户看到的是第 4 块的中段。 */
.ob-block, [id^="k-"], .sc-item { scroll-margin-top: calc(var(--topbar-h) + 116px); }

.ob-no {
  display: inline-flex; align-items: center; justify-content: center;
  width: 19px; height: 19px; margin-right: 7px;
  border-radius: 50%; background: var(--brand); color: #fff;
  font-size: 11.5px; font-weight: 700; vertical-align: 1px;
}
.ob-empty {
  padding: 12px 14px; border: 1px dashed var(--border-strong);
  border-radius: var(--radius-sm); background: var(--surface-2);
  font-size: 12.5px; color: var(--text-3);
}
.ob-tiny { font-size: 12px; line-height: 1.6; }

/* ---- 47.2b 板块内折叠（v2.18 需求 6）-------------------------------------
   需求原话：「党团妇工填写板块缩小一点，需要填写再展开，目前党团妇工页面
   占用板块太大了」—— 那 30 个输入框收进 <details>，收起态只留一条 52px
   的摘要条。

   为什么用原生 <details>：无 JS 也能开合（渐进增强），不会被脚本加载失败
   变成"永远打不开的板块"。展开逻辑（跳锚点 / 保存后回来）见 onboard.php
   的那段 <script>，这里只管长相。

   摘要条为什么是 grid 而不是 flex：收起态要在一行里放下三段东西 ——
   标题文字（可长可短）、若干类别标签（数量 0~5 不定）、右侧动作。
   flex 时中间那段标签会把右侧动作挤到下一行（1440 上就已经发生）；
   用 minmax(0,1fr) + auto + auto 三列，标签段可以自己换行、也可以省略，
   但标题段与动作段的位置永远不动。 */
.ob-fold > summary::-webkit-details-marker { display: none; }
.ob-fold > summary { list-style: none; }

.ob-fold-sum {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: center;
  gap: 10px 14px;
  padding: 11px 15px;
  cursor: pointer;
  user-select: none;
  background: var(--surface-2);
  border-top: 1px solid var(--border);
  transition: background .15s;
}
.ob-fold-sum:hover { background: var(--surface-hover); }
.ob-fold[open] > .ob-fold-sum { border-bottom: 1px solid var(--border); }

.ob-fold-txt { min-width: 0; }
.ob-fold-txt > b { display: block; font-size: 13px; font-weight: 700; color: var(--brand); }
.ob-fold-hint {
  display: block; margin-top: 2px;
  font-size: 11.5px; line-height: 1.5; color: var(--text-3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.ob-fold-tags { display: flex; flex-wrap: wrap; gap: 5px; justify-content: flex-end; }
.ob-fold-tag {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 2px 8px; border-radius: var(--radius-pill);
  border: 1px solid var(--ok-border); background: var(--ok-light); color: var(--ok-ink);
  font-size: 11.5px; font-weight: 600; white-space: nowrap;
}
.ob-fold-tag svg { flex: 0 0 auto; }
/* 待审 / 被驳回的那一类要能一眼看出来 —— 它和"已计入台账"不是一回事 */
.ob-fold-tag.is-wait { border-color: var(--warn-border); background: var(--warn-light); color: var(--warn-ink); }
.ob-fold-tag.is-empty { border-color: var(--border); background: var(--surface-3); color: var(--text-3); font-weight: 400; }

/* 右侧动作：收起时箭头朝下，展开后转 180° 朝上。
   箭头用 CSS 边框画（不引图标字体，也不额外插一个 lt_icon —— 它要跟着转）。 */
.ob-fold-go {
  display: inline-flex; align-items: center; gap: 6px;
  flex: 0 0 auto; padding: 5px 11px;
  border: 1px solid var(--brand-border); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--brand);
  font-size: 12.5px; font-weight: 600; white-space: nowrap;
}
.ob-fold-sum:hover .ob-fold-go { background: var(--brand-lighter); border-color: var(--brand); }
.ob-fold-arw {
  width: 0; height: 0; flex: 0 0 auto;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 5px solid currentColor;
  transition: transform .2s;
}
.ob-fold[open] .ob-fold-arw { transform: rotate(180deg); }
/* 展开后右侧动作文字跟着变。纯 CSS 换字，不引 JS：
   font-size:0 把服务端渲染的「展开填写 / 展开修改」藏起来（排版上仍占 0 宽），
   再用 ::before 补上「收起」。这么做是为了让无 JS 时也有正确的两态文案，
   而不是靠脚本去改 innerHTML —— 脚本没跑到时文案就错了。 */
.ob-fold[open] .ob-fold-go-t { font-size: 0; }
.ob-fold[open] .ob-fold-go-t::before { content: '收起'; font-size: 12.5px; }

@media (max-width: 700px) {
  /* 手机上三列排不下，改成两行：第一行「标题 + 右侧动作」，第二行类别标签。
     ★ 必须**显式指定 grid-area**，不能只改列数交给自动排布：DOM 顺序是
       txt → tags → go，而 tags 声明了 grid-column:1/-1，自动排布会把它
       推到第二行、再把 go 挤到第三行 —— 实测摘要条 135px（三行），
       和"缩小一点"的初衷正好相反。显式放位后是稳稳的两行。
     ⚠️ 副标题那行保持 nowrap + 省略号（不要改成 normal）：它在 390 宽要折成
       两行，摘要条会再涨 17px。标签那行已经把"填过哪几类"说清楚了。 */
  .ob-fold-sum { grid-template-columns: minmax(0, 1fr) auto; gap: 8px 10px; padding: 10px 12px; }
  .ob-fold-txt  { grid-area: 1 / 1; }
  .ob-fold-go   { grid-area: 1 / 2; }
  .ob-fold-tags { grid-area: 2 / 1 / 3 / 3; justify-content: flex-start; }
}

/* ---- 47.3 头像 + 只读信息条（合并成一行）--------------------------------- */
.ob-idrow { display: flex; align-items: flex-start; gap: 16px; flex-wrap: wrap; }
.ob-av { display: flex; flex-direction: column; align-items: center; gap: 5px; cursor: pointer; flex: 0 0 auto; }
.ob-av input[type="file"] { display: none; }
.ob-av-img {
  position: relative; display: block; width: 64px; height: 64px;
  border-radius: 50%; overflow: hidden; border: 2px solid var(--brand-border);
  background: var(--surface-3);
}
.ob-av-img img { width: 100%; height: 100%; object-fit: cover; }
.ob-av-badge {
  position: absolute; right: -1px; bottom: -1px;
  display: flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; border-radius: 50%;
  background: var(--brand); color: #fff; border: 2px solid var(--surface);
}
.ob-av-txt { font-size: 11.5px; color: var(--brand); }
.ob-av:hover .ob-av-img { border-color: var(--brand); }

/* ---- 47.3b 头像即时上传的状态反馈（v2.12 需求 5）------------------------
   需求原话是「无法上传头像；上传后要有状态实时查看」。
   前半句的根因在服务端（头像被别的字段连坐，见 onboard.php 的注释），
   后半句就靠这一段：选完图必须**当场**看得见四件事 ——
     ① 图选中了没有（缩略图立刻换）；
     ② 正在做什么（转圈遮罩 + "上传中 42%"）；
     ③ 成没成（绿色「头像已更新」，红字写清为什么没成）；
     ④ 存到哪儿了（绿色那行带上服务器返回的格式与像素尺寸）。
   状态行给两行高度上限：头像格只有 64px 宽，长句子要能折行又不能把
   右侧的只读信息条顶下去。 */
.ob-av-wrap { display: flex; flex-direction: column; align-items: center; gap: 4px; flex: 0 0 auto; }
.ob-av-mask {
  position: absolute; inset: 0; display: none;
  align-items: center; justify-content: center;
  background: rgba(12, 42, 97, .58);
}
.ob-av-img.is-busy .ob-av-mask { display: flex; }
.ob-av-mask i {
  width: 17px; height: 17px; border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, .32); border-top-color: #fff;
  animation: avSpin .68s linear infinite;
}
@keyframes avSpin { to { transform: rotate(360deg); } }
.ob-av-img.is-done { border-color: var(--ok); }
.ob-av-img.is-fail { border-color: var(--danger); }

.ob-av-status {
  max-width: 152px; margin: 0;
  font-size: 11px; line-height: 1.45; text-align: center;
  overflow-wrap: anywhere;
}
.ob-av-status[data-state="idle"] { color: var(--text-3); }
.ob-av-status[data-state="busy"] { color: var(--brand); }
.ob-av-status[data-state="ok"]   { color: var(--ok-ink); }
.ob-av-status[data-state="err"]  { color: var(--danger-ink); }
/* 「已选好、待上传」这一瞬间是过渡态，用品牌色但不用转圈，
   和真的在传（busy）区分开 —— 网络慢的时候用户能看出"还没开始传" */
.ob-av-status[data-state="pick"] { color: var(--brand-ink); }
@media (prefers-reduced-motion: reduce) {
  .ob-av-mask i { animation: none; border-top-color: rgba(255, 255, 255, .32); }
}

.ob-ro {
  flex: 1 1 380px; min-width: 0; margin: 0;
  display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
  gap: 6px 18px; align-content: start;
}
.ob-ro > div { min-width: 0; }
.ob-ro dt { font-size: 11.5px; color: var(--text-3); margin-bottom: 1px; }
.ob-ro dd { margin: 0; font-weight: 600; font-size: 13.5px; overflow-wrap: anywhere; }

/* ---- 47.4 技能证书条目 -------------------------------------------------- */
.sc-list { display: flex; flex-direction: column; gap: 8px; }
.sc-item {
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface-2); padding: 9px 11px; margin: 0;
}
.sc-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.sc-cat {
  display: inline-flex; align-items: center; gap: 4px;
  font-size: 11.5px; padding: 1px 7px; border-radius: var(--radius-pill);
  background: var(--brand-light); color: var(--brand-ink); white-space: nowrap;
}
.sc-title { font-size: 13.5px; }
.sc-file { font-size: 11.5px; margin-left: auto; white-space: nowrap; }
.sc-edit > summary {
  cursor: pointer; font-size: 12px; color: var(--text-3);
  margin-top: 6px; list-style: none;
}
.sc-edit > summary::-webkit-details-marker { display: none; }
.sc-edit > summary::before { content: '▸ '; }
.sc-edit[open] > summary::before { content: '▾ '; }
.sc-edit[open] > summary { color: var(--brand); }
.sc-fields {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 8px 10px; margin-top: 8px;
}
.sc-span { grid-column: 1 / -1; }
.sc-ops { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin-top: 9px; }
.sc-btns { display: flex; align-items: center; gap: 6px; }
.sc-del { color: var(--danger-ink); }
.sc-del:hover { color: var(--danger); border-color: var(--danger-border); }

/* ---- 47.5 材料模板下载行 ------------------------------------------------ */
.tpl-row {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin-top: 8px; padding: 7px 10px;
  background: var(--brand-lighter); border: 1px solid var(--brand-border);
  border-radius: var(--radius-xs);
}
.tpl-label { display: inline-flex; align-items: center; gap: 4px; font-size: 12px; color: var(--brand-ink); font-weight: 600; }
.tpl-link {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 2px 9px; font-size: 12px;
  background: var(--surface); border: 1px solid var(--brand-border);
  border-radius: var(--radius-pill);
}
.tpl-link:hover { background: var(--brand-light); }
.tpl-ext {
  font-size: 10.5px; padding: 0 5px; border-radius: var(--radius-pill);
  background: var(--brand-light); color: var(--brand-ink); text-transform: none;
}
.tpl-note { margin-top: 4px; }

/* ---- 47.6 窄屏 ---------------------------------------------------------- */
@media (max-width: 760px) {
  .ob-rail { padding: 9px 11px; top: calc(var(--topbar-h) + 4px); }
  .ob-rail-title span { display: none; }        /* 只留图标，把宽度让给进度条 */
  .ob-rail-left { gap: 7px; }

  /* ★ v2.12 需求 12 · 手机版顶部"盖住内容"的真凶就在这两行里。
     上一版已经把 .ob-chips 设成 nowrap + overflow-x:auto，想做"一行横向滑动"，
     但漏了最关键的一步：.ob-chip 既没有 flex:0 0 auto、也没有 white-space:nowrap。
     于是药丸照样被压到 49~66px 宽，四个字的中文标签只能一个字占一行 ——
     实测 9 个 chip 每个 150px 高、整条 .ob-rail 217px，
     等于 844px 手机屏的 26%；而它是 sticky 的，滚到哪儿盖到哪儿，
     正好压住"基础信息"那张卡片的标题。现在 nowrap 与 flex 已提到基础规则
     （见 .ob-chip），这里只负责把整排改成"一行横向滑动"。
     整条 chips 压到约 26px，.ob-rail 从 217px 降到约 90px（占屏 11%）。 */
  .ob-chips {
    gap: 5px; flex-wrap: nowrap; overflow-x: auto;
    -webkit-overflow-scrolling: touch; padding-bottom: 2px;
  }
  .ob-chip { padding: 2px 9px; font-size: 12px; }
  .ob-chips::-webkit-scrollbar { height: 0; }

  .ob-idrow { gap: 12px; }
  .ob-ro { flex-basis: 100%; grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .sc-head { gap: 6px; }
  .sc-file { margin-left: 0; }
  /* 锚点偏移要跟着 rail 的实际高度走：顶栏 62 + rail 约 90 + 一点余量。
     给 96px 是上面那个 217px 时代的旧值 —— 留太多会让"跳过去"之后
     标题上方空出一大块，看着像没跳。 */
  .ob-block, [id^="k-"], .sc-item { scroll-margin-top: calc(var(--topbar-h) + 100px); }
}

/* ---- 47.7 后台 · 材料模板列表（admin/doc_templates.php · 需求 11）-------- */
.tpl-item {
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface-2); margin-bottom: 8px; padding: 0;
}
.tpl-item[open] { background: var(--surface); }
.tpl-item.is-off { opacity: .72; }
.tpl-item > summary {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 10px 12px; cursor: pointer; list-style: none;
}
.tpl-item > summary::-webkit-details-marker { display: none; }
.tpl-item > summary::marker { content: ''; }
.tpl-dom { color: var(--brand); display: inline-flex; }
.tpl-name { font-size: 13.5px; }
.tpl-date { white-space: nowrap; }
.tpl-acts { display: inline-flex; gap: 6px; white-space: nowrap; }
.tpl-form { padding: 2px 12px 12px; border-top: 1px dashed var(--border); }
.tpl-foot { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 10px; }
.tpl-btns { display: flex; gap: 6px; }
/* 辅助表单（上架/下架、删除）：不占位，只提供 form="…" 的提交目标 */
.tpl-hidden { display: none; }
.tpl-delrow { padding: 0 12px 11px; }

/* ==========================================================================
   48. 入队申请门槛（join_us.php · 需求 10）
   --------------------------------------------------------------------------
   「不完整不能申请」这一条在视觉上要一眼看懂，否则用户只会觉得
   "按钮为什么是灰的"。所以这个卡片承担三件事：
     ① 左上角给一个 N/M 的分式（条件满足度），像进度一样；
     ② 必要项缺了 → 整行淡红底 + 左侧竖色条 + 右侧「去补」链接；
        选填项没交 → 中性灰，明确写着"不交也能提交"；
     ③ 底部按钮：能满足就亮着；不能就 disabled，并把"还差几项"写在按钮上 ——
        disabled 按钮本身不说话，必须由按钮文字说清楚原因。
   ========================================================================== */
.jgate {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}
.jgate-head {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  padding: 14px 16px;
  background: var(--surface-2);
  border-bottom: 1px solid var(--border);
}
.jg-score {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  min-width: 96px; padding: 6px 12px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.jg-score b { font-size: 24px; line-height: 1.1; color: var(--brand); }
.jg-score b small { font-size: 13px; color: var(--text-3); font-weight: 500; }
.jg-score span { font-size: 11.5px; color: var(--text-3); margin-top: 2px; }
.jg-state { flex: 1 1 240px; min-width: 0; }
.jg-state p { margin: 6px 0 0; font-size: 13px; color: var(--text-2); }

.jgate-list { padding: 12px 16px 4px; }
.jg-group + .jg-group { margin-top: 14px; }
.jg-group-t {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin: 0 0 8px; font-size: 13px; font-weight: 600; color: var(--text-2);
}
.jg-group-t i {
  font-style: normal; font-weight: 400;
  font-size: 11.5px; color: var(--text-3);
}
.jg-item {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 9px 12px; margin-bottom: 6px;
  border: 1px solid var(--border);
  border-left-width: 3px;
  border-radius: var(--radius-sm);
  background: var(--surface);
}
.jg-tick { display: inline-flex; flex: 0 0 auto; margin-top: 1px; }
.jg-txt { flex: 1 1 auto; min-width: 0; }
.jg-txt b { display: block; font-size: 13.5px; }
.jg-txt span { display: block; font-size: 12px; color: var(--text-3); margin-top: 2px; line-height: 1.5; }
.jg-go {
  flex: 0 0 auto; align-self: center;
  font-size: 12.5px; white-space: nowrap;
  padding: 4px 10px; border-radius: var(--radius-pill);
  border: 1px solid var(--border); color: var(--text-2);
}
.jg-go:hover { border-color: var(--brand-border); color: var(--brand); }

/* 已满足 / 必要项缺失 / 选填未交 —— 三态三种色彩语言 */
.jg-item.is-done { border-left-color: var(--ok-border); background: var(--ok-light); }
.jg-item.is-done .jg-tick { color: var(--ok-ink); }
.jg-item.is-miss { border-left-color: var(--danger-border); background: var(--danger-light); }
.jg-item.is-miss .jg-tick { color: var(--danger-ink); }
.jg-item.is-miss .jg-txt b { color: var(--danger-ink); }
.jg-item.is-opt  { border-left-color: var(--border); }
.jg-item.is-opt .jg-tick { color: var(--text-3); }

.jgate-foot {
  padding: 14px 16px 16px;
  border-top: 1px dashed var(--border);
  background: var(--surface-2);
}
.jgate-foot .btn-lg { width: 100%; justify-content: center; }
.jgate-foot .btn[disabled] {
  width: 100%; justify-content: center;
  opacity: .62; cursor: not-allowed;
}
.jg-hint { margin: 8px 0 0; font-size: 12px; color: var(--text-3); line-height: 1.6; }
.jg-done {
  display: flex; align-items: flex-start; gap: 9px;
  padding: 11px 13px;
  background: var(--ok-light); border: 1px solid var(--ok-border);
  border-radius: var(--radius-sm); color: var(--ok-ink); font-size: 13px;
}
.jg-done a { color: inherit; text-decoration: underline; }

@media (max-width: 760px) {
  .jgate-head { gap: 10px; padding: 12px; }
  .jg-score { min-width: 78px; padding: 5px 10px; }
  .jg-score b { font-size: 20px; }
  .jgate-list { padding: 10px 12px 2px; }
  /* 窄屏把「去补」挪到下一行靠右 —— 挤在同一行会把说明文字压成一列竖字 */
  .jg-item { flex-wrap: wrap; }
  /* 手机端这一行的唯一操作入口就是「去补这 N 项」这个链接（整行不可点），
     而它的自然高度只有 ~31px —— 手指点偏的概率明显偏高。
     这里抬到 36px 并加大左右内边距，不撑成 44px 大按钮：
     列表项本身有 9px 上下内边距，行与行之间不会被压在一起，
     为了"够点"把每行做成大按钮反而会让 6 行清单拉长一整屏。 */
  .jg-go {
    margin-left: 27px; align-self: flex-start;
    min-height: 36px; display: inline-flex; align-items: center;
    padding: 6px 14px;
  }
  .jgate-foot { padding: 12px; }
}

/* ---- 48.1 公开页提示条容器（includes/layout.php · public_start）-----------
   公开页（首页 / 入队指引 / 登录 / 核验…）没有 <main>，提示条得自己
   找一条水平基准线。宽度与站头 .site-head-inner 对齐，左右留同样的安全边距，
   否则在宽屏上提示条会顶到屏幕最左边，与页头错开一格。 */
.site-flash {
  max-width: 1180px;
  margin: 14px auto 0;
  padding: 0 20px;
}
.site-flash .alert { margin: 0 0 8px; }
@media (max-width: 760px) {
  .site-flash { padding: 0 14px; margin-top: 10px; }
}

/* ==========================================================================
   49. 公开查询页统一版式（v2.12 需求 7）

   需求原话：「队员核验 verify.php、车辆查询 vehicle_query.php、教练专家库
             experts_public.php 重新设计 UI 交互（当前太简单、太啰嗦，要符合
             2026 年视觉交付）；爱心公示 donations_public.php 也一样改。」

   「太简单」指的是：页面主体只有一个居中标题 + 一行灰字说明 + 一块白面板，
   三块扁平统计框看着像占位符，整页缺乏视觉重心。
   「太啰嗦」指的是：同一句话在四处出现 —— 页头 desc、section-head 的 <p>、
   面板里的 hint、页面底部的「信息说明」卡。读者要读三遍才确认自己看懂了一句。

   所以这次的改法不是"再加点装饰"，而是：
     ① 立重心   —— 顶部一块品牌渐变的 hero 卡，标题、一句话主张、右侧玻璃质感
                   KPI 数字。整页第一眼有明确的主语。
     ② 去重复   —— 同一件事只说一遍。长的解释统一收进 .pp-note 的
                   <details> 里，默认折叠（想读的人点开，不想读的人不被挡路）。
     ③ 有层级   —— 筛选卡上浮压住 hero 下沿，形成"标题区 → 操作区 → 结果区"
                   三段式，而不是三个等重的白盒子。
     ④ 统一语言 —— 四页共用同一套类名，以后改一处四页一起变；
                   不会出现"核验页像 2024、车辆页像 2022"。

   为什么 hero 用卡片而不是通栏：
     站点的公开页外壳是 .section（max-width 1180 居中）。做成通栏要么给整个
     外壳加负边距 hack，要么再写一套页面容器 —— 两种都容易在窄屏上出错。
     圆角卡片在 1180 容器里同样是"一整块品牌面"，而且和站内其它卡片同族。
   ========================================================================== */

.pp-hero {
  position: relative;
  overflow: hidden;
  border-radius: 20px;
  /* 底部内边距特意大于顶部：下面那块筛选卡要用负 margin 压上来 30px，
     压完之后 KPI 卡与筛选卡之间还得留出十几像素的呼吸位 ——
     否则 KPI 的下边框会紧贴筛选卡的上边框，看着像被裁掉了一截
     （实测原来只有 6px，观感上就是"数字块被面板切了"）。 */
  padding: 30px clamp(22px, 3vw, 36px) 44px;
  color: #fff;
  background:
    radial-gradient(700px 340px at 84% -14%, rgba(93, 156, 255, .40), transparent 62%),
    radial-gradient(520px 300px at 0% 112%, rgba(29, 95, 208, .46), transparent 66%),
    linear-gradient(150deg, #0A2550 0%, #123C86 54%, #1D5FD0 100%);
  box-shadow: 0 20px 44px -26px rgba(12, 42, 97, .62);
}
/* 网格纹理：只在上半部分可见，压得很淡 —— 纯渐变在宽屏上会显得"平" */
.pp-hero::before {
  content: "";
  position: absolute; inset: 0;
  background-image:
    linear-gradient(rgba(255, 255, 255, .055) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255, 255, 255, .055) 1px, transparent 1px);
  background-size: 46px 46px;
  -webkit-mask-image: radial-gradient(ellipse at 70% 0%, #000 10%, transparent 74%);
          mask-image: radial-gradient(ellipse at 70% 0%, #000 10%, transparent 74%);
  pointer-events: none;
}
/* 右下角水印：故意另画一个盾牌 SVG（不用 lt_logo_mark），
   因为那个会跟着管理员上传的位图 Logo 变，放大成水印会糊成一块。 */
.pp-hero-mark {
  position: absolute; right: -46px; bottom: -40px;
  width: clamp(150px, 17vw, 230px); height: auto;
  opacity: .07; pointer-events: none;
}
.pp-hero-in {
  position: relative; z-index: 1;
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  gap: 24px 30px; align-items: end;
}
.pp-kicker {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 4px 12px 4px 10px; margin-bottom: 14px;
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, .14);
  border: 1px solid rgba(255, 255, 255, .18);
  font-size: 11.5px; font-weight: 600; letter-spacing: 1.1px;
  text-transform: uppercase; color: #DCEAFB;
}
.pp-kicker svg { width: 14px; height: 14px; }
.pp-hero h1 {
  color: #fff; margin: 0 0 10px;
  font-size: clamp(24px, 2.5vw, 33px); line-height: 1.22; letter-spacing: -.3px;
}
.pp-lead { margin: 0; max-width: 46em; font-size: 14px; line-height: 1.75; color: #BFD5F4; }
.pp-lead b { color: #fff; font-weight: 600; }

/* ---- 右侧 KPI：玻璃质感，数字用等宽数字避免逐帧跳动 ---- */
.pp-kpis {
  display: grid; grid-auto-flow: column; grid-auto-columns: minmax(96px, auto);
  gap: 10px; flex: 0 0 auto;
}
.pp-kpi {
  padding: 13px 16px 14px; min-width: 0;
  border-radius: 14px;
  /* 底色比纯玻璃再实一点：hero 渐变越往下越亮，太透的卡在右下角会
     和白色筛选卡连成一片，看不出卡片边界。 */
  background: rgba(255, 255, 255, .14);
  border: 1px solid rgba(255, 255, 255, .2);
  -webkit-backdrop-filter: blur(6px);
          backdrop-filter: blur(6px);
}
.pp-kpi b {
  display: block; color: #fff; font-weight: 800; letter-spacing: -.5px;
  font-size: clamp(20px, 1.75vw, 26px); line-height: 1.1;
  font-variant-numeric: tabular-nums;
}
.pp-kpi span { display: block; margin-top: 4px; font-size: 11.5px; color: #A8C4E8; white-space: nowrap; }

/* ---- 筛选卡：上浮压住 hero 下沿 ---- */
.pp-panel {
  position: relative; z-index: 2;
  margin: -30px 0 0;
  padding: 20px clamp(16px, 2.2vw, 24px) 18px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 18px;
  box-shadow: 0 16px 36px -26px rgba(12, 42, 97, .34);
}
.pp-bar { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
.pp-bar .select { flex: 0 0 150px; }
.pp-bar .input { flex: 1 1 260px; min-width: 0; }
.pp-bar .btn { flex: 0 0 auto; padding: 9.5px 24px; }
/* 次要筛选（类型 / 状态 / 年份…）：跟在搜索行下面，用发丝线隔开 */
.pp-sub {
  display: flex; gap: 10px; flex-wrap: wrap; align-items: center;
  margin-top: 14px; padding-top: 14px; border-top: 1px dashed var(--border);
}
.pp-sub .select { flex: 0 0 140px; }
.pp-sub .pp-sub-lbl {
  font-size: 11.5px; font-weight: 700; letter-spacing: .8px;
  text-transform: uppercase; color: var(--text-3);
}
.pp-sub .btn { padding: 8px 18px; }
/* 「确认筛选」是这一页的**主操作键**（爱心公示页的年份/类型筛选），
   不能跟旁边的「重置条件」一样高 —— 更不能比上面那行的「搜索」还矮：
   上一版两条都吃 8px 的 padding，主键只有 34px，比搜索键（40px）矮一档，
   主次颠倒；手机上 34px 也低于 40px 的舒适点击高度。
   所以只把 .btn-brand 抬回 40px，幽灵键保持 34px，一大一小正好分主次。 */
/* 用 min-height 钉高度，不用 padding 凑 —— .pp-sub 里的 .btn 基数与
   .pp-bar 不同（实测 padding 8→9.5 只长 3px，凑到 40 要反复试），
   高度这件事直接说清楚比猜 padding 可靠。 */
.pp-sub .btn-brand { min-height: 40px; padding: 8px 22px; }
.pp-hint {
  margin: 12px 0 0; font-size: 12px; line-height: 1.7; color: var(--text-3);
}
.pp-hint .mono { font-size: 11.5px; }

/* ---- 折叠说明：默认只给一行摘要，点开才展开全文 ---- */
.pp-note {
  margin: 18px 0 0;
  border: 1px solid var(--border); border-radius: 14px;
  background: var(--surface-2);
}
.pp-note > summary {
  display: flex; align-items: center; gap: 9px;
  padding: 12px 16px; cursor: pointer; list-style: none;
  font-size: 13px; font-weight: 600; color: var(--text-2);
}
.pp-note > summary::-webkit-details-marker { display: none; }
.pp-note > summary svg { width: 16px; height: 16px; flex: none; color: var(--brand); }
.pp-note > summary .pp-note-more {
  margin-left: auto; font-size: 12px; font-weight: 600; color: var(--brand);
  display: inline-flex; align-items: center; gap: 3px;
}
.pp-note > summary .pp-note-more svg { width: 13px; height: 13px; transition: transform .18s; }
.pp-note[open] > summary .pp-note-more svg { transform: rotate(180deg); }
.pp-note[open] > summary .pp-note-more b { display: none; }
.pp-note-act { display: inline-flex; align-items: center; gap: 2px; }
.pp-note-act .pp-note-close { display: none; }
.pp-note[open] .pp-note-act .pp-note-open  { display: none; }
.pp-note[open] .pp-note-act .pp-note-close { display: inline; }
.pp-note-body {
  padding: 0 16px 15px; font-size: 13px; line-height: 1.85; color: var(--text-2);
  border-top: 1px dashed var(--border); padding-top: 13px; margin-top: 0;
}
.pp-note-body ul { margin: 8px 0 0; padding-left: 18px; }
.pp-note-body li { margin-bottom: 5px; }
.pp-note-body li:last-child { margin-bottom: 0; }
.pp-note-body b { color: var(--text-1); }

/* ---- 结果区标题条 ---- */
.pp-reshead {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin: 26px 0 14px;
}
.pp-reshead h2 { font-size: 17px; margin: 0; }
.pp-reshead .pp-reshead-hint { margin-left: auto; font-size: 12px; color: var(--text-3); }

/* ---- 空态 ---- */
.pp-empty {
  padding: 44px 24px 46px; text-align: center;
  border: 1px dashed var(--border-strong); border-radius: 16px;
  background: var(--surface-2);
}
.pp-empty-ico {
  display: inline-flex; align-items: center; justify-content: center;
  width: 62px; height: 62px; margin-bottom: 14px;
  border-radius: 50%; color: var(--brand);
  background: var(--brand-light); border: 1px solid var(--brand-border);
}
.pp-empty-ico svg { width: 28px; height: 28px; }
.pp-empty b { display: block; font-size: 15.5px; margin-bottom: 6px; }
.pp-empty p { margin: 0 auto; max-width: 480px; font-size: 13px; line-height: 1.8; color: var(--text-3); }

/* ---- 底部互跳卡（核验 ↔ 车辆，公示 ↔ 专家…） ---- */
.pp-xlink {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  margin-top: 24px; padding: 18px clamp(16px, 2.2vw, 24px);
  border-radius: 16px;
  border: 1px solid var(--brand-border);
  background: linear-gradient(100deg, var(--brand-lighter), var(--surface) 72%);
}
.pp-xlink-ico {
  display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; flex: none; border-radius: 12px;
  color: var(--brand); background: var(--surface);
  border: 1px solid var(--brand-border);
}
.pp-xlink-txt { min-width: 200px; flex: 1 1 auto; }
.pp-xlink-txt b { display: block; font-size: 14.5px; }
.pp-xlink-txt span { display: block; margin-top: 3px; font-size: 12.5px; color: var(--text-3); }
.pp-xlink .btn { flex: 0 0 auto; }

/* ---- 手机端 ---- */
@media (max-width: 900px) {
  .pp-hero-in { grid-template-columns: minmax(0, 1fr); gap: 20px; align-items: start; }
  /* KPI 换行摊平：grid-auto-flow:column 在窄屏会把每格压成一条竖字。
     列数按 KPI 个数定（PHP 侧输出 is-N），三种/四种各用最合适的排法：
       3 个 → 一行三列（不会像 2 列那样把第三个孤零零甩到第二行）
       4 个 → 两行两列（一行四列在 390 宽下每格只剩 78px，金额会被压断） */
  .pp-kpis { grid-auto-flow: row; grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .pp-kpis.is-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 640px) {
  .pp-hero { border-radius: 16px; padding: 22px 18px 32px; }
  .pp-kpi { padding: 11px 13px 12px; }
  .pp-kpi span { white-space: normal; }
  .pp-panel { margin-top: -20px; padding: 16px 14px 15px; border-radius: 15px; }
  .pp-bar .select, .pp-bar .input, .pp-bar .btn { flex: 1 1 100%; }
  .pp-bar .btn { justify-content: center; }
  .pp-sub .select { flex: 1 1 calc(50% - 5px); }
  .pp-sub .pp-sub-lbl { flex: 1 1 100%; }
  .pp-reshead .pp-reshead-hint { margin-left: 0; flex: 1 1 100%; }
  .pp-xlink { gap: 12px; }
  .pp-xlink-txt { min-width: 0; flex: 1 1 100%; }
  .pp-xlink .btn { flex: 1 1 100%; justify-content: center; }
}

/* --------------------------------------------------------------------------
   49.1 教练专家卡（v2.12 需求 7 · experts_public.php）

   原实现是"通用 card + 一堆行内 style"：头像 40px 挤在左上、领域徽标与
   资质清单直接铺开，四张卡排下来像四段散文。这里改成有明确分区的小卡：
     顶栏（头像 / 姓名 / 头衔机构 / 执教年限）→ 领域徽标 → 简介 → 资质清单 → 页脚。
   每个分区之间用发丝线或留白断开，扫一眼就知道先看哪里。
   -------------------------------------------------------------------------- */
.ex-grid {
  display: grid; gap: 16px;
  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
}
.ex-card {
  display: flex; flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 16px;
  overflow: hidden;
  transition: box-shadow .18s, border-color .18s, transform .18s;
}
.ex-card:hover {
  border-color: var(--brand-border);
  box-shadow: 0 18px 34px -24px rgba(12, 42, 97, .4);
  transform: translateY(-2px);
}
@media (prefers-reduced-motion: reduce) {
  .ex-card { transition: none; }
  .ex-card:hover { transform: none; }
}
/* 有形象照时压一条 16:10 封面；没传图就完全不占位（不显示灰框） */
.ex-cover { display: block; width: 100%; aspect-ratio: 16 / 10; object-fit: cover; }

.ex-body { padding: 16px 17px 16px; display: flex; flex-direction: column; flex: 1; }
.ex-top { display: flex; align-items: flex-start; gap: 12px; }
.ex-av {
  width: 54px; height: 54px; flex: 0 0 54px; border-radius: 14px;
  object-fit: cover; border: 1px solid var(--border);
  background: var(--brand-lighter);
}
.ex-id { min-width: 0; flex: 1 1 auto; }
.ex-id h3 {
  margin: 0; font-size: 16px; line-height: 1.3;
  display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
}
.ex-id .ex-sub {
  display: block; margin-top: 3px;
  font-size: 12.5px; color: var(--text-3); line-height: 1.5;
  overflow-wrap: anywhere;
}
.ex-years {
  flex: 0 0 auto; font-size: 11.5px; font-weight: 700;
  color: var(--brand-dark); background: var(--brand-light);
  border: 1px solid var(--brand-border);
  border-radius: var(--radius-pill); padding: 3px 9px;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.ex-doms { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 13px; }
.ex-intro {
  margin: 13px 0 0; padding-top: 13px;
  border-top: 1px dashed var(--border);
  font-size: 13px; line-height: 1.8; color: var(--text-2);
}
/* 资质清单：短横线打头，比原来的圆点列表省一行高度，也更像"凭证清单" */
.ex-certs { margin: 13px 0 0; padding: 12px 13px; list-style: none;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 12px; }
.ex-certs-t {
  display: block; margin-bottom: 7px;
  font-size: 11px; font-weight: 700; letter-spacing: .9px;
  text-transform: uppercase; color: var(--text-3);
}
.ex-certs li {
  position: relative; padding-left: 13px; margin-bottom: 4px;
  font-size: 12.5px; line-height: 1.65; color: var(--text-2);
}
.ex-certs li:last-child { margin-bottom: 0; }
.ex-certs li::before {
  content: ""; position: absolute; left: 0; top: .62em;
  width: 5px; height: 1.5px; border-radius: 1px; background: var(--brand);
}
.ex-foot {
  margin-top: auto; padding-top: 14px;
  display: flex; align-items: center; gap: 10px;
}
.ex-foot .ex-foot-hint { font-size: 11.5px; color: var(--text-3); }

/* 领域筛选胶囊条：横向滚动而不是折行 ——
   10 个领域在 390 宽下折成 3 行会把筛选区和结果区顶开一整屏。 */
.pp-doms {
  display: flex; flex-wrap: wrap; gap: 8px; margin: 18px 0 4px;
}
@media (max-width: 640px) {
  .ex-grid { grid-template-columns: minmax(0, 1fr); gap: 13px; }
  .ex-body { padding: 14px 14px 15px; }
  .ex-av { width: 46px; height: 46px; flex-basis: 46px; border-radius: 12px; }
  .pp-doms {
    flex-wrap: nowrap; overflow-x: auto; -webkit-overflow-scrolling: touch;
    scrollbar-width: none; padding-bottom: 2px; margin: 14px 0 2px;
  }
  .pp-doms::-webkit-scrollbar { display: none; }
  .pp-doms .chip { flex: 0 0 auto; white-space: nowrap; }
}

/* --------------------------------------------------------------------------
   49.2 爱心公示 · 捐赠明细行（v2.12 需求 7 · donations_public.php）

   原来每条捐赠是一行 `.list-row`：图标 + 标题串 + 右侧两个按钮。
   金额藏在标题行的一枚红色徽标里，一屏扫下来金额不齐、对不齐 ——
   而"公示"这个场景里，**金额本身就是最该被一眼扫到的东西**。
   改成三栏：左侧图标 / 中间主体 / 右侧金额（大号、等宽数字、右对齐）
   + 操作按钮。金额竖着排成一列，扫读时能直接比大小。
   -------------------------------------------------------------------------- */
.dn-list { display: flex; flex-direction: column; }
.dn-row {
  display: flex; align-items: flex-start; gap: 14px;
  padding: 16px 18px;
  border-bottom: 1px solid var(--border);
  transition: background .15s;
}
.dn-row:last-child { border-bottom: 0; }
.dn-row:hover { background: var(--surface-hover); }
.dn-mark {
  width: 40px; height: 40px; flex: 0 0 40px; border-radius: 12px;
  display: flex; align-items: center; justify-content: center;
  background: var(--danger-light); color: var(--danger);
}
.dn-mark.is-anon { background: var(--brand-light); color: var(--brand); }
.dn-mark svg { width: 19px; height: 19px; }
.dn-main { flex: 1 1 auto; min-width: 0; }
.dn-title {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  font-size: 15px; font-weight: 700; line-height: 1.4;
}
.dn-title a { color: var(--text); }
.dn-title a:hover { color: var(--brand); }
.dn-goods { margin-top: 6px; font-size: 12.5px; line-height: 1.65; color: var(--text-2); overflow-wrap: anywhere; }
.dn-meta {
  margin-top: 6px; display: flex; gap: 16px; flex-wrap: wrap;
  font-size: 12px; color: var(--text-3);
}
.dn-right {
  flex: 0 0 auto; display: flex; flex-direction: column;
  align-items: flex-end; gap: 10px;
}
.dn-amt {
  font-size: 19px; font-weight: 800; letter-spacing: -.4px;
  color: var(--danger); font-variant-numeric: tabular-nums; white-space: nowrap;
}
.dn-amt.is-goods { font-size: 13.5px; color: var(--brand); letter-spacing: 0; }
.dn-acts { display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }

@media (max-width: 640px) {
  .dn-row { flex-wrap: wrap; padding: 14px 14px; gap: 11px; }
  .dn-mark { width: 34px; height: 34px; flex-basis: 34px; border-radius: 10px; }
  .dn-mark svg { width: 17px; height: 17px; }
  /* 手机上金额与操作换到第二行：金额在左、按钮在右，
     右侧不留那半屏空白（竖排三栏在 390 宽下主体只剩 150px）。 */
  .dn-right {
    flex: 1 1 100%; flex-direction: row; align-items: center;
    justify-content: space-between; gap: 10px;
    padding-left: 45px;
  }
  .dn-amt { font-size: 17px; }
  .dn-acts { flex: 0 0 auto; }
  .dn-acts .btn { padding: 6px 11px; font-size: 12px; }
}

/* ---------------------------------------------------------------------------
   49.3 卡片内信息条 .pp-callout

   公开页改版前，卡片里的补充说明用的是旧皮肤的 .alert.alert-info（实心浅蓝块 +
   居中留白），混在新版版式里一眼就能看出是"上一代"的零件。这里换成一枚轻量
   提示条：淡品牌底 + 左侧 3px 品牌色描边 + 图标与文字基线对齐，克制但成套。

   只改公开页用到的形态，不动 .alert（后台表单、错误页还在用它）。
--------------------------------------------------------------------------- */
.pp-callout {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 11px 14px;
  border: 1px solid var(--brand-border);
  border-left: 3px solid var(--brand);
  border-radius: var(--radius-sm);
  background: var(--brand-lighter);
  color: var(--text-2); font-size: 13px; line-height: 1.66;
}
.pp-callout > svg { flex: 0 0 auto; margin-top: 2px; color: var(--brand); }
.pp-callout b { color: var(--text); }
.pp-callout a { color: var(--brand); font-weight: 600; }

/* ---------------------------------------------------------------------------
   50. 技能资质标签样式编辑器（v2.12 需求 11）

   后台「系统设置 → 外观皮肤」里的那一块：左边实时预览、右边改参数。
   版面照抄登录页背景那块的 .ls-edit（左边看右边调），
   同一个后台里两处"编辑可视化外观"的交互不该长得不一样。

   .sk-sw 的选中态为什么用**双环**（内白圈 + 外色圈）而不是加个对勾：
   12 个色块并排时，颜色本身就是主角，塞个白色对勾会把色块染成半个图案；
   双环是"围着它画一圈"，不动色块本身。深色（岩土棕）与浅色（晴空蓝）
   两种底色上都能看清选中了哪一个。
--------------------------------------------------------------------------- */
.sk-edit {
  display: grid;
  grid-template-columns: minmax(0, 300px) minmax(0, 1fr);
  gap: 18px;
  align-items: start;
}
.sk-view {
  padding: 16px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
}
.sk-stage { min-height: 104px; display: flex; align-items: center; }
.sk-view-tip {
  margin-top: 10px; font-size: 12px; color: var(--text-3);
  text-align: center; line-height: 1.6;
}
.sk-form { min-width: 0; }
.sk-form .field { margin-bottom: 12px; }
.sk-form .field.is-off { opacity: .42; }
.sk-form .field.is-off input { pointer-events: none; }

.sk-cwrap { display: flex; align-items: center; gap: 9px; }
.sk-color {
  width: 46px; height: 32px; padding: 2px;
  border: 1px solid var(--border-strong); border-radius: var(--radius-xs);
  background: var(--surface); cursor: pointer;
}
.sk-color::-webkit-color-swatch-wrapper { padding: 0; }
.sk-color::-webkit-color-swatch { border: 0; border-radius: 5px; }
.sk-hex { font-size: 12.5px; color: var(--text-2); }

.sk-pal { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.sk-sw {
  width: 24px; height: 24px; padding: 0; border: 0; border-radius: 7px;
  background: var(--c); cursor: pointer;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .12);
  transition: transform .12s ease, box-shadow .12s ease;
}
.sk-sw:hover { transform: translateY(-2px); }
.sk-sw.is-on {
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .12),
              0 0 0 2px var(--surface-2), 0 0 0 4px var(--c);
}

.sk-seg {
  display: inline-flex; flex-wrap: wrap; gap: 3px;
  padding: 3px;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface-2);
}
.sk-seg-i { position: relative; }
.sk-seg-i input { position: absolute; opacity: 0; pointer-events: none; }
.sk-seg-i > span {
  display: block; padding: 5px 12px; border-radius: var(--radius-xs);
  font-size: 12.5px; color: var(--text-2); cursor: pointer;
  transition: background .14s ease, color .14s ease;
}
.sk-seg-i > span:hover { color: var(--text); }
.sk-seg-i input:checked + span {
  background: var(--surface); color: var(--brand-dark);
  font-weight: 600; box-shadow: var(--shadow-sm);
}
.sk-seg-i input:focus-visible + span { outline: 2px solid var(--brand-mid); outline-offset: 1px; }
.sk-num { color: var(--brand); font-weight: 600; }

@media (max-width: 900px) {
  .sk-edit { grid-template-columns: minmax(0, 1fr); }
  .sk-stage { min-height: 0; }
}

/* ==========================================================================
   ★ v2.14 需求 1（v2.17 需求 1 更名并加生日窗口）：队史上的今天
   （admin/today_history.php）
   --------------------------------------------------------------------------
   这一页只在这一处用到，所以前缀统一 .th-*，不与别处共用；
   .badge / .table / .empty / .stat-* 全部复用既有的，不重新发明。

   两条设计约定：
     · 生日 / 周年是「人」的清单，用头像行（.th-row）而不是表格 ——
       头像比姓名一眼认得人，值班室念名单时更顺手；
     · 往年的任务是「事」的清单，用表格 —— 一行七个字段要能横向比较。
   ========================================================================== */
.th-year { margin-bottom: 18px; }
.th-year:last-child { margin-bottom: 4px; }
.th-year-badge {
  display: inline-flex; align-items: baseline; gap: 8px;
  margin-bottom: 8px; padding: 4px 12px;
  border-radius: var(--radius-pill);
  background: var(--brand-lighter);
  border: 1px solid var(--brand-border);
}
.th-year-badge b {
  font-size: 15px; font-weight: 800; color: var(--brand-dark);
  font-variant-numeric: tabular-nums; letter-spacing: .3px;
}
.th-year-badge span { font-size: 11.5px; color: var(--text-3); }

.th-list { display: flex; flex-direction: column; gap: 8px; }
.th-row {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 11px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface);
}
/* 今天生日的那一批加一层品牌蓝底：名单里最该被先看到的行 */
.th-row.is-today { background: var(--brand-lighter); border-color: var(--brand-border); }

/* v2.17 需求 1：只看未来 7 天时，按"哪天"分段；1 天（明天）那一段标品牌色 ——
   「明天」是唯一还需要提前张罗的一天（订蛋糕、备礼物），值得被先看到。 */
.th-dayhd {
  display: flex; align-items: baseline; gap: 8px;
  margin: 12px 0 8px; padding-bottom: 6px;
  border-bottom: 1px dashed var(--border);
}
.th-dayhd:first-child { margin-top: 0; }
.th-dayhd b { font-size: 13px; font-weight: 700; color: var(--text-2); }
.th-dayhd.is-near b { color: var(--brand); }
.th-av {
  flex: 0 0 auto; width: 38px; height: 38px; border-radius: 50%;
  object-fit: cover; background: var(--surface-2);
}
.th-main { min-width: 0; flex: 1 1 auto; }
.th-name {
  display: flex; align-items: center; flex-wrap: wrap; gap: 4px 6px;
  font-size: 13.5px; font-weight: 600; color: var(--text);
  min-width: 0;
}
.th-name a { color: var(--text); }
.th-name a:hover { color: var(--brand); }
.th-nick { font-size: 12px; font-weight: 500; color: var(--text-3); }
.th-meta {
  display: flex; flex-wrap: wrap; gap: 3px 12px;
  margin-top: 3px; font-size: 11.5px; color: var(--text-3);
}
.th-meta > span { display: inline-flex; align-items: center; gap: 4px; }
.th-meta svg { width: 12px; height: 12px; opacity: .8; }
.th-act { flex: 0 0 auto; }

/* 手机上：手机号复制按钮挤不下，让它落到姓名那一行下面占满整行。
   不隐藏它 —— 值班室大量操作是在手机上做的，复制手机号正是那时候要用的。 */
@media (max-width: 640px) {
  .th-row { flex-wrap: wrap; }
  .th-act { width: 100%; }
  .th-act .btn { width: 100%; }
}

/* ==========================================================================
   ★ v2.17 需求 1：登录祝福弹窗（includes/blessing.php）
   --------------------------------------------------------------------------
   只在队员生日当天 / 入队整年时出现，**一天一次**（标记在 cookie 里）。
   遮罩与定位直接复用既有的 .modal-mask / .modal-box（含"点空白关闭、
   Esc 关闭"那套脚本），这里只加祝福卡自己的排版。
   为什么所有颜色都走 CSS 变量、一个色值都不写死：
   十套皮肤里有两套是深色。写死"白底黑字"的弹窗在深色皮肤上会变成一块
   突兀的白板 —— 而这恰恰是给队员看的第一屏，最不该出错的地方。
   ========================================================================== */
.bless-box {
  max-width: 420px;
  padding: 26px 22px 22px;
  text-align: center;
  border: 1px solid var(--brand-border);
}
.bless-x {
  position: absolute; top: 10px; right: 10px;
  width: 30px; height: 30px; padding: 0; display: grid; place-items: center;
  border: 0; border-radius: 50%; cursor: pointer;
  background: transparent; color: var(--text-3);
}
.bless-x:hover { background: var(--surface-2); color: var(--text); }
.bless-box { position: relative; }
.bless-ico {
  width: 58px; height: 58px; margin: 0 auto 12px;
  display: grid; place-items: center; border-radius: 50%;
  background: var(--brand-lighter); color: var(--brand);
}
.bless-title { margin: 0 0 14px; font-size: 18px; font-weight: 800; color: var(--text); }
.bless-line + .bless-line { margin-top: 12px; padding-top: 12px; border-top: 1px dashed var(--border); }
.bless-kind {
  display: inline-block; margin-bottom: 6px; padding: 2px 10px;
  border-radius: var(--radius-pill);
  background: var(--brand-lighter); color: var(--brand-dark);
  font-size: 11.5px; font-weight: 700;
}
.bless-quote { margin: 0; font-size: 14px; line-height: 1.75; color: var(--text-2); }
.bless-foot { margin-top: 18px; }

/* v2.23 需求 4：教人「关掉以后去哪再看一眼」。
   必须常驻、不折叠、不靠 hover —— 人是在按下「收下祝福」的前一秒
   才会想到"关了就没了？"，那时候任何需要额外操作才能看到的提示都来不及。
   小字、浅底、贴着按钮上方，位置比字号更重要。 */
.bless-where {
  display: flex;
  align-items: flex-start;
  gap: 6px;
  margin: 16px 0 0;
  padding: 8px 10px;
  border-radius: 8px;
  background: var(--surface-2);
  font-size: 12.5px;
  line-height: 1.6;
  color: var(--text-3);
  text-align: left;
}
.bless-where svg { flex: 0 0 auto; margin-top: 2px; color: var(--brand); opacity: .8; }
.bless-where > span { min-width: 0; }
.bless-where b { color: var(--text-2); font-weight: 600; }

/* v2.18 需求 9：弹窗底部的「保存图片 / 分享祝福」。
   放在主按钮**下面**、字号更小、用 ghost 款 —— 主操作仍然是「收下祝福」，
   这两个是"顺手多做一件事"，不该跟主按钮抢注意力。 */
.bless-share {
  margin-top: 12px; padding-top: 12px;
  border-top: 1px dashed var(--border);
  display: flex; flex-wrap: wrap; gap: 8px; justify-content: center;
}
.bless-share .btn { display: inline-flex; align-items: center; gap: 5px; }
.bless-share .btn[disabled] { opacity: .6; cursor: default; }

@media (max-width: 520px) {
  .bless-box { padding: 22px 16px 18px; }
  .bless-title { font-size: 16px; }
  .bless-quote { font-size: 13.5px; }
}

/* v2.22 需求 4：「我的档案 → 我的祝福」里那几条可点回放的行。
   一行里放得下四样：什么名目、哪一天、语录开头、再看一次。
   窄屏时语录先折到第二行（.bl-text 给 flex:1 1 100%），
   名目和日期留在第一行，右边那个"再看一次"始终可见 —— 它是这一行的目的。 */
.bless-log { display: flex; flex-direction: column; gap: 8px; }
.bl-item {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text-2);
  text-decoration: none;
  transition: border-color .12s, background .12s, box-shadow .12s;
}
.bl-item:hover {
  border-color: var(--brand);
  background: var(--brand-light);
  box-shadow: 0 2px 10px rgba(29, 95, 208, .08);
}
.bl-kind {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-weight: 700;
  color: var(--text);
  white-space: nowrap;
}
.bl-kind svg { color: var(--brand); }
.bl-day { font-size: 12px; color: var(--text-3); font-variant-numeric: tabular-nums; }
.bl-text { flex: 1 1 100%; font-size: 12.5px; color: var(--text-3); }
.bl-go {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 2px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--brand);
  white-space: nowrap;
}
@media (min-width: 760px) {
  .bl-text { flex: 1 1 auto; }
}

/* ==========================================================================
   ★ v2.18 需求 9：祝福卡（bless_card.php 分享页 + 弹窗里的「保存图片」）
   --------------------------------------------------------------------------
   ★ 这一段的色值**一律写死**，不跟皮肤变量走。
     下载下来的图片是脱离页面的静态文件，不可能带皮肤；卡面若跟着皮肤变，
     用户拿到的图就与他看到的页面不一样 —— 而"分享出去的那张图好不好看"
     正是这次需求的核心诉求。深蓝 + 白卡 + 金色点缀是队伍主视觉，
     十套皮肤（含两套深色）下都成立。
     canvas 那边（assets/js/app.js 的 blessCanvas）用的是同一套色值，
     所以页面上看到的和下载到的长得一样。
   ========================================================================== */
.bcard-wrap { max-width: 560px; margin: 0 auto; padding: 8px 0 28px; }

.bcard {
  position: relative;
  border-radius: 24px;
  overflow: hidden;
  background: linear-gradient(150deg, #0A2757 0%, #123C86 52%, #1D5FD0 100%);
  box-shadow: 0 18px 44px rgba(6, 22, 52, .28);
  color: #fff;
}
/* 两团柔光：右上大、左下小。纯色底会显得很平，这两下就够撑起纵深 */
.bcard::before,
.bcard::after {
  content: ''; position: absolute; border-radius: 50%; pointer-events: none; z-index: 0;
}
.bcard::before {
  width: 300px; height: 300px; top: -130px; right: -90px;
  background: radial-gradient(circle, rgba(255, 255, 255, .18) 0%, rgba(255, 255, 255, 0) 70%);
}
.bcard::after {
  width: 240px; height: 240px; bottom: 26%; left: -120px;
  background: radial-gradient(circle, rgba(255, 255, 255, .10) 0%, rgba(255, 255, 255, 0) 70%);
}

.bcard-top {
  position: relative; z-index: 1;
  display: flex; align-items: center; gap: 12px;
  padding: 18px 20px 34px;
}
.bcard-logo {
  flex: 0 0 auto; width: 42px; height: 42px;
  display: grid; place-items: center; border-radius: 13px;
  background: rgba(255, 255, 255, .14); border: 1px solid rgba(255, 255, 255, .26);
}
.bcard-logo-img { width: 100%; height: 100%; object-fit: contain; border-radius: 12px; display: block; }
.bcard-logo-mark { display: block; line-height: 0; }
.bcard-team { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.bcard-team strong { font-size: 17px; font-weight: 800; letter-spacing: .5px; }
.bcard-team em {
  font-size: 10.5px; font-style: normal; font-weight: 600;
  letter-spacing: 2.4px; color: rgba(255, 255, 255, .62);
}
.bcard-day { flex: 0 0 auto; font-size: 12.5px; font-weight: 600; color: rgba(255, 255, 255, .82); }

/* 白卡向上压住深蓝抬头，做出叠一层的层次 */
.bcard-main {
  position: relative; z-index: 1;
  margin: 0 14px;
  padding: 0 20px 22px;
  border-radius: 20px;
  overflow: hidden;
  background: #fff;
  color: #12305E;
  text-align: center;
  box-shadow: 0 10px 30px rgba(4, 18, 45, .18);
}
/* 卡片顶部的渐变装饰条。
   色标给得密是有原因的：蓝直接插到金，中间会经过一段发脏的橄榄绿
   （RGB 线性插值的必然结果）。垫两个浅色站，过渡就落在"亮"的一侧。
   canvas 那边（blessCanvas）用的是同一串色标，图与页面才一致。 */
.bcard-main::before {
  content: ''; display: block; height: 6px; margin: 0 -20px;
  background: linear-gradient(90deg, #1D5FD0 0%, #3E92EA 46%, #8AC6F5 62%, #F0CF86 78%, #E9B949 100%);
}
.bcard-ava {
  position: relative; display: block;
  width: 128px; height: 128px; margin: 20px auto 0;
  padding: 5px; box-sizing: border-box;
  border-radius: 50%; background: #EAF1FC;
}
.bcard-ava img { width: 100%; height: 100%; border-radius: 50%; object-fit: cover; display: block; }
/* 头像右下角的小金徽章。★ 里面要有那颗星 —— canvas 画的图里是有的，
   页面上若只有一个光秃秃的圆点，两边就不一致了（用户会拿图与页面对比）。 */
.bcard-ava::after {
  content: '\2605';
  display: grid; place-items: center;
  position: absolute; right: 5px; bottom: 5px;
  width: 30px; height: 30px; border-radius: 50%;
  background: #E9B949; border: 3px solid #fff;
  color: #fff; font-size: 14px; line-height: 1;
}
.bcard-name { margin: 14px 0 0; font-size: 27px; font-weight: 800; letter-spacing: 1px; color: #12305E; }
.bcard-meta { margin: 6px 0 0; font-size: 13px; color: #7C8AA3; }
.bcard-hr { display: block; height: 0; margin: 18px 20px 0; border-top: 2px dashed #DCE4F0; }
.bcard-items { padding: 18px 0 0; }
.bcard-item + .bcard-item { margin-top: 18px; }
.bcard-badge {
  display: inline-block; padding: 6px 20px; border-radius: 999px;
  color: #fff; font-size: 14px; font-weight: 700;
  background: linear-gradient(90deg, #1D5FD0, #3B92EA);
}
.bcard-quote { margin: 12px 0 0; font-size: 15px; line-height: 1.85; color: #2B3B55; }
.bcard-tail { margin: 20px 0 0; font-size: 12.5px; color: #AFBACB; }

/* ---- 救援履历（v2.24 需求 9）----
   这一段与 canvas 上的画法一一对应（assets/js/app.js 的 blessResumeBox）：
   「两侧细线夹标题 → 两列数字格 → 每串清单一行胶囊」。
   改这里就必须同步改那边，否则用户下载到的图与页面看到的不是一张卡。
   颜色一律写死，不跟皮肤走 —— 理由见 bless_card_html 的注释。 */
.bcard-resume { margin: 26px 0 0; }
.bcard-rhd {
  display: flex; align-items: center; gap: 12px; margin: 0 0 18px;
  font-size: 12.5px; font-weight: 700; letter-spacing: 4px; color: #8696AE;
}
.bcard-rhd::before,
.bcard-rhd::after { content: ""; flex: 1 1 auto; height: 1px; background: #DCE4F0; }
.bcard-rhd span { flex: 0 0 auto; padding-left: 4px; }

.bcard-rfacts {
  display: grid; grid-template-columns: 1fr 1fr; gap: 14px 10px;
}
/* ★ v2.24 需求 9：数字格必须**左对齐**，不能跟着 .bcard-main 的 text-align:center 走。
   「入队时间」四个字在左边、下面那行「2014 年 10 月 22 日」却居中，两列还会各自
   居中到自己的半格里 —— 读起来像四组不相干的数字，而不是一张履历表。 */
.bcard-rf { display: flex; flex-direction: column; gap: 5px; min-width: 0; text-align: left; }
.bcard-rf em { font-style: normal; font-size: 12px; font-weight: 500; color: #93A2B8; }
.bcard-rf b {
  font-size: 18px; font-weight: 800; color: #12305E; line-height: 1.25;
  font-variant-numeric: tabular-nums;
}

.bcard-rsec { margin: 20px 0 0; }
.bcard-rsh {
  display: flex; align-items: baseline; gap: 8px; margin: 0 0 10px;
}
.bcard-rsh span { font-size: 13px; font-weight: 700; color: #2C6BC9; }
.bcard-rsh em {
  margin-left: auto; font-style: normal; font-size: 11.5px; color: #A6B3C6;
}
.bcard-rtags { display: flex; flex-wrap: wrap; gap: 7px; }
.bcard-rtag {
  padding: 5px 12px; border-radius: 999px;
  background: #F1F5FC; color: #3D5B85;
  font-size: 12.5px; font-weight: 500; line-height: 1.5;
}
.bcard-rtag.is-more { background: transparent; color: #A6B3C6; box-shadow: inset 0 0 0 1px #DCE4F0; }

.bcard-foot {
  position: relative; z-index: 1;
  padding: 20px; text-align: center;
  font-size: 12.5px; font-weight: 600; color: rgba(255, 255, 255, .78);
}
.bcard-dot { margin: 0 8px; opacity: .6; }

.bcard-acts { display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; margin-top: 18px; }
.bcard-acts .btn { display: inline-flex; align-items: center; gap: 6px; }
.bcard-hint { margin: 14px 0 0; text-align: center; font-size: 12.5px; color: var(--text-3); }
.bcard-hint svg { vertical-align: -2px; color: var(--brand); }

.bcard-miss {
  padding: 56px 22px; text-align: center;
  border: 1px solid var(--border); border-radius: 20px; background: var(--surface);
}
.bcard-miss-ico {
  width: 64px; height: 64px; margin: 0 auto 14px;
  display: grid; place-items: center; border-radius: 50%;
  background: var(--brand-lighter); color: var(--brand);
}
.bcard-miss h2 { margin: 0 0 8px; font-size: 18px; color: var(--text); }
.bcard-miss p { margin: 0 0 18px; font-size: 13.5px; line-height: 1.8; color: var(--text-3); }

@media (max-width: 520px) {
  .bcard-top { padding: 16px 16px 30px; gap: 10px; }
  .bcard-team strong { font-size: 15.5px; }
  .bcard-day { font-size: 11.5px; }
  .bcard-main { margin: 0 10px; padding: 0 16px 18px; }
  .bcard-main::before { margin: 0 -16px; }
  .bcard-ava { width: 104px; height: 104px; margin-top: 16px; }
  .bcard-name { font-size: 23px; }
  .bcard-meta { font-size: 12px; }
  .bcard-quote { font-size: 14px; line-height: 1.8; }
  /* 履历在窄屏上：两列保留（拆成一列会让卡片长一倍），只把字号收一点 */
  .bcard-rfacts { gap: 12px 8px; }
  .bcard-rf b { font-size: 16px; }
  .bcard-rtag { font-size: 12px; padding: 4px 10px; }
  .bcard-acts .btn { flex: 1 1 auto; justify-content: center; }
}

/* ==========================================================================
   ★ v2.14 需求 2：功勋排行榜（前台 hall.php + 后台 admin/hall_of_fame.php）
   --------------------------------------------------------------------------
   榜卡为什么用「等宽网格」而不是领奖台（2-1-3 那种）：
     领奖台必须在窄屏上打回 1-2-3，靠 order 属性在两个断点里各排一次 ——
     一处写错就是"手机上名次乱跳"，而这一页的核心信息恰好就是名次。
     等宽网格 + 名次角标在任何宽度下都读得对，前三名用配色区分，
     信息一点没少。

   名次配色只在榜卡上出现 .is-medal-1/2/3，且只会在第一页 ——
   翻页后名次从 13 起，不该再出现"金牌"（判断在 hall.php 里）。
   ========================================================================== */
.hof-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: 16px;
}
.hof-card {
  position: relative;
  display: flex; flex-direction: column;
  padding: 18px 20px 16px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 16px;
  box-shadow: 0 12px 30px -26px rgba(12, 42, 97, .38);
}
/* 前三名：顶部一条彩色横杠 + 极淡的底色。不用大色块 ——
   这一页是正式公示版面，不是活动海报。 */
.hof-card.is-medal-1 { border-color: #E8C36A; background: linear-gradient(180deg, #FFFBF0 0%, var(--surface) 62px); }
.hof-card.is-medal-2 { border-color: #C6CFDC; background: linear-gradient(180deg, #F8FAFC 0%, var(--surface) 62px); }
.hof-card.is-medal-3 { border-color: #E2C6A8; background: linear-gradient(180deg, #FFF9F4 0%, var(--surface) 62px); }
.hof-card.is-medal-1::before,
.hof-card.is-medal-2::before,
.hof-card.is-medal-3::before {
  content: ""; position: absolute; left: 18px; right: 18px; top: 0; height: 3px;
  border-radius: 0 0 3px 3px;
}
.hof-card.is-medal-1::before { background: linear-gradient(90deg, #E8C36A, #F3DDA4); }
.hof-card.is-medal-2::before { background: linear-gradient(90deg, #B9C4D2, #DCE3EC); }
.hof-card.is-medal-3::before { background: linear-gradient(90deg, #D8A97C, #EDCFB4); }

/* 名次角标：钉在右上角。刻意与 .hof-rank 文字并存 ——
   角标是"扫一眼就知道第几"，文字是"读出来确认第几"。 */
.hof-medal {
  position: absolute; right: 14px; top: 14px;
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border-radius: 50%;
  font-size: 13px; font-weight: 800; color: #fff;
  font-variant-numeric: tabular-nums;
  box-shadow: 0 2px 6px rgba(12, 42, 97, .22);
}
.hof-card.is-medal-1 .hof-medal { background: linear-gradient(140deg, #E0B449, #C69518); }
.hof-card.is-medal-2 .hof-medal { background: linear-gradient(140deg, #A9B6C6, #8092A6); }
.hof-card.is-medal-3 .hof-medal { background: linear-gradient(140deg, #CF9C6A, #AE7C46); }

.hof-head { display: flex; align-items: flex-start; gap: 13px; min-width: 0; }
.hof-av { flex: 0 0 auto; display: block; }
.hof-av img {
  display: block; width: 64px; height: 64px; border-radius: 50%;
  object-fit: cover; background: var(--surface-2);
  border: 2px solid var(--surface);
  box-shadow: 0 0 0 1px var(--border);
}
.hof-card.is-medal-1 .hof-av img { box-shadow: 0 0 0 2px #E8C36A; }
.hof-card.is-medal-2 .hof-av img { box-shadow: 0 0 0 2px #C6CFDC; }
.hof-card.is-medal-3 .hof-av img { box-shadow: 0 0 0 2px #E2C6A8; }
/* 右侧给名次角标（26px 宽 + right:14px = 距卡右沿 40px）留出的余量。
   这个数不能凭感觉写小：卡片内边距桌面 20px、手机 15px，
   所以桌面至少要 20px、手机至少要 25px 才能真正不撞上。
   早先桌面写 26 / 手机写 24，手机上正好差 1px —— 平时看不出，
   但姓名一长就会顶到角标上（走查里按"元素框重叠"判，就是红的）。 */
.hof-id { min-width: 0; flex: 1 1 auto; padding-right: 34px; }
.hof-name { margin: 2px 0 0; font-size: 17px; line-height: 1.3; letter-spacing: -.2px; }
.hof-name a { color: var(--text); }
.hof-name a:hover { color: var(--brand); }
.hof-title { margin: 4px 0 0; font-size: 12.5px; font-weight: 600; color: var(--brand-dark); }
.hof-role  { margin: 3px 0 0; font-size: 11.5px; color: var(--text-3); }
/* 名次文字放在 .hof-tags 行首（**不能绝对定位到卡片底部**：
   底部是整宽的「查看队员档案」按钮，压上去就是一次错位）。
   与右上角的圆形角标是同一件事的两种强度：角标用来扫，文字用来读。 */
.hof-rank {
  padding: 2px 9px; border-radius: var(--radius-pill);
  font-size: 11.5px; font-weight: 700; color: var(--brand-dark);
  background: var(--brand-lighter); border: 1px solid var(--brand-border);
  font-variant-numeric: tabular-nums;
}

.hof-tags { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin: 13px 0 0; }
.hof-stars { display: inline-flex; font-size: 14px; line-height: 1; letter-spacing: 1.5px; }
.hof-stars .is-on  { color: #E0A81F; font-style: normal; }
.hof-stars .is-off { color: #D8DEE7; font-style: normal; }
.hof-year {
  padding: 2px 9px; border-radius: var(--radius-pill);
  font-size: 11.5px; font-weight: 600; color: var(--text-2);
  background: var(--surface-2); border: 1px solid var(--border);
  font-variant-numeric: tabular-nums;
}

/* 事迹正文：等宽网格里每张卡的高度会被同一行的最高卡对齐（grid 默认 stretch），
   于是「事迹短的人」卡片底下会空一块。解决办法不是裁文字
   （裁了就读不全事迹，正好违背这一页的目的），而是让 .hof-foot 吃下剩余空间
   （margin-top:auto），把按钮统一压到卡片底部 —— 空的是中间，对齐的是按钮。 */
.hof-deed {
  margin: 12px 0 0;
  font-size: 13px; line-height: 1.85; color: var(--text-2);
  /* 事迹是用户手写的一段话，"说人话"比"对齐"重要：不断词、不禁用连字符 */
  word-break: normal; overflow-wrap: anywhere;
}
.hof-foot { margin-top: auto; padding-top: 13px; }
.hof-foot .btn { width: 100%; justify-content: center; }
.hof-foot-hint { font-size: 11.5px; color: var(--text-3); }

@media (max-width: 640px) {
  .hof-grid { grid-template-columns: minmax(0, 1fr); gap: 12px; }
  .hof-card { padding: 15px 15px 14px; }
  .hof-av img { width: 54px; height: 54px; }
  .hof-id { padding-right: 32px; }
  .hof-name { font-size: 16px; }
  .hof-deed { font-size: 12.5px; line-height: 1.8; }
}

/* ==========================================================================
   ★ v2.14 需求 4：后台功能搜索（侧栏顶部）
   --------------------------------------------------------------------------
   需求原话：「后台要支持一键搜索功能关键词，因为功能太多，有时候找不到
             只能一键搜索」。

   放侧栏不放顶栏的理由写在 includes/layout.php 的 render_sidebar() 里，
   这里只说样式上的两条：
     · 侧栏是**深色**的（--side-bg 系），所以搜索框要用半透明白，
       不能用全局的 .input（它是给白底页面写的，放这儿会变成一块白板）；
     · 配色全部走 --side-* 变量，皮肤切换时它会跟着变 ——
       写死 #0E2A5C 在「素纸白」皮肤下就是一块突兀的深色补丁。
   ========================================================================== */
.side-search {
  position: relative;
  flex: 0 0 auto;
  margin: 12px 10px 2px;
}
.ss-ico {
  position: absolute; left: 11px; top: 50%;
  transform: translateY(-50%);
  display: inline-flex; color: var(--side-text-3);
  pointer-events: none;
}
.ss-ico svg { width: 15px; height: 15px; }
.ss-in {
  width: 100%; height: 36px;
  /* 右边留 52px：清除叉（带文字时出现）与快捷键提示（空的时候出现）
     共用同一个位置，同一时刻只会有一个可见 —— 见 JS 里的 syncClear()。 */
  padding: 0 52px 0 32px;
  border: 1px solid rgba(255, 255, 255, .16);
  border-radius: var(--radius-sm);
  background: rgba(255, 255, 255, .09);
  color: #fff;
  font-size: 13px;
  font-family: inherit;
  transition: background .15s, border-color .15s;
}
.ss-in::placeholder { color: var(--side-text-3); }
.ss-in:hover { background: rgba(255, 255, 255, .13); }
.ss-in:focus {
  outline: none;
  background: rgba(255, 255, 255, .16);
  border-color: rgba(255, 255, 255, .46);
}
/* Safari 给 type=search 自绘了一个清除叉，位置与我们自己的按钮重合，
   两个叉叠在一起看着像渲染坏了。统一用自己的那个。 */
.ss-in::-webkit-search-cancel-button,
.ss-in::-webkit-search-decoration { -webkit-appearance: none; appearance: none; }
.ss-kbd {
  position: absolute; right: 9px; top: 50%;
  transform: translateY(-50%);
  min-width: 20px; padding: 1px 5px;
  border: 1px solid rgba(255, 255, 255, .2);
  border-radius: 5px;
  background: rgba(255, 255, 255, .08);
  color: var(--side-text-3);
  font-family: var(--mono); font-size: 11px; line-height: 16px;
  text-align: center; pointer-events: none;
}
.ss-clear {
  position: absolute; right: 8px; top: 50%;
  transform: translateY(-50%);
  width: 22px; height: 22px;
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; border-radius: 50%;
  background: rgba(255, 255, 255, .14);
  color: #fff; font-size: 15px; line-height: 1;
  cursor: pointer;
}
.ss-clear:hover { background: rgba(255, 255, 255, .26); }

/* ---- v2.17 需求 11：搜索的兜底出口 —— 「功能地图」 --------------------------
   搜索要求你先想得出关键词。想不出的时候（"改队伍名字的在哪儿？"）需要一条
   "按分类扫一遍"的路，所以这一条就贴在搜索框正下方，而不是藏进菜单里。
   配色全部走 --side-* 变量，皮肤切换时跟着变 —— 与搜索框同一条理由。 */
.ss-map {
  display: flex; align-items: center; gap: 7px;
  margin: 0 10px 4px; padding: 6px 10px;
  border: 1px dashed rgba(255, 255, 255, .18);
  border-radius: var(--radius-sm);
  background: rgba(255, 255, 255, .04);
  color: var(--side-text);
  font-size: 12.5px;
  text-decoration: none;
  transition: background .15s, border-color .15s;
}
.ss-map:hover { background: rgba(255, 255, 255, .12); border-color: rgba(255, 255, 255, .4); }
.ss-map > svg { flex: 0 0 auto; color: #9CC4FF; }
.ss-map > span { font-weight: 600; white-space: nowrap; }
.ss-map > em {
  margin-left: auto; font-style: normal; font-size: 11px;
  color: var(--side-text-3); white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
@media (max-width: 520px) { .ss-map > em { display: none; } }

/* ---- 结果浮层：盖在菜单上面，不把菜单挤走 ---- */
.ss-pop {
  position: absolute; left: 0; right: 0; top: calc(100% + 6px);
  z-index: 5;
  max-height: min(62vh, 430px);
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 6px;
  border: 1px solid rgba(255, 255, 255, .15);
  border-radius: var(--radius);
  background: var(--side-bg-2);
  box-shadow: var(--shadow-lg);
}
.ss-pop::-webkit-scrollbar { width: 5px; }
.ss-pop::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, .16); border-radius: 3px; }

.as-item {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 9px;
  border-radius: var(--radius-xs);
  color: var(--side-text);
  text-decoration: none;
}
.as-item.is-cur, .as-item:hover { background: var(--side-active-bg); }
.as-item:active { background: rgba(255, 255, 255, .18); }
.as-item-ico { flex: 0 0 auto; display: inline-flex; color: var(--side-text-dim); }
.as-item-ico svg { width: 16px; height: 16px; }
.as-item.is-cur .as-item-ico { color: #9CC4FF; }
.as-item-tx { min-width: 0; flex: 1 1 auto; }
.as-item-lb {
  display: block; font-size: 13px; font-weight: 600; color: #fff;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* 命中片段用亮蓝加粗。**不加底色** —— 深色底上的浅色高亮块
   在小字号下会糊成一团，反而不如只改字色清楚。 */
.as-item-lb mark { background: transparent; color: #9CC4FF; font-weight: 800; }
.as-item-gp {
  display: block; margin-top: 2px;
  font-size: 11px; color: var(--side-text-3);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.as-item-go { flex: 0 0 auto; font-size: 11px; color: var(--side-text-3); }
.as-item.is-cur .as-item-go { color: #9CC4FF; }

.as-hint, .as-empty {
  padding: 11px 11px 13px;
  font-size: 12px; line-height: 1.8; color: var(--side-text-3);
}
.as-hint b, .as-empty b { color: var(--side-text); font-weight: 600; }
.as-hint kbd {
  padding: 1px 5px; border: 1px solid rgba(255, 255, 255, .2);
  border-radius: 5px; background: rgba(255, 255, 255, .08);
  font-family: var(--mono); font-size: 11px; color: var(--side-text);
}
.as-empty a { color: #9CC4FF; text-decoration: none; }
.as-empty a:hover { text-decoration: underline; }
.as-empty-more {
  display: block; margin-top: 10px; padding-top: 10px;
  border-top: 1px dashed rgba(255, 255, 255, .12);
}

/* 手机上侧栏是抽屉：浮层不能超出视口，也不能被底部的手机导航条压住 */
@media (max-width: 860px) {
  .ss-pop { max-height: 56vh; }
}

/* ══════════════════════════════════════════════════════════════════════════
   登录页 · 演示账号「一键登录」（v2.14 需求 6 恢复）
   ──────────────────────────────────────────────────────────────────────────
   为什么是折叠块，而不是像老版本那样常驻一张账号表：
     v2.13 需求 6 把这块从登录页删掉的**唯一理由**就是它占了登录卡近一半
     高度（7 行表格），而它只在演示环境有意义 —— 正式上线后整块消失，
     于是"演示时很挤 / 正式时下半屏空着"两种观感都不对。
     恢复功能就得同时解决这个问题：收进 <details>，默认收起，点开才展开。
     卡片高度回到 v2.13 的水位，功能又回来了。

   按钮做成"两行小卡"而不是一排胶囊：
     演示时要按**身分**挑人（"以队长身分看看"），不是按姓名找。
     所以身分是主信息（大字、深色），姓名是辅助（小字、浅色）。

   ★ 这里全部用 :root 里已有的变量，不新增色值 —— 否则换皮肤
     （skins.css 会覆写 --brand / --surface 等）时这一块会留在旧配色上。
   ══════════════════════════════════════════════════════════════════════════ */
.demo-login {
  margin-top: 14px;
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface-2);
}
.demo-login > summary {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 9px 12px;
  cursor: pointer;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--text-2);
  list-style: none;                  /* Firefox 自带三角 */
  user-select: none;
  border-radius: var(--radius-sm);
}
.demo-login > summary::-webkit-details-marker { display: none; }   /* Chrome / Safari 自带三角 */
.demo-login > summary::after {       /* 自绘的展开箭头：默认三角各家大小不一 */
  content: "";
  margin-left: auto;
  width: 6px; height: 6px;
  border-right: 1.6px solid currentColor;
  border-bottom: 1.6px solid currentColor;
  transform: rotate(45deg) translate(-1px, -1px);
  transition: transform .18s;
}
.demo-login[open] > summary::after { transform: rotate(-135deg) translate(-1px, -1px); }
.demo-login > summary:hover { background: var(--surface-hover); color: var(--text); }
.demo-login > summary > svg { flex: none; color: var(--brand); }

.dl-body { padding: 0 12px 12px; }
.dl-hint {
  margin: 0 0 10px;
  font-size: 11.5px;
  line-height: 1.65;
  color: var(--text-3);
}
.dl-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(116px, 1fr));
  gap: 7px;
}
.dl-btn {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1px;
  padding: 7px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
  background: var(--surface);
  cursor: pointer;
  text-align: left;
  font: inherit;
  transition: border-color .15s, background .15s, box-shadow .15s;
}
.dl-btn:hover {
  border-color: var(--brand);
  background: var(--brand-lighter);
  box-shadow: var(--shadow-sm);
}
.dl-btn:active { transform: translateY(1px); }
.dl-btn:focus-visible { outline: 2px solid var(--brand-mid); outline-offset: 1px; }
.dl-role { font-size: 12px; font-weight: 700; color: var(--text); }
.dl-name { font-size: 10.5px; color: var(--text-3); }

/* 整页风格（.auth-card-glass）下卡片是浅玻璃底，字色要跟着换一套。
   为什么要单独写：这一套规则在 .auth-card-glass 里已经有一份同类表
   （h1 / .auth-sub / .hint / .auth-foot …），新块不跟上的话，
   在整页风格下会变成"浅灰字压在浅玻璃上"，也就是看不清。 */
.auth-card-glass .demo-login {
  background: rgba(255, 255, 255, .5);
  border-color: rgba(18, 60, 134, .22);
}
.auth-card-glass .demo-login > summary { color: #3B4A63; }
.auth-card-glass .demo-login > summary:hover { background: rgba(255, 255, 255, .65); color: #16233A; }
.auth-card-glass .dl-hint { color: #5A6B85; }
.auth-card-glass .dl-btn { background: #fff; border-color: rgba(18, 60, 134, .18); }
.auth-card-glass .dl-btn:hover { background: var(--brand-lighter); border-color: var(--brand); }
.auth-card-glass .dl-role { color: #16233A; }
.auth-card-glass .dl-name { color: #7A879C; }

/* 窄屏：胶囊最小宽度收一档，让 7 个身分尽量落在两列以内 */
@media (max-width: 420px) {
  .dl-grid { grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); gap: 6px; }
  .dl-btn { padding: 6px 8px; }
  .dl-role { font-size: 11.5px; }
}

/* ══════════════════════════════════════════════════════════════════════════
   身份核验页 · 命中多人的候选列表（v2.14 需求 7）
   ──────────────────────────────────────────────────────────────────────────
   需求要求"输入字段后直接出资质档案，不要简易结果页"。命中唯一一人时直接
   跳转；命中多人时（同名 / 用姓名查）才落到这一屏。

   ★ 这一屏必须看起来就"只是挑人"，不能像一份报告：
     所以只有 头像 + 姓名 + 身份徽标 + 编号 + 进档案的箭头，
     没有任务数、服务时长、技能标签 —— 那些正是需求说"没有必要"的东西。
     整行是 <a>，点哪都进得去（手机上尤其重要，手指不用瞄准小按钮）。

   ★ 用 flex 而不是 grid：姓名长短不一，grid 会按最长的那条对齐，
     短姓名的行右侧留一大片空白，看着像表格没填满。
   ══════════════════════════════════════════════════════════════════════════ */
.vfy-picks { display: flex; flex-direction: column; gap: 9px; }

.vfy-pick {
  display: flex;
  align-items: center;
  gap: 13px;
  padding: 11px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  text-decoration: none;
  color: inherit;
  transition: border-color .15s, background .15s, box-shadow .15s;
}
.vfy-pick:hover   { border-color: var(--brand); background: var(--brand-lighter); box-shadow: var(--shadow); }
.vfy-pick:active  { transform: translateY(1px); }
.vfy-pick:focus-visible { outline: 2px solid var(--brand-mid); outline-offset: 2px; }

.vfy-av {
  width: 46px; height: 46px;
  flex: none;
  border-radius: 50%;
  object-fit: cover;
  background: var(--surface-3);
}
.vfy-tx   { min-width: 0; flex: 1 1 auto; display: flex; flex-direction: column; gap: 5px; }
.vfy-name { display: flex; align-items: baseline; gap: 7px; font-size: 15px; font-weight: 700; color: var(--text); }
.vfy-nick { font-style: normal; font-size: 12px; font-weight: 500; color: var(--text-3); }
.vfy-tags { display: flex; flex-wrap: wrap; align-items: center; gap: 7px; font-size: 11.5px; color: var(--text-2); }
.vfy-go {
  flex: none;
  display: inline-flex; align-items: center; gap: 2px;
  font-size: 12px; font-weight: 700; color: var(--brand);
  white-space: nowrap;
}

@media (max-width: 600px) {
  .vfy-pick { gap: 10px; padding: 10px 12px; }
  .vfy-av   { width: 40px; height: 40px; }
  .vfy-go   { font-size: 11px; }
  .vfy-name { font-size: 14px; }
}

/* ============================================================================
   v2.16 需求 1：单页装修块（.pg-*）
   ----------------------------------------------------------------------------
   基调沿用「苹果优先、小米其次」：大留白、细线分隔、大数字、20px 圆角。
   这些块只出现在 page.php（关于我们 / 联系我们 / 志愿者招募）上，
   类名一律带 pg- 前缀，避免和后台或其他前台页撞车。
   ========================================================================== */

/* 块的外壳：统一上下留白。轮播块要贴边，用 .is-flush 去掉左右与上边距。 */
.pg-sec { padding: 42px 20px; max-width: 1180px; margin: 0 auto; }
.pg-sec.is-flush { padding: 0 0 34px; max-width: none; }
.pg-sec.is-tint {
  max-width: none;
  background: var(--surface-2, #f5f6f8);
  padding-left: 20px; padding-right: 20px;
}
.pg-sec.is-tint > .pg-head,
.pg-sec.is-tint > .pg-stats,
.pg-sec.is-tint > .pg-video { max-width: 1180px; margin-left: auto; margin-right: auto; }

.pg-head { margin-bottom: 24px; text-align: center; }
.pg-head h2 { font-size: 26px; line-height: 1.3; margin: 0 0 8px; letter-spacing: -.3px; }
.pg-head p  { margin: 0; color: var(--text-3); font-size: 14px; }

/* ---------- ① 全宽轮播 ---------- */
.pg-slider { position: relative; width: 100%; overflow: hidden; background: var(--surface-3, #eef0f3); }
.pg-slides { position: relative; height: 420px; }
.pg-slide {
  position: absolute; inset: 0;
  opacity: 0; visibility: hidden;
  transition: opacity .7s ease, visibility .7s;
  display: block; text-decoration: none;
}
.pg-slide.on { opacity: 1; visibility: visible; }
.pg-slide img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* 没有配图时不留一块死灰：给品牌色渐变 */
.pg-slide.is-plain { background: linear-gradient(135deg, #0e2a4d 0%, #17558f 55%, #2f8fd8 100%); }
.pg-slide-veil {
  position: absolute; inset: 0;
  background: linear-gradient(90deg, rgba(4,16,32,.62) 0%, rgba(4,16,32,.28) 46%, rgba(4,16,32,.06) 100%);
}
.pg-slide-cap {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: 0 28px 34px;
  max-width: 1180px; margin: 0 auto;
  display: flex; flex-direction: column; gap: 6px;
}
.pg-slide-cap b { color: #fff; font-size: 30px; line-height: 1.2; letter-spacing: -.5px; text-shadow: 0 2px 14px rgba(0,0,0,.45); }
.pg-slide-cap i { color: rgba(255,255,255,.86); font-style: normal; font-size: 15px; text-shadow: 0 1px 8px rgba(0,0,0,.4); }
.pg-slider-dots {
  position: absolute; left: 0; right: 0; bottom: 14px;
  display: flex; justify-content: center; gap: 8px;
}
.pg-dot {
  width: 26px; height: 4px; border-radius: 999px; border: 0; cursor: pointer;
  background: rgba(255,255,255,.45); padding: 0; transition: background .25s, width .25s;
}
.pg-dot.on { background: #fff; width: 38px; }

/* ---------- ② 图文介绍 ---------- */
.pg-split {
  display: grid; grid-template-columns: 1.05fr 1fr;
  gap: 34px; align-items: center;
}
.pg-split.is-right .pg-split-pic { order: 2; }
.pg-split-pic img { width: 100%; border-radius: 20px; display: block; box-shadow: 0 14px 40px rgba(15,40,80,.12); }
.pg-pic-ph {
  aspect-ratio: 4 / 3; border-radius: 20px;
  display: flex; align-items: center; justify-content: center;
  color: #fff; background: linear-gradient(135deg, #12365f, #2f8fd8);
  opacity: .85;
}
.pg-split-txt p { margin: 0 0 14px; font-size: 15px; line-height: 1.95; color: var(--text-2); }
.pg-split-txt p:last-child { margin-bottom: 0; }

/* ---------- ③ 宣传视频 ---------- */
.pg-video {
  position: relative; width: 100%;
  /* 16:9 —— 用 aspect-ratio 而不是 padding-top 百分比补丁，
     后者在 iframe/video 混排时会把高度算歪。 */
  aspect-ratio: 16 / 9;
  border-radius: 20px; overflow: hidden; background: #0a1420;
  box-shadow: 0 16px 44px rgba(15,40,80,.18);
}
.pg-video iframe, .pg-video video { width: 100%; height: 100%; border: 0; display: block; }
.pg-video-ph {
  display: flex; flex-direction: column; gap: 8px;
  align-items: center; justify-content: center; text-align: center;
  min-height: 190px; padding: 30px 24px;
  border: 1px dashed var(--border); border-radius: 20px;
  background: var(--surface); color: var(--text-2); text-decoration: none;
}
.pg-video-ph b { font-size: 16px; color: var(--text); }
.pg-video-ph span { font-size: 12.5px; color: var(--text-3); max-width: 560px; line-height: 1.7; word-break: break-all; }
.pg-video-ph.is-link:hover { border-color: var(--brand); color: var(--brand); }

/* ---------- ④ 数据展示 ---------- */
.pg-stats {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 14px;
}
.pg-stat {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 20px; padding: 22px 18px; text-align: center;
}
.pg-stat .pv {
  font-size: 34px; font-weight: 800; line-height: 1.1;
  color: var(--brand); letter-spacing: -1px;
  font-variant-numeric: tabular-nums;
}
.pg-stat .pv small { font-size: 14px; font-weight: 600; margin-left: 3px; color: var(--text-3); }
.pg-stat .pk { margin-top: 8px; font-size: 13px; color: var(--text-3); }

/* ---------- ⑤ 卡片组 ---------- */
.pg-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 14px; }
.pg-card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 20px; padding: 22px 20px;
}
.pg-card .pi {
  width: 44px; height: 44px; border-radius: 14px;
  display: flex; align-items: center; justify-content: center;
  background: var(--brand-lighter); color: var(--brand); margin-bottom: 12px;
}
.pg-card b { display: block; font-size: 15.5px; margin-bottom: 6px; }
.pg-card span { font-size: 13.5px; line-height: 1.8; color: var(--text-2); }

/* ---------- ⑥ 流程步骤 ---------- */
.pg-steps { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0; }
.pg-steps li {
  display: flex; gap: 16px; padding: 16px 0;
  border-bottom: 1px solid var(--border);
}
.pg-steps li:last-child { border-bottom: 0; }
.pg-steps .sn {
  flex: none; width: 30px; height: 30px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--brand); color: #fff; font-size: 14px; font-weight: 700;
}
.pg-steps b { display: block; font-size: 15px; margin-bottom: 4px; }
.pg-steps p { margin: 0; font-size: 13.5px; line-height: 1.85; color: var(--text-2); }

/* ---------- ⑦ 问答 ---------- */
.pg-faq { display: flex; flex-direction: column; gap: 10px; }
.pg-faq details {
  border: 1px solid var(--border); border-radius: 16px;
  background: var(--surface); overflow: hidden;
}
.pg-faq summary {
  cursor: pointer; list-style: none; padding: 15px 18px;
  font-size: 14.5px; font-weight: 600;
}
.pg-faq summary::-webkit-details-marker { display: none; }
.pg-faq summary::after { content: '＋'; float: right; color: var(--text-3); font-weight: 400; }
.pg-faq details[open] summary::after { content: '－'; }
.pg-faq p { margin: 0; padding: 0 18px 16px; font-size: 13.5px; line-height: 1.9; color: var(--text-2); }

.pg-rich { max-width: 1180px; margin: 0 auto; }
.pg-empty { color: var(--text-3); font-size: 13.5px; padding: 14px 0; }

/* ---------- 首页「关于我们」板块（v2.16 需求 1） ----------
   板块本体直接复用了 .pg-split / .pg-stats（与「关于我们」页同款），
   这里只补两件首页才需要的事：
     ① .home-about-gap —— 图文与数据之间留一口气。.pg-sec 原来负责这层间距，
        首页是 .section 外壳，没有它俩会贴在一起。
     ② .home-about-more —— 三个入口按钮。首页的"了解更多"是整块的落点，
        单独占一行居中，比塞在正文末尾更容易被点到。 */
.home-about-gap { margin-top: 42px; }
.home-about-more { margin-top: 30px; display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; }

@media (max-width: 860px) {
  .pg-sec { padding: 30px 16px; }
  .pg-slides { height: 260px; }
  .pg-slide-cap { padding: 0 16px 30px; }
  .pg-slide-cap b { font-size: 21px; }
  .pg-slide-cap i { font-size: 13px; }
  .pg-head h2 { font-size: 21px; }
  .pg-split { grid-template-columns: 1fr; gap: 20px; }
  .pg-split.is-right .pg-split-pic { order: 0; }
  .pg-stat .pv { font-size: 28px; }
  .pg-stats { gap: 10px; }
  .pg-stat { padding: 18px 12px; }
  .home-about-gap { margin-top: 26px; }
  .home-about-more { margin-top: 22px; }
}
@media (max-width: 520px) {
  .pg-slides { height: 200px; }
  .pg-stats { grid-template-columns: repeat(2, 1fr); }
}

/* ============================================================================
   v2.16 需求 2：荣誉墙（.hw-*）
   ========================================================================== */
/* 筛选条：分类 + 年份 + 搜索，三行各自可横向滚动（手机上一行放不下） */
/* v2.17 需求 7：顶部 hero 矩形块与下面那排筛选 chip 原来**严丝合缝贴在一起**
   （实测 hero.bottom 与「全部」chip.top 都是同一个 y，间隙 0px），
   观感上像把筛选条粘在了 banner 上。给筛选区补一段上间距 ——
   只加 margin-top，不动 hero 的 padding（那个值是为 KPI 卡留的呼吸位）。 */
.hw-filters { display: flex; flex-direction: column; gap: 10px; margin: 20px 0 20px; }
.hw-row { display: flex; gap: 7px; overflow-x: auto; padding-bottom: 2px; -webkit-overflow-scrolling: touch; }
.hw-row::-webkit-scrollbar { height: 4px; }
.hw-row .btn { white-space: nowrap; flex: none; }
.hw-search { display: flex; gap: 8px; }
.hw-search .input { flex: 1 1 auto; min-width: 0; }

.hw-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 16px; }
.hw-card {
  border: 1px solid var(--border); border-radius: 20px; overflow: hidden;
  background: var(--surface); display: flex; flex-direction: column;
  cursor: pointer; transition: transform .18s ease, box-shadow .18s ease;
}
.hw-card:hover { transform: translateY(-3px); box-shadow: 0 16px 34px rgba(15,40,80,.13); }
/* 没有配图的卡片点不开灯箱（JS 找不到 data-img 会直接 return），
   所以光标不能还是手型 —— 那是"能点"的承诺。 */
.hw-card.is-plain { cursor: default; }
.hw-card.is-plain:hover { transform: none; box-shadow: none; }
.hw-card:focus-visible { outline: 3px solid rgba(59,147,255,.45); outline-offset: 2px; }
.hw-pic { position: relative; aspect-ratio: 4 / 3; background: var(--surface-3, #eef0f3); }
.hw-pic img { width: 100%; height: 100%; object-fit: cover; display: block; }
.hw-pic .ph {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  color: #fff; font-size: 13px; letter-spacing: 2px;
  background: linear-gradient(135deg, #12365f, #2f8fd8);
}
.hw-cat {
  position: absolute; left: 10px; top: 10px;
  border-radius: 999px; padding: 3px 10px; font-size: 11px; font-weight: 700; color: #fff;
}
.hw-yr {
  position: absolute; right: 10px; top: 10px;
  border-radius: 999px; padding: 3px 10px; font-size: 11px; font-weight: 700;
  background: rgba(6,16,30,.62); color: #fff; backdrop-filter: blur(4px);
}
.hw-body { padding: 13px 14px 15px; display: flex; flex-direction: column; gap: 6px; flex: 1 1 auto; }
.hw-body b { font-size: 14.5px; line-height: 1.5; }
.hw-body span { font-size: 12.5px; color: var(--text-3); }
.hw-empty { text-align: center; padding: 50px 20px; color: var(--text-3); }

/* 灯箱：点开看大图 */
.hw-lb {
  position: fixed; inset: 0; z-index: 900;
  background: rgba(6,12,22,.86); backdrop-filter: blur(6px);
  display: flex; align-items: center; justify-content: center; padding: 24px;
}
.hw-lb[hidden] { display: none; }
.hw-lb-inner { max-width: 900px; width: 100%; max-height: 92vh; overflow: auto; text-align: center; }
.hw-lb-inner img { max-width: 100%; max-height: 70vh; border-radius: 16px; display: block; margin: 0 auto; }
.hw-lb-cap { margin-top: 14px; color: #eaf1fa; }
.hw-lb-cap b { display: block; font-size: 17px; margin-bottom: 6px; }
.hw-lb-cap span { font-size: 13px; color: rgba(234,241,250,.75); }
.hw-lb-x {
  position: absolute; right: 18px; top: 16px;
  width: 40px; height: 40px; border-radius: 50%; border: 0; cursor: pointer;
  background: rgba(255,255,255,.16); color: #fff; font-size: 18px;
}
@media (max-width: 560px) {
  .hw-grid { grid-template-columns: repeat(2, 1fr); gap: 10px; }
  .hw-body { padding: 10px 11px 12px; }
  .hw-body b { font-size: 13px; }
  .hw-body span { font-size: 11.5px; }
}

/* ============================================================================
   v2.16 需求 3 / 4：主页状态提示（长期未出勤 / 纪念）
   ========================================================================== */
/* 长期未出勤：醒目但不刺眼。用琥珀色而不是红色 ——
   红色是"违规/危险"，而"好久没来参加任务"不是处罚。 */
/* ★★ 选择器必须带变体类（.pp-note.is-idle / .pp-note.is-memorial），
   绝不能写成裸 .pp-note ——
   全站另有 6 个公开页用 <details class="pp-note"> 做「折叠说明」
   （核验结果 verify.php、功勋榜 hall.php、教练专家库 experts_public.php、
     荣誉墙 honor_wall.php、捐赠公示 donations_public.php、车辆查询 vehicle_query.php）。
   本块在文件里位置更靠后，裸选择器会把下面这几行一并压到那些 <details> 上：
     · display:flex  → 把一个 <details> 变成 flex 容器，摘要正文排版全乱；
     · padding       → 折叠块的留白被改；
     · .pp-note b    → 摘要里那枚「展开 / 收起」<b> 从 12px 被顶成 15px。
   这类"做了 A 页面、坏了 B 页面"的样式事故，静态看着一切正常，
   只有真打开那 6 个页面才看得出来。所以这里全部改成变体类限定。 */
.pp-note.is-idle,
.pp-note.is-memorial {
  display: flex; gap: 12px; align-items: flex-start;
  /* 覆盖基类 .pp-note 的 margin:18px 0 0 / border / background */
  margin: 0 0 16px; padding: 14px 16px; border-radius: 16px;
  border: 1px solid transparent;
}
.pp-note.is-idle    .ic,
.pp-note.is-memorial .ic { flex: none; margin-top: 1px; }
.pp-note.is-idle    b,
.pp-note.is-memorial b { display: block; font-size: 15px; margin-bottom: 4px; }
.pp-note.is-idle    span,
.pp-note.is-memorial span { font-size: 13px; line-height: 1.7; }
.pp-note.is-idle {
  background: rgba(255,171,46,.12); border: 1px solid rgba(255,171,46,.42); color: #8a5300;
}
.pp-note.is-idle .ic { color: #d98600; }
.pp-note.is-idle b { color: #7a4a00; }

/* 纪念态：整块深色，与灰化的页面形成对比，读起来像一段碑文 */
.pp-note.is-memorial {
  background: linear-gradient(135deg, #23303f, #38485a);
  border: 1px solid rgba(255,255,255,.14);
  color: #e8eef5;
  justify-content: center; text-align: center;
  flex-direction: column; align-items: center; gap: 6px;
}
.pp-note.is-memorial .ic { color: rgba(255,255,255,.72); margin: 0; }
.pp-note.is-memorial b { color: #fff; font-size: 16px; letter-spacing: .5px; margin: 0; }
.pp-note.is-memorial span { color: rgba(232,238,245,.82); }

/* 灰化：**只降饱和、不降对比**。
   用 filter: grayscale(1) 会把整页文字也一起变灰，正文可读性直接掉一档；
   这里只对"装饰性"的元素（头像、身份标签、统计卡彩色数字）降饱和，
   并把页面主色调到中性灰。 */
.pp-memorial { --brand: #6b7684; --brand-lighter: #eef0f2; }
.pp-memorial .pub-hero-av img,
.pp-memorial .pub-skill .tag,
.pp-memorial .badge { filter: grayscale(1); }
.pp-memorial .pub-hero::before,
.pp-memorial .pub-hero-av::before { opacity: .25; }

/* 打印档案版的纪念行（.archive-doc 里，屏幕上看不见，只在打印/PDF 出现） */
.ad-memorial {
  margin-top: 10px; padding: 8px 12px;
  border: 1px solid #b9c2cc; border-radius: 8px;
  background: #f2f4f6; color: #2b3440;
  font-size: 13px; letter-spacing: .5px; text-align: center;
}

/* ----------------------------------------------------------------------------
   纪念态卡片（教练库 .ex-card / 功勋榜 .hof-card，v2.16 需求 4）
   ----------------------------------------------------------------------------
   「同步到教练库、功勋排行榜里灰色」—— 做法与个人主页同一原则：
   **只对装饰元素降饱和**（头像、照片、星级、名次章、年份章），
   正文（姓名、称号、事迹）保持原有对比度。
   整卡 filter:grayscale(1) 会把事迹正文也一起压暗，读起来像"这条坏了"。
   ::after 那层淡灰罩子给出整体"褪色"的观感，pointer-events:none
   保证卡里的链接照样点得到。
---------------------------------------------------------------------------- */
.mem-card { position: relative; }
.mem-card > img,
.mem-card .ex-av,
.mem-card .hof-av img,
.mem-card .hof-stars,
.mem-card .hof-rank,
.mem-card .hof-year,
.mem-card .ex-years,
.mem-card .badge,
.mem-card .hof-medal { filter: grayscale(1); opacity: .82; }
.mem-card::after {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  background: rgba(88, 98, 112, .07); pointer-events: none;
}
/* 「深切缅怀」小标记：深灰实底，与卡片上的彩色 badge 明显不同 */
.mem-flag {
  display: inline-flex; align-items: center; gap: 4px;
  border-radius: 999px; padding: 2px 10px;
  font-size: 11px; font-weight: 700; letter-spacing: .5px;
  background: #5b6675; color: #fff;
  filter: none !important; opacity: 1 !important;
}
/* 折叠状态的卡片不参与行高拉伸。
   ★ 这是修一个真实的视觉故障：装备 / 车辆页用 .grid-3 装两张折叠卡，
     左边「任务装备清单」默认展开、很高；右边「出动车辆与分组」默认收起。
     grid 默认 align-items: stretch，于是收起的右卡被拉到和左卡一样高 ——
     在页面上就是一整块**大白板**，看着像加载失败。收起就该按内容高。 */
.grid-3 > .cmd-fold:not([open]) { align-self: start; }


/* ==========================================================================
   v2.27 需求 7 / 8：置顶的「我正在参与的任务」操作条
   --------------------------------------------------------------------------
   三条硬约束（都是需求原话推出来的）：
     ① 手机版要"置顶显示目前所有内容之上" —— 用 position:sticky 而不是 fixed：
        fixed 会脱离文档流，任务结束后要"恢复原来布局"就得再写一段 JS 去收，
        而 sticky 是布局内的，元素不见（live 为空）就自动恢复，不需要任何清理。
        top 取 --topbar-h：顶栏本身 sticky top:0 / z-index:40，
        不减去它的高度，这条会被压在顶栏底下看不见。
     ② 顶上要能一眼看出"人在不在现场"：绿点是"在场中"，灰点是"已报名"。
     ③ 按钮区不换行的前提下尽量紧凑：手机上三个按钮排一行，
        排不下时自动折到第二行（flex-wrap），不会把「签到」挤出屏幕。
   ========================================================================== */
.live-bar {
  border: 1px solid var(--brand);
  border-radius: 12px;
  padding: 11px 14px 10px;
  background: linear-gradient(135deg, var(--brand-lighter), var(--surface));
  box-shadow: 0 2px 10px rgba(29, 95, 208, .10);
  margin: 0 0 14px;
}
.live-bar.is-pin { position: sticky; top: var(--topbar-h); z-index: 36; }
.live-bar + .live-bar { margin-top: -4px; }

.lb-hd { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.lb-dot {
  flex: 0 0 auto; width: 8px; height: 8px; border-radius: 50%;
  background: var(--text-3);
}
.lb-dot.is-on { background: #12A150; box-shadow: 0 0 0 3px rgba(18, 161, 80, .16); animation: lb-pulse 1.8s ease-in-out infinite; }
@keyframes lb-pulse {
  0%, 100% { box-shadow: 0 0 0 3px rgba(18, 161, 80, .16); }
  50%      { box-shadow: 0 0 0 6px rgba(18, 161, 80, .04); }
}
.lb-name {
  font-size: 15px; font-weight: 700; color: var(--text-1);
  min-width: 0; max-width: 100%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.lb-role {
  flex: 0 0 auto; font-size: 11px; font-weight: 700; line-height: 1;
  padding: 3px 7px; border-radius: 999px;
  color: #fff; background: var(--brand);
}
.lb-st { font-size: 12px; color: var(--text-2); }
.lb-st.is-live { color: #0F7A3D; font-weight: 600; }
.lb-st.is-wait { color: #B45309; font-weight: 600; }
.lb-st.is-done { color: var(--text-3); }

.lb-meta { font-size: 12px; color: var(--text-3); margin-top: 3px; }
.lb-acts { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 9px; }
.lb-acts form { margin: 0; }
.lb-acts .btn { min-height: 34px; }
.lb-more { margin-left: auto; }
.lb-badge {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 16px; height: 16px; padding: 0 4px; margin-left: 5px;
  border-radius: 999px; background: var(--danger); color: #fff;
  font-size: 10.5px; font-weight: 700; line-height: 1;
}

/* 手机：贴着屏幕两边铺满，少一点"卡片浮在中间"的空隙，
   毕竟这一条是"站在原地就要点的东西"，不是阅读内容。 */
@media (max-width: 640px) {
  .live-bar.is-pin { margin: 0 -12px 12px; border-radius: 0; border-left: 0; border-right: 0; }
  .lb-acts .lb-more { margin-left: 0; }
}
