/*   苏州校园账号查询 · iOS 26 Liquid Glass 风格*/

:root {
  /*     一、调色板 —— 全站颜色唯一来源
     ---------------------------------------------------------------------
     规则：任何地方都不要再写颜色字面量。
       · 纯色            → 用 --tint / --ink / --green / --orange / --red 等 token；
       · 带透明度的同色  → 用下面的「RGB 三元组」token 拼 rgba(var(--x-rgb), α)，
                           这样换一次色号，全站同色系的深浅档位一起换。
     要改品牌蓝，只改 --tint / --tint-rgb / --tint-deep / --tint-dark 这 4 行。*/
  --bg: #ffffff;
  --ink: #1c1c21;
  --ink-2: rgba(28, 28, 33, 0.55);
  --ink-3: rgba(28, 28, 33, 0.35);
  --ink-rgb: 28, 28, 33;             /* 正文墨色的 RGB 三元组（全站灰阶透明度都由它派生） */
  --tint: #0a84ff;                   /* 品牌蓝 */
  --tint-rgb: 10, 132, 255;          /* 品牌蓝 RGB 三元组 */
  --tint-deep: #0060df;              /* 品牌蓝 · 深（亮色强调文字） */
  --tint-dark: #6fb4ff;              /* 品牌蓝 · 暗色模式（菜单 / 标签 / 链接） */
  --tint-dark-icon: #4aa3ff;         /* 品牌蓝 · 暗色模式（核验勾 / 图例 / 通知图标） */
  --tint-dark-rgb: 111, 180, 255;
  --green: #30b158;
  --orange: #ff9500;
  --red: #ff3b30;
  --purple: #7a5cff;                 /* 更新日志 / 历史版本强调色 */
  --purple-rgb: 155, 140, 255;
  --purple-light: #b3a4ff;
  --gray: #8e8e93;                   /* 「关于」等中性图标底色（app.js 也读它） */
  --indigo: #5e5ce6;                 /* 开源代码声明图标底色 */
  --surface-dark-rgb: 34, 34, 40;    /* 暗色模式卡片 / 玻璃底 */
  --shadow-rgb: 30, 50, 90;          /* 亮色阴影的冷调基色 */
  /* 蓝渐变实底（首页胶囊 / 搜索钮 / 选中态 …）与悬停态 —— 一套到处用 */
  --brand-grad: linear-gradient(135deg, #2f9bff, #0a6ef0);
  --brand-grad-hover: linear-gradient(135deg, #3ea4ff, #1478f5);
  --brand-grad-alt: linear-gradient(135deg, #3ba0ff, #0a6ef0);   /* 分段控件滑块专用：比主蓝渐变亮一档 */
  --brand-grad-rgb: 10, 110, 240;
  /* 弹窗实心蓝（关闭 / 验证身份 / 复制等主按钮） */
  --brand-solid: #2E96FF;
  --brand-solid-rgb: 46, 150, 255;
  /* 危险操作（清除本地缓存） */
  --danger-grad: linear-gradient(135deg, #ff5b5b, #e02020);
  --danger-rgb: 224, 32, 32;

  /*     二、毛玻璃 —— 全站唯一来源（分 3 档，改档只改这里）
     ---------------------------------------------------------------------
     · 悬浮档 float：贴在内容上的玻璃片（Tab 栏 / 各圆钮 / 回到顶部）。
       模糊大了会把背景抹平、像磨砂塑料，所以这档最轻。
     · 菜单档  menu ：顶栏两个菜单卡 / 遮罩 / 分享浮层。
     · 弹层档  sheet：下拉 / Toast / 各居中弹窗（面积大，要压住下面内容）。
     触屏降级档（@media (hover:none)）统一在文件末尾「移动端毛玻璃优化」段覆盖。*/
  --sat-glass: 200%;                 /* 玻璃饱和度（全站统一） */
  --sat-glass-mask: 150%;            /* 遮罩层饱和度 */
  --blur-xs: 4px;                    /* 极轻档：表单锁定遮罩（顶部渐变层已独立成 --blur-nav） */
  --blur-nav: 3px;                   /* 顶部渐变模糊档：比 --blur-xs 再轻一档（原共用 4px，观感偏糊） */
  --sat-nav: 140%;                   /* 顶部渐变层的饱和度（唯一保留的非 200% 档） */
  --blur-float: 8px;                 /* 悬浮档模糊半径 */
  --blur-menu: 14px;                 /* 菜单档模糊半径 */
  --blur-menu-scaled: 19.4px;        /* filter-pop 常驻 scale(0.72) 的模糊补偿值（14 ÷ 0.72） */
  --blur-sheet: 22px;                /* 弹层档模糊半径 */
  /* 触屏降级档：手机 GPU 对 backdrop-filter 采样率低，模糊半径与饱和度统一降一档 */
  --blur-touch-float: 6px;
  --blur-touch-menu: 10px;
  --blur-touch-menu-scaled: 13.9px;
  --sat-glass-touch: 160%;
  --glass-float: rgba(255, 255, 255, 0.12);   /* 悬浮档透底（亮色） */
  --glass-menu: rgba(255, 255, 255, 0.30);    /* 菜单档透底（亮色） */
  --glass-sheet: rgba(255, 255, 255, 0.52);   /* 弹层档透底（亮色） */
  --glass-card: rgba(255, 255, 255, 0.66);    /* 页面内卡片 / 分享卡（亮色） */
  --glass-dark-float: rgba(34, 34, 40, 0.28); /* 悬浮档透底（暗色） */
  --glass-dark-menu: rgba(34, 34, 40, 0.40);  /* 菜单档透底（暗色） */
  --mask-bg: rgba(18, 22, 30, 0.16);          /* 弹层遮罩（亮色） */
  --mask-bg-dark: rgba(0, 0, 0, 0.22);        /* 弹层遮罩（暗色） */

  /*     三、按钮表面 —— 全站唯一来源
     ---------------------------------------------------------------------
     ghost（次要 / 取消 / 关闭 / 工具钮）= 分享卡片「取消」那枚浅玻璃白胶囊；
     solid（主要 / 确认 / 危险之外的动作）= 实心蓝；
     两个 token 组一处定义，所有按钮只引用、不复写。*/
  --btn-ghost-bg: rgba(255, 255, 255, 0.7);
  --btn-ghost-bg-dark: rgb(40, 40, 46);
  --btn-ghost-fg: var(--ink);
  --btn-ghost-border: 1px solid var(--glass-border);
  --btn-ghost-shadow: var(--shadow-soft);
  --btn-solid-bg: var(--brand-solid);
  --btn-solid-fg: #fff;
  --btn-solid-shadow: 0 4px 24px rgba(46, 150, 255, 0.32), inset 0 1px 0 rgba(255, 255, 255, 0.35);
  /* 按压跟手光晕：浅底用深色光、实底用白光（深色光叠饱和蓝上会发脏） */
  --btn-glow-ghost: rgba(28, 28, 33, 0.42);
  --btn-glow-ghost-dark: rgba(255, 255, 255, 0.45);
  --btn-glow-solid: rgba(255, 255, 255, 0.72);

  --glass: #ffffff;
  --glass-strong: #ffffff;
  --glass-border: rgba(28, 28, 33, 0.08);
  /* ---- 玻璃描边 = 斜角打光光边（唯一权威配方） ----
     暗色 = 白光对角渐变（深底上白光才成立）；
     亮色 = 同一条「右上主光 → 左下副光」轴上的灰色版（白在白底不可见，只能换灰），
     不是一条死平灰线 —— 保留同一套明暗起伏，才有光感。
     三个 token 对应三类宿主各自的暗色 counterpart 几何：
       --stroke-grey      → 圆钮 / 品牌胶囊（stops 0/18/40/60/76/100）
       --stroke-grey-card → 各类卡片、弹窗、弹层（stops 0/12/30/50/78/100）
       --stroke-grey-flat → 底部 Tab 栏（横扁胶囊用更陡的 190deg）
     调浓淡只改这里的 alpha；亮色 0.38 峰值 ≈ 暗色 0.86 的观感等效。
     v0812：用户反馈"亮色灰描边太明显" → 三档等比 ×0.68（峰值 0.38→0.26）。
     v0813：用户再反馈"所有按钮都要改，不是只改卡片" → 三档再等比 ×0.73（峰值 0.26→0.19）。
           两次合计 ≈ 原始的一半。永远只等比乘、不动 6 个 stop 的位置
           （动了就破坏斜角光感 → 退回 v0806 用户否掉的死平灰线）；暗色白光环不动。
           实测环线对比度（环最暗 vs 玻璃内部，亮色）：圆钮 44 → 卡片 37 → 再压后约 32。 */
  --stroke-grey: linear-gradient(to bottom left,
    rgba(28, 28, 33, 0.19) 0%, rgba(28, 28, 33, 0.13) 18%, rgba(28, 28, 33, 0.07) 40%,
    rgba(28, 28, 33, 0.04) 60%, rgba(28, 28, 33, 0.06) 76%, rgba(28, 28, 33, 0.10) 100%);
  --stroke-grey-card: linear-gradient(to bottom left,
    rgba(28, 28, 33, 0.19) 0%, rgba(28, 28, 33, 0.12) 12%, rgba(28, 28, 33, 0.07) 30%,
    rgba(28, 28, 33, 0.04) 50%, rgba(28, 28, 33, 0.06) 78%, rgba(28, 28, 33, 0.10) 100%);
  --stroke-grey-flat: linear-gradient(190deg,
    rgba(28, 28, 33, 0.19) 0%, rgba(28, 28, 33, 0.12) 14%, rgba(28, 28, 33, 0.07) 30%,
    rgba(28, 28, 33, 0.04) 50%, rgba(28, 28, 33, 0.05) 70%, rgba(28, 28, 33, 0.11) 100%);
  --shadow-soft: 0 6px 24px rgba(30, 50, 90, 0.07), 0 1px 3px rgba(30, 50, 90, 0.05);
  --shadow-float: 0 14px 40px rgba(30, 50, 90, 0.12), 0 2px 8px rgba(30, 50, 90, 0.06);
  --radius-card: 32px;
  /* 列表页卡片式下拉（.dropdown--card：学校类型 / 所在地区）的默认最小宽度。
     这些菜单宽度是 max-content（跟着内容走），而「全部类型」那一列只有三四个字
     → 菜单会缩得很瘦、像一条。这里给一个统一下限，以后新加同类下拉直接吃这个值，
     不必各自写 min-width。只想整体调宽窄，改这一个数即可。 */
  --dd-card-min-w: 150px;
  --nav-h: 52px;
  /* 三张弹出卡片（右上角菜单卡 .menu-pop / 左上角筛选卡 .filter-pop / 列表页卡片式下拉
     .dropdown--card）共用的开合时长 —— 改这里三处一起改，别再各处写死。
     缓动两个值与原版逐字一致，只是收进变量方便统一改。 */
  --pop-in-dur: 0.5s;     /* 展开时长（menuPopIn 关键帧按它烘焙，改动需同步重算帧位百分比） */
  --pop-out-dur: 0.18s;   /* 收起时长：比原来的 0.28s 快一档 */
  --ease-pop-out: cubic-bezier(0.5, 0, 0.8, 0.3);
  --ease-pop-in: cubic-bezier(0.28, 1.5, 0.34, 1);
  --ease-spring: cubic-bezier(0.34, 1.3, 0.5, 1);
  /*弹卡入场缩放锚点（全站浮层共用一套语义 · 写死）
     锚点 = 卡片顶边靠右、再往左 --pop-anchor-x（= 22px），从这儿长出来。
      全站统一写死这一套（右上菜单卡 / 筛选卡 / 所有 .dropdown--card 下拉 / 学生验证下拉
        逐字同源），不再有任何动态量锚点的逻辑 —— 用户明确要求"从哪边出来到哪边"锁死。
        触发按钮一律右对齐到卡片右缘，按钮 44px → 左移 22px 正好锚在按钮中心。
     0 = 贴最右缘（改动前的旧行为）。数值越大越往左。 */
  --pop-anchor-x: 22px;
  /* 滑块专用缓动：强惯性平滑减速、落位紧致顺滑、不回弹不生硬 */
  --ease-glider: cubic-bezier(0.32, 0.72, 0, 1);
  --font: -apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro Text",
          "PingFang SC", "HarmonyOS Sans SC", "MiSans", "Noto Sans SC",
          "Microsoft YaHei UI", sans-serif;
}

* { margin: 0; padding: 0; box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

/*首屏防闪（v7.2.2）
   HTML 里静态存在的浮层（右上菜单 / 筛选卡 / 学生验证 / 其遮罩）默认带 hidden 属性。
   作用：在 style.css 尚未解析完、app.js 尚未执行的那段窗口内，元素根本不参与渲染，
   避免出现「菜单被打开」「一个空的验证弹窗」一闪而过。
   低端内核（QQ / OPPO 内置浏览器）CSS 解析与首屏渲染分块进行，这个窗口明显更长，所以更明显。
   就绪后由 app.js 移除 hidden（见 initFloatingLayers）。
    不要删这条：它是首屏不闪的唯一保障。 */
[hidden] { display: none !important; }

/* 注意：不要加 scroll-behavior: smooth —— 会让切页回顶变成滚动动画，造成"上一页位置被带过来"的观感 */

body {
  font-family: var(--font);
  color: var(--ink);
  background: var(--bg);
  min-height: 100vh;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
  letter-spacing: 0.01em;
  /* 全站禁止手动复制 / 长按菜单 / 双击缩放（复制功能仅由按钮点击触发，不受影响） */
  -webkit-user-select: none;
  -moz-user-select: none;
  user-select: none;
  -webkit-touch-callout: none;
  touch-action: manipulation;
}
/* 输入框保持可编辑、可选中（输错要能改，不影响全局禁复制） */
input, textarea {
  -webkit-user-select: text;
  -moz-user-select: text;
  user-select: text;
}

/*   按钮统一 —— 唯一权威规则
   ---------------------------------------------------------------------
   全站「胶囊按钮」只分三类表面，参数全部取自 :root 的 --btn-* token，
   各组件只引用、不再各自写底色 / 阴影 / 光晕 / 描边：
     ① 次要钮（浅玻璃白胶囊 = 分享卡片「取消」那枚）：
        .pill-btn（未加 .tinted）、.dev-tool（复制诊断 / 重新加载）、
        .verify-cancel（学生验证「关闭」）
     ② 主要钮（实心蓝 + 白字）：
        .dev-cancel、.info-cancel、.verify-submit、.guide-copy-btn
        （首页 / 更多页的 .pill-btn.tinted 是蓝渐变实底，同族另一档）
     ③ 危险钮：.dev-danger（红色渐变）
   按压跟手光晕（::before）与斜角打光描边（::after）随后统一挂到这三类上。*/

/* ---- ① 次要钮：浅玻璃白胶囊 ---- */
.pill-btn:not(.tinted),
.dev-tool,
.verify-cancel {
  background: var(--btn-ghost-bg);
  border: var(--btn-ghost-border);
  color: var(--btn-ghost-fg);
  box-shadow: var(--btn-ghost-shadow);
}
body.dark .pill-btn:not(.tinted),
body.dark .dev-tool,
body.dark .verify-cancel {
  background: var(--btn-ghost-bg-dark);
  border-color: var(--glass-border);
  color: var(--ink);
}

/* ---- ② 主要钮：实心蓝 + 白字 ---- */
.dev-cancel,
.info-cancel,
.verify-submit,
.guide-copy-btn {
  border: none;
  background: var(--btn-solid-bg);
  color: var(--btn-solid-fg);
  box-shadow: var(--btn-solid-shadow);
}

/* ---- ③ 危险钮：红色渐变 ---- */
.dev-danger {
  border: none;
  background: var(--danger-grad);
  color: #fff;
  box-shadow: 0 4px 24px rgba(var(--danger-rgb), 0.32), inset 0 1px 0 rgba(255, 255, 255, 0.45);
}

/* ---- 按压跟手光晕（--px/--py 由 initPressSpring 写入）----
   次要钮（浅底）→ 深色光，暗色模式翻白；
   主要 / 危险钮（实底）→ 白光（深色光叠在饱和色上会发脏）。
   收紧成实心光点：小胶囊 70px、整条宽钮 110px —— 旧版默认 farthest-corner
   软晕覆盖整钮，看不出"手指方向贴着的那颗光点"。 */
.pill-btn:not(.tinted)::before,
.dev-tool::before,
.verify-cancel::before {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  background: radial-gradient(circle 70px at var(--px, 50%) var(--py, 50%),
    var(--btn-glow-ghost), rgba(28, 28, 33, 0) 100%);
  opacity: 0; pointer-events: none;
  transition: opacity 0.18s ease;
}
.pill-btn:not(.tinted).pressing::before,
.dev-tool.pressing::before,
.verify-cancel.pressing::before { opacity: 1; }
body.dark .pill-btn:not(.tinted)::before,
body.dark .dev-tool::before,
body.dark .verify-cancel::before {
  background: radial-gradient(circle 70px at var(--px, 50%) var(--py, 50%),
    var(--btn-glow-ghost-dark), rgba(255, 255, 255, 0) 100%);
}
.pill-btn.tinted::before,
.dev-cancel::before,
.info-cancel::before,
.verify-submit::before,
.guide-copy-btn::before,
.dev-danger::before {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  background: radial-gradient(circle 110px at var(--px, 50%) var(--py, 50%),
    var(--btn-glow-solid), rgba(255, 255, 255, 0) 95%);
  opacity: 0; pointer-events: none;
  transition: opacity 0.18s ease;
}
.pill-btn.tinted.pressing::before,
.dev-cancel.pressing::before,
.info-cancel.pressing::before,
.verify-submit.pressing::before,
.guide-copy-btn.pressing::before,
.dev-danger.pressing::before { opacity: 1; }

/* ---- 斜角打光描边（与顶部圆钮 / 底部 Tab 栏同款）----
   ::after 画 1px 斜角渐变光边，光源左上斜打 → 右上主光、对角左下淡副光；
   mask content-box 排除内容区，光边只显示在边缘一圈。::before 仍是按压跟手光晕，二者不冲突。 */
.dev-tool::after,
.dev-cancel::after,
.dev-danger::after,
.info-cancel::after,
.verify-cancel::after,
.verify-submit::after,
.guide-copy-btn::after {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  padding: 1px;
  background: var(--stroke-grey-card);
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  pointer-events: none;
}
body.dark .dev-tool::after,
body.dark .dev-cancel::after,
body.dark .dev-danger::after,
body.dark .info-cancel::after,
body.dark .verify-cancel::after,
body.dark .verify-submit::after,
body.dark .guide-copy-btn::after {
  background: linear-gradient(to bottom left,
    rgba(255, 255, 255, 0.82) 0%, rgba(255, 255, 255, 0.50) 14%, rgba(255, 255, 255, 0.23) 32%,
    rgba(255, 255, 255, 0.08) 52%, rgba(255, 255, 255, 0.18) 76%, rgba(255, 255, 255, 0.34) 100%);
}

/* ---------- 顶部导航栏：渐变模糊 ----------
   三层（自下而上）：① 压暗层 .nav-dim（z39，独立元素）—— 一条按直线衰减的底色，
                       给爬升到顶栏的内容垫底，让顶栏压得住下方滚动内容；
                     ② 模糊层 .nav-blur（z40）—— backdrop-filter 的模糊半径 = --blur-nav
                       （比全站极轻档再轻一档，避免"整体太糊"）；
                     ③ 导航栏 .nav-bar（z50）。
    压暗层是滚动联动的：页面停在顶部（还没有内容钻到顶栏下）时它 opacity:0，
     完全不压暗；滚过阈值（body.scrolled）才淡入，回到顶部再淡出。见下面 .nav-dim 的注释。
    压暗与模糊已解耦：压暗是独立元素、且 z 更低 → 视觉顺序是
     「内容 → 先被压暗 → 再被模糊」。压暗不参与模糊层的 mask、也不盖在模糊之上压对比度。
   压暗底色分亮/暗两套，且语义不同：
     · 亮色 = 墨色 rgba(var(--ink-rgb), a) 的灰色压暗 → 更像"玻璃压暗"而非"白雾"。
     · 暗色 = rgba(14,15,19, a)（见 body.dark .nav-dim），色值保持不变。
   改模糊只动 --blur-nav；改压暗强度只动 .nav-dim 里的 alpha。 */
.nav-blur {
  /* 高度 100 → 130 → 122 → 112：先因"顶部渐变模糊太短了"加高到 130，
     后按用户"仅仅模糊稍微缩短 5-10px"缩到 122，
     再按"渐进模糊在缩 10px"缩到 112。
      高度只决定"这条曲线画多长"，曲线形状（斜率）与长度无关 ——
        遮罩是一条从 1.0 匀速降到 0 的直线，加高 = 同一条直线整体拉长，
        每一点的斜率不变 → 整体均匀变淡，不是"另接一截"。
        （早前两版之所以像拼接/补丁，是因为曲线里有"平台段"或"碎点段"，
         跟加高无关。详见下方遮罩注释。）
      这个高度不只影响模糊：JS 的 legendParkY() 是实测 .nav-blur 高度再减 35px 得到
        顶置搜索栏的停驻位 —— 高度一改，停驻位自动跟着往下挪（正是"从元素实测不写死"的设计意图），
        不必手工同步。同理顶部压暗层 ::after 是 inset:0，跟着一起变高。 */
  position: fixed; top: 0; left: 0; right: 0; height: 112px;
  z-index: 40; pointer-events: none;
  backdrop-filter: blur(var(--blur-nav)) saturate(var(--sat-nav));
  -webkit-backdrop-filter: blur(var(--blur-nav)) saturate(var(--sat-nav));
  will-change: backdrop-filter;   /* 滚动时不摘模糊层，防"糊→清→糊"闪 */
  /*  顶部渐进模糊 · 遮罩曲线（唯一权威，改动前先看这段）
      方向约定（别再搞反）：mask 的 0% = 屏幕顶部、100% = 屏幕底部；
       所以这条曲线 = "顶部最糊（1.0），越往下越淡，到底 100% 淡到 0"。
       强度高 = 糊；值掉得快 = 糊的范围短。
     演进史（每一版都被用户否过，原因都记在这，别再走回头路）：
       · 版 A `#000 0%, #000 22%, …` —— 同色双停靠 = 人造"满值平台"，
         平台结束处必出台阶 → "另加了一段、中间一小段不糊"。
       · 版 B `#000 0%, 0.86@18%, 0.72@34%, …` —— 单调递减了，但碎点切段、顶上不够实。
       · 版 C 直线（0→0.5@45%→0）—— 均匀衰减太快，中上部就掉到 0.5 → "不够糊"。
       · 版 D 先慢后快（每段降幅递增）—— 前段虽慢但值不高，后段尾巴太陡、显得短 →
         用户："前半段模糊太小，后半段又短"。
     正解（本版 E）："高平台 + 长尾" —— 顶部一大段保持接近满强度（降幅极小），
       让"糊"的范围尽量往下铺；到下半段才逐渐收，且尾巴拖得长（不会一刀切掉）。
       数值上：前 48% 只掉 0.185（够实、够长），之后降幅逐段增大、从高位慢慢收到 0。
     数值（10 段，每段降幅严格递增 0.035→0.32）：
       1.00 → .965 → .925 → .875 → .815 → .740 → .640 → .500 → .320 → 0
       停靠点: 0 / 12 / 24 / 36 / 48 / 60 / 72 / 83 / 92 / 100 %
      斜率全程单调递增（越往下退得越快）→ 视觉上仍是一条光滑单曲线，
        不是"忽快忽慢的分段"。这正是"够实"与"一个整体"的兼容点：
        平台靠极小斜率实现（不是靠同色双停靠的"零斜率平台"）。
      高度（现 112px）只是把这条曲线整体拉长/缩短，形状（斜率规律）不变。 */
  -webkit-mask-image: linear-gradient(to bottom,
      #000 0%,
      rgba(0,0,0,0.965) 12%,
      rgba(0,0,0,0.925) 24%,
      rgba(0,0,0,0.875) 36%,
      rgba(0,0,0,0.815) 48%,
      rgba(0,0,0,0.740) 60%,
      rgba(0,0,0,0.640) 72%,
      rgba(0,0,0,0.500) 83%,
      rgba(0,0,0,0.320) 92%,
      transparent 100%);
          mask-image: linear-gradient(to bottom,
      #000 0%,
      rgba(0,0,0,0.965) 12%,
      rgba(0,0,0,0.925) 24%,
      rgba(0,0,0,0.875) 36%,
      rgba(0,0,0,0.815) 48%,
      rgba(0,0,0,0.740) 60%,
      rgba(0,0,0,0.640) 72%,
      rgba(0,0,0,0.500) 83%,
      rgba(0,0,0,0.320) 92%,
      transparent 100%);
  /*  这里不再有 background —— 压暗层已挪到独立元素 .nav-dim（在它下面一层）。
     原因：压暗必须跟着滚动阈值出现/消失（页面顶部什么都不滚动时顶上压一条灰带很难看），
     而 `background-image` 是不可过渡的，没法做淡入淡出；独立元素上用 opacity 过渡才行。
      放在 .nav-blur 外面（而不是它的 ::after）是本轮的关键修复：见 .nav-dim 注释。 */
}
/* 压暗层：只有滚动起来、内容开始钻到顶栏下面时才浮现。
   阈值与「大标题折叠成置顶标题」同一拍 —— body.scrolled（由 app.js onScroll 维护，
   scrollY > 46，与 FOLD_START_Y 同值），回到顶部自动淡出。 */
/* 压暗层 · 独立元素（原 `.nav-blur::after` 已删，见下）
   用户反馈："有一层渐显的暗色遮罩影响到了模糊效果"。
   根因：压暗层原是 `.nav-blur` 的 伪元素，被关在模糊层内部 ——
     ① 它会再吃一次父级 mask（父层 mask 裁内容，伪元素也在内）→ 两条衰减曲线相乘，
        某段压暗偏重、某段偏轻，看着就像"模糊被干扰"；
     ② 它是半透明灰盖在已模糊的内容之上，会把模糊后的对比度再压一道 →
        同一段内容在 scrolled 前后"糊的程度"看起来不一样。
   正解：把压暗拆成独立层，放在模糊层*下面*（z-index 39 < .nav-blur 的 40）——
     视觉顺序变成：内容 → 先被压暗 → 再被模糊 → 最上面是纯模糊。
     于是「压暗」与「模糊」彻底解耦：压暗只负责给内容垫一层底色，
     模糊只管糊，两者不再相乘、不再互相干扰。
    它自己不带 backdrop-filter（不然又多一层模糊），只画一条底色 + 一条 mask。
    高度/位置与 .nav-blur 保持一致（fixed top:0 height:112px），否则压暗范围对不上。
    衰减曲线按用户指定的三段式节奏自绘（上 30% 深顶 → 中段缓慢叠降 → 末段细腻收到无压暗），
      不再与 .nav-blur 的 mask 强绑 —— 用户明确要"压暗自己有分层节奏"，见下。 */
.nav-dim {
  position: fixed; top: 0; left: 0; right: 0; height: 112px;
  z-index: 39;                 /* 低于 .nav-blur(40) → 真正"在模糊下面" */
  pointer-events: none;
  opacity: 0;                  /* 静止（页面顶部）→ 完全不压暗 */
  transition: opacity 0.35s cubic-bezier(0.33, 1, 0.68, 1);
  /* 纵向压暗衰减（三段式，用户指定节奏）：
       ① 上 30%（0→30%）   ：满强度深顶，alpha 保持 0.075 不衰减（"上深"）；
       ② 中 30~70%（30→70%）：从满强度缓慢叠降，降幅逐段加大（"慢慢叠层"）；
       ③ 下 70~100%（70→100%）：从残值极细腻收到 0，
          并且 末段越接近底越慢（90%→100% 几乎贴着 0 滑行）→
          用户明确"特别最小一段 10 一定要细腻过渡到无压暗"。
      要点：末段每段降幅必须逐段递减（0.0062 → 0.0048 → 0.0035 → 0.0021 → 0）
        才是"越到底越慢"的细腻收尾；若递减反了 (=越来越陡) 就会出现"咔一下收没"的边界感。
      归零点落在 100%（层底），但此时残余只有 0.0008 级的极小值，
        肉眼已是"全无压暗"，不会形成硬边。
      强度 0.075 与原峰值一致 → 压暗深度不变，只改"怎么收"。 */
  background: linear-gradient(to bottom,
      rgba(var(--ink-rgb), 0.075) 0%,
      rgba(var(--ink-rgb), 0.075) 30%,
      rgba(var(--ink-rgb), 0.0700) 42%,
      rgba(var(--ink-rgb), 0.0612) 52%,
      rgba(var(--ink-rgb), 0.0485) 60%,
      rgba(var(--ink-rgb), 0.0340) 68%,
      rgba(var(--ink-rgb), 0.0225) 75%,
      rgba(var(--ink-rgb), 0.0128) 82%,
      rgba(var(--ink-rgb), 0.0058) 89%,
      rgba(var(--ink-rgb), 0.0012) 95%,
      rgba(var(--ink-rgb), 0) 100%);
}
body.scrolled .nav-dim { opacity: 1; }
/* 返回上一级时的过渡抑制（route() 在恢复滚动位置的那两帧挂上 .no-blur-anim）：
   把压暗层的 opacity 过渡时长归零 —— 即便出现一帧 scrollY=0，也不会看到
   "压暗层消失再淡入"的重播动画，视觉上直接就是返回后的最终状态。 */
body.no-blur-anim .nav-dim { transition: none !important; }
.nav-bar {
  position: fixed; top: 0; left: 0; right: 0; height: var(--nav-h);
  z-index: 50; display: flex; align-items: center; justify-content: flex-start;
  padding: 0 16px;
}

/* ---------- 顶部导航栏 · 手势禁区 ----------
   一条透明的固定横带，从视口顶延伸到「右上两个按钮的下缘」——即 54px
   （按钮 44px 居中于 calc(var(--nav-h)/2 + 6px) = 32px → 盒顶 10、盒底 54，见 .filter-pop 顶部注释）。
   目的（用户要求）：
     · 这一条上不触发页面滚动（触摸上下滑被吃掉）；
     · 这一条上不触发页面点击（点击不穿透到下面的卡片 / 链接）。
   但悬浮按钮与浮层照常工作：
     · z-index 46 —— 高于内容与 .nav-blur(40)/顶置搜索栏(45)，低于 .nav-bar(50)
       → 导航栏自己的返回/筛选/分享/菜单按钮（都在 .nav-bar 内，z50）压在它上面，点击不受影响；
       → z>46 的浮层（.menu-pop / .filter-pop 等，z65）同样在其之上，照常可点。
    只拦触摸滚动与点击（用户明确选择）：不拦鼠标 wheel，桌面端把鼠标停顶部仍可滚。
    这条带必须真实可命中（pointer-events: auto） —— 因为"拦触摸滚动"要靠它身上的
      `touch-action: none` 在浏览器手势判定阶段生效。不能用 JS preventDefault：
      · 滚屏一开始，touchmove 的 cancelable 变 false，preventDefault 无效，控制台还会刷
        "Ignored attempt to cancel a touchmove event with cancelable=false"；
      · document 级拦触摸会干扰搜索框的指针拖拽手势（实测拖拽完全失效）。
      实测：这两条正是"拖拽没效果 + 报错"的根因，已改成纯 CSS 方案。
    z-index 46 仍低于 .nav-bar(50) → 导航按钮/浮层照常压在它上一层，可点；
      "拦点击穿透"仍由 JS 捕获阶段 click 处理（本层 z 更低，点不到导航按钮，不影响）。
    双击回顶由本层自己的事件判定（见 app.js），不依赖冒泡到 document。 */
.nav-touch-block {
  position: fixed; top: 0; left: 0; right: 0;
  height: calc(var(--nav-h) / 2 + 6px + 22px);   /*54px：右上按钮盒底 */
  z-index: 46;
  pointer-events: auto;      /* 必须 auto：touch-action 需要本层被命中才生效 */
  touch-action: none;        /* 这条带上的触摸一律不算滚屏 —— 纯 CSS，不用 JS 干预 */
  -webkit-touch-action: none;
  background: transparent;   /* 纯透明，只吃手势不挡视觉 */
  -webkit-tap-highlight-color: transparent;
}
.nav-title {
  font-size: 17px; font-weight: 600; opacity: 0; transform: translateY(14px);
  transition: opacity 0.28s ease, transform 0.28s var(--ease-spring);
}
body.scrolled .nav-title { opacity: 1; transform: translateY(6px); }
/* 二级界面（带返回钮）标题让位给返回钮：左缩进到返回钮右侧，整体仍左对齐 */
body.can-back .nav-title { margin-left: 46px; }
/* 大标题折叠视图（无缝飞行逻辑见 app.js）：
   页面含正文大标题时挂 body.has-ltitle，把顶栏标题常驻隐藏——不让
   body.scrolled 提前点亮它（否则大标题还露在内容区，顶栏标题就先出现，
   两个标题并存断档）。滚动越过阈值后，正文主文本被克隆成 .title-fly
   覆盖在顶栏中央直接充当顶栏标题，原 .nav-title 始终隐藏：全程一条字链。 */
body.has-ltitle .nav-title { opacity: 0; transform: translateY(14px); }
.title-fly {
  position: fixed; left: 0; top: 0; z-index: 60; margin: 0; pointer-events: none;
  white-space: nowrap; will-change: transform; transform-origin: 0 0;
}

.nav-back {
  position: absolute; left: 14px; top: calc(50% + 6px); transform: translateY(-50%);
  display: inline-flex; align-items: center; justify-content: center; cursor: pointer;
  width: 44px; height: 44px; border-radius: 50%;
  /* 玻璃底 / 描边 / 投影统一在「毛玻璃统一」「玻璃描边」「投影」三段，此处只管版式 */
  color: var(--ink); font-family: inherit;
}
/* 入场 / 出场：与学校列表页筛选按钮（.nav-filter）完全同款节拍 ——
   隐藏态 opacity:0 + scale(0.6) + visibility:hidden，进二级界面（body.can-back）时
   放大浮现；返回上一级时反向缩回。用 visibility 而非 display：display 不可过渡，
   会导致「直接瞬间出现 / 瞬间消失」（这正是此前返回按钮的观感）。 */
.nav-back {
  opacity: 0; visibility: hidden; pointer-events: none;
  transform: translateY(-50%) scale(0.6);
  transition: opacity 0.22s ease, transform 0.28s var(--ease-spring), visibility 0.22s, background 0.2s, color 0.2s;
}
body.can-back .nav-back {
  opacity: 1; visibility: visible; pointer-events: auto; transform: translateY(-50%) scale(1);
}
body.can-back .nav-back:active { transform: translateY(-50%) scale(0.86); background: rgba(var(--ink-rgb), 0.06); }
.nav-back i { font-size: 17px; }

/* ---------- 左上角筛选按钮（仅学校列表页；与返回按钮互斥） ---------- */
.nav-filter {
  display: none; align-items: center; justify-content: center; cursor: pointer;
  width: 44px; height: 44px; border-radius: 50%;
  color: var(--ink); font-family: inherit;
  transition: transform 0.2s var(--ease-spring), background 0.2s, color 0.2s;
}
.nav-filter:active { transform: scale(0.86); background: rgba(var(--ink-rgb), 0.06); }
.nav-filter i { font-size: 16px; transition: transform 0.28s var(--ease-spring); }
/* 首页左上角品牌名（极光万能墙）：毛玻璃横胶囊，与三点按钮同材质/同高度/同水平线，仅首页显示。
   玻璃底 / 描边 / 投影与顶部圆钮同组（见「毛玻璃统一」「玻璃描边」「投影」三段），此处只管版式。 */
.nav-brand-home {
  position: absolute; left: 14px; top: calc(50% + 6px); transform: translateY(-50%);
  display: none; align-items: center; justify-content: center;
  height: 44px; padding: 0 18px; border-radius: 999px;
  font-size: 15px; font-weight: 700; letter-spacing: 0.02em; color: var(--ink);
  pointer-events: auto; white-space: nowrap;
}
/* 品牌胶囊（首页）跟手弹 + 扫光：并入 app.js initPressSpring —— 与右上角按钮/弹窗关闭钮
   完全同款阻尼（按压微放大 → tanh 限幅跟手位移 + 方向拉伸 → 松手欠阻尼弹簧回弹）；
   touch-action:none 让拖拽不被页面滚动抢占；光效 = ::before 径向光点随手指扫动
   （--px/--py 由 initPressSpring 写入百分比，手指拖出后贴胶囊边缘，脱手即灭不锁交互） */
.nav-brand-home {
  touch-action: none;
  cursor: grab;
  -webkit-user-select: none; user-select: none;
  -webkit-touch-callout: none;
}
/* 按压扫光（同顶部圆钮 / 弹窗关闭钮同款径向光点）：亮色浅灰光 / 暗色白光 */
.nav-brand-home::before {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  background: radial-gradient(circle 70px at var(--px, 50%) var(--py, 50%),
    rgba(var(--ink-rgb), 0.26), rgba(var(--ink-rgb), 0) 100%);
  opacity: 0; pointer-events: none;
  transition: opacity 0.18s ease;
}
.nav-brand-home.pressing::before { opacity: 1; }
body.dark .nav-brand-home::before {
  background: radial-gradient(circle 70px at var(--px, 50%) var(--py, 50%),
    rgba(255, 255, 255, 0.45), rgba(255, 255, 255, 0) 100%);
}
/* 首页品牌名斜角打光描边（与顶部按钮同款）：右上主光 + 左下淡副光。
   亮色 = 暗色那道白光环的灰色版（同一 to bottom left 轴，保留光感）。 */
.nav-brand-home::after {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  padding: 1px;
  background: var(--stroke-grey);
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  pointer-events: none;
  transform: translateZ(0);   /* 强制独立合成层：修 mask 环未分层时端部被 AA 抹断 */
}
body.dark .nav-brand-home::after {
  background: linear-gradient(to bottom left,
    rgba(255, 255, 255, 0.86) 0%, rgba(255, 255, 255, 0.54) 18%, rgba(255, 255, 255, 0.22) 40%,
    rgba(255, 255, 255, 0.07) 60%, rgba(255, 255, 255, 0.17) 76%, rgba(255, 255, 255, 0.34) 100%);
}
body.view-home .nav-brand-home { display: flex; }

/* 只在学校列表页出现；任何带返回按钮的二级界面一律让位给返回。
   滚动显隐：默认隐藏，滚过「标识说明」卡片（其顶部越过导航栏底部）后由 JS 加 .visible 显示 */
body.view-schools:not(.can-back) .nav-filter {
  display: inline-flex; opacity: 0; visibility: hidden; pointer-events: none;
  transform: scale(0.6);
  transition: opacity 0.22s ease, transform 0.28s var(--ease-spring), visibility 0.22s, background 0.2s, color 0.2s;
}
body.view-schools:not(.can-back) .nav-filter.visible {
  opacity: 1; visibility: visible; pointer-events: auto; transform: scale(1);
}
body.view-schools:not(.can-back) .nav-filter.visible:active { transform: scale(0.86); }
/* 搜索结果页：右上角筛选按钮常显（与列表页同一套筛选入口/弹卡，不依赖滚动显隐） */
body.view-search:not(.can-back) .nav-filter {
  display: inline-flex; opacity: 1; visibility: visible; pointer-events: auto;
  transform: scale(1);
}
body.view-search:not(.can-back) .nav-filter:active { transform: scale(0.86); }
/* 展开态：图标高亮 + 轻微放大，呼应右上角菜单的开闭反馈 */
.nav-filter.open { color: var(--brand, var(--tint)); background: rgba(var(--tint-rgb), 0.12); }
.nav-filter.open i { transform: scale(1.08); }

/* 筛选弹出卡片：与右上角菜单同款开闭动画，锚点改到右上角（与筛选按钮同侧） */
.filter-pop {
  position: fixed;
  /* 卡片顶到按钮底的空隙 = 按钮顶到页顶（10px），上下对称：
     按钮中心 = nav-h/2 + 6（按钮盒顶 10、盒底 54）→ 卡片顶 = 54 + 10 = 64px */
  top: calc(var(--nav-h) / 2 + 6px + 22px + 10px);
  /*  right 必须跟着锚点一起补偿：本卡常驻 scale(0.72)，缩放中心右移 a 后，
     静止态的可视右缘会跟着左移 a×(1−0.72)=0.28a（约 6px），卡片就不贴 14px 了。
     把盒子的 right 反向减掉这 0.28a，静止位与改动前像素一致（仍距视口右缘 14px），
     只有「缩放锚点」往左挪——两件事彻底解耦。
     0.28 = 1 − 常驻缩放 0.72，改 baseScale 时这里要同步改（JS 侧 .filter-pop 的 baseScale:0.72）。 */
  right: calc(14px - var(--pop-anchor-x) * 0.28); left: auto; z-index: 65;
  width: 380px; max-width: calc(100vw - 28px);
  border-radius: 30px; padding: 4px 16px 10px;
  /* 玻璃材质统一在「毛玻璃统一」段的 `.menu-pop, .filter-pop` 规则里（勿在此处改 blur/透底，
     改了会被那条后定义的规则覆盖，等于死代码）。 */
  border: none;   /* 灰边框由 ::after 斜角打光光边替代（描边只留一层） */
  /* 阴影是「空间属性」，会被常驻 scale(0.72) 一起缩小 → 按 1/0.72 补偿（14/38→19/53）。
      只有空间属性需要补偿：blur 要、box-shadow 要；saturate 是色彩属性不补偿。 */
  box-shadow: 0 19px 53px rgba(var(--shadow-rgb), 0.18);
  opacity: 0; pointer-events: none;
  /* 开合锚点 = 卡片右上角再往左 --pop-anchor-x（与 .menu-pop 同一套，见 :root 注释）。
     常驻 scale(0.72) 下锚点右移会让静止位漂移，所以上面 right 已做等量反向补偿，
     这里只管"从哪长出来"。隐藏态 transform 与 filterPopOut 的 0% 同值。 */
  transform-origin: calc(100% - var(--pop-anchor-x)) 0%;
  transform: translate(0, 0) scale(0.1152);   /*0.16 × 0.72，与菜单卡隐藏态同比例 */
  transition: opacity 0.18s ease 0.03s, transform var(--pop-out-dur) var(--ease-pop-out);
}
.filter-pop.show {
  opacity: 1; pointer-events: auto; transform: translateY(0) scale(0.72);
  animation: filterPopIn var(--pop-in-dur) linear;   /* 与右上角菜单卡完全同一段动画 */
  transition: opacity 0.16s ease, transform var(--pop-in-dur) var(--ease-pop-in);
}
/* 关闭：沿同一条曲线反向缩回（与 .menu-pop:not(.show) 同款写法），
   visibility 延迟到回缩结束再收 */
.filter-pop:not(.show) {
  animation: filterPopOut var(--pop-out-dur) linear reverse both;
  transition: visibility 0s linear var(--pop-out-dur), box-shadow var(--pop-out-dur) ease,
              backdrop-filter var(--pop-out-dur) ease;
}
/* ---------------------------------------------------------------------
   开合曲线 = 右上角菜单卡那条（menuPopIn / menuPopOut）逐帧照抄
   ---------------------------------------------------------------------
   只做两处必要换算，曲线本身一个数值都没动：
     ① 缩放终值：本卡常驻 scale(0.72) → 每帧 scale 一律 ×0.72
        （0.160→0.11520 … 峰值 1.031→0.74232 … 1.000→0.72000）；
     ② 锚点镜像：菜单卡锚自身右上角（100% 0%），本卡锚自身右上角（top right）。
        两卡锚点都在顶边，所以 translateY 那记"往下兜 12px 再浮上来"的方向不变，
        照抄即可（不需要取反 —— 取反是给"贴底边向上展开"的 .drop-up 用的）。
   border-radius / z-index 用本卡自己的值（30px / 40 → 65），时序、缓动、opacity
   区间、pointer-events 起止点全部与菜单卡一致。时长同样是 --pop-in-dur / --pop-out-dur。 */
@keyframes filterPopIn {
  0% { transform: translate(0px, 0px) scale(0.11520); opacity: 0.400; border-radius: 30px; z-index: 40; pointer-events: none; }
  5% { transform: translate(0px, 3.6px) scale(0.14040); opacity: 0.530; border-radius: 30px; z-index: 40; }
  10% { transform: translate(0px, 11.1px) scale(0.20160); opacity: 0.660; border-radius: 30px; z-index: 40; }
  15% { transform: translate(0px, 17.9px) scale(0.28008); opacity: 0.790; border-radius: 30px; z-index: 40; }
  20% { transform: translate(0px, 21.8px) scale(0.36360); opacity: 0.920; border-radius: 30px; z-index: 40; }
  25% { transform: translate(0px, 22.4px) scale(0.44352); opacity: 1.000; border-radius: 30px; z-index: 40; }
  30% { transform: translate(0px, 20.3px) scale(0.51480); opacity: 1.000; border-radius: 30px; z-index: 65; }
  35% { transform: translate(0px, 16.4px) scale(0.57528); opacity: 1.000; border-radius: 30px; z-index: 65; }
  40% { transform: translate(0px, 12.0px) scale(0.62496); opacity: 1.000; border-radius: 30px; z-index: 65; }
  45% { transform: translate(0px, 7.7px) scale(0.66312); opacity: 1.000; border-radius: 30px; z-index: 65; }
  50% { transform: translate(0px, 3.8px) scale(0.68472); opacity: 1.000; border-radius: 30px; z-index: 65; }
  54% { transform: translate(0px, 1.4px) scale(0.70920); opacity: 1.000; border-radius: 30px; z-index: 65; }
  59% { transform: translate(0px, 0.2px) scale(0.72432); opacity: 1.000; border-radius: 30px; z-index: 65; }
  64% { transform: translate(0px, 0px) scale(0.73440); opacity: 1.000; border-radius: 30px; z-index: 65; }
  69% { transform: translate(0px, 0px) scale(0.74088); opacity: 1.000; border-radius: 30px; z-index: 65; }
  73% { transform: translate(0px, -0.1px) scale(0.74376); opacity: 1.000; border-radius: 30px; z-index: 65; }
  79% { transform: translate(0px, -0.05px) scale(0.73800); opacity: 1.000; border-radius: 30px; z-index: 65; }
  85% { transform: translate(0px, 0px) scale(0.73008); opacity: 1.000; border-radius: 30px; z-index: 65; }
  92% { transform: translate(0px, 0px) scale(0.72288); opacity: 1.000; border-radius: 30px; z-index: 65; }
  96% { transform: translate(0px, 0px) scale(0.72072); opacity: 1.000; border-radius: 30px; z-index: 65; pointer-events: none; }
  100% { transform: translate(0px, 0px) scale(0.72000); opacity: 1.000; border-radius: 30px; z-index: 65; pointer-events: auto; }
}
@keyframes filterPopOut {
  0% { transform: translate(0px, 0px) scale(0.11520); opacity: 0.000; border-radius: 30px; z-index: 40; }
  8% { transform: translate(0px, 3.7px) scale(0.16560); opacity: 0.183; border-radius: 30px; z-index: 40; }
  17% { transform: translate(0px, 6.7px) scale(0.21600); opacity: 0.367; border-radius: 30px; z-index: 40; }
  25% { transform: translate(0px, 9px) scale(0.26640); opacity: 0.550; border-radius: 30px; z-index: 40; }
  33% { transform: translate(0px, 10.7px) scale(0.31680); opacity: 0.733; border-radius: 30px; z-index: 65; }
  42% { transform: translate(0px, 11.7px) scale(0.36720); opacity: 0.917; border-radius: 30px; z-index: 65; }
  50% { transform: translate(0px, 12px) scale(0.41760); opacity: 1.000; border-radius: 30px; z-index: 65; }
  58% { transform: translate(0px, 11.7px) scale(0.46800); opacity: 1.000; border-radius: 30px; z-index: 65; }
  67% { transform: translate(0px, 10.7px) scale(0.51840); opacity: 1.000; border-radius: 30px; z-index: 65; }
  75% { transform: translate(0px, 9px) scale(0.56880); opacity: 1.000; border-radius: 30px; z-index: 65; }
  83% { transform: translate(0px, 6.7px) scale(0.61920); opacity: 1.000; border-radius: 30px; z-index: 65; }
  92% { transform: translate(0px, 3.7px) scale(0.66960); opacity: 1.000; border-radius: 30px; z-index: 65; }
  100% { transform: translate(0px, 0px) scale(0.72000); opacity: 1.000; border-radius: 30px; z-index: 65; }
}
/* 卡片内的筛选面板去掉自身卡片外观，只保留分隔线 */
.filter-pop .filter-panel {
  margin: 0; padding: 0; background: none; border: none; border-radius: 0; box-shadow: none;
}
/* 弹出卡片内（左上角筛选）的下拉菜单：进一步收紧阈值，从卡片位置展开也不会碰到底部 Tab 栏 */
.filter-pop .dropdown-menu { max-height: min(38vh, 280px); }
.filter-pop-head {
  display: flex; align-items: center; justify-content: space-between;
  padding: 11px 2px 10px; border-bottom: 1px solid var(--glass-border);
}
.fp-title {
  display: flex; align-items: center; gap: 7px;
  font-size: 16px; font-weight: 700; color: var(--ink);
}
.fp-title i { font-size: 13px; color: var(--ink-3); }
.fp-reset {
  border: none; background: rgba(var(--ink-rgb), 0.06); color: var(--ink-2);
  font-family: inherit; font-size: 13px; font-weight: 600;
  padding: 6px 13px; border-radius: 999px; cursor: pointer;
  transition: background 0.18s, transform 0.18s var(--ease-spring);
}
.fp-reset:active { transform: scale(0.92); background: rgba(var(--ink-rgb), 0.12); }

/* ---------- 右上角按钮组（筛选 / 分享 / 暗色，整体固定在右上角） ---------- */
.nav-right {
  position: absolute; right: 14px; top: calc(50% + 6px); transform: translateY(-50%);
  display: flex; align-items: center; gap: 10px;
}
/* ---------- 右上角三点菜单 ---------- */
.nav-menu {
  display: inline-flex; align-items: center; justify-content: center; cursor: pointer;
  width: 44px; height: 44px; border-radius: 50%;
  color: var(--ink); font-family: inherit;
  transition: transform 0.2s var(--ease-spring), background 0.2s;
}
.nav-menu:active { transform: scale(0.86); background: rgba(var(--ink-rgb), 0.06); }
.nav-menu i { font-size: 20px; transition: transform 0.28s var(--ease-spring); }
/* 菜单展开态：按钮高亮变蓝 + 图标轻微放大（与左上角筛选按钮同款反馈） */
.nav-menu.open { color: var(--brand, var(--tint)); background: rgba(var(--tint-rgb), 0.12); }
.nav-menu.open i { transform: scale(1.08); }

/* ---------- 右上角分享按钮（仅详情页；位于「更多/暗色」按钮左侧） ----------
   规格与 .nav-menu 完全一致（44px 圆钮 / 同材质 / 同水平线），右缘紧贴：
   nav-menu 占 right 14→58，故本钮 right: 64px（与菜单留 6px 间隙）。 */
.nav-share {
  display: none; align-items: center; justify-content: center; cursor: pointer;
  width: 44px; height: 44px; border-radius: 50%;
  color: var(--ink); font-family: inherit;
}
/* 入场 / 出场：从右侧「更多 / 暗色」圆钮（#navMenu）位置向左飘出、收回。
   本钮 right:64、暗色钮 right:14 —— 两钮中心相距 50px（44 宽 + 6 间隙），
   故隐藏态 translateX(50px) 时本钮中心正好压在暗色钮中心上，观感就是
   「暗色圆钮向左生出一颗分享钮」；进详情页飘出，离开详情页原路收回。
   与返回 / 筛选钮同款节拍：用 visibility 而非 display（display 不可过渡，
   那正是此前「瞬间出现 / 瞬间消失」的原因）。 */
.nav-share {
  opacity: 0; visibility: hidden; pointer-events: none;
  transform: translateX(50px) scale(0.6);
  transition: opacity 0.22s ease, transform 0.28s var(--ease-spring), visibility 0.22s, background 0.2s, color 0.2s;
}
body.view-detail .nav-share {
  display: inline-flex;
  opacity: 1; visibility: visible; pointer-events: auto;
  transform: translateX(0) scale(1);
}
body.view-detail .nav-share:active { transform: translateX(0) scale(0.86); background: rgba(var(--ink-rgb), 0.06); }
.nav-share i { font-size: 17px; }
/* 详情页右上角双圆钮：顶栏标题左右留白收窄，避免与按钮重叠（仅详情页生效） */
body.view-detail .nav-title {
  max-width: calc(100% - 240px);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.menu-pop {
  position: fixed;
  /* 同筛选卡：卡片顶到按钮底的空隙 = 按钮顶到页顶（10px），上下对称 */
  top: calc(var(--nav-h) / 2 + 6px + 22px + 10px);
  right: 14px;
  /* 层级须高于列表页右侧索引条（.jump-rail z60）——菜单右缘与索引条有重叠区域，被盖住即不可点不可见；
     且高于顶部导航（50）。取 66：仍低于遮罩/弹窗(70/75)，与同层浮层互斥不冲突 */
  z-index: 66;
  min-width: 190px; border-radius: 26px; padding: 5px;
  /* 玻璃材质统一在「毛玻璃统一」段的 `.menu-pop, .filter-pop` 规则里（勿在此处改 blur/透底）。 */
  border: none;   /* 灰边框由 ::after 斜角打光光边替代（描边只留一层） */
  box-shadow: 0 14px 38px rgba(var(--shadow-rgb), 0.18);
  opacity: 0; pointer-events: none; transform: translate(0, 0) scale(0.16);
  /* 入场锚点 = 卡片顶边靠右、再往左 --pop-anchor-x（见 :root 注释）：
     由小长大、途中往下兜一个小 U 再浮上来。
     本卡常驻 scale(1)（静止态就是满尺寸），锚点左移不会影响静止位，无需 right 补偿。 */
  transform-origin: calc(100% - var(--pop-anchor-x)) 0%;
  /* 苹果式进出分离：入场（.show）从按钮处放射放大 + 弹性过冲 pop 到位；
     出场（非 .show）快而收敛地缩回按钮 */
  transition: opacity 0.18s ease 0.03s, transform var(--pop-out-dur) var(--ease-pop-out);
}
.menu-pop.show {
  opacity: 1; pointer-events: auto;
  transform: translateY(0) scale(1);
  animation: menuPopIn var(--pop-in-dur) linear;
}
/* 菜单行：内容整体随卡片一起出现（不再逐行错峰；动画由卡片自身完成） */
.menu-pop .menu-row { opacity: 0; transform: translateY(4px);
  transition: background 0.18s, opacity 0.22s ease, transform 0.22s ease; }
.menu-pop.show .menu-row { opacity: 1; transform: none; transition-delay: 0.1s; }
/* 右上角菜单整体缩小（仅作用于 .menu-pop，不影响 dev-sheet / glassySheet 的 .menu-row） */
/* 一级菜单行字：已还原到 34px 之前的 15px/600（用户要求"改之前什么样就什么样"）。
   二级菜单（卡片式下拉 .dropdown--card / 各子面板）从未被这条影响，保持各自的缩小字号不动。 */
.menu-pop .menu-row { padding: 11px 14px; font-size: 15px; font-weight: 600; min-height: 46px; gap: 12px; }
/* 右上菜单开/关：锚点=卡片自身右上角，由小(0.16)长大、途中往下兜一个小 U 再浮上来。
   纯 CSS 烘焙（timing 用 linear，曲线形状全在帧里），时长 --pop-in-dur 与缓动未动。
   U/N 幅度：深兜 22px（0~50% 帧位不变）；过冲峰值 scale 1.033（73% 到峰，明显小过冲）。
    关键修正（用户第三轮）：上一版把峰值推到 1.085 且卡片在 58% 就加速冲过 1.0 ——
   读起来是「路径最后一段自己就在过冲/回弹」，而不是「先干净落到 1.0、再补一记小过冲」。
   本版改为：卡片减速落进 1.0（54% 0.985 → 59% 1.006 → 64% 1.020 → 69% 1.029，
   每帧增量递减=减速靠近终点），到 73% 才轻轻多探到 1.033（小过冲），之后平滑收回到 1.000。
   即「路径走完、落位、然后一个小的过冲回来」，过冲小且长在减速后的弧顶，不抢戏、不刻意。
   向下展开段=U(menuPopIn)、向上展开段=N(menuPopInUp，仅 translateY 取反），filterPopIn 为
   同曲线按常驻 scale(0.72) 烘焙的副本（scale=本帧×0.72、translateY 取绝对像素不×0.72）。三处菜单卡同步生效。 */
@keyframes menuPopIn {
  0% { transform: translate(0px, 0px) scale(0.160); opacity: 0.400; border-radius: 26px; z-index: 40; pointer-events: none; }
  5% { transform: translate(0px, 3.6px) scale(0.195); opacity: 0.530; border-radius: 26px; z-index: 40; }
  10% { transform: translate(0px, 11.1px) scale(0.280); opacity: 0.660; border-radius: 26px; z-index: 40; }
  15% { transform: translate(0px, 17.9px) scale(0.389); opacity: 0.790; border-radius: 26px; z-index: 40; }
  20% { transform: translate(0px, 21.8px) scale(0.505); opacity: 0.920; border-radius: 26px; z-index: 40; }
  25% { transform: translate(0px, 22.4px) scale(0.616); opacity: 1.000; border-radius: 26px; z-index: 40; }
  30% { transform: translate(0px, 20.3px) scale(0.715); opacity: 1.000; border-radius: 26px; z-index: 66; }
  35% { transform: translate(0px, 16.4px) scale(0.799); opacity: 1.000; border-radius: 26px; z-index: 66; }
  40% { transform: translate(0px, 12.0px) scale(0.868); opacity: 1.000; border-radius: 26px; z-index: 66; }
  45% { transform: translate(0px, 7.7px) scale(0.921); opacity: 1.000; border-radius: 26px; z-index: 66; }
  50% { transform: translate(0px, 3.8px) scale(0.951); opacity: 1.000; border-radius: 26px; z-index: 66; }
  54% { transform: translate(0px, 1.4px) scale(0.985); opacity: 1.000; border-radius: 26px; z-index: 66; }
  59% { transform: translate(0px, 0.2px) scale(1.006); opacity: 1.000; border-radius: 26px; z-index: 66; }
  64% { transform: translate(0px, 0px) scale(1.020); opacity: 1.000; border-radius: 26px; z-index: 66; }
  69% { transform: translate(0px, 0px) scale(1.029); opacity: 1.000; border-radius: 26px; z-index: 66; }
  73% { transform: translate(0px, -0.1px) scale(1.033); opacity: 1.000; border-radius: 26px; z-index: 66; }
  79% { transform: translate(0px, -0.05px) scale(1.025); opacity: 1.000; border-radius: 26px; z-index: 66; }
  85% { transform: translate(0px, 0px) scale(1.014); opacity: 1.000; border-radius: 26px; z-index: 66; }
  92% { transform: translate(0px, 0px) scale(1.004); opacity: 1.000; border-radius: 26px; z-index: 66; }
  96% { transform: translate(0px, 0px) scale(1.001); opacity: 1.000; border-radius: 26px; z-index: 66; pointer-events: none; }
  100% { transform: translate(0px, 0px) scale(1.000); opacity: 1.000; border-radius: 26px; z-index: 66; pointer-events: auto; }
}
@keyframes menuPopOut {
  0% { transform: translate(0px, 0px) scale(0.160); opacity: 0.000; border-radius: 26px; z-index: 40; }
  8% { transform: translate(0px, 3.7px) scale(0.230); opacity: 0.183; border-radius: 26px; z-index: 40; }
  17% { transform: translate(0px, 6.7px) scale(0.300); opacity: 0.367; border-radius: 26px; z-index: 40; }
  25% { transform: translate(0px, 9px) scale(0.370); opacity: 0.550; border-radius: 26px; z-index: 40; }
  33% { transform: translate(0px, 10.7px) scale(0.440); opacity: 0.733; border-radius: 26px; z-index: 66; }
  42% { transform: translate(0px, 11.7px) scale(0.510); opacity: 0.917; border-radius: 26px; z-index: 66; }
  50% { transform: translate(0px, 12px) scale(0.580); opacity: 1.000; border-radius: 26px; z-index: 66; }
  58% { transform: translate(0px, 11.7px) scale(0.650); opacity: 1.000; border-radius: 26px; z-index: 66; }
  67% { transform: translate(0px, 10.7px) scale(0.720); opacity: 1.000; border-radius: 26px; z-index: 66; }
  75% { transform: translate(0px, 9px) scale(0.790); opacity: 1.000; border-radius: 26px; z-index: 66; }
  83% { transform: translate(0px, 6.7px) scale(0.860); opacity: 1.000; border-radius: 26px; z-index: 66; }
  92% { transform: translate(0px, 3.7px) scale(0.930); opacity: 1.000; border-radius: 26px; z-index: 66; }
  100% { transform: translate(0px, 0px) scale(1.000); opacity: 1.000; border-radius: 26px; z-index: 66; }
}
/* ---- 居中弹窗（信息弹窗 / 开发者模式 / 动效效果 / 学生验证）的开合 ----
   v7.2.2：以「上浮」为主，只叠一点点缩放 + 轻微回弹。
   缩放幅度刻意压到极小（0.94 → 1），避免早期版本那种「从中心整体放大」的夸张感；
   位移仍以 translateY 为主，缩放造成的每帧 layout 重算可以忽略。
    性能关键（v7.2.2 修）：关键帧里绝不出现 translate(-50%,-50%) 这种百分比位移。
     百分比是相对元素自身尺寸解析的 → 每帧都要重新测量元素、重算矩阵，
     在 QQ / OPPO 内置浏览器这类老 WebView 上，配合大尺寸 backdrop-filter 会逐帧重抓背景
     → 表现为「抽搐 / 频闪」。居中改由 .sheet-center 外层承担（静态 transform，不参与动画），
     关键帧里只剩纯像素 translateY 与纯小数 scale → 每帧纯合成，零 layout 开销。 */
@keyframes sheetPopIn {
  /* 上浮 + 一点点缩放 + 轻微回弹：
     缩放幅度压得很小（0.94 → 1），观感上只是「轻轻涨起来」而不是「从中心变大」；
     55% 处过冲到 scale(1.006) 再回落，就是那点若有若无的回弹，幅度刻意做小（+0.6%）。 */
  0%   { transform: translateY(14px) scale(0.94); opacity: 0;    pointer-events: none; }
  55%  { transform: translateY(0px)  scale(1.006); opacity: 1; }
  100% { transform: translateY(0px)  scale(1);     opacity: 1;    pointer-events: auto; }
}
@keyframes sheetPopOut {
  0%   { transform: translateY(0px)  scale(1);     opacity: 1;    pointer-events: auto; }
  100% { transform: translateY(14px) scale(0.94);  opacity: 0;    pointer-events: none; }
}
/* 关闭：沿同一个小 U 反向缩回（形状不变，只是把时长调快了）；visibility 延迟到回缩结束再收 */
.menu-pop:not(.show) {
  animation: menuPopOut var(--pop-out-dur) linear reverse both;
  transition: visibility 0s linear var(--pop-out-dur), box-shadow var(--pop-out-dur) ease,
              backdrop-filter var(--pop-out-dur) ease;
}
/* ---- 向上展开的镜像版（列表页下拉贴到页面底部、翻上去时用）----
   开合动画里那记"往下兜一个小 U 再浮上来"的方向必须跟着翻：
   向下展开时卡片先往锚点的反方向（下）兜一下 —— 向上展开时锚点在卡片底边，
   那一下就该往上兜，于是同一段曲线做垂直镜像（translateY 取反），观感就是一个 N。
   只有 translateY 反号，scale / opacity / 圆角 / z-index / 时序（时长与缓动）全部逐帧照抄。 */
@keyframes menuPopInUp {
  0% { transform: translate(0px, 0px) scale(0.160); opacity: 0.400; border-radius: 26px; z-index: 40; pointer-events: none; }
  5% { transform: translate(0px, -3.6px) scale(0.195); opacity: 0.530; border-radius: 26px; z-index: 40; }
  10% { transform: translate(0px, -11.1px) scale(0.280); opacity: 0.660; border-radius: 26px; z-index: 40; }
  15% { transform: translate(0px, -17.9px) scale(0.389); opacity: 0.790; border-radius: 26px; z-index: 40; }
  20% { transform: translate(0px, -21.8px) scale(0.505); opacity: 0.920; border-radius: 26px; z-index: 40; }
  25% { transform: translate(0px, -22.4px) scale(0.616); opacity: 1.000; border-radius: 26px; z-index: 40; }
  30% { transform: translate(0px, -20.3px) scale(0.715); opacity: 1.000; border-radius: 26px; z-index: 66; }
  35% { transform: translate(0px, -16.4px) scale(0.799); opacity: 1.000; border-radius: 26px; z-index: 66; }
  40% { transform: translate(0px, -12.0px) scale(0.868); opacity: 1.000; border-radius: 26px; z-index: 66; }
  45% { transform: translate(0px, -7.7px) scale(0.921); opacity: 1.000; border-radius: 26px; z-index: 66; }
  50% { transform: translate(0px, -3.8px) scale(0.951); opacity: 1.000; border-radius: 26px; z-index: 66; }
  54% { transform: translate(0px, -1.4px) scale(0.985); opacity: 1.000; border-radius: 26px; z-index: 66; }
  59% { transform: translate(0px, -0.2px) scale(1.006); opacity: 1.000; border-radius: 26px; z-index: 66; }
  64% { transform: translate(0px, 0px) scale(1.020); opacity: 1.000; border-radius: 26px; z-index: 66; }
  69% { transform: translate(0px, 0px) scale(1.029); opacity: 1.000; border-radius: 26px; z-index: 66; }
  73% { transform: translate(0px, 0.1px) scale(1.033); opacity: 1.000; border-radius: 26px; z-index: 66; }
  79% { transform: translate(0px, 0.05px) scale(1.025); opacity: 1.000; border-radius: 26px; z-index: 66; }
  85% { transform: translate(0px, 0px) scale(1.014); opacity: 1.000; border-radius: 26px; z-index: 66; }
  92% { transform: translate(0px, 0px) scale(1.004); opacity: 1.000; border-radius: 26px; z-index: 66; }
  96% { transform: translate(0px, 0px) scale(1.001); opacity: 1.000; border-radius: 26px; z-index: 66; pointer-events: none; }
  100% { transform: translate(0px, 0px) scale(1.000); opacity: 1.000; border-radius: 26px; z-index: 66; pointer-events: auto; }
}
@keyframes menuPopOutUp {
  0% { transform: translate(0px, 0px) scale(0.160); opacity: 0.000; border-radius: 26px; z-index: 40; }
  8% { transform: translate(0px, -3.7px) scale(0.230); opacity: 0.183; border-radius: 26px; z-index: 40; }
  17% { transform: translate(0px, -6.7px) scale(0.300); opacity: 0.367; border-radius: 26px; z-index: 40; }
  25% { transform: translate(0px, -9px) scale(0.370); opacity: 0.550; border-radius: 26px; z-index: 40; }
  33% { transform: translate(0px, -10.7px) scale(0.440); opacity: 0.733; border-radius: 26px; z-index: 66; }
  42% { transform: translate(0px, -11.7px) scale(0.510); opacity: 0.917; border-radius: 26px; z-index: 66; }
  50% { transform: translate(0px, -12px) scale(0.580); opacity: 1.000; border-radius: 26px; z-index: 66; }
  58% { transform: translate(0px, -11.7px) scale(0.650); opacity: 1.000; border-radius: 26px; z-index: 66; }
  67% { transform: translate(0px, -10.7px) scale(0.720); opacity: 1.000; border-radius: 26px; z-index: 66; }
  75% { transform: translate(0px, -9px) scale(0.790); opacity: 1.000; border-radius: 26px; z-index: 66; }
  83% { transform: translate(0px, -6.7px) scale(0.860); opacity: 1.000; border-radius: 26px; z-index: 66; }
  92% { transform: translate(0px, -3.7px) scale(0.930); opacity: 1.000; border-radius: 26px; z-index: 66; }
  100% { transform: translate(0px, 0px) scale(1.000); opacity: 1.000; border-radius: 26px; z-index: 66; }
}

/* 筛选卡（左上角按钮弹出）内容下坠淡入：头部 + 面板自上方 -7px 沉落到位，
   随卡片弹性展开同频（与菜单行/下拉同一"苹果下浮"方向） */
@keyframes filterIn {
  from { opacity: 0; transform: translateY(-7px); }
  to   { opacity: 1; transform: none; }
}
.filter-pop.show .filter-pop-head,
.filter-pop.show .filter-panel {
  animation: filterIn var(--pop-in-dur) cubic-bezier(0.2, 0.8, 0.3, 1) both;
}
.filter-pop.show .filter-panel { animation-delay: 0.06s; }
/* 卡片触点光效：径向渐变跟随手指（--px/--py 由 JS 写入），按压显示、松手残留后淡出。
   亮色模式浅灰光晕（柔和中性灰，白底上清晰可见） */
.menu-pop::before {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  background: radial-gradient(circle at var(--px, 50%) var(--py, 50%),
    rgba(var(--ink-rgb), 0.20), rgba(var(--ink-rgb), 0) 58%);
  opacity: 0; pointer-events: none; z-index: 1;
  transition: opacity 0.22s ease;
}
.menu-pop.glow::before { opacity: 1; }
body.dark .menu-pop::before {
  background: radial-gradient(circle at var(--px, 50%) var(--py, 50%),
    rgba(255, 255, 255, 0.48), rgba(255, 255, 255, 0) 58%);
}
/* 筛选卡片同款触点光效（--px/--py 由 JS 写入，与菜单卡片一致） */
.filter-pop::before {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  background: radial-gradient(circle at var(--px, 50%) var(--py, 50%),
    rgba(var(--ink-rgb), 0.20), rgba(var(--ink-rgb), 0) 58%);
  opacity: 0; pointer-events: none; z-index: 1;
  transition: opacity 0.22s ease;
}
.filter-pop.glow::before { opacity: 1; }
body.dark .filter-pop::before {
  background: radial-gradient(circle at var(--px, 50%) var(--py, 50%),
    rgba(255, 255, 255, 0.48), rgba(255, 255, 255, 0) 58%);
}
/* 筛选卡片斜角打光描边（与菜单卡片同款）：右上主光，对角左下淡副光，光照呼应。
   亮色 = 暗色那道白光的灰色版（同轴，保留光感） */
.filter-pop::after {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  padding: 1px;
  background: var(--stroke-grey-card);
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  pointer-events: none;
}
body.dark .filter-pop::after {
  background: linear-gradient(to bottom left,
    rgba(255, 255, 255, 0.84) 0%, rgba(255, 255, 255, 0.52) 12%, rgba(255, 255, 255, 0.21) 30%,
    rgba(255, 255, 255, 0.07) 50%, rgba(255, 255, 255, 0.17) 78%, rgba(255, 255, 255, 0.34) 100%);
}
/* 卡片斜角打光描边（顶部按钮同款）：右上主光，对角左下加一道淡副光，光照呼应。
   亮色 = 暗色那道白光的灰色版（同轴，保留光感） */
.menu-pop::after {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  padding: 1px;
  background: var(--stroke-grey-card);
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  pointer-events: none;
}
body.dark .menu-pop::after {
  background: linear-gradient(to bottom left,
    rgba(255, 255, 255, 0.84) 0%, rgba(255, 255, 255, 0.52) 12%, rgba(255, 255, 255, 0.21) 30%,
    rgba(255, 255, 255, 0.07) 50%, rgba(255, 255, 255, 0.17) 78%, rgba(255, 255, 255, 0.34) 100%);
}
.menu-row {
  display: flex; align-items: center; gap: 11px; cursor: pointer;
  padding: 11px 13px; border-radius: 13px; font-size: 15px; font-weight: 600;
  min-height: 52px; box-sizing: border-box;   /* 固定最小行高：开关行与箭头行等高 */
  position: relative;                        /* 灰块伪元素定位基准 */
  transition: background 0.18s;
}
/* 按压/拖动灰色高亮块：独立伪元素层，圆角放大到 26px 匹配卡片；
   原行外轮廓(13px)与内容不受影响（v0792 修正：滑块=长按灰块，非滚动条） */
.menu-row::before {
  content: ''; position: absolute; inset: 0; z-index: 0;
  border-radius: 26px; background: transparent;
  transition: background 0.18s; pointer-events: none;
}
.menu-row > * { position: relative; z-index: 1; }
/* 拖动预览滑块：按下/拖动时高亮手指所在行。
   做成一整块可滑动的滑块（而不是每一行各自点亮），上下换行动时它是滑过去的，
   不会"啪"地直接切换；圆角是全胶囊（999px），跟原来那层灰块的"大圆角"同一个观感。 */
.menu-slider {
  position: absolute; left: 0; top: 0; z-index: 0;
  width: 0; height: 0;
  /* 大圆角：整块是全胶囊（等于行高的一半），与原来那层灰块同一个观感 */
  border-radius: 999px;
  background: rgba(var(--ink-rgb), 0.09);
  opacity: 0; pointer-events: none;
  transition: transform 0.28s var(--ease-spring), width 0.28s var(--ease-spring),
              height 0.28s var(--ease-spring), opacity 0.16s ease;
}
.menu-slider.on { opacity: 1; }
body.dark .menu-slider { background: rgba(255, 255, 255, 0.12); }
/* 菜单卡里那块 26px 灰色长按框：整个伪元素删掉（连 content 都不要）。
   之前只是设成透明，但暗色模式下行元素自己的按压底色（13px 小圆角矩形）还会漏出来。 */
.menu-pop .menu-row::before { content: none; }
.menu-pop .menu-row:active { background: transparent; }
body.dark .menu-pop .menu-row:active { background: transparent; }
/* 拖动预览高亮：已交给 .menu-slider 这块滑块，行自身不再各自点亮（否则和滑块叠成两层） */
.menu-row.drag-hover::before { background: transparent; }
.menu-ico { display: grid; place-items: center; color: var(--ink-2); }
.menu-ico i { font-size: 18px; }
.menu-row .t { flex: 1; }

/* iOS 开关（轨道更高更圆润，圆角更饱满）。
   显式尺寸/圆角：在 menu-pop / dev-sheet / glassySheet 等所有上下文保证视觉一致 */
.ios-switch {
  flex: none; width: 48px; height: 30px; border-radius: 999px; position: relative;
  background: rgba(var(--ink-rgb), 0.16); transition: background 0.25s ease;
  box-sizing: border-box;
}
.ios-switch i {
  position: absolute; top: 2px; left: 2px; width: 26px; height: 26px; border-radius: 50%;
  background: #fff; box-shadow: 0 2px 6px rgba(0,0,0,0.22);
  transition: transform 0.28s var(--ease-spring);
}
.ios-switch.on { background: var(--green); }
.ios-switch.on i { transform: translateX(18px); }   /* 48-2-26-2=18，避免外溢 */

.menu-chev { color: var(--ink-3); flex: none; }
.menu-chev i { font-size: 13px; }

/* ---------- 开发者模式弹层（遮罩 + 居中信息卡） ---------- */
.dev-mask {
  position: fixed; inset: 0; z-index: 70;
  /* 遮罩底色统一在「遮罩升级」段（--mask-bg / --mask-bg-dark） */
  opacity: 0; pointer-events: none;
  transition: opacity 0.3s ease;
}
.dev-mask.show { opacity: 1; pointer-events: auto; touch-action: none; }
/* 弹窗打开时锁住整页：背景不可滚动、不可被触摸（只能操作弹窗本身） */
body.dev-open { overflow: hidden; }
/* 学校选择菜单打开期间：锁住整页滚动（用户要求 —— 菜单开着时别的地方不许上下滑，
   必须先关掉菜单才能滚页面）。overscroll-behavior 顺带掐掉滚动连锁。 */
body.vf-dd-lock { overflow: hidden; overscroll-behavior: none; }

.dev-sheet {
  /*  居中方式（v7.2.2 性能修）：inset:0 + margin:auto + height:fit-content
     —— 静态居中，不产生任何动画开销；关键帧里因此只剩纯像素 translateY / 纯小数 scale。
     为什么不用 left:50%/top:50% + 关键帧 translate(-50%,-50%)：百分比位移每帧都要
     按元素自身尺寸重算矩阵，老 WebView 上会逐帧重抓 backdrop 背景 → 频闪。
      必须显式 width + height:fit-content，否则 inset:0 会把盒子拉伸填满视口
        （这正是之前「所有弹窗被撑成同一尺寸」的成因）。 */
  position: fixed; inset: 0; margin: auto; z-index: 75;
  width: min(360px, calc(100% - 36px)); height: fit-content;
  max-height: 78vh; display: flex; flex-direction: column;
  transform: translateY(14px) scale(0.94);
  /* 毛玻璃材质统一在「毛玻璃统一」段的弹层档（--glass-sheet / --blur-sheet） */
  border: none;   /* 灰边框由 ::after 斜角打光光边替代（统一弹窗描边） */
  border-radius: 32px; padding: 20px;
  box-shadow: 0 24px 60px rgba(var(--shadow-rgb), 0.28);
  opacity: 0; pointer-events: none;
  transition: opacity 0.28s ease;
}
.dev-sheet.show {
  opacity: 1; pointer-events: auto; transform: translateY(0) scale(1);
  animation: sheetPopIn var(--pop-in-dur) cubic-bezier(0.22, 1, 0.36, 1) both;
}
.dev-sheet:not(.show) { animation: sheetPopOut var(--pop-out-dur) cubic-bezier(0.4, 0, 1, 1) both; }

.dev-sheet-head {
  display: flex; align-items: center; gap: 8px; justify-content: center;
  flex: none; font-size: 16px; font-weight: 700; padding-bottom: 12px; color: var(--ink);
}
.dev-sheet-head i { font-size: 18px; color: var(--tint); }
.dev-sheet-body { min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch; padding: 0 2px; }
.dev-sheet-foot { flex: none; padding-top: 14px; }
.dev-tools-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 10px; }
.dev-tool {
  cursor: pointer; font-family: inherit; font-size: 15px; font-weight: 600;
  height: 48px; padding: 0 16px; border-radius: 24px;
  /* 表面（底色 / 描边 / 阴影 / 字色）统一在「按钮统一」段 —— 次要钮档 */
  display: flex; align-items: center; justify-content: center; gap: 6px; white-space: nowrap;
  position: relative; overflow: hidden; touch-action: none; will-change: transform;
}
.dev-tool i { font-size: 13px; }
/* 无 :active 加深：按压反馈交给 .pressing 光晕（防长按"闪一下"） */

/* ---------- 信息弹窗（使用教程 / 最新公告）---------- */
.info-mask {
  position: fixed; inset: 0; z-index: 70;
  /* 遮罩底色统一在「遮罩升级」段（--mask-bg / --mask-bg-dark） */
  opacity: 0; pointer-events: none;
  transition: opacity 0.3s ease;
}
.info-mask.show { opacity: 1; pointer-events: auto; touch-action: none; }
.info-sheet {
  /*  居中方式见 .dev-sheet 注释（inset:0 + margin:auto + height:fit-content） */
  position: fixed; inset: 0; margin: auto; z-index: 75;
  width: min(360px, calc(100% - 36px)); height: fit-content;
  max-height: 72vh; display: flex; flex-direction: column;
  transform: translateY(14px) scale(0.94);
  /* 毛玻璃材质统一在「毛玻璃统一」段的弹层档（--glass-sheet / --blur-sheet） */
  border: none;   /* 灰边框由 ::after 斜角打光光边替代（统一弹窗描边） */
  border-radius: 32px; padding: 20px;
  box-shadow: 0 24px 60px rgba(var(--shadow-rgb), 0.28);
  opacity: 0; pointer-events: none;
  transition: opacity 0.28s ease;
}
.info-sheet.show {
  opacity: 1; pointer-events: auto; transform: translateY(0) scale(1);
  animation: sheetPopIn var(--pop-in-dur) cubic-bezier(0.22, 1, 0.36, 1) both;
}
.info-sheet:not(.show) { animation: sheetPopOut var(--pop-out-dur) cubic-bezier(0.4, 0, 1, 1) both; }
.info-sheet-head {
  display: flex; align-items: center; gap: 8px; justify-content: center;
  flex: none; font-size: 16px; font-weight: 700; padding-bottom: 14px; margin-bottom: 14px;
  color: var(--ink); border-bottom: 1px solid rgba(var(--ink-rgb), 0.07);
}
.info-sheet-head i { font-size: 18px; color: var(--tint); }
.info-sheet-body { min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch; padding: 0 2px; }
.info-sheet-body .content-card { background: rgba(var(--ink-rgb), 0.04); margin-bottom: 12px; }
.info-sheet-body .content-card:last-child { margin-bottom: 0; }
.info-sheet-foot { flex: none; padding-top: 16px; }
.info-cancel {
  width: 100%; cursor: pointer; font-family: inherit; font-size: 15px; font-weight: 700;
  height: 48px; padding: 0 16px; border-radius: 24px;
  /* 表面统一在「按钮统一」段 —— 主要钮档（实心蓝 + 白字） */
  position: relative; overflow: hidden; touch-action: none; will-change: transform;
  transition: filter 0.2s;
}
/* 无 :active 变暗：按压反馈交给 .pressing 光晕（白光跟手），
   实心钮 filter 变暗会在光晕淡入前整片压暗，看起来“长按闪一下” */

/* ---------- 打开资料页受限引导弹窗（QQ/微信内置浏览器） ---------- */
.guide-wrap { text-align: center; }
.guide-desc { font-size: 14px; line-height: 1.8; color: var(--ink-2); margin-bottom: 12px; }
.guide-desc strong { color: var(--ink); }
.guide-steps {
  background: rgba(var(--tint-rgb), 0.07); border: 1px dashed rgba(var(--tint-rgb), 0.35);
  border-radius: 14px; padding: 12px 14px; margin-bottom: 12px;
}
.guide-steps p { font-size: 13px; line-height: 1.9; color: var(--ink-2); margin: 0; }
.guide-steps p + p { margin-top: 2px; }
.guide-steps strong { color: var(--ink); }
.guide-copy-btn {
  cursor: pointer; font-family: inherit;
  height: 48px; padding: 0 16px; border-radius: 24px; font-size: 15px; font-weight: 700;
  /* 表面统一在「按钮统一」段 —— 主要钮档 */
  display: inline-flex; align-items: center; gap: 6px;
  position: relative; overflow: hidden; touch-action: none; will-change: transform;
}
/* 无 :active 变暗：按压反馈交给 .pressing 白光晕（同 info-cancel） */
.guide-tip { margin-top: 10px; font-size: 12px; line-height: 1.7; color: var(--ink-3); }
.guide-tip strong { color: var(--ink-2); }

/*维护模式横幅*/
.maintenance-banner {
  position: fixed; left: 0; right: 0; top: var(--nav-h); z-index: 40;
  pointer-events: none;
  display: flex; align-items: center; justify-content: center; gap: 8px;
  padding: 8px 14px; font-size: 13px; font-weight: 600; color: #8a5e00;
  /* 近乎全透明、仅留一丝黄，不做毛玻璃模糊 */
  background: rgba(255, 208, 84, 0.13);
  border-bottom: 1px solid rgba(255, 196, 64, 0.35);
}
.maintenance-banner[hidden] { display: none; }
.maintenance-banner i { color: #c9820a; }
body.dark .maintenance-banner { color: #ffd98a; background: rgba(255, 198, 80, 0.1); border-bottom-color: rgba(255, 200, 100, 0.22); }
body.dark .maintenance-banner i { color: #ffce6a; }

/* 维护横幅显示时，把页面内容整体下移，避免遮挡卡片（横幅已是最低层级，仅作视觉避让） */
body.maint-on .view { padding-top: calc(var(--nav-h) + 58px); }

/*学生验证（独立页面，v7.2.2 起取代居中弹窗）
   原 #verifyMask / #verifySheet（fixed 居中卡片）已移除，改由 #view-verify
   常规页面视图承载：大标题 + 表单 + 页脚，与其他页面同一套设计语言。
   样式要点（v7.2.2 二次重写，彻底去「框套框」）：
   · 表单不再套一层玻璃卡 —— 那会让「卡片框」里再嵌「输入框框」，框套框。
   · 改为「iOS 设置」式分组：每一行 = 左标签 + 右控件，行与行之间一条细分隔线；
     控件本身无边框无底色，只在聚焦时整行亮起，视觉上只有一个平面。
   · 「关于验证」是左竖线注脚，不是方框。 */
.verify-page-inner {
  display: flex; flex-direction: column; gap: 16px;
  /* 键盘弹出时为滚动留出呼吸位（由 JS 在聚焦时补充 padding-bottom） */
  padding-bottom: 4px;
}
.verify-form {
  display: flex; flex-direction: column;
  /* 分组容器：一条可见的细描边圆角面（比卡片轻，避免框套框观感）。
     圆角走全站大圆角体系（--radius-card = 32px），不再写死小圆角。
      不能用 overflow:hidden —— 学校下拉菜单是 absolute 弹出，会被裁掉；
        圆角靠首/末行自身圆角贴合（见下方 .vf-row:first/last-child）。 */
  position: relative;
  border-radius: var(--radius-card);
  background: rgba(255, 255, 255, 0.55);
  border: 1px solid var(--glass-border);
}
/* 首/末行贴住容器大圆角（替代 overflow:hidden 的裁剪，且不裁弹出菜单） */
.verify-form > .vf-row:first-child { border-top-left-radius: var(--radius-card); border-top-right-radius: var(--radius-card); }
.verify-form > .vf-row:last-child { border-bottom-left-radius: var(--radius-card); border-bottom-right-radius: var(--radius-card); }
.vf-row:focus-within:first-child { border-top-left-radius: var(--radius-card); border-top-right-radius: var(--radius-card); }
.vf-row {
  display: flex; align-items: center; gap: 12px;
  min-height: 56px; padding: 6px 16px;
  cursor: text;
}
.vf-row + .vf-row { border-top: 1px solid rgba(var(--ink-rgb), 0.06); }
.vf-label {
  flex: none; display: flex; align-items: center; gap: 6px;
  font-size: 14.5px; font-weight: 600; color: var(--ink);
  white-space: nowrap;
}
.vf-opt {
  font-size: 10.5px; font-weight: 600; font-style: normal; color: var(--ink-3);
  padding: 1px 6px; border-radius: 999px; background: rgba(var(--ink-rgb), 0.06);
}
.vf-q { font-size: 14px; font-weight: 800; font-style: normal; color: var(--tint-deep); letter-spacing: 0.01em; }
/* 控件：无边框、无底色 —— 与行融为一体，杜绝「框里的框」 */
.vf-ctl { flex: 1; min-width: 0; display: flex; justify-content: flex-end; }
.vf-input {
  width: 100%; box-sizing: border-box; font-family: inherit;
  font-size: 15px; color: var(--ink); text-align: right;
  height: 44px; padding: 0; border: none; border-radius: 0;
  background: transparent; transition: none;
}
.vf-input:focus { outline: none; }
.vf-input::placeholder { color: var(--ink-3); }
/* 聚焦整行亮起（不再给控件加内部描边） */
.vf-row:focus-within { background: rgba(var(--tint-rgb), 0.05); }
.vf-error { font-size: 13px; font-weight: 600; color: #e0392b; padding: 0 4px; }
.vf-error:empty { display: none; }

/* ---- 学校选择器：U 形展开动画下拉 ----
    参照物是学校列表页内联筛选的那只下拉（.dropdown--card），不是左上角筛选弹卡
      （.filter-pop）里的那只。两者观感必须一致，故这里逐项复用 --card 的视觉 token：
        · 触发区无框、只留当前值 + 箭头、整组靠右
        · 菜单材质 = 菜单档 --glass-menu + blur(--blur-menu)（比弹层档更透亮）
        · 菜单圆角 26px、内边距 6px、斜角打光描边（--stroke-grey-card）与 --card 逐字一致
        · 选项胶囊圆角 999px、字号 14px
        · 展开/收起动画 = 同一段 menuPopIn / menuPopOut（那记"往下兜"的小 U）
   与 --card 的唯一差异（按用户要求）：
     ① 不接拖拽 —— 不给 touch-action:none，保留原生滚动；
     ② 菜单限高内部滚动 —— 148 个学校条目，必须能上下滑动翻看（--card 靠加列降高，这里条目多不适用）。 */
.vf-row--pick { cursor: default; padding-right: 8px; }

/* ① 触发区：无框，值右对齐（同 .dropdown--card .dropdown-trigger） */
.vf-drop { position: static; width: 100%; }
.vf-drop .dropdown-trigger {
  width: 100%; padding: 0; border: none; background: transparent; box-shadow: none;
  justify-content: flex-end; gap: 6px; font-size: 14.5px; font-weight: 600; color: var(--ink);
  height: 44px; border-radius: 0;
}
.vf-drop .dropdown-trigger:active { transform: none; }
.vf-drop .dd-value {
  margin-left: auto; min-width: 0; max-width: 100%;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  color: var(--ink-3); text-align: right;
}
.vf-drop .dd-value.has-val { color: var(--ink); }
body.dark .vf-drop .dropdown-trigger { background: transparent; }
.vf-drop .dd-caret { font-size: 12px; color: var(--tint); transition: transform 0.25s var(--ease-spring); }
.vf-drop.open .dd-caret { transform: rotate(180deg); }
.vf-drop.open .dd-value { color: var(--tint); }

/* ②③④ 菜单：右对齐触发区右缘、材质/圆角/描边同列表页内联下拉；
   动画用 --card 同一段 menuPopIn / menuPopOut（U 形展开）
    结构是「外壳 .dropdown-menu（不滚、承载描边）+ 内层 .vf-drop-scroll（滚内容）」——
      描边如果挂在滚动容器上会跟着内容滚走（就是上一版的 bug）。
      外壳 overflow:hidden 把内层裁进圆角，滚的是内层。
    top 用 6px（= .vf-row 的上下 padding）而不是 calc(100%+6px)：
      与列表页内联下拉同款 —— 菜单顶边对齐触发区顶边，视觉上是"从控件里长出来"，
      而不是飘在下方另一个东西。触发区 44px 居中在 56px 行内，故顶边距行顶 6px。 */
.vf-drop .dropdown-menu {
  /* left / width 由 JS 按「最长学校名 + 内边距」实测写入（左缘对齐卡片），这里给兜底值 */
  position: absolute; left: 0; right: auto; top: 6px; z-index: 60;
  display: flex; flex-direction: column;                 /* 列表撑满，其余元素绝对定位浮在上面 */
  width: auto; min-width: 200px; max-width: none;
  border: none; border-radius: 26px; padding: 0;
  background: var(--glass-menu);
  backdrop-filter: blur(var(--blur-menu)) saturate(var(--sat-glass));
  -webkit-backdrop-filter: blur(var(--blur-menu)) saturate(var(--sat-glass));
  box-shadow: 0 14px 38px rgba(var(--shadow-rgb), 0.20);
  /* 验证页没有底部 tab 栏，菜单可以给高一点（250 条学校，越高越好翻） */
  max-height: min(74vh, 620px);
  overflow: hidden;                    /* 裁内层滚动内容到圆角内 */
  overscroll-behavior: contain;
  /* 锚点 = 与右上角菜单卡 .menu-pop 逐字同一套（写死 calc(100% - 22px) 0%，不动态量） */
  transform-origin: calc(100% - var(--pop-anchor-x)) 0%;
  opacity: 0; visibility: hidden; pointer-events: none;
  transform: translate(0, 0) scale(0.16);
  transition: opacity 0.16s ease, transform var(--pop-out-dur) var(--ease-pop-out),
              visibility 0s linear var(--pop-out-dur);
}
/* 悬浮搜索岛：不占卡片顶部的位置，浮在列表上方；列表滑到它后面时被下面的
   渐进模糊层吃掉（内容不是"撞上一条实边"，而是渐隐进搜索框底下）。 */
.vf-drop .vf-drop-search {
  position: absolute; top: 10px; left: 10px; right: 10px; z-index: 4;
  display: flex; align-items: center; gap: 8px;
  height: 42px; padding: 0 12px; border-radius: 999px;
  background: rgba(255, 255, 255, 0.72);
  border: 1px solid var(--glass-border);
  box-shadow: 0 6px 18px rgba(var(--shadow-rgb), 0.10);
  /*  毛玻璃走全站 token（菜单档，与右上角菜单卡 / 左上角筛选卡同款同源），不再手写 14px/180% */
  backdrop-filter: blur(var(--blur-menu)) saturate(var(--sat-glass));
  -webkit-backdrop-filter: blur(var(--blur-menu)) saturate(var(--sat-glass));
}
body.dark .vf-drop .vf-drop-search { background: rgba(58, 58, 64, 0.74); }
.vf-drop .vf-drop-search > i { font-size: 13px; color: var(--ink-3); flex: none; }
.vf-drop .vf-search-input {
  flex: 1; min-width: 0; border: none; background: transparent; padding: 0; margin: 0;
  font-family: inherit; font-size: 15px; font-weight: 600; line-height: 1.3;
  color: var(--ink); outline: none; -webkit-appearance: none;
}
.vf-drop .vf-search-input::placeholder { color: var(--ink-3); font-weight: 500; }
.vf-drop .vf-search-clear {
  flex: none; width: 22px; height: 22px; padding: 0; border: none; background: transparent;
  display: flex; align-items: center; justify-content: center;
  color: var(--ink-3); font-size: 15px; cursor: pointer;
}
/* 渐进模糊层：顶部实、往下渐隐（mask 同时控制 blur 强度与底色），
   列表内容滑过时被它吃掉 —— 这就是"搜索框后面是渐进模糊"。 */
.vf-drop .vf-drop-fade {
  position: absolute; left: 0; right: 0; top: 0; height: 94px; z-index: 2;
  pointer-events: none;
  background: linear-gradient(to bottom,
    rgba(255, 255, 255, 0.88) 0%, rgba(255, 255, 255, 0.74) 46%, rgba(255, 255, 255, 0) 100%);
  /*  同样是菜单档 token。原本这里手写 blur(14px) saturate(170%)，
     170% 是全站唯一一个 170 —— 已对齐为 --sat-glass(200%)，与上面的搜索框、与全站玻璃同一档。
     （"往下渐隐"的观感由下方 mask-image 负责，不靠降饱和度实现。） */
  backdrop-filter: blur(var(--blur-menu)) saturate(var(--sat-glass));
  -webkit-backdrop-filter: blur(var(--blur-menu)) saturate(var(--sat-glass));
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 44%, rgba(0, 0, 0, 0.55) 72%, transparent 100%);
  mask-image: linear-gradient(to bottom, #000 0%, #000 44%, rgba(0, 0, 0, 0.55) 72%, transparent 100%);
}
body.dark .vf-drop .vf-drop-fade {
  background: linear-gradient(to bottom,
    rgba(30, 30, 34, 0.92) 0%, rgba(30, 30, 34, 0.78) 46%, rgba(30, 30, 34, 0) 100%);
}
/* 右侧字母引导条：与列表页 .jump-rail / .jump-key 同一套观感（弹性键 + 蓝色高亮） */
.vf-drop .vf-drop-index {
  position: absolute; right: 3px; top: 62px; bottom: 8px; z-index: 3;
  width: 24px; display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: 2px 0; overflow: hidden;
  touch-action: none; user-select: none; -webkit-user-select: none;
}
.vf-drop .vf-drop-index span {
  flex: 1 1 0; min-height: 0; width: 22px;
  display: grid; place-items: center; border-radius: 8px;
  background: rgba(var(--ink-rgb), 0.05);
  font-size: 10.5px; font-weight: 700; color: var(--ink-2);
  transition: background 0.15s ease, color 0.15s ease;
}
.vf-drop .vf-drop-index span.on { background: var(--tint); color: #fff; }
body.dark .vf-drop .vf-drop-index span { background: rgba(255, 255, 255, 0.07); color: var(--ink-2); }
body.dark .vf-drop .vf-drop-index span.on { background: var(--tint); color: #fff; }
/* 小屏：筛选浮层里的字母条同样整条收窄（与列表页 .jump-rail 同一策略、同一断点）。
   条宽 24→20、字母宽 22→18、字号 10.5→9.5；键高由 span 的 `flex:1 1 0` 摊分，
   所以只要条本身够矮，字母就一起变矮，不需要逐键写死。 */
@media (max-width: 480px) {
  .vf-drop .vf-drop-index { width: 20px; right: 2px; }
  .vf-drop .vf-drop-index span { width: 18px; font-size: 9.5px; }
}
.vf-drop .vf-drop-empty {
  position: absolute; left: 0; right: 0; top: 62px; z-index: 3;
  padding: 26px 14px; text-align: center;
  font-size: 13.5px; font-weight: 600; color: var(--ink-3);
}
/* 内层：真正滚动的容器（描边 / 模糊 / 圆角都在外壳上，所以它们不会滚走）。
   顶部 padding 给悬浮搜索岛留空 —— 内容从搜索岛下方开始，往上滑就钻到它背后。 */
.vf-drop .vf-drop-scroll {
  flex: 1 1 auto; min-width: 0; min-height: 0; max-height: none;
  padding: 60px 32px 8px 6px;
  position: relative;                  /* 作为 offsetParent：让选项 offsetTop 直接相对本容器取布局值 */
  overflow-y: auto; overflow-x: hidden;
  overscroll-behavior: contain; -webkit-overflow-scrolling: touch;
  /*  必须 touch-action:none —— 由 JS 全权接管滚动。
     用 pan-y 会让浏览器在第一次 touchmove 后判成"滚页"→ 直接发 pointercancel，
     JS 的 pointermove 只跑到第一个就断了（表现为"滑不动"）。
     JS 内自己实现：跟手滚 + 速度采样 + 松手惯性 + 边界橡皮筋。 */
  touch-action: none;
}
/* 斜角打光描边：与列表页内联下拉 / 右上角菜单卡同款同 token */
.vf-drop .dropdown-menu::after {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  padding: 1px; background: var(--stroke-grey-card);
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor; mask-composite: exclude;
  pointer-events: none;
}
body.dark .vf-drop .dropdown-menu { background: var(--glass-dark-menu); }
body.dark .vf-drop .dropdown-menu::after {
  background: linear-gradient(to bottom left,
    rgba(255, 255, 255, 0.84) 0%, rgba(255, 255, 255, 0.52) 12%, rgba(255, 255, 255, 0.21) 30%,
    rgba(255, 255, 255, 0.07) 50%, rgba(255, 255, 255, 0.17) 78%, rgba(255, 255, 255, 0.34) 100%);
}
.vf-drop.open .dropdown-menu {
  opacity: 1; visibility: visible; pointer-events: auto;
  transform: translateY(0) scale(1);
  animation: menuPopIn var(--pop-in-dur) linear;
  /*  transition 里不含 transform（与 .menu-pop.show 对齐）——
     否则 animation 与 transition 同时管 transform = "两套动画"打架。入场全交给关键帧。 */
  transition: opacity 0.16s ease, visibility 0s;
}
.vf-drop:not(.open) .dropdown-menu {
  animation: menuPopOut var(--pop-out-dur) linear reverse both;
}
/* 触屏档：模糊降一档（与全站触摸档同口径） */
@media (hover: none) {
  .vf-drop .dropdown-menu {
    backdrop-filter: blur(var(--blur-touch-menu)) saturate(var(--sat-glass-touch));
    -webkit-backdrop-filter: blur(var(--blur-touch-menu)) saturate(var(--sat-glass-touch));  }
}

/* ⑤ 选项：胶囊圆角（同 .dropdown--card .dd-option） */
.vf-drop .dd-option {
  display: flex; align-items: center; gap: 10px; width: 100%; padding: 10px 12px;
  border: none; border-radius: 999px;
  cursor: pointer; font-family: inherit; font-size: 14px; font-weight: 600; color: var(--ink);
  background: transparent; text-align: left;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  transition: background 0.18s ease;
}
.vf-drop .dd-option:active { background: rgba(var(--ink-rgb), 0.08); }
/* 选中项 = 只变字色（与全站 .dd-option.on 统一，见 2344 行注释） */
.vf-drop .dd-option.on { color: var(--tint-deep); }
.vf-drop .dd-option.on i { color: var(--tint); }
body.dark .vf-drop .dd-option.on { color: var(--tint-dark); }
.vf-drop .dd-group-h {
  margin: 6px 4px 2px; padding: 4px 8px 6px; font-size: 12.5px; font-weight: 800;
  color: var(--ink); letter-spacing: 0.01em; border-bottom: 1px solid var(--glass-border);
}
.vf-drop .dd-group-h:first-child { margin-top: 2px; }

/* 「关于验证」注脚：左侧细竖线 + 小字，不做方框（避免与表单形成框套框） */
.verify-note {
  margin: -2px 2px 0;
  padding: 2px 0 2px 14px;
  border-left: 2px solid rgba(var(--tint-rgb), 0.28);
  display: flex; flex-direction: column; gap: 7px;
}
.verify-note p {
  margin: 0; font-size: 12.5px; line-height: 1.7; color: var(--ink-3);
  display: flex; gap: 8px; align-items: flex-start;
}
.verify-note p i { color: var(--tint); font-size: 12px; margin-top: 3px; flex: none; }
/* 正文必须整体包在一个 span 里：<p> 是 flex 容器，里面的 <strong> 等元素会各自变成独立
   flex item，被排成一列一列（曾出现文字被切成左右两栏的 bug）。span 内部照常行内折行。 */
.verify-note p > span { flex: 1 1 auto; min-width: 0; }

.verify-page-actions {
  display: flex; align-items: center; gap: 12px;
  margin-top: 2px;
}
.verify-page-actions .verify-cancel,
.verify-page-actions .verify-submit { flex: 1; }

/* 锁定遮罩：覆盖整个验证表单区，倒计时结束自动取消 */
#verifyBody { position: relative; }
.vf-overlay {
  position: absolute; inset: 0; z-index: 5;
  background: rgba(255, 255, 255, 0.82);
  backdrop-filter: blur(var(--blur-xs)); -webkit-backdrop-filter: blur(var(--blur-xs));
  border-radius: 18px;
  display: flex; align-items: center; justify-content: center;
}
.vf-overlay[hidden] { display: none; }
.vf-overlay-inner {
  position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%); z-index: 6;
  display: flex; flex-direction: column; align-items: center; gap: 7px;
  background: rgba(255, 255, 255, 0.92);
  border: 1px solid var(--glass-border);
  border-radius: 16px; padding: 16px 24px;
  box-shadow: var(--shadow-soft);
}
.vf-overlay-inner i { font-size: 20px; color: var(--orange); }
.vf-overlay-inner span { font-size: 14px; font-weight: 700; color: var(--ink); }
#vfCountdown { font-size: 22px; font-weight: 800; font-variant-numeric: tabular-nums; color: var(--orange); }
/* 验证系统出错提示（无倒计时，仅一行说明） */
.vf-overlay-inner .vf-err-note { font-size: 13px; font-weight: 600; color: var(--ink-3); max-width: 220px; text-align: center; line-height: 1.5; }
body.dark .vf-overlay { background: rgba(20, 20, 24, 0.82); }
body.dark .vf-overlay-inner { background: rgb(var(--surface-dark-rgb)); border-color: rgba(255, 255, 255, 0.1); }
body.dark .vf-overlay-inner span { color: #fff; }
body.dark .vf-overlay-inner i, body.dark #vfCountdown { color: #ff9f68; }
.verify-form.is-locked .vf-input { opacity: .5; pointer-events: none; }
.verify-submit:disabled { opacity: .5; pointer-events: none; }
.verify-submit {
  flex: 1; cursor: pointer; font-family: inherit; font-size: 15px; font-weight: 700;
  display: flex; align-items: center; justify-content: center; gap: 8px;
  height: 48px; padding: 0 16px; border-radius: 24px;
  /* 表面统一在「按钮统一」段 —— 主要钮档 */
  position: relative; overflow: hidden; touch-action: none; will-change: transform;
}
/* 无 :active 变暗：按压反馈交给 .pressing 白光晕（同 info-cancel） */

/* 验证弹窗关闭按钮：次要操作 → 走「按钮统一」段的次要钮档（浅玻璃白胶囊，与主钮实心蓝区分主次） */
.verify-cancel {
  flex: 1; cursor: pointer; font-family: inherit; font-size: 15px; font-weight: 600;
  height: 48px; padding: 0 16px; border-radius: 24px;
  /* 表面统一在「按钮统一」段 —— 次要钮档（同分享卡片「取消」） */
  position: relative; overflow: hidden; touch-action: none; will-change: transform;
}
/* 无 :active 加深：按压反馈交给 .pressing 光晕（同 .dev-tool） */

body.dark .verify-note { border-left-color: rgba(var(--tint-rgb), 0.4); }
body.dark .verify-note p { color: var(--ink-3); }
body.dark .verify-form { background: rgba(255, 255, 255, 0.045); border-color: rgba(255, 255, 255, 0.08); }
body.dark .vf-row + .vf-row { border-top-color: rgba(255, 255, 255, 0.07); }
body.dark .vf-row:focus-within { background: rgba(var(--tint-rgb), 0.14); }
body.dark .vf-opt { background: rgba(255, 255, 255, 0.1); color: var(--ink-3); }
body.dark .vf-input { color: var(--ink); }
body.dark .vf-error { color: #ff6b60; }

.dev-sec {
  margin: 18px 0 6px; font-size: 11.5px; font-weight: 700; letter-spacing: 0.05em;
  text-transform: uppercase; color: var(--ink-3);
}
.dev-sec:first-child { margin-top: 2px; }
.dev-info-row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 12px 4px; font-size: 14px;
}
.dev-info-row + .dev-info-row { border-top: 1px solid var(--glass-border); }
.dev-info-row .k { color: var(--ink-2); }
.dev-info-row .v { font-weight: 600; font-variant-numeric: tabular-nums; text-align: right; }
.dev-ua {
  margin: 8px 2px 0; padding: 8px 10px; border-radius: 10px;
  font-size: 11px; line-height: 1.5; color: var(--ink-3); word-break: break-all;
  background: rgba(var(--ink-rgb), 0.04); font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.dev-block-label { margin: 12px 2px 4px; font-size: 11.5px; font-weight: 700; color: var(--ink-2); }
.dev-pre {
  margin: 0; padding: 10px 12px; border-radius: 10px; max-height: 130px; overflow: auto;
  font-size: 11px; line-height: 1.55; white-space: pre-wrap; word-break: break-all;
  background: rgba(var(--ink-rgb), 0.05); color: var(--ink);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
.dev-danger {
  margin-top: 16px; width: 100%; cursor: pointer; font-family: inherit;
  height: 48px; padding: 0 16px; border-radius: 24px; font-size: 15px; font-weight: 700;
  /* 表面统一在「按钮统一」段 —— 危险钮档（红色渐变） */
  position: relative; overflow: hidden; touch-action: none; will-change: transform;
}
.dev-cancel {
  margin-top: 10px; width: 100%; cursor: pointer; font-family: inherit;
  height: 48px; padding: 0 16px; border-radius: 24px; font-size: 15px; font-weight: 600;
  /* 表面统一在「按钮统一」段 —— 主要钮档（实心蓝 + 白字） */
  position: relative; overflow: hidden; touch-action: none; will-change: transform;
}
/* dev-danger / dev-cancel 无 :active 变暗：按压反馈交给 .pressing 光晕 */

/* ---------- 详情页：分享时把「校名卡」(.detail-hero) 摘出扶起 ----------
     点右上角分享按钮 → 走与列表页长按完全同一套 3D-Touch 动画（克隆卡飞到屏幕
     中心 + 下方弹分享菜单），所以 detail-hero 也得进 peek 的「原卡隐藏 / 克隆卡
     卡化」两态：.peek-src = 原位隐身占位；.peek-ghost = 浮层里的摘出卡。
     这里只补 detail-hero 自身的圆角/底色/边距，.peek-ghost 通用规则（fixed 定位、
     触点光、跟手拖拽）照常生效。 */
.detail-hero.peek-src { visibility: hidden; }
.detail-hero.peek-ghost {
  margin: 0;
  border-radius: 38px;
  box-shadow: var(--shadow-float);
}
/* 摘出卡常态（.peek-ghost 的 hover/active 复位规则会把底色刷成纯白，
   详情页这张是半透明玻璃卡，需按自身材质还原，暗色同理） */
.detail-hero.peek-ghost:hover,
.detail-hero.peek-ghost:active { background: rgba(255, 255, 255, 0.78); }
body.dark .detail-hero.peek-ghost:hover,
body.dark .detail-hero.peek-ghost:active { background: rgba(var(--surface-dark-rgb), 0.7); }
/* 校名卡顶部那道蓝色氛围光原本画在 ::before 上，而摘出态 ::before 被
   .peek-ghost 的触点光效占用（同一伪元素，后者声明更靠后胜出）——
   不补回来的话摘出的卡会比原卡"秃"一块，跟原卡对不上。改画到 ::after。 */
.detail-hero.peek-ghost::after {
  content: ""; position: absolute; inset: -40% -20% auto; height: 70%;
  background: radial-gradient(ellipse at 50% 0%, rgba(var(--tint-rgb),0.24), transparent 70%);
  pointer-events: none;
}

/* ---------- 视图容器 ---------- */
.view { display: none; padding: calc(var(--nav-h) + 14px) 18px 100px; max-width: 680px; margin: 0 auto; }
/*  入场动画不能挂在 .view 本体上（本轮修的 bug，曾因此把顶部渐进模糊弄没了）——
   两个坑连着踩，一句话说清：
   ① `.view` 本体若带 `transform`（viewIn 的 translateY + scale），它会创建层叠上下文，
      把大标题里的 `z-index:41` 关死在里面 → 整个 .view 在外部只是 z-index:auto，
      跟 .nav-blur(z40) 比层级退化成 DOM 顺序 → 动画那 0.4s 里标题被判在模糊层下面 = "加载时糊、加载完不糊"；
   ② 那就把 .view 抬到 z-index:45 跨过模糊层？也不行 —— .view 是整页内容的容器，
      抬上去等于把卡片列表全部盖在模糊层之上，滚上来的内容再也不被糊 → 渐进模糊直接失效
      （用户原话："渐进模糊直接没了"）。
   ⇒ 正解：把入场动画下移到 .view 的每个直接子元素上（`.view.active > *`）。
      这样 `.view` 本体没有 transform → 不创建层叠上下文 → 大标题的 z-index:41 照常跨过
      .nav-blur(z40)，动画全程标题都清清爽爽；而内容各块仍然一起上滑淡入，观感与原来完全一致
      （所有子元素共用同一段动画、同一时刻起跑，视觉上就是一个整体在滑，不会各滑各的）。
    `.view` 本体必须保持 `position: static`（不要给 relative/z-index）——
      一旦定位就会重新成为层叠上下文的候选，把上面的分析推翻。 */
.view.active { display: block; }
/*  入场动画排除掉大标题（.large-title）：标题必须保持"无 transform"状态，
   否则它自己就成了层叠上下文，内层 .lt-main 的 z-index:41 会被关死 → 被顶部模糊层糊住。
   标题的不入场代价可忽略：它本来就是页面最上面一块，入场位移 14px 在视觉上几乎看不出；
   内容各块（卡片/筛选/列表/页脚）照常整体上滑淡入，观感与原来一致。 */
/*  入场动画的作用范围 —— 全站一条规则，不再逐页/逐类名打补丁。
   要求（用户反复强调"统一"）：所有页面的「大标题」都必须保持"无 transform"状态，
   否则它会（或它的祖先会）创建层叠上下文，把 `.lt-main` 的 z-index:41 关死 → 被顶部模糊层糊住。

   但各页的标题包裹层级不一致，这是坑的核心：
     · 首页/列表/搜索/公告/教程/更多/验证 → `<main class="view">` 直接含 `<h2 class="large-title">`；
     · 详情/关于/设置/更新日志/历史版本/法律页 → 标题被塞进一层 wrapper div
       （`#detailBody` / `#legalBody`），即 `.view > div > h2.large-title`。
   只写 `> *:not(.large-title)` 时，那层 wrapper div 照样吃到 transform → 二级页标题仍被糊。

   ⇒ 正解：把动画"下沉一层"，且只在真正包着标题的那层 wrapper 上下沉。
      · 直接子元素不是"包着标题的 wrapper" → 它自己播动画（`.view.active > *`）；
      · 直接子元素是包着标题的 wrapper（`#legalBody` / `#detailBody`，判据 `:has(.large-title)`）
        → 不播它本身（播了会重新关住标题），改播它内部除标题外的每一个子元素。
     这样两条要求同时满足：
       a) wrapper 自身无 transform → `.lt-main` 的 z-index:41 照常跨过模糊层 → 标题不被糊；
       b) wrapper 里的正文各块照常上滑淡入 → 二级页不再"整个被定住"。
      关键在于"下沉一层"必须限定在含标题的 wrapper 上：若无差别地下沉一层，
        首页 `.home-hero` 这类普通容器会被播两遍（自身 + 子元素），动画叠成双份。
      绝不能再把 wrapper 整个排除（`:not(:has(.large-title))` 那种写法）——
        那会把 wrapper 连同它全部内容一起踢出动画，二级页只剩页脚能动
        （用户原话："所有二级界面的加载动画全没了…只有底下一个备案信息能正常动画进入，
         其他东西都被定住了"）。这正是该写法踩的坑，别再绕回去。 */
.view.active > *:not(.large-title) {
  animation: viewIn 0.4s var(--ease-spring);
}
/* 仅对"包着标题的 wrapper"取消它自身的动画，并把动画下沉给它的每个子元素（标题除外） */
.view.active > *:has(.large-title) { animation: none; }
.view.active > *:has(.large-title) > *:not(.large-title) {
  animation: viewIn 0.4s var(--ease-spring);
}
/* 长页面（学校列表、搜索结果、更新日志历史/详情、公告、教程等）统一以顶部为缩放原点，
   避免列表过长时 viewIn 动画把顶部可见内容甩动、与各短页面动画观感不一致。
    三级选择器都要覆盖（直接子元素 + 含标题 wrapper 的内部子元素），否则漏一处就会出现
      "顶部内容被甩动"与别页观感不一致。 */
#view-schools > *:not(.large-title),
#view-search > *:not(.large-title),
#view-page > *:not(.large-title),
#view-notices > *:not(.large-title),
#view-tutorial > *:not(.large-title),
#view-verify > *:not(.large-title),
#view-page > *:has(.large-title) > *,
#view-detail > *:has(.large-title) > * { transform-origin: top; }
@keyframes viewIn {
  from { opacity: 0; transform: translateY(14px) scale(0.99); }
  to   { opacity: 1; transform: none; }
}
/* 全部页面（含学校列表页）统一套用上面的 viewIn，不单独写动画 */

.large-title {
  font-size: 34px; font-weight: 800; letter-spacing: -0.02em;
  margin: 4px 2px 18px;
}
/* 大标题：只抬主标题那一行（.lt-main）到顶部模糊层(.nav-blur z40) 之上，
   副标题小字（.large-title small）不抬 —— 它该被糊就被糊。
    这两行必须分开（用户明确要求："把顶部大标题和小标题分开…我要的是顶部大标题，
      下面那个描述小标题不要也塞上来"）。`.lt-main` 是 <span>，`small` 是它后面的兄弟节点，
      各自有独立的层叠位置，所以只给 .lt-main 写 z-index 是完全可行的。
    但"只抬内层"有个前提：它的祖先链上不能有新的层叠上下文把它关住。本项目踩过四次坑：
      ① `.view` 本体带 viewIn 的 transform → 关住；② `.view.active > *` 带 transform → 关住；
      ③ `.large-title` 整块带 transform → 关住；
      ④ 二级页的标题被包在 wrapper div 里（`.view > div#detailBody/#legalBody > .large-title`），
         那层 div 是 `.view` 的直接子元素、照样吃到动画 → 再次关住（这轮才发现的漏网之鱼）。
      现在四处都堵上了：动画挂在 `.view.active > *:not(.large-title):not(:has(.large-title))` 上
      （见上方 1525-1542 行那条全站唯一规则）、`.large-title` 本体不带 transform、
      也不写 z-index（保持 static，不给自己造上下文）。
      ⇒ 于是 `.lt-main` 的 z-index:41 能直接跟最外层的 `.nav-blur`(40) 比，干净地赢。
    千万不要为了"稳一点"就把 z-index 写到 `.large-title` 整块上 —— 那样 small 会跟着一起被抬起来，
      正是本轮要修的问题。
    :not(.title-fly) —— 克隆层（.title-fly，z-index:60 + fixed）自己就是顶层，
      而且它的 :hover/text 样式不能被这条相对定位影响。 */
.large-title:not(.title-fly) .lt-main { position: relative; z-index: 41; }
.large-title small { display: block; font-size: 14px; font-weight: 500; color: var(--ink-2); margin-top: 5px; letter-spacing: 0.02em; }
/* 大标题折叠：克隆浮层覆盖 .large-title 的边距，保持与正文主文本行同字同样式 */
.title-fly.large-title { margin: 0; }
/* 折叠期间隐藏正文主文本（.lt-main）避免与飞行克隆叠影；副标题 small 原位保留继续上滑 */
body.fold-live .view.active .large-title .lt-main { visibility: hidden; }

/* ---------- 玻璃基元 ----------
   性能优化：页面内卡片不再使用实时 backdrop-filter（手机端多层实时模糊是卡顿主因），
   改用预混合的半透明白 + 边框高光，视觉近似、开销极低。
   实时毛玻璃只保留在固定悬浮层：顶部导航模糊、Tab 栏、搜索圆钮、Toast。 */
.glass {
  background: var(--glass-card);
  border: 1px solid var(--glass-border);
  box-shadow: var(--shadow-soft);
}

/*   首页 · 搜索引擎式布局*/
.home-hero {
  min-height: calc(100vh - 340px);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center; padding-top: 18px;
}
.home-title { font-size: 40px; font-weight: 800; letter-spacing: -0.02em; }
.home-title em { font-style: normal; color: var(--ink); }
.home-sub { margin-top: 10px; color: var(--ink-2); font-size: 15px; }

.search-shell {
  width: 100%; max-width: 560px; margin-top: 30px;
  display: flex; align-items: center; gap: 10px;
  border-radius: 36px; padding: 8px 8px 8px 22px;
  background: rgba(255, 255, 255, 0.86);
  /*  原来的 `border: 1px solid var(--glass-border)`（一条死平灰线）已删除 →
     改由下方 ::after 的斜角打光描边（--stroke-grey-card）替代。
     描边只留一层（见「玻璃描边」段）：灰 border 与发光 ::after 叠在一起 =
     亮色下描边加倍、灰线压在光边上，正是之前按钮上踩过的坑。
     三个搜索框的描边现在收在下面同一条规则里，亮/暗色两套都在那里。 */
  border: none;
  box-shadow: var(--shadow-float);
  transition: transform 0.3s var(--ease-spring), box-shadow 0.3s;
  position: relative; /* × 清除按钮绝对定位锚点 + ::after 描边的定位锚点 */
}

/* 搜索框的斜角打光描边（全站三个搜索框共用这一条，唯一权威来源）：
   · 首页 #homeSearchForm / 搜索页 #pageSearchForm —— 都是 .search-shell，天然一起命中；
   · 列表页顶置 #legendSearchForm —— .legend-search（不同族，所以并列写进选择器）。
   token 用 --stroke-grey-card（与卡片 / 弹层 / 菜单同款同源），几何与 .menu-pop::after 逐字一致：
   1px 光边 + mask 排除内容区，画一圈「右上主光 → 左下副光」的对角渐变。
   亮色 = 暗色那道白光的灰色版（同轴，保留光感）；暗色 = 白光对角渐变（深底上白光才成立）。
    两个宿主都必须 position: relative（一个已经写了，.legend-search 也在自己的段落里补了），
      否则 inset:0 会锚到最近的定位祖先上，光边跑到别处去。 */
.search-shell::after,
.legend-search::after {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  padding: 1px;
  background: var(--stroke-grey-card);
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  pointer-events: none;
}
body.dark .search-shell::after,
body.dark .legend-search::after {
  background: linear-gradient(to bottom left,
    rgba(255, 255, 255, 0.84) 0%, rgba(255, 255, 255, 0.52) 12%, rgba(255, 255, 255, 0.21) 30%,
    rgba(255, 255, 255, 0.07) 50%, rgba(255, 255, 255, 0.17) 78%, rgba(255, 255, 255, 0.34) 100%);
}

.search-shell:focus-within { box-shadow: 0 18px 50px rgba(var(--tint-rgb), 0.18); }
.search-shell i.mag { font-size: 18px; flex: none; color: var(--ink-3); }
.search-shell input {
  flex: 1; border: none; outline: none; background: none;
  font: inherit; font-size: 17px; color: var(--ink); min-width: 0;
  padding-right: 6px; /* × 按钮透明悬浮在输入区右端，不挤压输入内容；长文字从按钮下透出可见 */
}
.search-shell input::placeholder { color: var(--ink-3); }
.search-go {
  flex: none; border: none; cursor: pointer; font-family: inherit;
  position: relative;
  width: 44px; height: 44px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 16px; color: #fff;
  background: var(--brand-grad);
  box-shadow: 0 6px 18px rgba(var(--tint-rgb), 0.35), inset 0 1px 1px rgba(255,255,255,0.4);
  touch-action: none;
  -webkit-user-select: none; user-select: none;
  -webkit-touch-callout: none;
  transition: filter 0.2s;   /* transform 交给 JS 弹簧（initPressSpring），与 nav 圆钮同套 */
}
/* 无 :active 缩放/压暗：按压反馈交给 JS 物理（.pressing 白扫光 + 跟手位移/拉伸/回弹） */
/* 按压白扫光（蓝渐变实心底钮 → 白光，与 verify-submit/dev-cancel 同思路；圆钮取 circle 70px）：
   --px/--py 由 initPressSpring 写入百分比，手指拖出后贴圆钮边缘，脱手即灭 */
.search-go::before {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  background: radial-gradient(circle 70px at var(--px, 50%) var(--py, 50%),
    rgba(255, 255, 255, 0.72), rgba(255, 255, 255, 0) 100%);
  opacity: 0; pointer-events: none;
  transition: opacity 0.18s ease;
}
.search-go.pressing::before { opacity: 1; }
.search-clear {
  position: absolute; right: 58px; top: 50%; transform: translateY(-50%);
  z-index: 2; border: none; background: transparent; cursor: pointer;
  width: 36px; height: 36px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 16px; color: var(--ink-3);
  transition: color 0.2s, opacity 0.2s;
}
.search-clear:hover { color: var(--ink); }
.search-clear[hidden] { display: none; }

.home-actions { display: flex; gap: 14px; margin-top: 26px; flex-wrap: wrap; justify-content: center; }
/* 首页入口胶囊（使用教程 / 最新公告）：并入 app.js initPressSpring —— 与右上角/关闭钮/品牌胶囊
   同款跟手弹 + 扫光；touch-action:none 防拖动被页面滚动抢占；文字防长按选中 */
.pill-btn {
  position: relative;
  display: inline-flex; align-items: center; gap: 8px;
  cursor: pointer; font-family: inherit;
  padding: 13px 26px; border-radius: 999px; font-size: 15.5px; font-weight: 600;
  /* 表面（底色 / 描边 / 阴影 / 光晕）统一在「按钮统一」段 —— 次要钮档 */
  touch-action: none;
  -webkit-user-select: none; user-select: none;
  -webkit-touch-callout: none;
  transition: background 0.22s;   /* transform 交给 JS 弹簧驱动（initPressSpring），CSS 只留背景过渡 */
}
.pill-btn:hover { background: rgba(var(--ink-rgb), 0.03); }
/* 无 :active 缩放：按压/拖动反馈全部交给 JS 物理（.pressing 扫光 + 跟手位移/拉伸/回弹） */
.pill-btn.tinted { color: #fff; border: 1px solid rgba(255,255,255,0.35); background: var(--brand-grad); box-shadow: 0 8px 24px rgba(var(--tint-rgb),0.42), 0 2px 14px rgba(var(--tint-rgb),0.34); }
.pill-btn.tinted:hover { background: var(--brand-grad-hover); }
/* 3D Touch 卡片：复制按钮改橙色（与蓝色分享/QQ 区分，避免全蓝），覆盖 tinted 蓝渐变、暗色与触屏 hover */
.peek-copy.pill-btn.tinted,
.peek-copy.pill-btn.tinted:hover {
  background: linear-gradient(135deg, #ffb24d, #ff7a00);
  box-shadow: 0 8px 24px rgba(255, 138, 0, 0.42), 0 2px 14px rgba(255, 138, 0, 0.34);
}
body.dark .peek-copy.pill-btn.tinted,
body.dark .peek-copy.pill-btn.tinted:hover {
  background: linear-gradient(135deg, #ffb24d, #ff7a00);
  box-shadow: 0 8px 24px rgba(255, 138, 0, 0.42), 0 2px 14px rgba(255, 138, 0, 0.34);
}
@media (hover: none) {
  .peek-copy.pill-btn.tinted:hover,
  body.dark .peek-copy.pill-btn.tinted:hover { background: linear-gradient(135deg, #ffb24d, #ff7a00); }
}
.pill-btn i { font-size: 16px; }
/* 按压扫光 / 描边统一在「按钮统一」段（--px/--py 由 initPressSpring 写入，脱手即灭） */
/* 触屏：拖动/长按后 :hover 会粘住 → 松手还原静态底色（tinted 还原蓝渐变） */
@media (hover: none) {
  .pill-btn:hover { background: rgba(255, 255, 255, 0.7); }
  body.dark .pill-btn:not(.tinted):hover { background: rgb(40, 40, 46); }
  .pill-btn.tinted:hover,
  body.dark .pill-btn.tinted:hover { background: var(--brand-grad); }
}

/* ---------- 底部信息 footer ---------- */
.site-footer { margin-top: 34px; text-align: center; }
.footer-links { font-size: 11px; line-height: 1.55; }
.footer-links a { color: var(--ink-2); text-decoration: none; transition: color 0.2s; margin: 0 3px; }
.footer-links a:hover { color: var(--tint); }
.footer-copy { margin-top: 2px; font-size: 11px; color: var(--ink-3); line-height: 1.45; }
.footer-copy a { color: var(--ink-3); text-decoration: none; }
.footer-copy a:hover { color: var(--tint); }
/* 公安备案行：图标在左、文字在右，垂直居中对齐（相关部门标准格式）；
   ICP 备案与公安备案左右排布，中间竖线分隔 */
.beian-line { display: inline-flex; align-items: center; gap: 6px; vertical-align: middle; }
.beian-icon { width: 15px; height: 15px; flex: none; }
.beian-sep { color: var(--ink-4, rgba(var(--ink-rgb),0.35)); font-size: 11px; flex: none; }
/* 触屏设备：禁用页脚链接 hover 粘滞（触摸拖动后 :hover 会粘住一直高亮），
   按压反馈只保留按下瞬间（:active 变色，松手即恢复） */
@media (hover: none) {
  .footer-links a:hover { color: var(--ink-2); }
  .footer-links a:active { color: var(--tint); }
  .footer-copy a:hover { color: var(--ink-3); }
  .footer-copy a:active { color: var(--tint); }
}

/*   学校列表 / 搜索结果 · 卡片*/
.card-list { display: flex; flex-direction: column; gap: 13px; }

.school-card {
  position: relative;
  display: flex; align-items: center; gap: 15px;
  padding: 17px 18px; border-radius: var(--radius-card); cursor: pointer;
  background: #ffffff;
  border: 1px solid var(--glass-border);
  box-shadow: var(--shadow-soft);
  transition: transform 0.25s var(--ease-spring), background 0.25s;
}
.school-card:hover { background: rgba(var(--ink-rgb), 0.025); }
.school-card:active { transform: scale(0.975); }

.school-info { flex: 1; min-width: 0; }
.school-name { display: flex; align-items: center; gap: 6px; min-width: 0; padding-right: 20px; font-size: 16.5px; font-weight: 700; letter-spacing: -0.01em; }
.sn-text { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
/* 核验勾 / 取消勾：固定配色，不跟随「设置 → 页面颜色」的主色调 ——
   核验状态是一种固定语义色（平台已核验=蓝），不能因为用户换了主色调就变样。 */
.name-verified { flex: none; font-size: 15px; color: #0a84ff; }
.name-unverified { flex: none; font-size: 15px; color: var(--red); }
.school-fullname { margin-top: 3px; font-size: 12.5px; color: var(--ink-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.school-meta { margin-top: 6px; display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
.op-box { margin-left: auto; flex: none; font-size: 11px; font-weight: 700; padding: 3px 10px; border-radius: 999px; white-space: nowrap; border: 1px solid currentColor; }
.op-box.op-green { color: #1e9e4a; background: rgba(48,177,88,0.12); }
.op-box.op-red { color: #e0322b; background: rgba(255,59,48,0.12); }
.op-box.op-gray { color: #6b7177; background: rgba(154,160,166,0.16); }
.op-box.op-blue { color: var(--tint-deep); background: rgba(var(--tint-rgb),0.12); }
.op-box.op-yellow { color: #d99400; background: rgba(255, 193, 7, 0.18); }
.chip { font-size: 11px; font-weight: 600; padding: 3px 9px; border-radius: 999px; background: rgba(var(--tint-rgb),0.10); color: var(--tint-deep); }
.chip.gray { background: rgba(var(--ink-rgb),0.07); color: var(--ink-2); }
.chip.warn { background: rgba(255,149,0,0.14); color: #c26a00; }
/* 关键词/警示框的暗色适配：灰色框在暗色下不再隐形（地区标签与关键词同色，共用 gray） */
body.dark .chip { background: rgba(var(--tint-rgb),0.20); color: #7ab8ff; }
body.dark .chip.gray { background: rgba(255,255,255,0.10); color: rgba(255,255,255,0.62); }
body.dark .chip.warn { background: rgba(255,149,0,0.20); color: #ffb340; }
.blocked-reason { color: #c0392b; font-weight: 600; }
body.dark .blocked-reason { color: #ff6b60; }
/* 卡片右侧运营状态圆点（取代原三角箭头，整卡仍可点进详情）
   绝对定位脱离 flex 流，使 .school-info 占满卡片宽度，
   .op-box 的 margin-left:auto 即可与圆点右边缘平齐右对齐 */
.status-orb { position: absolute; right: 18px; top: 50%; transform: translateY(-50%); width: 12px; height: 12px; border-radius: 50%; transition: transform 0.2s var(--ease-spring); }
.status-orb::after { content: ''; position: absolute; inset: -4px; border-radius: 50%; background: inherit; opacity: 0.2; }
.school-card:active .status-orb { transform: translateY(-50%) scale(0.82); }

/* 同校多账号：一张独立大卡片（一个边框/圆角，内部账号块间分隔线） */
.school-group-card { display: block; padding: 0; cursor: default; }
.school-group-card:active { transform: none; }
/* 账号说明条（如星海实验中学无账号公告）：显示在第一个账号上方，淡金色底 + 警示图标 */
.sg-note {
  display: flex; align-items: flex-start; gap: 9px;
  margin: 10px 12px 4px; padding: 11px 13px;
  border-radius: 14px; font-size: 12.5px; line-height: 1.6;
  color: var(--ink-2); font-weight: 500;
  background: rgba(255, 196, 0, 0.10); border: 1px solid rgba(255, 176, 0, 0.22);
}
.sg-note i { color: #e8a400; margin-top: 2px; flex: none; font-size: 13px; }
body.dark .sg-note { color: rgba(255, 255, 255, 0.78); background: rgba(255, 176, 0, 0.12); border-color: rgba(255, 176, 0, 0.28); }
body.dark .sg-note i { color: #ffc94d; }
.sg-head { padding: 9px 18px 13px; font-size: 12.5px; color: var(--ink-3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; border-top: 1px solid rgba(var(--ink-rgb), 0.06); }
.sg-item { position: relative; padding: 13px 18px; cursor: pointer; transition: background 0.15s; }
.sg-item + .sg-item { border-top: 1px solid rgba(var(--ink-rgb), 0.06); }
.sg-item:active { background: rgba(var(--ink-rgb), 0.05); }
.sg-item .status-orb { top: 26px; }
.status-green { background: var(--green); }
.status-red { background: var(--red); }
.status-gray { background: #9aa0a6; }
.status-blue { background: var(--tint); }
/* 备用账号：固定黄（不跟主色调，属于状态语义色） */
.status-yellow { background: #ffc107; }
.card-chevron { flex: none; font-size: 14px; color: var(--ink-3); margin-left: auto; }

/* 列表页顶部标识说明卡
   —— 纯粹的静态卡片，不参与任何动画、没有状态类、不变形。
     滚过阈值时它照常在文档流里跟着滚走（该看不见就看不见），
     顶上的搜索栏是另一层独立浮层（见下方 .legend-topsearch），与它无关。
     用户明确要求："我不要从标识说明变出来了…我不要根据标识说明的卡片本体来做变形了。" */
.legend-card {
  margin: 0 0 16px; padding: 8px 16px;
  /*  灰 border 已删除 → 由下方 ::after 的斜角打光描边（--stroke-grey-card）替代。
     描边只能留一层：灰线 + 发光光边叠着，亮色下就是描边加倍。 */
  background: var(--glass); border: none;
  border-radius: 28px; box-shadow: var(--shadow-soft);
  position: relative;
}
.legend-head { display: flex; align-items: center; gap: 6px; font-size: 13px; font-weight: 700; color: var(--ink-2); margin-bottom: 9px; }
.legend-head i { color: #0a84ff; }
.legend-items { display: flex; flex-wrap: wrap; gap: 8px 18px; }
.legend-item { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--ink-2); font-weight: 500; }
.legend-verified { color: #0a84ff; font-size: 14px; }
.legend-unverified { color: var(--red); font-size: 14px; }
.legend-dot { width: 10px; height: 10px; border-radius: 50%; flex: none; }

/* 顶置搜索栏（独立浮层）
   —— 滚过「标识说明卡顶边越过接管线」这个阈值后，在导航栏下方渐显出来；
        滚回阈值之上就渐隐消失。仅此而已，没有任何形变 / 位移 / 克隆。
    位置：`position: fixed`，top 取 --legend-park（JS 写入的固定值 65px，
      与 .nav-blur 的高度解耦 —— 模糊层再怎么加高，搜索框都钉在 65px 不动，
      叠在模糊层上也没关系，用户明确接受这个重叠。见 app.js 的 LEGEND_PARK_Y）。
      left/right 与 .view 同宽同边距，所以它横向上跟列表内容严格对齐。
    之所以要 fixed：它是"顶置"的，必须脱离文档流独立浮在顶上，
      不能跟着 #legendCard 一起滚走 —— 这也正是它必须与卡片分离的原因。
    z-index 45：夹在「大标题主文本(41) / 渐进模糊层(40)」之上、「顶部导航栏(50)」之下，
      滚上来的大标题会被它盖住，而它自己也永远钻不到导航栏文字上面去。
    pointer-events：默认 none（不挡下面的滚动 / 卡片点击），
      只有完全显形时才由 .show 放开那一块的点击与输入。 */
.legend-topsearch {
  position: fixed;
  top: var(--legend-park, 65px);
  left: 50%;
  transform: translateX(-50%);
  width: min(680px, 100%);
  padding: 0 18px;
  box-sizing: border-box;
  z-index: 45;
  pointer-events: none;
  /* 渐显：只做 opacity + 轻微下移收尾（不做横向位移、不做缩放 —— 早前的坑）。
      这里不能带 backdrop-filter: 0（隐形时就把模糊关掉）。见下方 .show 里的说明。 */
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.18s ease-out, transform 0.18s ease-out, visibility 0s linear 0.18s;
}
@media (max-width: 480px) {
  /* 与 .view 的小屏边距保持一致（14px） */
  .legend-topsearch { padding: 0 14px; }
}
/* 显形：渐显 + 从"上移 6px"归位 —— 一个极小的迎入动作，够自然但不夸张。
    关键（本轮修的 bug）：毛玻璃必须有自己的淡入过渡。
     浏览器对 backdrop-filter 是"可插值"的，但它不会跟着 opacity 一起淡 ——
     父层 opacity 0→1 时，模糊要么全程满强度要么全程没有（实测就是"淡入过程中没有毛玻璃"：
     元素一点点浮现，可背后一直是清晰的，直到最后一下才糊）。所以这里显式给
     backdrop-filter 写一段与 opacity 同长同缓动的过渡，让"透明度"和"模糊强度"同步爬升。
      被 .show 覆盖的声明只在这里写、基础规则里绝不能写 backdrop-filter: 0 ——
       那样会在渐隐结束（visibility 转 hidden）时把模糊也一起卸掉，重新显形那一瞬
       浏览器要先重建过滤层再插值，观感就是"糊 → 跳一下 → 糊"。
       写在这里的代价只是：元素不可见期间仍保留过滤层（本就不可见，无视觉成本）。 */
.legend-topsearch.show {
  opacity: 1;
  visibility: visible;
  transform: translateX(-50%) translateY(0);
  pointer-events: auto;
  transition: opacity 0.18s ease-out, transform 0.18s ease-out, visibility 0s;
}
/* 模糊强度单独一条过渡（与上面 opacity 同参数）：0px → 21px（菜单档 14px 的 1.5 倍）。
   半径略放大是刻意的 —— 渐显是一条"薄玻璃片浮起来"的过程，起手若只有 14px，
   前半程几乎看不出糊；21px 让它在半透明阶段就已经明显是块玻璃。
    终点值只写在这条过渡里：过渡结束后它会继续作为生效值留着，
      所以不必（也不能）回写到基础规则里。 */
.legend-topsearch .legend-search {
  backdrop-filter: blur(0px) saturate(var(--sat-glass));
  -webkit-backdrop-filter: blur(0px) saturate(var(--sat-glass));
  transition: backdrop-filter 0.18s ease-out, -webkit-backdrop-filter 0.18s ease-out;
}
.legend-topsearch.show .legend-search {
  backdrop-filter: blur(21px) saturate(var(--sat-glass));
  -webkit-backdrop-filter: blur(21px) saturate(var(--sat-glass));
}
.legend-topsearch:not(.show) { transform: translateX(-50%) translateY(-6px); }
/* 设置里「列表页顶置搜索框」开关（默认关）：
   关闭时 body.no-topsearch 恒在 → 整条搜索栏不渲染（与 updateLegend() 里的短路双保险）。
    用 display:none 而非 visibility —— 关着时连布局 / 合成层都不建，彻底零成本。 */
body.no-topsearch .legend-topsearch { display: none !important; }

/* 搜索栏本体 = 整体胶囊，全站唯一的框（不是"框套框"）：
   一条胶囊槽（圆角 999px，浅底 + 描边），输入框在左、清除钮 + 圆形搜索钮在右内嵌。
   它自己就是那个框，外面没有任何再包一层的东西。
    背景 / 毛玻璃的实际值统一在下方「列表页顶置搜索栏 · 毛玻璃」段（唯一权威来源），
      此处只管几何与排版 —— 和 .menu-pop / .filter-pop 的分工方式一致（改档只改一处）。 */
.legend-search {
  display: flex;
  align-items: center; gap: 6px;
  padding: 5px 5px 5px 16px;
  border-radius: 999px;
  box-shadow: var(--shadow-float);
  position: relative;   /* ::after 斜角打光描边的定位锚点（与 .search-shell 同款同 token） */
  /* iOS 26 弹性拖拽（与右上角菜单卡 / 筛选卡 / 卡片式下拉同一套物理，见 app.js initPanelSpring）：
     · touch-action:none 是前提 —— 没有它，手指一按浏览器就判定成滚屏 → pointercancel
       → 拖拽瞬间被掐死（下拉菜单当初"没效果"就是这个根因）。
     · 整条都是拖拽热区（含输入框内部）：用户明确要"哪怕里面的框也能识别出拖拽"。
       做法见下：整条 user-select:none + input 也禁止选择（否则在 input 上拖动会被
       浏览器当成"选文字"吞掉手势）；轻点聚焦由 initPanelSpring 的 DRAG_T(3px) 延迟捕获保证。 */
  touch-action: none;
  user-select: none; -webkit-user-select: none;
  /* 拖动/回弹动画由 JS 逐帧写内联 transform；CSS 这边只留一条回弹收尾用的过渡，
     平时为空（内联被清掉即回基线），与三张弹卡同款做法。 */
  will-change: transform;
}
/*  输入框同样禁止文本选择 / 拖拽选择，否则在框内按住拖动会被浏览器判成
   "拖选文字"，指针手势被它吞掉 → 整条拖不起来（用户实测"只能拖边框"的根因）。
   这不影响输入与聚焦：轻点（位移<3px）仍正常落焦并弹出键盘；select 只是禁掉
   "长按/拖动选中文字"这一种手势，输入、光标移动、删除都照常。 */
.legend-search input {
  user-select: none; -webkit-user-select: none;
  touch-action: none;
}
/* 拖拽触点扫光：与右上角菜单卡（.menu-pop::before）同构同款 ——
   径向渐变跟随手指（--px/--py 由 JS 写入），按住即亮、脱手即淡出。
    必须 z-index 压过输入框（用 2，高于 input 的默认层），否则光点被输入框文字盖住看不见
      （上一版就是没给 z-index，光效被 prove 到看不见 = 用户说的"光效还是边框那儿"）。
   亮色用中性灰光晕（白底上清晰可见），与菜单卡逐值一致。 */
/* 拖拽触点扫光：跟手性对齐胶囊关闭按钮（.verify-cancel / .nav-back / .pill-btn）——
   它们用固定半径的小光点（circle 70px），而不是菜单卡那种 relative 的大软晕。
   为什么这条要跟胶囊钮而不是菜单卡：本条是 680×44 的极长胶囊，
   沿用它原来（菜单卡同款）的 `… 0) 58%` 停止点 → 光团半径 ≈ 0.58×680 ≈ 394px，
   整条都亮成一片、根本看不出"光点在手指底下"，观感就是"光效还在边框那儿"。
   换成固定 70px 硬光点（与胶囊钮逐值一致）后，光点小而实、清晰贴着手指走：
   · 中段 → 贴着上下直边滑；· 两端 → 绕端头圆弧走（几何由 JS 的胶囊投影保证，见 app.js）。
   颜色沿用菜单卡档的灰（0.20）以匹配这条玻璃片的质感，只改"形状的聚散"。 */
.legend-search::before {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  background: radial-gradient(circle 70px at var(--px, 50%) var(--py, 50%),
    rgba(var(--ink-rgb), 0.20), rgba(var(--ink-rgb), 0) 100%);
  opacity: 0; pointer-events: none; z-index: 2;
  transition: opacity 0.22s ease;
}
.legend-search.glow::before { opacity: 1; }
body.dark .legend-search::before {
  background: radial-gradient(circle 70px at var(--px, 50%) var(--py, 50%),
    rgba(255, 255, 255, 0.48), rgba(255, 255, 255, 0) 100%);
}
/* 输入框与按钮本体要压在光点之上（文字/图标不能被光盖住），
   故给它们各自 position+z-index；光点 z2、它们 z3。 */
.legend-search input,
.legend-search .legend-search-clear,
.legend-search .legend-search-go { position: relative; z-index: 3; }

/* ---- 列表页顶置搜索栏 · 毛玻璃（用户单独指定的效果） ----
   三档玻璃里取菜单档（--glass-menu 0.30 + blur(--blur-menu) 14px）：
   · 不取悬浮档（0.12 / 8px）：那档太透，搜索框里的文字会跟背后的学校卡片糊在一起；
   · 不取弹层档（0.52 / 22px）：那档是给大面积弹层的，用在一条细胶囊上会像磨砂塑料板。
   菜单档 = 「压在内容上的小玻璃片」的甜点位，与右上角菜单卡 / 筛选卡同一材质语言。
   列表页正在滚动的卡片会从它下面经过，模糊与饱和在这里才真正看得出来 —— 这也是
   首页 / 搜索页那两个搜索框不做毛玻璃的原因（它们是嵌在静态内容里的表单控件，
   背后没有东西可糊，加了反而会和周围卡片材质打架、读不出边界）。
    模糊半径的静态值只写在上面「渐显」段的两条规则里（.legend-topsearch 0px / .show 21px）
      —— 那儿的选择器都带 .legend-topsearch 前缀，特异性(0,2,0) 高于这里的 (0,1,0)，
      而且那条同时负责"淡入过程"，所以静态值必须以它为准。这一段只负责：底色 + 饱和度，
      以及基础规则里那份每帧都在的 backdrop-filter 兜底（供触屏 / 降级段覆盖）。
    若要在别处单独调这条的模糊半径，改上面那两条，别改这里 —— 改了也会被压掉。 */
.legend-search {
  background: var(--glass-menu);
  backdrop-filter: blur(var(--blur-menu)) saturate(var(--sat-glass));
  -webkit-backdrop-filter: blur(var(--blur-menu)) saturate(var(--sat-glass));
  /* 与悬浮玻璃控件同款：滚动 / 拖动时合成器预保留过滤层，
     防「糊 → 清晰 → 又糊」的闪（该元素正是随滚动浮现的，最吃这一项）。 */
  will-change: backdrop-filter;
}
body.dark .legend-search { background: var(--glass-dark-menu); }
/* 触屏降级档：手机 GPU 对 backdrop-filter 采样率低 → 半径降一档（与全站触屏档一致） */
@media (hover: none) {
  .legend-search {
    backdrop-filter: blur(var(--blur-touch-menu)) saturate(var(--sat-glass-touch));
    -webkit-backdrop-filter: blur(var(--blur-touch-menu)) saturate(var(--sat-glass-touch));
  }
}
/* 毛玻璃被手动关闭时（更多 → 设置 → 动效档）：与其它弹层同款降级 —— 去模糊 + 改不透明实底 */
body.no-card-blur .legend-search {
  backdrop-filter: none !important;
  -webkit-backdrop-filter: none !important;
  background: #ffffff !important;
}
body.dark.no-card-blur .legend-search { background: #1e1e24 !important; }

/* 搜索钮：圆形实心主色钮（与首页/搜索页的 .search-go 同族），点按由 initPressSpring 接管。
   32px 直径 = 胶囊内高，严丝合缝嵌在右端，不会在胶囊里留出上下空隙。 */
.legend-search-go {
  flex: none; border: none; cursor: pointer; font-family: inherit;
  width: 32px; height: 32px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 13px; color: #fff;
  background: var(--brand-grad);
  box-shadow: 0 4px 12px rgba(var(--tint-rgb), 0.32);
  touch-action: none;
  -webkit-user-select: none; user-select: none;
}
.legend-search input {
  flex: 1; min-width: 0; border: none; outline: none; background: none;
  font: inherit; font-size: 14.5px; font-weight: 500; color: var(--ink);
  /* 文字左侧内缩由 .legend-search 自己的 padding-left(16px) 负责，这里不再重复加。 */
  padding: 0;
}
.legend-search input::placeholder { color: var(--ink-3); font-weight: 500; }
/* 清除钮：默认隐形但占位不塌（宽度靠 max-width 在 0 ↔ 26px 之间过渡，
   避免出现/消失时输入框与搜索钮整体横跳）。有内容时 .has-text 才显形。
    不给它加 pointer-events:none 之外的花活 —— 隐形期间必须点不到，
      否则输入框右侧会有一小块"透明却吃点击"的死区。 */
.legend-search-clear {
  flex: none; border: none; cursor: pointer; padding: 0; background: none;
  width: 26px; height: 26px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 14px; color: var(--ink-3);
  opacity: 0; visibility: hidden; pointer-events: none;
  max-width: 0; margin-right: -6px;      /* 抵消父级 gap，隐形时完全不吃横向空间 */
  transition: opacity 0.16s ease-out, max-width 0.16s ease-out,
              margin-right 0.16s ease-out, color 0.16s ease-out;
  -webkit-tap-highlight-color: transparent;
}
.legend-search.has-text .legend-search-clear {
  opacity: 1; visibility: visible; pointer-events: auto;
  max-width: 26px; margin-right: 0;
}
.legend-search-clear:hover { color: var(--ink-2); }
.legend-search-clear:active { transform: scale(0.9); }
/* 暗色：颜色跟随 --ink-3 / --ink-2 自动适配，无需额外规则。 */
/* 暗色：搜索栏的玻璃底由 --glass 自带暗色适配，无需额外加深色块。 */

.seg {
  flex: none; border: 1px solid var(--glass-border); cursor: pointer; font-family: inherit;
  font-size: 13.5px; font-weight: 600; padding: 8px 17px; border-radius: 999px;
  color: var(--ink-2); background: rgba(var(--ink-rgb), 0.05);
  transition: all 0.22s var(--ease-spring);
}
.seg.on { color: #fff; background: var(--brand-grad); border-color: transparent; box-shadow: 0 5px 14px rgba(var(--tint-rgb),0.3); }
.seg:not(.on):hover { background: rgba(var(--ink-rgb), 0.09); }

.result-count { font-size: 13px; color: var(--ink-2); margin: 2px 4px 14px; }

/* ---------- 学校列表：统计卡 / 状态筛选 / 自定义下拉 / 分组 ---------- */
.stat-card {
  display: flex; gap: 10px; margin: 2px 0 18px; padding: 16px 12px;
  /*  灰 border 已删除 → 由下方与 .legend-card 合写的那条 ::after 斜角打光描边替代。 */
  border-radius: 32px; background: var(--glass); border: none;
  box-shadow: var(--shadow-soft);
  position: relative;
}
/* 列表页两张「信息面」卡（收录统计 .stat-card / 标识说明 .legend-card）的斜角打光描边：
   与卡片 / 弹层 / 菜单 / 三个搜索框同款同 token（--stroke-grey-card），几何逐字一致。
   亮色 = 暗色那道白光的灰色版（同轴，保留「右上主光 → 左下副光」的光感）；
   暗色 = 白光对角渐变（深底上白光才成立）。
    两个宿主都必须 position: relative（各自的基础规则里已补）。
    .legend-card 的暗色底另有一条 `body.dark .legend-card`（见暗色段），
      它把 background 改成不透明深色 —— 这里只管描边，两者不冲突。 */
.stat-card::after,
.legend-card::after {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  padding: 1px;
  background: var(--stroke-grey-card);
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  pointer-events: none;
}
body.dark .stat-card::after,
body.dark .legend-card::after {
  background: linear-gradient(to bottom left,
    rgba(255, 255, 255, 0.84) 0%, rgba(255, 255, 255, 0.52) 12%, rgba(255, 255, 255, 0.21) 30%,
    rgba(255, 255, 255, 0.07) 50%, rgba(255, 255, 255, 0.17) 78%, rgba(255, 255, 255, 0.34) 100%);
}

.stat-item { flex: 1; position: relative; text-align: center; }
.stat-item + .stat-item::before {
  content: ''; position: absolute; left: 0; top: 18%; height: 64%; width: 1px; background: var(--glass-border);
}
.stat-num { font-size: 25px; font-weight: 800; letter-spacing: -0.02em; line-height: 1; font-variant-numeric: tabular-nums; }
.stat-num.ok { color: #1ea44f; }
.stat-num.warn { color: #ff9f0a; }
.stat-label { margin-top: 7px; font-size: 12px; color: var(--ink-3); font-weight: 500; }

/* 列表页筛选区：状态 + 分类 + 学校类型 + 所在地区合并为一块面板。
   底色做半透明（不是不透明白）——它身上的两个卡片式下拉要靠 backdrop-filter 出磨砂感，
   面板若是不透明白底，菜单的毛玻璃就完全没有东西可磨（磨了也看不出来）。
    面板自己不做毛玻璃（用户明确只要描边）：它占满整行宽度、面积大，
      一旦自己也糊，把下面的学校卡片整片抹平、层次反而塌；而且它身上还叠着两个
      已经带毛玻璃的下拉菜单，再糊一层就成了「玻璃里套玻璃」，菜单的边界读不出来。
      这一条刻意与右上角筛选弹卡（.filter-pop，自带毛玻璃）区分开 —— 两者处境不同。 */
.filter-panel {
  margin-bottom: 18px; padding: 6px 18px;
  background: rgba(255, 255, 255, 0.72);
  /*  灰 border 已删除 → 由下方 ::after 的斜角打光描边（--stroke-grey-card）替代。描边只留一层。 */
  border: none;
  border-radius: 26px; box-shadow: var(--shadow-soft);
  position: relative;
}

/* 列表页筛选面板的斜角打光描边：与卡片 / 弹层 / 菜单 / 搜索框 / 两张信息面卡
   同款同 token（--stroke-grey-card），几何逐字一致。
   亮色 = 暗色那道白光的灰色版（同轴，保留「右上主光 → 左下副光」的光感）；
   暗色 = 白光对角渐变（深底上白光才成立）。
    选择器只写 `.filter-panel`，不要写成 `body.dark .filter-pop .filter-panel` 那种组合 ——
      右上角筛选弹卡（.filter-pop）内部也复用了一个 .filter-panel，它外面已经有
      .filter-pop::after 画着同一道描边了，再叠一层就是双层描边。
      而那条 `body.dark .filter-panel`（暗色段）是故意要同时命中两处的（它只管底色）。 */
.filter-panel::after {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  padding: 1px;
  background: var(--stroke-grey-card);
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  pointer-events: none;
}
body.dark .filter-panel::after {
  background: linear-gradient(to bottom left,
    rgba(255, 255, 255, 0.84) 0%, rgba(255, 255, 255, 0.52) 12%, rgba(255, 255, 255, 0.21) 30%,
    rgba(255, 255, 255, 0.07) 50%, rgba(255, 255, 255, 0.17) 78%, rgba(255, 255, 255, 0.34) 100%);
}
.filter-row { display: flex; align-items: center; gap: 14px; padding: 13px 0; }
.filter-row + .filter-row { border-top: 1px solid var(--glass-border); }
.filter-label {
  flex: none; width: 36px; font-size: 13.5px; font-weight: 600; color: var(--ink-2);
  letter-spacing: 0.02em; white-space: nowrap;
}
/* 四字标签（学校类型 / 所在地区）：标签加宽，右侧下拉相应缩短 */
.filter-label.wide { width: 58px; }
.filter-row .segmented, .filter-row .dropdown { flex: 1; min-width: 0; }

/* 通用 iOS 分段控件：玻璃槽 + 滑动高亮块（状态 / 分类共用） */
.segmented {
  position: relative; display: flex; gap: 0;
  padding: 4px; border-radius: 24px;
  background: rgba(var(--ink-rgb), 0.06); border: 1px solid var(--glass-border);
  box-shadow: inset 0 1px 2px rgba(var(--ink-rgb), 0.06);
  /* 触摸手势分配（关键）：只让浏览器接管「垂直 pan」，横向留给 JS 的滑块拖动。
     旧版这里是 touch-action:none —— 整块分段控件（列表页上是一整条横栏，面积不小）
     把上下滑也一起吞了，手指落在这条上想滚页面却纹丝不动，体感像卡住。
     改成 pan-y 后：上下滑正常滚页面（长按未成立时手指一动即取消拖动），
     横向移动浏览器不接管 → pointermove 照常派发给 JS → 长按后左右拖滑块切换。 */
  touch-action: pan-y; -webkit-touch-action: pan-y;
}
.segmented .seg {
  position: relative; z-index: 1; flex: 1; border: none; background: transparent; box-shadow: none;
  border-radius: 13px; padding: 9px 0; text-align: center; font-size: 13.5px; font-weight: 600;
  color: var(--ink-2); cursor: pointer; font-family: inherit; transition: color 0.25s ease;
  white-space: nowrap;
}
.segmented .seg.on { color: #fff; background: transparent; }
.segmented .seg:not(.on):hover { background: rgba(var(--ink-rgb), 0.05); }
.seg-thumb {
  position: absolute; z-index: 0; left: 0; top: 0; height: calc(100% - 8px);
  border-radius: 13px; pointer-events: none;
  background: var(--brand-grad-alt);
  box-shadow: 0 4px 14px rgba(var(--brand-grad-rgb), 0.38);
  transition: transform 0.36s var(--ease-spring), width 0.36s var(--ease-spring), background 0.25s ease;
}
/* 滑块长按拖拽动效：与顶部圆钮 / 弹窗关闭钮同一套物理 ——
   按下并长按成立 → 触点径向扫光亮起（::before，--px/--py 由 app.js bindSegDrag 写入），
   拖动中按速度做方向拉伸（跟手鼓包），松手由 --ease-spring 欠阻尼弹回吸附位。
   .dragging 只管扫光与阴影加强；位移/缩放由 JS 写内联 transform（它同时承载定位 translate）。 */
.seg-thumb::before {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  background: radial-gradient(circle at var(--px, 50%) var(--py, 50%),
    rgba(255, 255, 255, 0.45), rgba(255, 255, 255, 0) 100%);
  opacity: 0; pointer-events: none;
  transition: opacity 0.18s ease;
}
.seg-thumb.dragging::before { opacity: 1; }
.seg-thumb.dragging { box-shadow: 0 6px 20px rgba(var(--brand-grad-rgb), 0.5); }


.dropdown { position: relative; }
.dropdown-trigger {
  width: 100%; display: flex; align-items: center; gap: 8px;
  padding: 12px 16px; border-radius: 26px; cursor: pointer; font-family: inherit;
  font-size: 14px; font-weight: 600; color: var(--ink);
  background: var(--glass); border: 1px solid var(--glass-border); box-shadow: var(--shadow-soft);
  transition: transform 0.2s var(--ease-spring), border-color 0.2s ease;
}
.dropdown-trigger:active { transform: scale(0.98); }
.dd-value { margin-left: auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dd-caret { font-size: 12px; color: var(--ink-3); flex: none; transition: transform 0.25s var(--ease-spring); }
.dropdown.open .dd-caret { transform: rotate(180deg); }
.dropdown-menu {
  position: absolute; top: calc(100% + 8px); left: 0; right: 0; z-index: 50;
  /* 底色 / 模糊由「毛玻璃统一」弹层档统一给（--glass-sheet / --blur-sheet） */
  border: 1px solid var(--glass-border); border-radius: 28px; padding: 7px;
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.18);
  opacity: 0; transform: translateY(-6px) scale(0.98); pointer-events: none;
  transition: opacity 0.22s var(--ease-spring), transform 0.22s var(--ease-spring);
  /* 超长菜单：阈值内滚动选择，避免展开后盖过底部 Tab 栏 / 超出视口 */
  max-height: min(42vh, 320px); overflow-y: auto;
  overscroll-behavior: contain; -webkit-overflow-scrolling: touch;
}
.dropdown.open .dropdown-menu { opacity: 1; transform: none; pointer-events: auto; }
.dd-option {
  display: flex; align-items: center; gap: 10px; width: 100%; padding: 11px 12px;
  border: none; border-radius: 20px; cursor: pointer; font-family: inherit;
  font-size: 14px; font-weight: 600; color: var(--ink); background: transparent; text-align: left;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  transition: background 0.18s ease;
}
.dd-option i { width: 18px; text-align: center; color: var(--ink-3); font-size: 14px; flex: none; }
.dd-option:active { background: rgba(var(--ink-rgb), 0.08); }
/* 选中项 = 只变字色（主题蓝），不做任何背景色块。
    用户明确要求："选中项只是变颜色，不要有一个胶囊圈着"——三处 U/N 下拉
      （联系人式设置下拉 / 学校类型 / 所在地区 / 学生验证·所在学校）统一为此观感。
      原先的浅色圆角底（rgba(tint,0.12)）在胶囊圆角下看起来就是一个"胶囊圈"，
      已整条去掉，避免与拖动预览滑块（.menu-slider）的灰块叠成两层。 */
.dd-option.on { color: var(--tint-deep); }
.dd-option.on i { color: var(--tint); }
/* 地区下拉：苏州行政区划层级分组标题（非可点选项） */
.dd-group-h { margin: 6px 4px 2px; padding: 4px 8px 6px; font-size: 12.5px; font-weight: 800; color: var(--ink); letter-spacing: 0.01em; border-bottom: 1px solid var(--glass-border); }
.dd-sub-h { margin: 6px 4px 1px; padding: 2px 8px; font-size: 11px; font-weight: 600; color: var(--ink-3); opacity: 0.85; }

.group-label {
  font-size: 13px; font-weight: 800; color: var(--ink-2); letter-spacing: 0.04em;
  padding: 14px 4px 8px; margin-bottom: 10px; border-bottom: 1px solid var(--glass-border);
}


.empty-state { text-align: center; padding: 70px 20px; color: var(--ink-2); }
.empty-state i { font-size: 50px; color: var(--ink-3); margin-bottom: 14px; display: block; }
.empty-state p { font-size: 14.5px; line-height: 1.8; }

/* ---------- 搜索页顶部输入 ---------- */
.search-head { margin-bottom: 18px; }
.search-head .search-shell { margin-top: 0; max-width: none; }

/*   详情页*/
.detail-hero {
  border-radius: 38px; padding: 34px 24px 26px; text-align: center;
  background: rgba(255, 255, 255, 0.78);
  border: 1px solid var(--glass-border);
  box-shadow: var(--shadow-float);
  position: relative; overflow: hidden;
}
.detail-hero::before {
  content: ""; position: absolute; inset: -40% -20% auto; height: 70%;
  background: radial-gradient(ellipse at 50% 0%, rgba(var(--tint-rgb),0.24), transparent 70%);
  pointer-events: none;
}
.detail-name { font-size: 23px; font-weight: 800; letter-spacing: -0.01em; display: inline-flex; align-items: center; gap: 7px; margin: 0 auto; }
.detail-name .verified { font-size: 18px; color: #0a84ff; flex: none; }
.detail-tags { margin-top: 10px; display: flex; gap: 7px; justify-content: center; flex-wrap: wrap; }

.qq-plain { margin-top: 20px; display: flex; align-items: center; gap: 13px; text-align: left; }
.qq-plain > i { flex: none; width: 42px; height: 42px; border-radius: 13px; display: grid; place-items: center; background: linear-gradient(135deg, #43c6ff, var(--tint)); box-shadow: 0 5px 14px rgba(var(--tint-rgb),0.3); font-size: 22px; color: #fff; }
.qq-main { flex: 1; min-width: 0; }
.qq-num-label { font-size: 12px; color: var(--ink-2); font-weight: 600; }
.qq-num { font-size: 21px; font-weight: 800; font-variant-numeric: tabular-nums; letter-spacing: 0.03em; }
.copy-btn {
  margin-left: auto; flex: none; border: none; cursor: pointer; font-family: inherit;
  padding: 11px 20px; border-radius: 999px; font-size: 14px; font-weight: 700; color: #fff;
  background: var(--brand-grad);
  box-shadow: 0 6px 16px rgba(var(--tint-rgb),0.32);
  transition: transform 0.2s var(--ease-spring), background 0.25s;
}
.copy-btn:active { transform: scale(0.92); }
.copy-btn.done { background: linear-gradient(135deg, #34c26a, #1ea44f); box-shadow: 0 6px 16px rgba(48,177,88,0.32); }
/* 资料页打开：与复制同一张卡，作为第二行整体动作（不再单独割裂的实验性标签） */
.qq-actions {
  margin-top: 13px; padding-top: 13px; border-top: 1px solid var(--glass-border);
  display: flex; align-items: center; gap: 10px;
}
.btn-qq-profile {
  flex: none; border: none; cursor: pointer; font-family: inherit;
  padding: 9px 18px; border-radius: 999px; font-size: 13px; font-weight: 600; color: #fff;
  background: linear-gradient(135deg, #12b7f5, #0a8ad6);
  box-shadow: 0 6px 16px rgba(10,150,214,0.28);
  transition: transform 0.2s var(--ease-spring), background 0.25s;
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
}
.btn-qq-profile i { font-size: 0.95rem; }
.btn-qq-profile:active { transform: scale(0.96); }
.qq-actions-note { margin-left: auto; display: inline-flex; align-items: center; gap: 5px; font-size: 11px; color: var(--ink-3); }
.qq-actions-note i { font-size: 11px; }

.info-group { margin-top: 16px; border-radius: 22px; overflow: hidden; }
.info-row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 15px 19px; font-size: 15px;
  background: rgba(255, 255, 255, 0.66);
}
.info-row + .info-row { border-top: 1px solid rgba(var(--ink-rgb),0.06); }
.info-row .k { color: var(--ink-2); flex: none; }
.info-row .v { font-weight: 600; text-align: right; }
.op-green { color: #1e9e4a; }
.op-red { color: #e0322b; }
.op-gray { color: #6b7177; }
.op-blue { color: var(--tint-deep); }
.op-yellow { color: #d99400; }
.acc-id { font-weight: 700; margin-right: 8px; font-variant-numeric: tabular-nums; }
.info-row .v.ok { color: var(--green); }
.info-row .v.warn { color: var(--orange); }

.desc-card { margin-top: 16px; border-radius: 30px; padding: 18px 20px; font-size: 14.5px; line-height: 1.85; color: var(--ink); }
.desc-card h4 { font-size: 13px; color: var(--ink-2); font-weight: 700; margin-bottom: 8px; letter-spacing: 0.04em; }

.notice-strip {
  margin-top: 16px; border-radius: 26px; padding: 13px 15px;
  display: flex; gap: 12px; align-items: flex-start;
  background: rgba(255, 149, 0, 0.08); border: 1px solid rgba(255, 149, 0, 0.16);
  font-size: 12.5px; line-height: 1.75; color: var(--ink-2);
}
.notice-strip i {
  flex: none; width: 27px; height: 27px; border-radius: 9px;
  display: grid; place-items: center; font-size: 13px;
  background: rgba(255, 149, 0, 0.15); color: var(--orange);
}
.notice-strip a { color: var(--tint-deep); font-weight: 700; text-decoration: none; }
body.dark .notice-strip { color: rgba(255, 255, 255, 0.75); background: rgba(255, 170, 60, 0.10); border-color: rgba(255, 170, 60, 0.22); }
body.dark .notice-strip i { background: rgba(255, 170, 60, 0.16); }
body.dark .notice-strip a { color: var(--tint-dark); }

.qq-card { margin-top: 16px; border-radius: 30px; padding: 16px 18px; background: #ffffff; border: 1px solid var(--glass-border); box-shadow: var(--shadow-soft); }
.qq-card .qq-plain { margin-top: 0; }

/*   内容页（公告 / 教程 / 法务）*/
.content-card { border-radius: var(--radius-card); padding: 22px 22px; margin-bottom: 14px; }
.content-card h3 { font-size: 16.5px; font-weight: 700; display: flex; align-items: center; gap: 9px; }
.content-card .date { font-size: 12px; color: var(--ink-3); font-weight: 500; margin-left: auto; flex: none; }
.content-card p { margin-top: 9px; font-size: 14.5px; line-height: 1.85; color: var(--ink-2); }
/* 使用教程 / 最新公告：data/modals_data.php 的 HTML 卡片样式（与 content-card 同风格） */
.modal-card { border-radius: var(--radius-card); padding: 20px 20px; margin-bottom: 14px; }
.modal-card h3 { font-size: 15.5px; font-weight: 700; margin: 0 0 8px; display: flex; align-items: center; gap: 8px; }
.modal-card p { margin: 7px 0 0; font-size: 14px; line-height: 1.85; color: var(--ink-2); }
.modal-card strong { color: var(--ink); }
.step-num {
  flex: none; width: 26px; height: 26px; border-radius: 9px; display: grid; place-items: center;
  font-size: 13px; font-weight: 800; color: #fff;
  background: var(--brand-grad); box-shadow: 0 4px 10px rgba(var(--tint-rgb),0.3);
}

/* ---------- 更多页 ---------- */
.more-group { border-radius: var(--radius-card); overflow: hidden; margin-bottom: 16px; }
.more-row {
  display: flex; align-items: center; gap: 13px; padding: 15px 18px; cursor: pointer;
  background: rgba(255, 255, 255, 0.66);
  transition: background 0.2s;
}
.more-row:hover { background: rgba(var(--ink-rgb), 0.03); }
.more-row:active { background: rgba(255,255,255,0.9); }
.more-row + .more-row { border-top: 1px solid rgba(var(--ink-rgb),0.06); }
.more-ico { flex: none; width: 32px; height: 32px; border-radius: 10px; display: grid; place-items: center; color: #fff; box-shadow: inset 0 1px 1px rgba(255,255,255,0.4), 0 3px 8px rgba(var(--shadow-rgb),0.16); }
.more-ico i { font-size: 16px; color: #fff; }
.more-row .t { flex: 1; font-size: 15.5px; font-weight: 600; }
.group-label { font-size: 13px; font-weight: 700; color: var(--ink-2); margin: 4px 6px 9px; letter-spacing: 0.05em; }
/* 更多页顶部：站点版本 / 数据版本卡片 */
.version-card { border-radius: var(--radius-card); padding: 4px 18px; margin-bottom: 16px; cursor: pointer; transition: background 0.18s; }
.version-card:active { background: rgba(var(--ink-rgb), 0.05); }
.vc-row { display: flex; align-items: center; gap: 10px; padding: 12px 0; }
.vc-row + .vc-row { border-top: 1px solid rgba(var(--ink-rgb), 0.06); }
.vc-k { display: flex; align-items: center; gap: 9px; font-size: 13px; font-weight: 600; color: var(--ink-2); }
.vc-k i { font-size: 13px; color: var(--ink-3); }
.vc-v { margin-left: auto; font-size: 14px; font-weight: 700; color: var(--ink); font-variant-numeric: tabular-nums; text-align: right; min-width: 0; }

/*   底部 Tab 栏 · iOS 26 悬浮胶囊 + 独立搜索圆钮*/
.tab-wrap {
  position: fixed; left: 0; right: 0;
  bottom: calc(14px * var(--tab-scale, 1) + env(safe-area-inset-bottom, 0px));
  z-index: 60; display: flex; justify-content: center; align-items: center;
  gap: calc(12px * var(--tab-scale, 1));
  pointer-events: none; padding: 0 calc(16px * var(--tab-scale, 1));
}
/* 二级界面（带返回按钮）隐藏底部 Tab 栏 */
body.hide-tab .tab-wrap { display: none; }
.tab-bar {
  pointer-events: auto; display: flex; align-items: center; gap: calc(4px * var(--tab-scale, 1));
  height: calc(56px * var(--tab-scale, 1)); padding: calc(5px * var(--tab-scale, 1));
  border-radius: 999px; position: relative;
  touch-action: none; user-select: none; -webkit-user-select: none;
  /* 玻璃底 / 描边 / 投影统一在「毛玻璃统一」「玻璃描边」「投影」三段，此处只管版式。
     独立合成层 + 整体橡皮筋回弹。 不要加 will-change: transform —— backdrop-filter 与
     常驻 will-change:transform 同元素时，元素被提升为合成层、背景快照相对该层捕获，
     位移/滚动时会取到过期背景 → 闪烁（v0803 移除）。 */
  transform: translateZ(0); backface-visibility: hidden; -webkit-backface-visibility: hidden;
  transform-origin: center;
}
/* 点击切换：整个 Tab 栏按压动效（独立 scale 属性，不与拖拽 transform 冲突）——
   按下微缩 → 轻微过冲 → 回弹，iOS 液态手感 */
.tab-bar.tap-press { animation: tabPress 0.5s var(--ease-spring); }
@keyframes tabPress {
  0%   { scale: 1; }
  30%  { scale: 0.96; }
  62%  { scale: 1.015; }
  100% { scale: 1; }
}
/* 搜索圆钮的 tap-press 点击动画已移除：initPressSpring 的弹簧接管全部按压反馈，
   独立的 `scale` 属性会和弹簧写的 transform:scale() 叠乘打架（松手原位闪一下）。
   @keyframes tabPress 仍供 .tab-bar.tap-press 使用。 */
/* 触点光效：径向渐变跟随手指（--px/--py 由 JS 写入），按压/拖拽显示、松手消失，与卡片/按钮同款。
   亮色模式浅灰光晕（柔和中性灰）；
   收紧到 circle 70px 实心光点（和胶囊钮/圆钮一致）—— 旧版默认 circle = farthest-corner
   软晕覆盖整栏、看不出"贴边方向"，拖出后体感"绕中心转"。 */
.tab-bar::before {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  background: radial-gradient(circle 70px at var(--px, 50%) var(--py, 50%),
    rgba(var(--ink-rgb), 0.30), rgba(var(--ink-rgb), 0) 100%);
  opacity: 0; pointer-events: none; z-index: 2;
  transition: opacity 0.22s ease;
}
.tab-bar.glow::before { opacity: 1; }
body.dark .tab-bar::before {
  background: radial-gradient(circle 70px at var(--px, 50%) var(--py, 50%),
    rgba(255, 255, 255, 0.70), rgba(255, 255, 255, 0) 100%);
}

/* 灵动滑块：跟随选中项 / 手指拖拽 */
.tab-glider {
  position: absolute; top: calc(3px * var(--tab-scale, 1)); left: 0;
  height: calc(100% - 6px * var(--tab-scale, 1));
  width: calc(74px * var(--tab-scale, 1)); border-radius: 999px; z-index: 0;
  background: #e9e9ee;
  border: 1px solid rgba(var(--ink-rgb), 0.05);
  box-shadow: 0 5px 16px rgba(var(--shadow-rgb), 0.14);
  opacity: 0; pointer-events: none; will-change: transform;
  transition: transform 0.5s var(--ease-glider), width 0.5s var(--ease-glider),
              opacity 0.22s ease, scale 0.3s var(--ease-glider);
}
.tab-glider.show { opacity: 1; }
/* 拖动滑块时保持矩形/胶囊原形：不上下竖弹（苹果液态玻璃：形状不拉伸、可水平拖出框）；
   过渡仅留 opacity（拖动中 transform 由 JS 内联禁过渡、即时跟手，松手恢复吸附动画） */
.tab-bar.dragging .tab-glider { transition: opacity 0.25s ease; }

/* 滑块玻璃层次（demo indicator 的 highlight/innerShadow）—— 只属于"鼓出姿态"：
   ::before = 顶部内侧高光 + 底部内侧细影；::after = 内侧白描边 + 顶部亮部 + 底部内侧泛光。
   两层面板默认 opacity:0（正常姿态的滑块没有任何内发光/内高光），
   .glow-on 类只在鼓出时挂上（落位脉冲 / 长按滑块内部）→ 内发光随之亮起。 */
.tab-glider::before {
  content: ''; position: absolute; inset: 0; border-radius: inherit; pointer-events: none; z-index: 0;
  opacity: 0; transition: opacity 0.3s ease;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0) 46%);
  box-shadow:
    inset 0 1px 0.5px rgba(255, 255, 255, 0.95),
    inset 0 -2px 3px rgba(var(--ink-rgb), 0.08);
}
.tab-glider::after {
  content: ''; position: absolute; inset: 0; border-radius: inherit; pointer-events: none; z-index: 0;
  opacity: 0; transition: opacity 0.3s ease;
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.95), rgba(255, 255, 255, 0.3) 42%, rgba(255, 255, 255, 0) 66%);
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, 0.95),
    inset 0 1px 5px rgba(var(--ink-rgb), 0.13),
    inset 0 -8px 16px rgba(255, 255, 255, 0.9);
}
.tab-glider.glow-on::before,
.tab-glider.glow-on::after { opacity: 1; }
/* 暗色：滑块为深色玻璃胶囊，鼓出时的高光/内发光按深底配方微调 ——
   顶部高光降档防过曝，内描边保留亮边，底部泛光提亮一点让玻璃鼓起来更有体积感 */
body.dark .tab-glider::before {
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.34), rgba(255, 255, 255, 0) 46%);
  box-shadow:
    inset 0 1px 0.5px rgba(255, 255, 255, 0.5),
    inset 0 -2px 3px rgba(0, 0, 0, 0.3);
}
body.dark .tab-glider::after {
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.28) 42%, rgba(255, 255, 255, 0) 66%);
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, 0.65),
    inset 0 1px 6px rgba(0, 0, 0, 0.25),
    inset 0 -8px 18px rgba(255, 255, 255, 0.55);
}

.tab-item {
  border: none; cursor: pointer; font-family: inherit; background: none;
  display: flex; flex-direction: column; align-items: center; gap: calc(2px * var(--tab-scale, 1));
  min-width: calc(74px * var(--tab-scale, 1)); height: calc(46px * var(--tab-scale, 1));
  justify-content: center; padding: 0 calc(14px * var(--tab-scale, 1)); border-radius: 999px;
  color: var(--ink-2); font-size: calc(10.5px * var(--tab-scale, 1)); font-weight: 600;
  transition: color 0.25s ease, transform 0.25s var(--ease-spring);
  position: relative; z-index: 1;
  transform: translateZ(0); backface-visibility: hidden; -webkit-backface-visibility: hidden;
}
.tab-item i { font-size: calc(21px * var(--tab-scale, 1)); }
.tab-item:active { transform: translateZ(0) scale(0.9); }
.tab-item.on { color: var(--tint-deep); }
/* 维护模式：被禁用的「列表」tab 置灰，提示不可进入（点击/滑拖均已拦截） */
.tab-item.maint-disabled { opacity: 0.32; filter: grayscale(0.6); cursor: not-allowed; }
.tab-item.maint-disabled:active { transform: none; }
body.dark .tab-item.maint-disabled { opacity: 0.28; }

.tab-search {
  pointer-events: auto; flex: none; cursor: pointer;
  width: calc(56px * var(--tab-scale, 1)); height: calc(56px * var(--tab-scale, 1));
  border-radius: 50%; display: grid; place-items: center;
  color: var(--ink);
  transform: translateZ(0); backface-visibility: hidden; -webkit-backface-visibility: hidden;
  touch-action: none;   /* 同 .tab-bar：backdrop-filter 元素上不加常驻 will-change（防重采样闪烁） */
}
.tab-search i { font-size: calc(21px * var(--tab-scale, 1)); }

/*小屏 / 矮视口：底部控件等比收
   只缩控件（Tab 栏 / 圆钮 / 图标 / 内间距），不动字号体系、也不做整体缩放。
   系数由 JS 写进根元素的 --tab-scale（多档递进，最大只缩 10%）：
     >760 不缩 / 700~760 → 0.95 / 640~700 → 0.93 / <640 → 0.90；老小屏机（短边 ≤360）至少 0.92。
   本文件里所有 tab 尺寸都写成 calc(基准 × var(--tab-scale, 1))，宽、高、图标、间距同一个系数
   —— 曾经只缩高度不缩宽度，看着像被压扁（已修）。
    滑块（glider）的宽度与位移由 JS 按 offsetLeft / offsetWidth 算，会自动跟随。 */
.tab-search.on { color: #fff; background: var(--brand-grad) !important; border-color: transparent; }

/* ---------- Toast ---------- */
.toast {
  position: fixed; top: calc(var(--nav-h) + 10px); left: 50%;
  transform: translateX(-50%) translateY(-18px) scale(0.96);
  z-index: 200; padding: 11px 20px; border-radius: 999px;
  font-size: 14px; font-weight: 600; color: var(--ink);
  /* 底色 / 模糊由「毛玻璃统一」弹层档统一给（--glass-sheet / --blur-sheet） */
  border: 1px solid rgba(255,255,255,0.85);
  box-shadow: 0 10px 34px rgba(0,0,0,0.18);
  opacity: 0; visibility: hidden; pointer-events: none;
  display: flex; align-items: center; gap: 9px;
  /* 单行胶囊：宽度跟随左右边距，超长文本用省略号截断，不换行成多行 */
  max-width: calc(100vw - 32px); white-space: nowrap; overflow: hidden;
  transition: opacity 0.32s ease, transform 0.42s var(--ease-spring), visibility 0.32s;
}
.toast span {
  min-width: 0; overflow: hidden; white-space: nowrap;
}
.toast.show { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(0) scale(1); }
.toast i { font-size: 1rem; color: var(--tint); flex-shrink: 0; }

/* ---------- 响应式 ---------- */
@media (min-width: 600px) {
  .info-sheet, .dev-sheet { width: min(460px, calc(100% - 48px)); }
}

@media (max-width: 480px) {
  .home-title { font-size: 32px; }
  .large-title { font-size: 30px; }
  .search-go { width: 40px; height: 40px; font-size: 15px; }
  /* Tab 项：小屏专用「更窄的左右内边距」，但必须跟着 --tab-scale 一起缩 ——
     写死 62px / 10px 会让小屏下只有高度在缩、宽度纹丝不动（用户看到的"被压扁"）。
     另外这里不给纵向 padding：高度由 .tab-bar / .tab-item 的 scale 控制，
     再塞 8px 纵向内边距会把图标和标签挤在一起。 */
  .tab-item {
    min-width: calc(62px * var(--tab-scale, 1));
    padding: 0 calc(10px * var(--tab-scale, 1));
  }
  .view { padding-left: 14px; padding-right: 14px; }
}

/*   深色模式 · iOS 风格全站暗色适配（body.dark）*/
body.dark {
  --bg: #0e0f13;
  --ink: #f2f2f7;
  --ink-2: rgba(242, 242, 247, 0.62);
  --ink-3: rgba(242, 242, 247, 0.4);
  --glass: rgb(30, 30, 36);
  --glass-strong: rgb(42, 42, 50);
  --glass-border: rgba(255, 255, 255, 0.14);
  --shadow-soft: 0 6px 24px rgba(0, 0, 0, 0.45), 0 1px 3px rgba(0, 0, 0, 0.4);
  --shadow-float: 0 14px 40px rgba(0, 0, 0, 0.55), 0 2px 8px rgba(0, 0, 0, 0.4);
}

/* 暗色顶部压暗：色值保持不变（深色底本来就需要压得住，用户明确"暗色不要动"）。
   挂在独立层 .nav-dim（与亮色同一处，不再是 .nav-blur::after）——
   与模糊层解耦，同样只在滚动后浮现（body.scrolled 一视同仁）。
   模糊半径两套模式共用 --blur-nav，只在 .nav-blur 里定义一次，这里不重复。 */
body.dark .nav-dim {
  /* 与亮色同规律（三段式：上 30% 满强度深顶 → 中段缓慢叠降 → 末段细腻收到无压暗），
     色值/强度（0.92）保持不变（用户明确"暗色不要动"，深色底本来就需要压得住）。
      末段每段降幅同样逐段递减，90%→100% 几乎贴着 0 滑行。 */
  background: linear-gradient(to bottom,
      rgba(14, 15, 19, 0.92) 0%,
      rgba(14, 15, 19, 0.92) 30%,
      rgba(14, 15, 19, 0.859) 42%,
      rgba(14, 15, 19, 0.751) 52%,
      rgba(14, 15, 19, 0.595) 60%,
      rgba(14, 15, 19, 0.417) 68%,
      rgba(14, 15, 19, 0.276) 75%,
      rgba(14, 15, 19, 0.157) 82%,
      rgba(14, 15, 19, 0.071) 89%,
      rgba(14, 15, 19, 0.015) 95%,
      rgba(14, 15, 19, 0) 100%);
}

body.dark .nav-back,
body.dark .nav-filter,
body.dark .nav-share,
body.dark .nav-menu,
body.dark .nav-brand-home {
  background: var(--glass-dark-float);
  border-color: var(--glass-border);
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.4);
}
body.dark .nav-back:active,
body.dark .nav-filter:active,
body.dark .nav-menu:active { background: rgba(255, 255, 255, 0.12); }
body.dark .nav-filter.open,
body.dark .nav-menu.open { color: var(--tint-dark); background: rgba(var(--tint-dark-rgb), 0.18); }

/* 两个顶栏菜单的暗色透底：与亮色同步做"更通透"（0.66 → 0.40），
   向暗色 Tab 栏（rgba(var(--surface-dark-rgb),0.28~0.34)）靠拢，亮/暗两模式观感对齐。 */
body.dark .menu-pop,
body.dark .filter-pop {
  background: var(--glass-dark-menu);
  border-color: var(--glass-border);
  box-shadow: 0 14px 38px rgba(0, 0, 0, 0.55);
}
/* filter-pop 因 scale(0.72) 需阴影补偿，暗色与亮色同倍率 */
body.dark .filter-pop { box-shadow: 0 19px 53px rgba(0, 0, 0, 0.55); }
body.dark .filter-pop .filter-panel { background: none; }
body.dark .fp-reset { background: rgba(255, 255, 255, 0.1); }
body.dark .fp-reset:active { background: rgba(255, 255, 255, 0.18); }
body.dark .menu-row:active { background: rgba(255, 255, 255, 0.08); }
/* ↑ 仅筛选卡 / dev-sheet / glassySheet 的行还在用；菜单卡里已被上面的透明规则覆盖 */

body.dark .glass {
  background: var(--glass);
  border-color: var(--glass-border);
  box-shadow: var(--shadow-soft);
}

body.dark .legend-card { background: rgb(var(--surface-dark-rgb)); border-color: rgba(255, 255, 255, 0.08); }
body.dark .legend-head, body.dark .legend-item { color: var(--ink-2); }
/* 标识说明标题旁的信息图标(i)在暗色下保持蓝色，不被统一灰化 */
body.dark .legend-head i { color: #4aa3ff; }
/* 图例图标保持彩色：暗色下用更亮的同色系，避免并入灰阶 */
body.dark .legend-verified.fa-solid { color: #4aa3ff; }
body.dark .legend-unverified.fa-solid { color: #ff6b60; }
/* 列表卡片与详情页的核验/未核验图标同为 fa-solid，需压过全局 body.dark .fa-solid{color:inherit} */
body.dark .name-verified.fa-solid { color: #4aa3ff; }
body.dark .name-unverified.fa-solid { color: #ff6b60; }
body.dark .detail-name .verified { color: #4aa3ff; }
body.dark .legend-dot.status-green { background: #2fbf63; }
body.dark .legend-dot.status-red { background: #ff5b52; }
body.dark .legend-dot.status-gray { background: #8a9097; }
body.dark .legend-dot.status-blue { background: var(--tint-dark-icon); }
/* 备用账号（黄）：标识说明里用暗黄；卡片圆点/状态框在暗色下提亮保证可读 */
.legend-dot.status-yellow { background: #ffc107; }
body.dark .legend-dot.status-yellow { background: #ffd24d; }
body.dark .status-yellow { background: #ffd24d; }
body.dark .op-yellow { color: #ffd24d; }
body.dark .op-box.op-yellow { color: #ffd24d; background: rgba(255, 210, 77, 0.16); }

body.dark .search-shell {
  background: rgb(40, 40, 46);
  border-color: var(--glass-border);
}
body.dark .pill-btn:hover { background: rgba(255, 255, 255, 0.08); }
/* 修复：暗色下 base 规则盖掉 .pill-btn.tinted（如首页"最新公告"蓝钮），需保留蓝渐变 */
body.dark .pill-btn.tinted {
  color: #fff; border-color: rgba(255, 255, 255, 0.35);
  background: var(--brand-grad);
  box-shadow: 0 8px 24px rgba(var(--tint-rgb), 0.32);
}

body.dark .school-card {
  background: rgb(var(--surface-dark-rgb));
  border-color: var(--glass-border);
}
body.dark .school-card:hover { background: rgba(255, 255, 255, 0.05); }

body.dark .seg { background: rgba(255, 255, 255, 0.08); color: var(--ink-2); }
body.dark .seg:not(.on):hover { background: rgba(255, 255, 255, 0.14); }

body.dark .filter-panel { background: rgba(var(--surface-dark-rgb), 0.75); border-color: rgba(255,255,255,0.08); }
/* 半透明原因同亮色：面板身上的两个卡片式下拉要靠 backdrop-filter 出磨砂感，不透底就没得磨 */
body.dark .filter-label { color: var(--ink-2); }
body.dark .segmented { background: rgba(255, 255, 255, 0.07); border-color: rgba(255,255,255,0.08); }
body.dark .segmented .seg,
body.dark .segmented .seg.on { background: transparent; color: var(--ink-2); }
body.dark .segmented .seg.on { color: #fff; }
body.dark .segmented .seg:not(.on):hover { background: rgba(255, 255, 255, 0.08); }
body.dark .seg-thumb { background: var(--brand-grad-alt); border-color: transparent; box-shadow: 0 4px 14px rgba(var(--brand-grad-rgb), 0.5); }

body.dark .stat-num.ok { color: #4fd08a; }
body.dark .stat-num.warn { color: #ffb340; }
body.dark .dropdown-trigger { background: rgb(var(--surface-dark-rgb)); }
body.dark .dropdown-menu { background: rgb(28, 28, 32); border-color: var(--glass-border); }
body.dark .dd-option:active { background: rgba(255, 255, 255, 0.1); }
/* 暗色选中项同样只变字色（与亮色统一，无背景色块） */
body.dark .dd-option.on { color: var(--tint-dark); }
body.dark .dd-option.on i { color: var(--tint-dark); }
body.dark .dd-group-h { border-bottom-color: rgba(255, 255, 255, 0.1); }
body.dark .group-label { background: var(--bg); border-color: var(--glass-border); }

body.dark .detail-hero {
  background: rgba(var(--surface-dark-rgb), 0.7);
  border-color: var(--glass-border);
}
body.dark .qq-plain > i { box-shadow: 0 4px 12px rgba(var(--tint-rgb),0.32); }
body.dark .qq-card { background: rgb(var(--surface-dark-rgb)); border-color: rgba(255, 255, 255, 0.08); }
body.dark .qq-actions { border-top-color: rgba(255, 255, 255, 0.08); }
body.dark .qq-actions-note { color: rgba(255, 255, 255, 0.4); }

body.dark .info-row {
  background: rgba(var(--surface-dark-rgb), 0.62);
  border-top-color: rgba(255, 255, 255, 0.08);
}
body.dark .info-row + .info-row { border-top: 1px solid rgba(255, 255, 255, 0.08); }

body.dark .desc-card { color: var(--ink); }

body.dark .notice-strip {
  background: rgba(255, 149, 0, 0.12);
  border-color: rgba(255, 149, 0, 0.28);
  color: #ffcf8a;
}
body.dark .notice-strip a { color: #ffcf8a; }

body.dark .more-row {
  background: rgba(var(--surface-dark-rgb), 0.62);
  border-top-color: rgba(255, 255, 255, 0.08);
}
body.dark .more-row:hover { background: rgba(255, 255, 255, 0.05); }
body.dark .more-row:active { background: rgba(0, 0, 0, 0.3); }
body.dark .more-row + .more-row { border-top: 1px solid rgba(255, 255, 255, 0.08); }
body.dark .sg-head { color: rgba(255, 255, 255, 0.4); border-top-color: rgba(255, 255, 255, 0.08); }
body.dark .sg-item + .sg-item { border-top: 1px solid rgba(255, 255, 255, 0.08); }
body.dark .sg-item:active { background: rgba(255, 255, 255, 0.06); }
body.dark .vc-row + .vc-row { border-top: 1px solid rgba(255, 255, 255, 0.08); }
body.dark .vc-k { color: rgba(255, 255, 255, 0.62); }
body.dark .vc-k i { color: rgba(255, 255, 255, 0.4); }
body.dark .vc-v { color: #fff; }

body.dark .tab-bar {
  background: var(--glass-dark-float);
  border-color: var(--glass-border);
  box-shadow: 0 14px 38px rgba(0, 0, 0, 0.5);
}
/* 暗色滑块：深色玻璃胶囊。正常态无光感 —— 对齐亮色滑块只留极淡弱边框、无顶部高光线
   （亮色 border 0.05 / 无 inset）。上一版 border 0.22 + inset 顶线让正常态叠了一圈白线。
   鼓出时的内发光/高光由 .glow-on 的 ::before/::after 负责（opacity:0 门控），不在此处。 */
body.dark .tab-glider {
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.20), rgba(255, 255, 255, 0.09));
  border-color: rgba(255, 255, 255, 0.08);
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.5);
}
/* 暗色选中项：滑块已由亮白胶囊改为深色玻璃，文字/图标翻白保证对比（不再"一大坨纯白"） */
body.dark .tab-item.on { color: #fff; }
body.dark .tab-item.on i { opacity: 0.96; }
body.dark .tab-search {
  background: var(--glass-dark-float);
  border-color: var(--glass-border);
  box-shadow: 0 14px 38px rgba(0, 0, 0, 0.5);
}
/* 修复：暗色下 base 规则优先级盖掉 .tab-search.on，需单独声明选中态蓝渐变 */
body.dark .tab-search.on { color: #fff; background: var(--brand-grad) !important; border-color: transparent; }

body.dark .toast {
  background: rgba(36, 36, 42, 0.66);
  border-color: rgba(255, 255, 255, 0.12);
  color: #f2f2f5;
  box-shadow: 0 10px 34px rgba(0,0,0,0.55);
}
body.dark .toast i { color: var(--tint-dark-icon); }

body.dark .ios-switch { background: rgba(255, 255, 255, 0.22); }
/* 修复：暗色下 base 规则盖掉 .ios-switch.on；深色模式开启后开关显示蓝色高亮 */
body.dark .ios-switch.on { background: var(--tint); }

body.dark .dev-mask { background: rgba(0, 0, 0, 0.28); }
body.dark .dev-sheet {
  background: rgba(var(--surface-dark-rgb), 0.8);
  border-color: var(--glass-border);
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.6);
}
body.dark .dev-info-row + .dev-info-row { border-top: 1px solid rgba(255, 255, 255, 0.08); }
/* dev-cancel / info-cancel 为 demo Tinted blue 实心蓝 + 白字，不分主题，无需暗色覆盖 */

body.dark .info-mask { background: rgba(0, 0, 0, 0.28); }
body.dark .info-sheet {
  background: rgba(var(--surface-dark-rgb), 0.8);
  border-color: var(--glass-border);
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.6);
}
body.dark .info-sheet-head { color: var(--ink); border-bottom-color: rgba(255, 255, 255, 0.08); }
body.dark .info-sheet-body .content-card { background: rgba(255, 255, 255, 0.06); }
body.dark .dev-sheet-head { color: var(--ink); }
body.dark .dev-ua { background: rgba(255, 255, 255, 0.06); color: var(--ink-2); }
body.dark .dev-pre { background: rgba(255, 255, 255, 0.07); color: var(--ink); }
/* dev-tool / verify-cancel 无 :active 加深（同亮色，光晕反馈） */

/*   FontAwesome 全局 & 新组件样式（无 emoji，统一用 FA 图标）*/
.fa-solid, .fa-brands, .fa-regular { line-height: 1; }

/* 内容卡片标题 / 描述里的图标 */
.content-card h3 i { font-size: 16px; color: var(--tint); }
.desc-card h4 i { color: var(--tint); margin-right: 7px; }

/* 详情页状态小圆点（替代 ● 符号） */
.dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 7px; vertical-align: middle; }
.dot.green { background: var(--green); }
.dot.red { background: var(--red); }
.dot.gray { background: #9aa0a6; }
.dot.blue { background: var(--tint); }

/* 已屏蔽账号 */
.qq-plain.blocked > i { background: linear-gradient(135deg, #ff9a9a, #e04545); box-shadow: 0 5px 14px rgba(224, 69, 69, 0.3); }
.qq-num.muted { font-size: 15px; font-weight: 600; color: var(--ink-2); letter-spacing: 0; }

/* 法务页 meta 与有序列表 */
.legal-meta { font-size: 13px; color: var(--ink-2); margin: -8px 2px 18px; }
.legal-ol { padding-left: 22px; margin-top: 10px; }
.legal-ol li { font-size: 14.5px; line-height: 1.85; color: var(--ink-2); margin-bottom: 7px; }

/* 举报邮箱一键复制 */
.email-list { display: flex; flex-direction: column; gap: 10px; margin-top: 10px; }
.email-item {
  display: flex; align-items: center; gap: 10px; width: 100%; text-align: left; cursor: pointer; font-family: inherit;
  font-size: 14.5px; font-weight: 600; color: var(--tint-deep);
  padding: 13px 15px; border-radius: 14px; border: 1px solid var(--glass-border);
  background: rgba(var(--tint-rgb), 0.06); transition: background 0.2s, transform 0.2s var(--ease-spring);
}
.email-item i { color: var(--tint); }
.email-item:active { transform: scale(0.98); background: rgba(var(--tint-rgb), 0.12); }

/* 重要段落高亮 */
.content-card.important { border-color: rgba(255, 149, 0, 0.4); box-shadow: 0 0 0 1px rgba(255, 149, 0, 0.25), var(--shadow-soft); }
.content-card.important h4 i { color: var(--orange); }

/* 法务页子条目 */
.sub-list { margin-top: 12px; display: flex; flex-direction: column; gap: 13px; }
.sub-h { font-size: 13.5px; font-weight: 700; color: var(--ink); margin-bottom: 4px; }
.sub-item p { margin-top: 0; font-size: 14px; }

/* ---------- 更新日志 ---------- */
.cl-head { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; }
.cl-ver { font-size: 19px; font-weight: 800; letter-spacing: -0.02em; color: var(--ink); }
.cl-tag { font-size: 11px; font-weight: 700; padding: 3px 10px; border-radius: 999px; }
.cl-tag.stable { background: rgba(var(--tint-rgb), 0.12); color: var(--tint-deep); }
.cl-tag.beta { background: rgba(var(--purple-rgb), 0.18); color: var(--purple); }
.cl-date { font-size: 12.5px; color: var(--ink-3); font-weight: 500; margin-left: auto; }
.cl-title { font-size: 15px; font-weight: 700; margin-top: 9px; color: var(--ink); }
.cl-sum { font-size: 14px; line-height: 1.8; color: var(--ink-2); margin-top: 5px; }
.cl-details { margin-top: 15px; display: flex; flex-direction: column; gap: 14px; }
.cl-cat-h {
  display: inline-flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 700; color: var(--tint-deep);
  background: rgba(var(--tint-rgb), 0.10); padding: 4px 11px; border-radius: 999px; margin-bottom: 9px;
}
.cl-cat-h i { font-size: 11px; }
.cl-cat ul { list-style: none; display: flex; flex-direction: column; gap: 7px; }
.cl-cat li { font-size: 14px; line-height: 1.7; color: var(--ink-2); position: relative; padding-left: 16px; }
.cl-cat li::before { content: ""; position: absolute; left: 2px; top: 9px; width: 5px; height: 5px; border-radius: 50%; background: var(--tint); }

/* ---------- 更新日志 · 苹果系统更新风 ---------- */
.cl-hero { text-align: center; padding: 16px 20px 14px; }
.cl-hero-icon {
  width: 74px; height: 74px; border-radius: 50%; margin: 0 auto 14px; display: grid; place-items: center;
  background: linear-gradient(135deg, #34c26a, #1ea44f); box-shadow: 0 10px 26px rgba(30, 164, 79, 0.32);
}
.cl-hero-icon i { font-size: 36px; color: #fff; line-height: 1; }
.cl-hero-ver { font-size: 34px; font-weight: 800; letter-spacing: -0.02em; color: var(--ink); cursor: pointer; -webkit-tap-highlight-color: transparent; }
.cl-hero-ver:active { opacity: 0.6; }
.cl-hero-status { margin-top: 4px; font-size: 15px; font-weight: 600; color: #1ea44f; }
.cl-hero-sub { margin-top: 6px; font-size: 12.5px; color: var(--ink-3); }
/* 更新日志 hero：当前版本附带的 数据版本 / 数据更新 */
.cl-hero-meta { margin-top: 12px; display: flex; flex-direction: column; gap: 5px; align-items: center; }
.cl-hero-meta span { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--ink-2); }
.cl-hero-meta i { color: var(--ink-3); font-size: 12px; }

.cl-current { margin: 6px 0 14px; padding: 18px 18px 16px; border-radius: 28px; background: var(--glass); border: 1px solid var(--glass-border); box-shadow: var(--shadow-soft); }
.cl-sec-label { font-size: 13px; font-weight: 700; color: var(--tint-deep); letter-spacing: 0.02em; margin-bottom: 8px; }
.cl-current .cl-head { margin-bottom: 8px; }

.cl-history-entry {
  position: relative; display: flex; align-items: center; gap: 13px; cursor: pointer; margin: 4px 0 6px; padding: 15px 16px;
  border-radius: 26px; background: var(--glass); border: 1px solid var(--glass-border); box-shadow: var(--shadow-soft);
}
/* 时钟圆底：外层 span 固定 34x34 圆，内层 <i> 绝对定位居中——FA 图标由 ::before 绘制，
   图标元素本身必须是纯 inline 的 <i>，任何 flex/grid 都会破坏其字形定位，故用绝对定位强制居中 */
.cl-he-ico {
  position: relative; display: block; flex: none;
  width: 34px; height: 34px; border-radius: 50%;
  background: rgba(var(--purple-rgb), 0.14); color: var(--purple);
}
.cl-he-ico i {
  position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
  font-size: 15px; line-height: 1;
}
.cl-he-t { font-size: 16px; font-weight: 700; color: var(--ink); }
.cl-he-count { margin-left: auto; font-size: 13px; color: var(--ink-3); }
.cl-he-chev { font-size: 14px; color: var(--ink-3); flex: none; }

.cl-history-list { display: flex; flex-direction: column; gap: 12px; margin-top: 4px; }
.cl-ver-card {
  position: relative; cursor: pointer; padding: 16px 18px; border-radius: 26px; background: var(--glass);
  border: 1px solid var(--glass-border); box-shadow: var(--shadow-soft);
}
.cl-ver-card .cl-head { margin-bottom: 6px; }
.cl-ver-card .cl-sum { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.cl-vc-more { margin-top: 12px; font-size: 13.5px; font-weight: 600; color: var(--tint-deep); display: flex; align-items: center; gap: 5px; }
.cl-vc-more i { font-size: 11px; }

.cl-detail-title { font-size: 17px; font-weight: 700; color: var(--ink); margin: 2px 0 6px; }

/*设置页（更多 → 设置）
   动效三档下拉（复用全站 U 形展开下拉）+ 液态通知开关。 */
.settings-row { display: flex; align-items: center; gap: 12px; padding: 14px 16px; min-height: 56px; }
/* 同一张卡片里的多行设置项之间给一条细分隔线（与 .more-row 一致） */
.settings-row + .settings-row { border-top: 1px solid rgba(var(--ink-rgb), 0.06); }
body.dark .settings-row + .settings-row { border-top-color: rgba(255, 255, 255, 0.07); }
/*  .more-group 默认 overflow:hidden（圆角裁剪）会把 absolute 展开的下拉菜单裁掉，
   设置页这一组要放开；同时把菜单层级提到全站最高档（Tab 栏 60 / 顶栏菜单 70+）。 */
.more-group.settings-group { overflow: visible; }
.settings-drop .dropdown-menu { z-index: 90; }
/* 设置页下拉菜单右缘齐平卡片右边框（顶死右边预留边距）：行有 16px 右内边距，
   菜单 right:-16px 抵消这一截，使完整弹出后右缘正好落在卡片右边框上、不再缩进 16px。
    用 .dropdown--card.settings-drop（特异性 0,3,0）盖过 .dropdown--card .dropdown-menu 的 right:0（特异性 0,2,0，且声明更靠后）。 */
.dropdown--card.settings-drop .dropdown-menu { right: -16px; }
.settings-k { flex: 1 1 auto; min-width: 0; font-size: 15px; font-weight: 600; color: var(--ink); }
/* 右侧控件一律贴右：开关与下拉的右边缘对齐，各项之间也就自然对齐了 */
.settings-row .ios-switch { margin-left: auto; flex: none; }
.settings-drop { margin-left: auto; }

/* 主色调 · 自定义：色盘 / RGB 代码输入 / 预览块（右侧一律贴右，与开关对齐） */
.settings-row .accent-picker {
  margin-left: auto; flex: none; width: 46px; height: 30px; padding: 0; cursor: pointer;
  border: 1px solid var(--glass-border); border-radius: 9px; background: transparent;
  -webkit-appearance: none; appearance: none;
}
.settings-row .accent-input {
  margin-left: auto; flex: none; width: min(62vw, 210px); height: 34px; padding: 0 10px;
  border: 1px solid var(--glass-border); border-radius: 10px;
  background: rgba(var(--ink-rgb), 0.04);
  font-family: inherit; font-size: 13px; color: var(--ink); text-align: right; outline: none;
}
.settings-row .accent-input.bad { border-color: var(--red); color: var(--red); }
body.dark .settings-row .accent-input { background: rgba(255, 255, 255, 0.06); }
/* 主色调 · 季节档：四季预览行（可点选试色） */
.settings-row.pick { cursor: pointer; }
.settings-row.pick:active { background: rgba(var(--ink-rgb), 0.045); }
.settings-row.pick.on .settings-k { color: var(--tint); }
.settings-row.pick.on::after { content: '\2713'; margin-left: 8px; font-size: 13px; font-weight: 700; color: var(--tint); }
.accent-dot { margin-left: auto; flex: none; width: 26px; height: 26px; border-radius: 50%; border: 1px solid var(--glass-border); box-shadow: inset 0 0 0 3px rgba(255, 255, 255, 0.5); }
body.dark .accent-dot { box-shadow: inset 0 0 0 3px rgba(0, 0, 0, 0.28); }

.settings-row .accent-preview {
  margin-left: auto; flex: none; width: 46px; height: 26px;
  border-radius: 8px; border: 1px solid var(--glass-border);
}

/*季节背景元素（仅「设置 → 页面颜色 → 季节色」档）
   借用本地 FontAwesome 的四季图标（春 新芽 / 夏 太阳 / 秋 叶子 / 冬 雪花），
   由 app.js 随机撒几个大的（位置/大小/旋转/透明度随机），每次进入都不同。
   覆盖层不拦交互；透明度很低（0.07~0.14）只做氛围，不压文字。 */
.season-decor { position: fixed; inset: 0; z-index: 1; pointer-events: none; overflow: hidden; }
.season-item { position: absolute; display: block; line-height: 1; }

/*动效档位 · 第三档「关闭全部动效」
   把全站动画 / 过渡 / 平滑滚动一律掐掉（模糊由 no-*-blur 五个 class 负责）。
   拖拽等交互仍然可用，只是没有缓动与回弹——这是「完全静态」的预期效果。 */
body.no-motion *,
body.no-motion *::before,
body.no-motion *::after {
  animation: none !important;
  transition: none !important;
  scroll-behavior: auto !important;
}

/* ---------- 暗色模式补充 ---------- */
body.dark .fa-solid, body.dark .fa-brands, body.dark .fa-regular { color: inherit; }
body.dark .content-card h3 i,
body.dark .desc-card h4 i { color: var(--tint-dark); }
body.dark .email-item { background: rgba(var(--tint-rgb), 0.14); color: var(--tint-dark); border-color: var(--glass-border); }
body.dark .email-item i { color: var(--tint-dark); }
body.dark .cl-tag.stable { background: rgba(var(--tint-rgb), 0.2); color: var(--tint-dark); }
body.dark .cl-tag.beta { background: rgba(var(--purple-rgb), 0.22); color: var(--purple-light); }
body.dark .cl-ver { color: var(--ink); }
body.dark .cl-sec-label { color: var(--tint-dark); }
body.dark .cl-cat-h { background: rgba(var(--tint-rgb), 0.18); color: var(--tint-dark); }
body.dark .content-card.important { border-color: rgba(255, 149, 0, 0.45); }
body.dark .legal-meta { color: var(--ink-2); }
body.dark .empty-state i { color: var(--ink-3); }
body.dark .cl-hero-icon { box-shadow: 0 12px 30px rgba(30, 164, 79, 0.25); }
body.dark .cl-hero-status { color: var(--green); }
body.dark .cl-he-ico { background: rgba(var(--purple-rgb), 0.22); color: var(--purple-light); }
body.dark .cl-vc-more { color: var(--tint-dark); }

/* 学校列表页：右侧竖排索引条（iOS 通讯录式），可点按 / 上下拖动快速跳到对应区域 */
.jump-rail {
  position: fixed; right: 7px; top: 50%; transform: translateY(-50%);
  z-index: 60; display: flex; flex-direction: column; align-items: center; gap: 2px;
  /* 不出现内部上下滚动：用弹性高度把全部字母塞进可视区；屏幕过矮时键自动缩小自适应 */
  height: calc(100vh - 130px); max-height: 600px;
  padding: 4px 2px;
  touch-action: none;            /* 拖动切换字母时不触发页面滚动（避免与页面上下滑打架） */
  scrollbar-width: none;
  /* 默认隐藏：仅在列表页且滚过 SCROLL_TOP_THRESHOLD(500px) 后由 .show 淡入，
     与「回到顶部」共用同一阈值；未滚过时不占位、不抢点 */
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity 0.22s ease, visibility 0.22s;
}
/* 滚动超过阈值后淡入（与 back-to-top 同一出现时机） */
.jump-rail.show { opacity: 1; visibility: visible; pointer-events: auto; }
.jump-rail::-webkit-scrollbar { display: none; }
.jump-key {
  flex: 1 1 0; min-height: 0; width: 24px; padding: 0; overflow: hidden;
  display: grid; place-items: center; border: none;
  border-radius: 10px; background: rgba(var(--ink-rgb), 0.05); cursor: pointer; font-family: inherit;
  font-size: 11px; font-weight: 700; color: var(--ink-2);
}
.jump-key.wide { width: auto; min-width: 24px; padding: 0 6px; font-size: 10px; }
/* :hover 只对真正支持悬停的设备（桌面/鼠标）生效；
   触屏（hover: none，如手机）不加载这些规则——点击后 :hover 伪类粘滞
   也无样式可显示，高亮统一由 .jump-key.on 控制 */
@media (hover: hover) {
  .jump-key:hover:not(.disabled) { background: rgba(var(--tint-rgb), 0.14); color: var(--tint-deep); }
}
.jump-key:active:not(.disabled) { background: var(--tint); color: #fff; }
/* 跟随页面滚动高亮的当前分组（蓝色标块） */
.jump-key.on { background: var(--tint); color: #fff; }
.jump-key.disabled { opacity: 0.28; pointer-events: none; background: transparent; }
body.dark .jump-key { background: rgba(255, 255, 255, 0.07); color: var(--ink-2); border-color: rgba(255, 255, 255, 0.08); }
@media (hover: hover) {
  body.dark .jump-key:hover { background: rgba(var(--tint-rgb), 0.28); color: #fff; border-color: transparent; }
}
body.dark .jump-key.on { background: var(--tint); color: #fff; }
body.dark .jump-key.disabled { opacity: 0.3; background: transparent; }

/* 小屏（手机）整体收窄字母引导条 —— 整条一起缩，不按各字母的锚点各自缩。
   实现要点：只改「整条的尺寸」这一个入口，各键高度来自 .jump-key 的 `flex:1 1 0`
   （= 可用高 ÷ 字母数），所以整条一矮，全部字母等比一起变矮，天然满足"整体缩小"。
   尺寸项对应关系（只动这三样，其余全部继承，绝不逐键写死）：
     · width   → 键宽（.jump-key 的 width 继承自这里，24px → 20px）
     · right   → 距右边缘（7px → 5px，贴边更省横向空间）
     · font-size 走 .jump-key 自身的值，这里一并调小
     · height 用 dvh 替代 vh：手机地址栏收起/展开时 vh 不跟随，dvh 才是真实可视高，
       顺带解决"地址栏一动整条就跳"的问题；减法从 130 收到 150 让整条更矮一截。
    只缩放视觉尺寸，跟手拖拽 / 滚到哪高亮哪的逻辑一字未动 ——
      它们靠 getBoundingClientRect() 实测，与像素尺寸解耦，缩小后照常工作。 */
@media (max-width: 480px) {
  .jump-rail {
    right: 5px;
    width: 20px;
    height: calc(100dvh - 150px);
  }
  .jump-rail .jump-key { font-size: 10px; }
  .jump-rail .jump-key.wide { min-width: 20px; padding: 0 4px; font-size: 9px; }
}
/* 屏幕特别矮（如横屏手机）再收一档，保证整条不会顶到顶部模糊层 / 底部 Tab 栏 */
@media (max-height: 560px) {
  .jump-rail { height: calc(100dvh - 160px); max-height: 460px; }
}

/*   全局回到顶部按钮 · 滚动超阈值浮现；所有页面通用；
   学校列表页(右侧有索引条)自动左移避让，绝不与之重叠*/
.back-to-top {
  position: fixed;
  right: 16px;
  bottom: calc(86px + env(safe-area-inset-bottom, 0px));
  z-index: 65;
  width: 46px; height: 46px; border-radius: 50%;
  display: grid; place-items: center;
  color: var(--tint-deep); font-size: 17px; cursor: pointer;
  opacity: 0; visibility: hidden; pointer-events: none;
  transform: scale(0.6);
  transition: opacity 0.22s ease, transform 0.28s var(--ease-spring), visibility 0.22s;
}
.back-to-top.show { opacity: 1; visibility: visible; pointer-events: auto; transform: none; }
.back-to-top.show:active { transform: scale(0.86); }
/* 无底部 Tab 栏的二级页：按钮贴近底部 */
body.hide-tab .back-to-top { bottom: calc(18px + env(safe-area-inset-bottom, 0px)); }
/* 学校列表页（右侧有索引条）：左移避让，绝不与跳转条重叠 */
/* 仅当右侧蓝色索引条与「回到顶部」按钮实际重叠时才左移避让；不重叠则留在原右下角 */
.back-to-top.rail-shift { right: 50px; }
body.dark .back-to-top { background: var(--glass-dark-float); color: #fff; }

/*iOS 26 液态玻璃 G2 连续曲线（superellipse / squircle）
   给卡片、弹窗、列表等面元素启用「曲率连续」圆角——比普通圆角（G1）更圆润柔和、
   无生硬折角，视觉如苹果 Liquid Glass。基于 border-radius 增强，不破坏阴影/边框/毛玻璃。
   支持 corner-shape 的浏览器（Chrome 120+ / Edge / Safari 15.4+，含 QQ/微信内置浏览器）
   生效；Firefox 等不支持时自动回退普通圆角，无副作用。
   全圆胶囊（999px，如 Tab 栏/圆钮/chip）不适用，不在此列。 */
@supports (corner-shape: superellipse) {
  /* 卡片 / 面 */
  .school-card,
  .school-group-card,
  .content-card,
  .desc-card,
  .glass,
  .stat-card,
  .legend-card,
  .version-card,
  .cl-current,
  .cl-ver-card,
  .cl-hero,
  .cl-history-list,
  .detail-hero,
  .card-list,
  .more-row,
  .group-label,
  /* 弹窗 / 浮层 */
  .info-sheet,
  .dev-sheet,
  .menu-pop,
  .filter-panel,
  .filter-pop,
  .dropdown-menu,
  .toast,
  /* 控件面 */
  .segmented,
  .seg,
  .dropdown-trigger,
  .dd-option,
  .qq-card,
  .notice-strip,
  .back-to-top,
  .search-shell,
  .jump-key {
    corner-shape: superellipse;
  }
}

/*筛选区圆角（适配全站大圆角，非胶囊）
   列表页顶部筛选框（filter-panel）与右上角筛选弹卡（filter-pop）：
   容器与下拉菜单用与卡片一致的大圆角（32px），选项框（槽/下拉按钮）20px、
   按钮与选项 16px 递进；不再是 999px 胶囊、也不是直角矩形；两处筛选视觉一致。 */
.filter-panel,
.filter-pop {
  border-radius: 32px;
}
.filter-panel .segmented,
.filter-pop .segmented,
.filter-panel .dropdown-trigger,
.filter-pop .dropdown-trigger {
  border-radius: 20px;
}
.filter-panel .dropdown-menu,
.filter-pop .dropdown-menu {
  border-radius: 24px;
  /* 卡片内下拉的模糊背后是卡片白底（blur 无可见效果），直接用实色底，不做半透 */
  background: #ffffff;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
body.dark .filter-panel .dropdown-menu,
body.dark .filter-pop .dropdown-menu {
  background: rgb(28, 28, 32);
}
.filter-panel .seg,
.filter-pop .seg,
.filter-panel .seg-thumb,
.filter-pop .seg-thumb,
.filter-panel .dd-option,
.filter-pop .dd-option {
  border-radius: 16px;
}

/*弹窗体系统一（内部圆角 / 动画）
   使用教程 / 最新公告 / 开发者模式 / 学生验证 / 引导浏览器 等所有居中弹窗：
   材质（毛玻璃透底 / 模糊 / 描边）统一在「毛玻璃统一」「弹窗斜角打光描边」两段，
   此处只留弹性回弹动画；内部按钮 / 输入框 / 信息卡统一适度大圆角（18px），
   信息展示小块 14px；开发者面板底部按钮与学生验证一致改为左右放置。 */
.info-sheet,
.dev-sheet {
  transition: opacity 0.2s ease, transform 0.38s cubic-bezier(0.3, 1.1, 0.4, 1);
}
/* 统一弹窗斜角打光描边（与菜单/筛选卡片同款）：右上主光 + 左下淡副光，光照呼应。
   亮色 = 暗色那道白光的灰色版（同轴，保留光感）。
    v7.2.2 已把此处所有「性能降档／延迟显现」的改动全部撤销，恢复原样：
      不要给这条描边加 opacity/transition-delay，也不要按 UA 换成 border。 */
.info-sheet::after,
.dev-sheet::after {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  padding: 1px;
  background: var(--stroke-grey-card);
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  pointer-events: none;
}
body.dark .info-sheet::after,
body.dark .dev-sheet::after {
  background: linear-gradient(to bottom left,
    rgba(255, 255, 255, 0.84) 0%, rgba(255, 255, 255, 0.52) 12%, rgba(255, 255, 255, 0.21) 30%,
    rgba(255, 255, 255, 0.07) 50%, rgba(255, 255, 255, 0.17) 78%, rgba(255, 255, 255, 0.34) 100%);
}
/* 内部主控件：适度大圆角（框内不激进）。
   注：.info-cancel / .dev-cancel / .dev-danger / .verify-submit /
   .verify-cancel / .guide-copy-btn 均已改用「弹窗按钮 · demo 同款胶囊」段
   的规格（圆角 = 高/2），不再走这条 18px 规则。
   .vf-input 已改为「无边框行内控件」（见学生验证段），也不再需要圆角。 */
/* 信息展示小块：低调圆角 */
.dev-info-row,
.dev-sec,
.dev-pre {
  border-radius: 14px;
}
/* 开发者面板底部按钮：与学生验证一致，关闭 / 清除缓存左右放置 */
.dev-sheet-foot {
  display: flex; flex-wrap: wrap; gap: 10px;
}
.dev-tools-grid {
  flex-basis: 100%; margin-bottom: 0;
}
.dev-danger,
.dev-cancel {
  flex: 1; margin-top: 0;
}
/* 弹窗遮罩：淡色暗罩 + 轻模糊（三档参数见下方「遮罩升级」段，此处不再重复声明） */

/*浮层滚动条常驻（仅下拉菜单 / 弹窗内容，可滚动时可见）
   告诉用户这些弹出卡片 / 菜单是可以滑动翻看的；
   只作用于浮层，不动页面本身滚动条。 */
.dropdown-menu, .dev-sheet-body, .info-sheet-body {
  overflow-y: scroll;
  scrollbar-width: thin;
  scrollbar-color: rgba(var(--ink-rgb), 0.3) transparent;
}
.dropdown-menu::-webkit-scrollbar,
.dev-sheet-body::-webkit-scrollbar,
.info-sheet-body::-webkit-scrollbar { width: 5px; }
.dropdown-menu::-webkit-scrollbar-track,
.dev-sheet-body::-webkit-scrollbar-track,
.info-sheet-body::-webkit-scrollbar-track { background: transparent; }
.dropdown-menu::-webkit-scrollbar-thumb,
.dev-sheet-body::-webkit-scrollbar-thumb,
.info-sheet-body::-webkit-scrollbar-thumb { background: rgba(var(--ink-rgb), 0.25); border-radius: 3px; }
body.dark .dropdown-menu::-webkit-scrollbar-thumb,
body.dark .dev-sheet-body::-webkit-scrollbar-thumb,
body.dark .info-sheet-body::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.25); }

/*毛玻璃降级设置（鸿蒙低配 / 手动关闭）
   关闭某项毛玻璃 = 完全移除模糊 + 半透明背景改不透明实色（亮色白块 / 暗色深灰块），
   不再残留「半透明无模糊」状态。nav-blur 为纯模糊层（无背景），关闭即完全透明无效果。 */
body.no-prog-blur .nav-blur { backdrop-filter: none !important; -webkit-backdrop-filter: none !important; }
body.no-tab-blur .tab-bar,
body.no-search-blur .tab-search,
body.no-btn-blur .nav-back,
body.no-btn-blur .nav-filter,
body.no-btn-blur .nav-share,
body.no-btn-blur .nav-menu,
body.no-btn-blur .back-to-top,
body.no-card-blur .menu-pop,
body.no-card-blur .filter-pop,
body.no-card-blur .dropdown-menu,
body.no-card-blur .toast,
body.no-card-blur .info-sheet,
body.no-card-blur .dev-sheet {
  backdrop-filter: none !important;
  -webkit-backdrop-filter: none !important;
  background: #ffffff !important;
}
body.dark.no-tab-blur .tab-bar,
body.dark.no-search-blur .tab-search,
body.dark.no-btn-blur .nav-back,
body.dark.no-btn-blur .nav-filter,
body.dark.no-btn-blur .nav-share,
body.dark.no-btn-blur .nav-menu,
body.dark.no-btn-blur .back-to-top,
body.dark.no-card-blur .menu-pop,
body.dark.no-card-blur .filter-pop,
body.dark.no-card-blur .dropdown-menu,
body.dark.no-card-blur .toast,
body.dark.no-card-blur .info-sheet,
body.dark.no-card-blur .dev-sheet {
  background: #1e1e24 !important;
}

/*毛玻璃统一（更透、更高级）
   分三档（v0810 起），参数全部取自 :root 的毛玻璃 token，此处只做「分组」：
   · 悬浮控件（Tab 栏 / 搜索钮 / 各圆形按钮 / 首页品牌胶囊 / 回到顶部）
     = 透底 --glass-float + blur --blur-float + sat --sat-glass。
     这层是"贴在内容上的玻璃片"，模糊大了会把背景彻底抹平、看着像磨砂塑料。
   · 两个顶栏菜单（右上角菜单卡 / 左上角筛选卡）= 透底 --glass-menu + blur --blur-menu
     —— 单独一条规则，见下方「 两个顶栏菜单…」；比悬浮控件略实（要压住菜单文字）。
   · 其余弹层（下拉 / Toast / 各 sheet）= 透底 --glass-sheet + blur --blur-sheet：面积大、
     下面就是被虚化的遮罩内容，保持较实的底以免内容透上来。
    各控件自身的 background / backdrop-filter 一律删除，只留这里一处（改档只改 :root）。
   暗色模式由各组件 body.dark 规则覆盖背景色（模糊沿用本条）；触屏档见 @media (hover: none)。 */
.tab-bar, .tab-search, .nav-back, .nav-filter, .nav-share, .nav-menu, .nav-brand-home, .back-to-top {
  background: var(--glass-float);
  backdrop-filter: blur(var(--blur-float)) saturate(var(--sat-glass));
  -webkit-backdrop-filter: blur(var(--blur-float)) saturate(var(--sat-glass));
  /* 滚动时浏览器会为保帧率临时摘掉 backdrop-filter（表现为"糊→清晰→又糊"的闪）。
     显式声明 will-change 让合成器预先保留该过滤层，不中途摘除。
     注意：只能写 backdrop-filter，绝不能写 transform（会把后代采样链截断）。 */
  will-change: backdrop-filter;
}
.dropdown-menu, .toast,
.info-sheet, .dev-sheet {
  background: var(--glass-sheet);
  backdrop-filter: blur(var(--blur-sheet)) saturate(var(--sat-glass));
  -webkit-backdrop-filter: blur(var(--blur-sheet)) saturate(var(--sat-glass));
}
/*  两个顶栏菜单（右上角菜单卡 .menu-pop / 左上角筛选卡 .filter-pop）玻璃统一 —— 唯一权威规则 
   以前 menu-pop 混在「弹层 0.52 / blur22」组里、filter-pop 自己单写 30px/278%，两边各自漂移
   → 观感不一致。现统一到这一条，参数与「悬浮控件」（Tab 栏 / 圆钮）同族：
   · 透底 0.52 → 0.30：向 Tab 栏 / 圆钮（0.12）那种"通透玻璃"靠拢（弹层要压住菜单文字，
     故比圆钮略实一点；再往下调就压不住文字了）。暗色对应 0.66 → 0.40，见 body.dark 段。
   · 模糊 14px / 饱和度 200%：与悬浮控件同族，不再用 22px 那种"磨砂塑料"观感。
   · will-change: backdrop-filter：与悬浮控件同款 —— 滚动 / 切页时合成器预保留过滤层，
     避免"糊→清晰→又糊"的闪（这是上一轮给按钮做的同一项优化）。
    .filter-pop 常驻 transform: scale(0.72)。实测确认：backdrop 模糊是「空间滤镜」，
      会被缩放一起缩小（blur 29px 在 scale(0.72)/scale(0.5) 下等效 21px / 15px），
      故 filter-pop 的 blur 必须按 1/0.72 预补偿：14 → 19.4（触屏档 10 → 13.9）。
      saturate 是「色彩滤镜」不随缩放变化 → 两边一律 200%，绝不能再给 filter-pop 278%
      （那是旧 bug：筛选卡的背景被多饱和 39%，这正是两个菜单"看着不一样"的主因之一）。
      box-shadow 同为空间属性，filter-pop 的 19/53 补偿保留有效。 */
.menu-pop, .filter-pop {
  background: var(--glass-menu);
}
/* will-change 只挂在「展开态」：与悬浮控件同款 —— 重绘 / 滚动时合成器预保留过滤层，
   防"糊→清晰→又糊"的闪。 不能常驻在基础规则上：常驻 will-change 会让"平时隐藏"的浮层
   一直占着一个独立合成层，正好抵消上面「visibility:hidden 让隐藏浮层彻底退出渲染」的
   防切页闪烁优化（v0803 那条）。只在这两个浮层真正可见时才需要这层保险。 */
.menu-pop.show, .filter-pop.show {
  will-change: backdrop-filter;
}
.menu-pop {
  backdrop-filter: blur(var(--blur-menu)) saturate(var(--sat-glass));
  -webkit-backdrop-filter: blur(var(--blur-menu)) saturate(var(--sat-glass));
}
.filter-pop {
  backdrop-filter: blur(var(--blur-menu-scaled)) saturate(var(--sat-glass));
  -webkit-backdrop-filter: blur(var(--blur-menu-scaled)) saturate(var(--sat-glass));
}

/*  悬浮玻璃控件的「常驻独立合成层」—— 唯一权威规则 
   背景：这些元素都带 backdrop-filter（玻璃），拖动时 JS 每帧往 style.transform 写
   位移/拉伸（initPressSpring / initPanelSpring）。若那份 transform 里没有 3D 分量，
   元素的合成层会在拖动过程中被重建/降级；重建 = 重新捕获一次背后的画面
   → 玻璃后面的文字逐帧闪（图/图标在玻璃上、不在 backdrop 里，所以不闪，
   这正是用户报的"只有文字一闪一闪"）。同类先例早已存在：
   .tab-bar / .tab-search / .tab-item 就是靠常驻 transform: translateZ(0) 保住这一层
   （见 app.js STATIC_TARGETS 里 ".tab-search 丢了会在拖拽起止各闪一下" 的注释）。
   做法：只给一个 Z 向提升，不改任何二维几何 —— 用独立属性 `translate`，
   它会与元素自身的 transform（translateY(-50%) / scale / 入场动画）自动叠加，
   所以不必逐个去改各状态下的 transform 声明，也不会互相覆盖。
   （不支持独立 translate 属性的旧内核会直接忽略本行 → 退化为原状，无副作用。） */
.nav-back, .nav-filter, .nav-share, .nav-menu, .nav-brand-home, .back-to-top,
.menu-pop, .filter-pop, .peek-menu {
  translate: 0 0 1px;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
}

/* 菜单行对齐：图标区固定等宽，文字起点统一 */
.menu-ico { width: 22px; flex: none; display: grid; place-items: center; color: var(--ink-2); }
/* 筛选弹卡内部字号放大一档（仅右上角筛选弹卡，顶部筛选框不受影响） */
.filter-pop .seg { font-size: 14.5px; }
.filter-pop .dd-option { font-size: 15px; }
.filter-pop .dropdown-trigger { font-size: 15px; }

/*遮罩升级：毛玻璃 + 一点点灰
   背景模糊基础上加极淡的灰色（仅 10-15%），轻微压暗突出弹窗，不灰秃秃 */
.info-mask, .dev-mask {
  background: var(--mask-bg);
  backdrop-filter: blur(var(--blur-menu)) saturate(var(--sat-glass-mask));
  -webkit-backdrop-filter: blur(var(--blur-menu)) saturate(var(--sat-glass-mask));
}
body.dark .info-mask,
body.dark .dev-mask {
  background: var(--mask-bg-dark);
  backdrop-filter: blur(var(--blur-menu)) saturate(var(--sat-glass-mask));
  -webkit-backdrop-filter: blur(var(--blur-menu)) saturate(var(--sat-glass-mask));
}

/*浮层隐藏时完全退出渲染（根治切页闪烁）
   backdrop-filter 会强制元素成为独立合成层；隐藏的浮层若只 opacity:0 仍会参与
   合成层重建，页面切换动画期间就会闪。非显示态设 visibility:hidden（延迟到
   关闭动画结束再隐藏），显示态立即 visible——打开/关闭动画完全不变，
   但隐藏时彻底退出渲染，不再闪烁。 */
/* 两张顶栏卡片（右上菜单卡 / 左上筛选卡）的开合：时长与缓动取自 :root 的统一变量，
   与列表页卡片式下拉（.dropdown--card）同拍 —— 改 --pop-in-dur / --pop-out-dur 三处一起变。 */
.menu-pop, .filter-pop {
  visibility: hidden;
  transition: opacity 0.18s ease 0.03s, transform var(--pop-out-dur) var(--ease-pop-out),
              visibility 0s linear var(--pop-out-dur);
}
.menu-pop.show, .filter-pop.show {
  visibility: visible;
  transition: opacity 0.16s ease, transform var(--pop-in-dur) var(--ease-pop-in), visibility 0s;
}
.info-sheet, .dev-sheet {
  visibility: hidden;
  /* transform 由 sheetPopIn/sheetPopOut 关键帧接管（不再走 transition）；
     visibility 延迟到收起动画(--pop-out-dur)结束再隐藏，保证关闭动画完整可见 */
  transition: opacity 0.2s ease, visibility 0s linear var(--pop-out-dur);
}
.info-sheet.show, .dev-sheet.show {
  visibility: visible;
  transition: opacity 0.2s ease, visibility 0s;
}
.dropdown-menu {
  visibility: hidden;
  transition: opacity 0.22s var(--ease-spring), transform 0.22s var(--ease-spring), visibility 0s linear 0.22s;
}
.dropdown.open .dropdown-menu {
  visibility: visible;
  transition: opacity 0.22s var(--ease-spring), transform 0.22s var(--ease-spring), visibility 0s;
}

/*   卡片式下拉 .dropdown--card —— 列表页内联筛选的「学校类型 / 所在地区」专用
   ---------------------------------------------------------------------
   与筛选弹卡（左上角按钮弹出）里的同款下拉区分开，本档只作用于列表页内联面板：
     ① 触发区不要「框」：只留当前值 + 下拉箭头（箭头就是"这里可点"的提示），整组靠右；
     ② 菜单沿用右上角菜单卡（.menu-pop）同款的开闭动画，锚点取卡片自身的角；
     ③ 右对齐（菜单右缘贴触发区右缘），宽度自适应内容（width: max-content）；
     ④ 落位由 JS 决定：默认向下（菜单顶边直接对齐触发区顶边），
        底部放不下就翻成向上（.drop-up，底边对齐触发区底边、动画锚点翻到右下角）；
        两边都放不下且只能缩得很小时，改为先滚页把触发区挪到能完全展开的位置
        —— 见 app.js cardPlacement() / scrollThenOpenCard()。
     ⑤ 选项两列铺开（.dd-grid），「全部X」与「当前选中项」各独占一整行排在最前
        —— 见 app.js 的菜单构建函数。
    本段放在通用规则之后、并带 .dropdown--card 前缀（更高特异性），
      才能覆盖上面 `.dropdown.open .dropdown-menu` 的默认过渡与
      「浮层滚动条常驻」段的 overflow-y: scroll。改动时勿把前缀去掉。*/

/* ⓪ 列表页内联筛选（.filter-panel 里的「学校类型 / 所在地区」）的菜单贴页边。
   默认 .dropdown--card 的菜单右缘对齐触发区右缘，而触发区又缩在 .filter-panel 的
   水平内边距里 → 菜单右缘距页面内容边还差「面板内边距」这一截，看着"往里缩了一块"。
   这里把菜单右缘再往右顶出这一段，正好落在页面的内容边线上
   —— 与设置页 .settings-drop 的 right:-16px（抵消行内 16px 内边距）是同一套做法。
    要抵消的是面板自己的水平内边距（.filter-panel 的 18px），不是 .view 的边距；
      两者别再混。改 .filter-panel 的 padding 时这条要跟着改。
    只作用于列表页那张面板内的下拉；设置页 / 筛选弹卡内的下拉走各自规则，不受影响。
    特异性必须压过下面的 .dropdown--card .dropdown-menu（0,2,0），故带上 .filter-panel 前缀。 */
.filter-panel .dropdown--card .dropdown-menu { right: -18px; }

/* ① 触发区：把「框」去掉，只留值 + 箭头 */
.dropdown--card .dropdown-trigger {
  width: auto; max-width: 100%; min-width: 0;
  min-height: 44px;                    /* 与同排分段控件同高，行高不塌 */
  margin-left: auto;                   /* 整组靠右 */
  padding: 0 4px; gap: 6px;
  background: none; border: none; box-shadow: none;
  transition: transform 0.2s var(--ease-spring), color 0.2s ease;
}
/*  暗色必须单独再压一条：`body.dark .dropdown-trigger`（0,2,1）比上面那条（0,2,0）
   特异性高，暗色下会把 background:none 顶掉 → 触发区被填成 rgb(34,34,40) 实底、
   配着 20px 圆角看着就是个按钮（亮色没这条规则，所以只有暗色出现）。
   只压背景 —— border / box-shadow 上面已归零，不受影响；文字与箭头照旧，
   点击区域仍是整块（含文字），所以"点文字就能展开菜单"。 */
body.dark .dropdown--card .dropdown-trigger { background: none; }
.dropdown--card .dropdown-trigger:active { transform: scale(0.96); }
.dropdown--card .dd-value { margin-left: 0; }
.dropdown--card .dd-caret { color: var(--tint); }
/* 展开态反馈：当前值与箭头转品牌蓝（依然不画框） */
.dropdown--card.open .dd-value { color: var(--tint); }

/* ②③④ 菜单：右对齐自适应宽 + 不内部滚动 + 右上卡片动画 */
.dropdown--card .dropdown-menu {
  left: auto; right: 0;
  top: 0; bottom: auto;                /* 默认向下：菜单顶边直接对齐触发区顶边 */
  width: max-content;                  /* 宽度跟着内容走 */
  min-width: var(--dd-card-min-w);     /* 内容短时别缩成一条 —— 统一下限，见 :root 的 --dd-card-min-w */
  /* 横向只留页面左右各 14px 的边距（与顶部导航/卡片同口径），剩下全给菜单用 ——
     单列装不下时靠加列把高度压下去，所以宽度上限要放宽。 */
  max-width: calc(100vw - 28px);
  max-height: none;                    /* 不内部滚动（高度交给列数适配） */
  overflow: visible;                   /* 覆盖「浮层滚动条常驻」段的 overflow-y: scroll */
  overscroll-behavior: contain;
  /*  touch-action: none 是这套交互能成立的前提：
     没有它，手指按在菜单上拖动会被浏览器判定成"滚页面"→ pointercancel →
     整卡拖拽/行滑块瞬间被掐死（这就是上一版"做完全没效果"的根因）。 */
  touch-action: none;
  /* 毛玻璃：与右上角菜单卡（.menu-pop）同一档 —— 透底 --glass-menu + blur --blur-menu，
     两处观感必须一致（用户反馈本菜单"模糊度 / 通透度不对"）。
     旧值走的是弹层档（--glass-sheet 0.52 / blur 22）：底色太实、模糊太重，贴在白色筛选面板上
     像磨砂塑料，跟右上角菜单卡的透亮玻璃明显两个样。 */
  background: var(--glass-menu);
  backdrop-filter: blur(var(--blur-menu)) saturate(var(--sat-glass));
  -webkit-backdrop-filter: blur(var(--blur-menu)) saturate(var(--sat-glass));
  border: none;
  border-radius: 26px;                 /* 与右上角菜单卡同一圆角 */
  padding: 6px;                        /* 与右上角菜单卡同一内边距 */
  box-shadow: 0 14px 38px rgba(var(--shadow-rgb), 0.18);
  /* 层级必须高于列表页右侧字母引导条（.jump-rail z60）——
     菜单右缘和引导条有重叠区域，被盖住就是半个菜单不可见不可点 */
  z-index: 66;
  /* 锚点 = 卡片顶边靠右、再往左 --pop-anchor-x（与右上角菜单卡 .menu-pop 逐字同一套）。
      用户明确要求：别再动态量，就用锁死的那一套 ——「从哪边出来到哪边」写死。
        历史教训：动态量 rect 会被入场起始态 scale(0.16) 污染 + 各宿主 offsetParent 不一致，
        导致锚点"一会儿正常、一会儿从最左边/右边/中间出来"。
        现在与永不失效的 .menu-pop / .filter-pop 完全一致：calc(100% - 22px) 0%，纯 CSS 写死。 */
  transform-origin: calc(100% - var(--pop-anchor-x)) 0%;
  opacity: 0; visibility: hidden;
  transform: translate(0, 0) scale(0.16);
  transition: opacity 0.18s ease 0.03s, transform var(--pop-out-dur) var(--ease-pop-out),
              visibility 0s linear var(--pop-out-dur);
}
/* 暗色：`body.dark .dropdown-menu` 那条是不透明纯色底（rgb 28,28,32），会把毛玻璃整个吃掉 ——
   这里用更高特异性改回半透明深色底，且与右上角菜单卡同档（--glass-dark-menu），
   让 backdrop-filter 的磨砂感透出来，两处菜单亮/暗观感都对齐 */
body.dark .dropdown--card .dropdown-menu { background: var(--glass-dark-menu); }

/* 斜角打光描边：与右上角菜单卡（.menu-pop::after）同款同 token（--stroke-grey-card）——
   玻璃菜单没有描边就会糊进页面背景、边界读不出来。亮色 = 暗色那道白光的灰色版（同轴，保留光感）；
   暗色 = 白光对角渐变（深底上白光才成立）。几何与 .menu-pop::after 逐字一致。 */
.dropdown--card .dropdown-menu::after {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  padding: 1px;
  background: var(--stroke-grey-card);
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  pointer-events: none;
}
body.dark .dropdown--card .dropdown-menu::after {
  background: linear-gradient(to bottom left,
    rgba(255, 255, 255, 0.84) 0%, rgba(255, 255, 255, 0.52) 12%, rgba(255, 255, 255, 0.21) 30%,
    rgba(255, 255, 255, 0.07) 50%, rgba(255, 255, 255, 0.17) 78%, rgba(255, 255, 255, 0.34) 100%);
}
/* 液态通知悬浮投影：由 ov 自身 box-shadow 提供（严格贴 ov 形状，不再错位成"第二层"）；
   canvas 那圈投影已乘 vis 在停住时归零，避免桌面端宽胶囊左侧露馅。 */
#liquidToastOv {
  box-shadow: 0 8px 24px rgba(30, 50, 90, 0.16), 0 2px 6px rgba(30, 50, 90, 0.10);
}
body.dark #liquidToastOv {
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.40), 0 2px 6px rgba(0, 0, 0, 0.28);
}
/* 液态通知描边：与右上角菜单卡（.menu-pop::after）/ 卡片下拉（.dropdown--card ...::after）
   同款同 token（--stroke-grey-card），几何逐字一致。玻璃+毛玻璃没有这道斜角描边就会
   糊进页面背景、边界读不出来。亮色=暗灰斜角（同轴保留光感）/ 暗色=白光对角渐变（深底上白光才成立）。 */
#liquidToastOv::after {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  padding: 1px;
  background: var(--stroke-grey-card);
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  pointer-events: none;
}
body.dark #liquidToastOv::after {
  background: linear-gradient(to bottom left,
    rgba(255, 255, 255, 0.84) 0%, rgba(255, 255, 255, 0.52) 12%, rgba(255, 255, 255, 0.21) 30%,
    rgba(255, 255, 255, 0.07) 50%, rgba(255, 255, 255, 0.17) 78%, rgba(255, 255, 255, 0.34) 100%);
}
.dropdown--card.open .dropdown-menu {
  opacity: 1; visibility: visible; pointer-events: auto;
  transform: translateY(0) scale(1);
  animation: menuPopIn var(--pop-in-dur) linear;   /* 与右上角菜单卡完全同一段动画 */
  /*  transition 里不含 transform —— 与 .menu-pop.show 逐字对齐。
     历史这里多写了一条 `transform var(--pop-in-dur) ...`：于是 animation 播 menuPopIn 的同时，
     transition 也在对 transform 插值 → 两套动画同时管 transform 互相打架，
     观感就是"两套动画 / 乱七八糟"。入场位移/缩放全交给 menuPopIn 关键帧，transition 只管淡入与显隐。 */
  transition: opacity 0.16s ease, visibility 0s;
}
.dropdown--card:not(.open) .dropdown-menu {
  animation: menuPopOut var(--pop-out-dur) linear reverse both;   /* 同拍：沿小 U 反向缩回 */
}
/* 向上展开（底部放不下时由 JS 挂 .drop-up）：底边对齐触发区底边，动画锚点翻到右下角，
   同时把开合关键帧换成垂直镜像版 —— 原来那记"往下兜"的 U 变成往上兜的 N */
.dropdown--card.drop-up .dropdown-menu {
  top: auto; bottom: 0;
  /* 翻成向上展开：锚点仍是「触发按钮中心」，只把纵向从顶边翻到底边（同 .menu-pop 的 menuPopInUp 镜像） */
  transform-origin: calc(100% - var(--pop-anchor-x)) 100%;
}
.dropdown--card.drop-up.open .dropdown-menu { animation-name: menuPopInUp; }
.dropdown--card.drop-up:not(.open) .dropdown-menu { animation-name: menuPopOutUp; }

/* ⑤ 选项区：列数由 --dd-cols 控制（JS 按"能不能整张放下"逐列加，不设上限）。
     默认 1 列 = 普通竖排；地区菜单起始就是 2 列。 */
.dropdown--card .dd-grid {
  display: grid;
  grid-template-columns: repeat(var(--dd-cols, 1), minmax(0, 1fr));
  gap: 2px;
}
/* 选项圆角跟滑块对齐（全胶囊）：蓝色选中框才不会跟滑块"两层不同圆角" */
.dropdown--card .dd-option { border-radius: 999px; }
.dropdown--card .dd-option--full { margin-bottom: 2px; }
/* 「全部X」独占一行那颗的选中态：现在全站 .dd-option.on 都已改为只变色、无底色
   （见 2344 行注释），本规则保留当"保险"（万一将来 .on 又加了底色，这里优先清掉）。
   注释保留原用户诉求：独占一行置顶的"当前值"不要被压一层色块，只保留文字选中蓝。 */
.dropdown--card .dd-option--full.on,
body.dark .dropdown--card .dd-option--full.on { background: transparent; }








/*   圆形按钮 · demo 同款按压跟手弹（毛玻璃之上）
   ---------------------------------------------------------------------
   demo（LiquidButton.kt / InteractiveHighlight）按压语义：
   ① scale 1+4/48≈1.08 放大 ② 按钮跟随手指位移（tanh 饱和）+
   朝手指方向非等比拉伸 ③ 松手欠阻尼弹簧振荡回弹。
   CSS 的 :active/transition 做不了「跟手」，因此 transform 全部由
   app.js 的 initPressSpring（JS 物理弹簧 + rAF）驱动；
   这里只保留 ::before 径向高光（--px/--py 由 JS 写入）。
   tab-search 已并入 initPressSpring，与其余三钮同套动态。
   弹窗胶囊钮（.dev-tool / .dev-cancel / .dev-danger / .info-cancel）亦并入，
   规格取自 demo 的 buildButtons.ts：高 48 / 左右内边距 16 / 字号 15 /
   圆角 = 高/2 = 24 胶囊 + DEFAULT_SHADOW(0 4px 24px rgba(0,0,0,.1))
   + DEFAULT_HIGHLIGHT(顶部 1px 白，alpha .5)。只取形状与质感，不引 WebGL
   液态玻璃（移动端 backdrop 折射帧率太低）。*/
.nav-back::before,
.nav-filter::before,
.nav-share::before,
.nav-menu::before,
.tab-search::before,
.back-to-top::before {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  background: radial-gradient(circle 70px at var(--px, 50%) var(--py, 50%),
    rgba(var(--ink-rgb), 0.26), rgba(var(--ink-rgb), 0) 100%);
  opacity: 0; pointer-events: none;
  transition: opacity 0.18s ease;
}
.nav-back.pressing::before,
.nav-filter.pressing::before,
.nav-share.pressing::before,
.nav-menu.pressing::before,
.tab-search.pressing::before,
.back-to-top.pressing::before { opacity: 1; }
/* 暗色模式：顶部圆钮 / tab-search / 回到顶部 的高光翻白（深底上深灰光不可见） */
body.dark .nav-back::before,
body.dark .nav-filter::before,
body.dark .nav-share::before,
body.dark .nav-menu::before,
body.dark .tab-search::before,
body.dark .back-to-top::before {
  background: radial-gradient(circle 70px at var(--px, 50%) var(--py, 50%),
    var(--btn-glow-ghost-dark), rgba(255, 255, 255, 0) 100%);
}

/* 按压跟手弹（initPressSpring 驱动）：禁用浏览器手势接管，
   否则移动端按住拖动会被当作页面滚动（pointercancel）→ 跟手中断 */
.nav-back,
.nav-filter,
.nav-menu,
.tab-search,
#backToTop { touch-action: none; }

/*移动端分类卡片灰底滞留修复
   根因：触摸设备上 :hover 会「粘住」（触摸后保持到下次触摸），
   配合 .segmented 的 touch-action:none 浏览器不接管手势、更不会清除
   → 分类/状态/层次等选项拖动或长按后一直残留灰底。
   修复：@media (hover: none) 下禁用 hover 灰底，按压反馈改 :active
   （仅按下瞬间显示，松手即消失，不滞留）；同时去掉系统 tap 高亮
   与长按文本选中。 */
@media (hover: none) {
  .seg:not(.on):hover,
  .segmented .seg:not(.on):hover,
  body.dark .seg:not(.on):hover,
  body.dark .segmented .seg:not(.on):hover { background: transparent; }
  .seg:active { background: rgba(var(--ink-rgb), 0.12); }
  body.dark .seg:active { background: rgba(255, 255, 255, 0.16); }
}
.seg,
.segmented .seg {
  -webkit-tap-highlight-color: transparent;
  user-select: none; -webkit-user-select: none;
}

/*移动端毛玻璃优化（触屏设备）
   手机端 GPU 对 backdrop-filter 的采样率低：模糊半径越大降采样越狠，观感越糊，
   且滚动/动画时实时重采样易闪烁。触屏设备统一降低模糊半径与饱和度 →
   采样更充分、更细腻，闪烁减轻；PC 保持不变。
   不加 !important：优先保留「毛玻璃效果」里用户手动关闭的开关（body.no-* 规则）。 */
@media (hover: none) {
  /* 统一毛玻璃组（悬浮控件 + 弹层）：blur→6px、sat→160%（采样更足，滚动/动画不易闪） */
  .tab-bar, .tab-search, .nav-back, .nav-filter, .nav-share, .nav-menu, .back-to-top,
  .dropdown-menu, .toast,
  .info-sheet, .dev-sheet {
    backdrop-filter: blur(var(--blur-touch-float)) saturate(var(--sat-glass-touch));
    -webkit-backdrop-filter: blur(var(--blur-touch-float)) saturate(var(--sat-glass-touch));
  }
  /* 两个顶栏菜单独立档位：桌面 14px/200% 的触屏档 = 10px/160%（口诀：触屏一律降一档）。
     filter-pop 仍按 1/0.72 预补偿 blur（10→13.9）；saturate 不补偿（两边都是 160%）。
      旧值 menu-pop 6px vs filter-pop 18px(等效13px) 差了一倍 —— 触屏上两菜单明显不一致的主因。 */
  .menu-pop {
    backdrop-filter: blur(var(--blur-touch-menu)) saturate(var(--sat-glass-touch));
    -webkit-backdrop-filter: blur(var(--blur-touch-menu)) saturate(var(--sat-glass-touch));
  }
  .filter-pop {
    backdrop-filter: blur(var(--blur-touch-menu-scaled)) saturate(var(--sat-glass-touch));
    -webkit-backdrop-filter: blur(var(--blur-touch-menu-scaled)) saturate(var(--sat-glass-touch));
  }
  /* 列表页卡片式下拉（学校类型 / 所在地区）也与 .menu-pop 同档：触屏 10px / 160%
     （不加这条会落进上面的通用弹层组 6px，跟右上角菜单卡又差一档） */
  .dropdown--card .dropdown-menu {
    backdrop-filter: blur(var(--blur-touch-menu)) saturate(var(--sat-glass-touch));
    -webkit-backdrop-filter: blur(var(--blur-touch-menu)) saturate(var(--sat-glass-touch));
  }
  /*  不要再给这些元素（或其祖先）加常驻 will-change: transform ——
     带 transform/will-change 的祖先会成为新的「backdrop root」，其内部 backdrop-filter
     元素的采样范围被缩到该祖先自身（.nav-bar 是透明的）→ 玻璃采不到真实页面内容，
     只剩自身白底 = 白遮罩感；且每帧在 root 间跳变 = 闪烁。v0803 已整块移除。 */
}

/* 触屏设备：禁用 :hover 背景（触摸长按/拖动后 :hover 会粘住，松手回不到默认态）。
   触屏没有悬停概念，按压反馈交给 .pressing 高光与 :active，松手即恢复默认。 */
@media (hover: none) {
  .school-card:hover { background: #ffffff; }
  body.dark .school-card:hover { background: rgb(var(--surface-dark-rgb)); }
  .school-group-card:hover, .sg-item:hover { background: transparent; }
  /* 更多页入口行：长按后 :hover 粘住 → 松手恢复默认 */
  .more-row:hover { background: rgba(255, 255, 255, 0.66); }
  body.dark .more-row:hover { background: rgba(var(--surface-dark-rgb), 0.62); }
}

/* 卡片长按跟手高光辅助：禁文本选择/长按菜单（保证高光跟手不被系统打断） */
.school-card, .sg-item {
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}

/* 背光反射已按用户要求移除（相邻卡片不再发光） */

/*玻璃描边：亮色=灰色斜角光边 / 暗色=白色斜角光边
   两模式共用同一套「右上主光 → 左下副光」对角光边（token 定义见 :root 的 --stroke-grey*）：
   · 暗色：深底上白光才成立 → 白色对角渐变（.tab-bar 用更陡的 190deg）。
   · 亮色：白底上白对白不可见 → 同一轴换成灰色，保留同一套明暗起伏（= 光感），
     而不是一根死平灰线；峰值 0.38 ≈ 暗色 0.86 的观感等效。
   环的画法：圆形按钮走「单层径向 mask」，其余（胶囊/圆角矩形）走「content-box XOR mask」。
   描边一律只由 ::after 画，元素本体不再加原生 border（防 1px+1px 叠成又厚又深）。 */
.nav-back, .nav-filter, .nav-share, .nav-menu, .nav-brand-home, .tab-search, .back-to-top {
  box-shadow: 0 3px 10px rgba(var(--shadow-rgb), 0.18);   /* 单层简洁投影（范围缩小） */
}
body.dark .nav-back,
body.dark .nav-filter,
body.dark .nav-share,
body.dark .nav-menu,
body.dark .nav-brand-home,
body.dark .tab-search,
body.dark .back-to-top {
  box-shadow: 0 3px 10px rgba(0, 0, 0, 0.28);
}
.tab-bar {
  box-shadow: 0 6px 18px rgba(var(--shadow-rgb), 0.22);   /* 单层简洁投影（范围缩小） */
}
body.dark .tab-bar {
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.35);
}
/* 斜角打光描边：圆形按钮（返回 / 筛选 / 分享 / 更多 / 底部搜索钮 / 回到顶部）
    圆要素一律用「单层径向 mask」抠 1px 环，不要用 content-box XOR 双层 mask：
   后者在元素未进入独立合成层时，圆环左端切点会被抗锯齿抹平成一截直线（观感"不连贯"，
   且要拖一下（JS 写 inline transform 提升合成层）才恢复成圆）—— PC / 移动端 GPU 路径
   偶发，headless 软件光栅化不复现。径向单层 mask 没有 XOR 合成步骤，任何光栅化路径都稳定成环。
   亮色描边 = 暗色那道白光环的灰色版（同一个 to bottom left 轴），保留光感。 */
.nav-back::after,
.nav-filter::after,
.nav-share::after,
.nav-menu::after,
.tab-search::after,
.back-to-top::after {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  background: var(--stroke-grey);
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 1.2px), #000 calc(100% - 1px));
          mask: radial-gradient(farthest-side, transparent calc(100% - 1.2px), #000 calc(100% - 1px));
  pointer-events: none;
}
body.dark .nav-back::after,
body.dark .nav-filter::after,
body.dark .nav-share::after,
body.dark .nav-menu::after,
body.dark .tab-search::after,
body.dark .back-to-top::after {
  background: linear-gradient(to bottom left,
    rgba(255, 255, 255, 0.86) 0%, rgba(255, 255, 255, 0.54) 18%, rgba(255, 255, 255, 0.22) 40%,
    rgba(255, 255, 255, 0.07) 60%, rgba(255, 255, 255, 0.17) 76%, rgba(255, 255, 255, 0.34) 100%);
}
/* 搜索钮选中态 = 纯品牌蓝实心底（非玻璃）：不画斜角光边。
   蓝底上的灰环（亮色）/ 白环（暗色）都只会像多出的一圈描边；
   .tab-search.on 本就把 border-color 置 transparent，设计意图就是「实心无边框」，
   这条把 ::after 光环一并收掉，两模式一致。 */
.tab-search.on::after,
body.dark .tab-search.on::after { background: none; }
/* 斜角打光描边：底部 Tab 栏（横向宽扁胶囊，用更陡的 190deg）
   为什么不能用 to bottom left（45°）：宽扁元素上渐变投影会退化，四个角只落在
   0% / H÷(W+H) / W÷(W+H) / 100%，主光与副光被挤到「左右两端」，对角感消失、
   副光只剩左端一条竖边，怎么加浓都不像光。
   改 190deg 后：0% = 右上角（主光，右强左弱沿顶边衰减），100% = 左下角
   （副光沿底边左侧自然铺开）—— 仍是「右上主光 + 左下副光」，但在扁胶囊上看得见。 */
.tab-bar::after {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  padding: 1px;
  /* 亮色 = 暗色白光的灰色版（同 190deg 轴，保留光感） */
  background: var(--stroke-grey-flat);
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  pointer-events: none;
}
body.dark .tab-bar::after {
  background: linear-gradient(190deg,
    rgba(255, 255, 255, 0.84) 0%, rgba(255, 255, 255, 0.50) 14%, rgba(255, 255, 255, 0.19) 30%,
    rgba(255, 255, 255, 0.08) 50%, rgba(255, 255, 255, 0.14) 70%, rgba(255, 255, 255, 0.38) 100%);
}

/* 描边只留一层（v0809）：亮 / 暗都只由 ::after 的斜角光边画描边（亮色=灰色版 / 暗色=白色），
   一律不再叠原生 border —— border(1px) + ::after 光环(1px) 会叠成又厚又深的一圈。
   本版把亮色从「原生 border 灰线」改回「::after 灰色斜角光边」：原生 border 只能画一根
   死平灰线、画不出方向光感；圆钮改用径向单层 mask 后，::after 光环已无 AA 断环隐患。 */
.nav-back, .nav-filter, .nav-share, .nav-menu, .tab-search, .back-to-top, .tab-bar, .nav-brand-home {
  border: none;
}

/* 悬浮卡片事件隔离：卡片上触摸不启动页面滚动（touch-action:none 从手势源头禁止，
   无需 JS preventDefault，避免 Chrome Intervention 警告）；
   内部下拉菜单恢复 pan-y，保持自身可滚；卡片外页面滚动不受影响 */
.menu-pop, .filter-pop { touch-action: none; }
.menu-pop .dropdown-menu, .filter-pop .dropdown-menu { touch-action: pan-y; }

/*历史版本入口卡片 + 版本卡片：点击选中动效
   按下缩放 + 瞬时背景反馈（:active 驱动，松手自动恢复，无粘滞） */
.cl-history-entry,
.cl-ver-card {
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
  transition: transform 0.2s var(--ease-spring), background 0.18s;
}
.cl-history-entry:active,
.cl-ver-card:active {
  transform: scale(0.97);
  background: rgba(var(--ink-rgb), 0.05);
}
body.dark .cl-history-entry:active,
body.dark .cl-ver-card:active {
  background: rgba(255, 255, 255, 0.10);
}

/* 开源代码声明页：代码块样式 */
.legal-code {
  background: rgba(var(--ink-rgb), 0.06);
  border-radius: 14px;
  padding: 14px 16px;
  margin: 12px 0 0;
  overflow-x: auto;
  font-size: 12.5px;
  line-height: 1.7;
  font-family: 'SF Mono', 'JetBrains Mono', Consolas, Menlo, monospace;
  color: var(--ink);
  white-space: pre;
}
body.dark .legal-code {
  background: rgba(255, 255, 255, 0.07);
}

/* 法务页（含开源代码声明）正文链接样式 */
#legalBody a {
  color: var(--tint);
  text-decoration: underline;
  text-underline-offset: 2px;
  word-break: break-all;
}
body.dark #legalBody a { color: var(--tint-dark-icon); }

/*学校卡片 / 更多页一级菜单：统一按压（历史版本卡片同款）
   按下 scale(0.97) + 瞬时背景反馈，:active 驱动、松手自动恢复，无粘滞；亮暗双主题 */
.school-card, .more-row {
  transition: transform 0.2s var(--ease-spring), background 0.18s;
}
.school-card:active, .more-row:active {
  transform: scale(0.97);
  background: rgba(var(--ink-rgb), 0.05);
}
body.dark .school-card:active,
body.dark .more-row:active {
  background: rgba(255, 255, 255, 0.10);
}

/*长按卡片 Peek：卡片飞向中心 + 下方分享菜单（js initCardPeek）
   3D Touch peek 交互：长按学校卡片 → 克隆卡平移到屏幕中心、原卡隐藏占位、卡片下方弹
   分享菜单。视觉：ghost 阴影跟随原卡（--shadow-soft，避免归位阴影观感割裂）；
   菜单 = 上一版玻璃分享卡（头部学校图标 + 名称 + URL + 复制行/取消），弹在卡片下方。
   层级：mask 73 < ghost 75 < menu 78（高于一切浮层/弹窗） */
.peek-mask {
  position: fixed; inset: 0; z-index: 73;
  background: var(--mask-bg);
  backdrop-filter: blur(var(--blur-menu)) saturate(var(--sat-glass-mask));
  -webkit-backdrop-filter: blur(var(--blur-menu)) saturate(var(--sat-glass-mask));
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity 0.24s ease, visibility 0s linear 0.24s;
}
.peek-mask.show {
  opacity: 1; visibility: visible; pointer-events: auto;
  transition: opacity 0.24s ease;
  /* 浮层期间手势不带动页面滚动（与 info/verify 遮罩同款） */
  touch-action: none; -webkit-touch-action: none;
}
/* 收拢中（dismiss 后 ~560ms）：仅保留遮罩可见度让暗背景淡出，不再让遮罩继续吃点击。
   旧版同时开 pointer-events:auto，会在收拢的 ~580ms 内把整屏所有点击（含右半胶囊的菜单）全吃掉，
   用户看到「蓝色打开但点不动」= 卡死。document click swallow（app.js 里 guardUntil 期间）已
   能吞掉同一次手势的 click 穿透，无需再用遮罩的命中测试来堵点击。 */
.peek-mask.closing {
  visibility: visible; pointer-events: none;
  touch-action: none; -webkit-touch-action: none;
}
body.dark .peek-mask { background: rgba(0, 0, 0, 0.22); }

/* 分享浮层打开期间锁定页面滚动（js 加/去 body.peek-open，配上方遮罩 touch-action） */
body.peek-open { overflow: hidden; }

/* 原卡原位隐藏（占位不跳），由克隆 ghost 顶上 */
.school-card.peek-src,
.sg-item.peek-src { visibility: hidden; }

/* 飞向中心的克隆卡：尺寸由 JS 写入；阴影跟随原卡体系，飞行/归位不跳变。
   多账号组卡账号行摘出后「卡化」：补白卡底 + 边框 + 圆角 + 柔和阴影，
   但呈现的是行本身正常态（非按压/选中态），归位回普通行视觉一致。
   分享态下为「可拖拽的纯展示」：允许 pointer 按压/跟手拖拽（整卡弹簧 + 触点扫光，
   见下方 ::before 光照层），但任何 click 一律吞掉（见 app.js 全局兜底）——
   不跳详情；touch-action:none 防浮层滑动带动页面 */
.peek-ghost {
  position: fixed; z-index: 75; margin: 0;
  box-sizing: border-box;
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-soft);
  will-change: transform;
  cursor: default;
  pointer-events: auto;
  -webkit-user-select: none; user-select: none;
  -webkit-tap-highlight-color: transparent;   /* 拖拽按压不闪选中色 */
  touch-action: none; -webkit-touch-action: none;
}
/* 组卡账号行（多学校卡片内）摘出：卡化 = 正常态卡片外观（白底/边框/圆角），
   而非选中/按压底；行内容、字号、内边距保持与组卡内一致 */
.sg-item.peek-ghost {
  background: #ffffff;
  border: 1px solid var(--glass-border);
  border-radius: 28px;
  box-shadow: var(--shadow-soft);
}
body.dark .sg-item.peek-ghost { background: rgb(var(--surface-dark-rgb)); }

/* 摘出卡触点光效（3D-Touch 浮层内）：按住/拖动整卡时径向高光跟随手指
   （--px/--py 由 initPanelSpring 写入，.glow 按住即挂、脱手即消）。
   光斑形状 = 圆（与列表页/面板同款，跟手即圆），不做任何跟随卡片尺寸的自适应。
   这里只修一处范围 bug：原本渐变到 farthest-corner 的 58% 就截断归零 —— 圆角比
   直角边更远（对角线），光还没到卡片圆角就散尽了，于是"光的眼里这张卡只有中间
   约 2/3"，边缘/圆角不亮。停止点放到 100%（延伸到最远角），光效范围 = 卡片范围，
   四角全亮。z-index 2 叠于卡内容之上 */
.peek-ghost::before {
  content: ''; position: absolute; inset: 0; height: auto; border-radius: inherit;
  background: radial-gradient(circle at var(--px, 50%) var(--py, 50%),
    rgba(var(--ink-rgb), 0.20), rgba(var(--ink-rgb), 0) 100%);
  opacity: 0; pointer-events: none; z-index: 2;
  transition: opacity 0.18s ease;
}
.peek-ghost.glow::before { opacity: 1; }
body.dark .peek-ghost::before {
  background: radial-gradient(circle at var(--px, 50%) var(--py, 50%),
    rgba(255, 255, 255, 0.40), rgba(255, 255, 255, 0) 100%);
}

/* 摘出卡在 3D-Touch 浮层内只表现"跟手拖拽弹簧 + 触点光"：
   按住/悬停一律还原常态——不整体缩放、不灰底、圆点不收缩、hover 不加亮，
   不给"可点击/被选中"的按压暗示。声明晚于 .school-card/.sg-item 各 :hover/:active
   规则（898/945/1490/1552/2473/2477），同特异性靠后置覆盖；拖拽期 transform 由
   initPanelSpring 内联驱动（内联优先于样式表，不受此段影响） */
.peek-ghost:hover,
.peek-ghost:active { transform: none; }
.peek-ghost:hover,
.peek-ghost:active { background: #ffffff; }
body.dark .peek-ghost:hover,
body.dark .peek-ghost:active { background: rgb(var(--surface-dark-rgb)); }
.peek-ghost:active .status-orb { transform: translateY(-50%); }

/* 分享菜单顶部告知条：解释当前是"分享该学校"界面 + 如何退出（防误入者不知所云） */
.peek-note {
  display: flex; align-items: flex-start; gap: 8px;
  margin: 2px 4px 6px; padding: 10px 12px;
  border-radius: 14px;
  background: rgba(var(--ink-rgb), 0.055);
  font-size: 11.5px; line-height: 1.55; font-weight: 500;
  color: var(--ink-2);
}
.peek-note i { flex: none; font-size: 12px; color: var(--tint); margin-top: 1px; }
body.dark .peek-note { background: rgba(255, 255, 255, 0.06); color: rgba(255, 255, 255, 0.62); }
body.dark .peek-note i { color: var(--tint-dark-icon); }

/* 卡片下方分享菜单（上一版玻璃分享卡视觉：头部学校信息 + 复制行 + 取消）。
   苹果"下浮"入场：隐藏态略收在卡片底缘上方（-8px），显示时沿 top 原点向下
   沉降展开（scale 0.97→1 + 下坠 8px），收拢反向缩回卡片下方——与 .dropdown-menu
   同向下坠落位，不做上浮 */
.peek-menu {
  position: fixed; z-index: 78; padding: 8px;
  border-radius: 30px;
  background: var(--glass-card);
  backdrop-filter: blur(var(--blur-sheet)) saturate(var(--sat-glass));
  -webkit-backdrop-filter: blur(var(--blur-sheet)) saturate(var(--sat-glass));
  box-shadow: 0 18px 50px rgba(var(--shadow-rgb), 0.22);
  opacity: 0; visibility: hidden; pointer-events: none;
  transform-origin: top center;
  transform: translateY(-8px) scale(0.97);
  transition: opacity 0.2s ease, transform 0.3s var(--ease-spring), visibility 0s linear 0.3s;
  touch-action: none; -webkit-touch-action: none;
}
.peek-menu.show {
  opacity: 1; visibility: visible; pointer-events: auto;
  transform: translateY(0) scale(1);
  transition: opacity 0.16s ease, transform 0.36s var(--ease-spring), visibility 0s;
}
/* 斜角打光描边（menu-pop 同款）：右上主光，对角左下淡副光。
   亮色 = 暗色那道白光的灰色版（同轴，保留光感） */
.peek-menu::after {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  padding: 1px;
  background: var(--stroke-grey-card);
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  pointer-events: none;
}
body.dark .peek-menu::after {
  background: linear-gradient(to bottom left,
    rgba(255, 255, 255, 0.84) 0%, rgba(255, 255, 255, 0.52) 12%, rgba(255, 255, 255, 0.21) 30%,
    rgba(255, 255, 255, 0.07) 50%, rgba(255, 255, 255, 0.17) 78%, rgba(255, 255, 255, 0.34) 100%);
}
body.dark .peek-menu {
  background: rgba(var(--surface-dark-rgb), 0.74);
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.55);
}
/* 分享菜单整卡触点光效：按住菜单空白区（复制/取消按钮之外）整卡跟手拖拽时，
   径向高光跟随手指（同 ghost/menu-pop 质感；按钮自身按压光效见 .pill-btn） */
.peek-menu::before {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  background: radial-gradient(circle at var(--px, 50%) var(--py, 50%),
    rgba(var(--ink-rgb), 0.20), rgba(var(--ink-rgb), 0) 58%);
  opacity: 0; pointer-events: none; z-index: 2;
  transition: opacity 0.18s ease;
}
.peek-menu.glow::before { opacity: 1; }
body.dark .peek-menu::before {
  background: radial-gradient(circle at var(--px, 50%) var(--py, 50%),
    rgba(255, 255, 255, 0.40), rgba(255, 255, 255, 0) 58%);
}
.peek-head {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 12px 12px;
}
.peek-ico {
  width: 42px; height: 42px; border-radius: 14px; flex: none;
  display: grid; place-items: center;
  color: #fff; font-size: 18px;
  background: linear-gradient(135deg, var(--tint), #54b0ff);
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.25), 0 4px 10px rgba(var(--tint-rgb), 0.26);
}
.peek-titles { min-width: 0; flex: 1; }
.peek-name {
  font-size: 16px; font-weight: 700;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.peek-sub {
  margin-top: 2px; font-size: 11.5px; color: var(--ink-3);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  direction: ltr; text-align: left;
}
body.dark .peek-sub { color: rgba(255, 255, 255, 0.50); }
/* 分享操作按钮 = 页面通用胶囊按钮（.pill-btn）：复制 = 蓝渐变实底（tinted）；取消 = 浅玻璃灰底。
   左右并排（flex row），与开发者模式/信息弹层等双钮布局一致。
   按压光感（径向扫光 + 跟手位移/拖拽拉伸/弹簧回弹）由 initPressSpring 统一接管
   （惰性绑定 .peek-copy/.peek-cancel，见 app.js），此处只铺版式 */
.peek-actions {
  display: flex; gap: 10px;
  padding: 2px 2px 4px;
}
.peek-actions .peek-btn {
  flex: 1; min-width: 0; justify-content: center;
  margin: 0; padding: 13px 12px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* ---- 详情页内：两枚按钮 = 各自独立的圆钮（取消合成胶囊）----
   两钮各归各位（分享 right:64 / 更多 right:14，天然 6px 间隙），
   材质 / 圆角 50% / 斜角光边 / 毛玻璃 / 阴影全部沿用 .nav-share / .nav-menu 自身样式，
   按压 / 拖拽 / 点击由各自独立的 initPressSpring 实例负责（与列表页圆钮完全一致）。
   不再有「透明半片」「拼缝圆角」「统一描边」「镜像 transform」等胶囊专用覆写。 */
body.view-detail .nav-share,
body.view-detail .nav-menu {
  z-index: 2;                            /* 高于顶栏其它内容，低于浮层 */
  pointer-events: auto;                  /* 恢复各自可点（胶囊版曾让位成纯图标层 pointer-events:none） */
}

/* 分享钮「打开」态：与右钮 .nav-menu.open 完全同款 —— 图标变品牌蓝 + 底色淡蓝 + 图标轻微放大。
   （胶囊废弃后两钮是各自独立圆钮，蓝高亮直接落在分享钮自身，不再依赖胶囊衬底渐变） */
.nav-share.open { color: var(--brand, var(--tint)); background: rgba(var(--tint-rgb), 0.12); }
.nav-share.open i { transform: scale(1.08); }
body.dark .nav-share.open { color: var(--tint-dark); background: rgba(var(--tint-dark-rgb), 0.18); }



/* ---- 分享跳转页（#/add/<token>）：一次性时效链接落地页，见 app.js renderAdd ---- */
.add-page {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  text-align: center; gap: 14px;
  min-height: 54vh; padding: 48px 24px;
}
/* 图标只留本身：不要外面的圆角方形底板 / 底色 / 投影（用户明确要求） */
.add-page .add-icon {
  display: flex; align-items: center; justify-content: center;
  font-size: 44px; line-height: 1; color: var(--brand, var(--tint));
  background: none; box-shadow: none; border-radius: 0;
}
.add-page .add-title { font-size: 19px; font-weight: 700; color: var(--ink); }
.add-page .add-desc { font-size: 14px; line-height: 1.6; color: var(--ink-2); max-width: 300px; }
