/* ============ 主题变量 ============ */
/* 动效时长令牌：全站统一三档。此前各处 0.1~0.9s 各写各的，同类交互在不同组件上
   快慢不一（同样是悬停，有的 0.15s 有的 0.2s），整体观感很「散」；
   缓动多数沿用默认 ease，只在展开、脉冲等有明确语义的地方单独指定 */
:root {
  --dur-fast: 0.15s; /* 悬停、按压等即时反馈 */
  --dur-base: 0.25s; /* 淡入、展开等结构性变化 */
  --dur-slow: 0.45s; /* 进度条等大幅变化 */
  --ease: cubic-bezier(0.4, 0, 0.2, 1); /* 标准缓动：展开、位移等需要「控制感」的场景 */
}
:root[data-theme='dark'] {
  /* color-scheme：让原生控件（select 下拉面板/滚动条/自动填充底色）跟随暗色渲染，
     否则暗色 UI 里弹出的下拉菜单仍是亮色白底，割裂感明显 */
  color-scheme: dark;
  --bg: #14101a;
  --bg-grad: radial-gradient(ellipse at top, #1d1526 0%, #141019 60%);
  --card: #191422;
  --card-hover: #211a2b;
  --border: #2a2233;
  --border-light: #3a3044;
  --text: #f0e9e2;
  /* 次要文字提亮到 AA 级：壁纸上仍需足够对比才不发虚 */
  --text-dim: #bdb0a2;
  --text-dimmer: #93877b;
  --accent: #f97316;
  --accent-light: #fb923c;
  --accent-bg: rgba(249, 115, 22, 0.12);
  --accent-glow: 0 0 20px rgba(249, 115, 22, 0.18);
  /* 小字号橙色文字专用：暗色取更亮的 accent-light，亮色须用深烧橙才够 AA 对比度 */
  --accent-text: #fb923c;
  --success: #34d399;
  /* 绿色小字专用（同 --accent-text 思路）：暗色亮翡翠在深玻璃底对比已够，用本色 */
  --success-text: #34d399;
  --warn: #fbbf24;
  --error: #f87171;
  --shadow: 0 4px 24px rgba(0, 0, 0, 0.3);
  /* 玻璃投影（与亮色同一组变量，两套主题各自给值）。暗色下刻意贴近改版前的
     0.1 / 0.14——深底上投影本来就几乎不可见，加太重只会把玻璃面压成一团黑 */
  --glass-shadow: 0 8px 32px rgba(0, 0, 0, 0.14);
  --glass-shadow-hover: 0 14px 40px rgba(0, 0, 0, 0.2);
  /* Liquid Glass 玻璃变量组：暗色下玻璃更暗更收敛。
     底色不透明度（用户要求更通透）：0.82→0.68，壁纸透出 32%——文字对比度由
     blur(28px)+saturate(180%) 的 backdrop-filter 兜底：壁纸被强模糊后亮度
     趋于均匀，正文对比仍稳在 AA 级（正文 #f0e9e2 深玻璃底 ≈ 8:1） */
  --glass-bg: rgba(30, 24, 40, 0.55);
  /* 玻璃描边统一调实：原白 0.14 压在深玻璃 + 模糊壁纸上对比不足，
     圆角抗锯齿一叠加就「发虚」——顶栏/侧栏/hero/卡片统一用这一档 */
  --glass-border: rgba(255, 255, 255, 0.24);
  /* hover / 强调态：比默认再亮一档，边缘更「实」 */
  --glass-border-strong: rgba(255, 255, 255, 0.4);
  --glass-hi: rgba(255, 255, 255, 0.22);
  --glass-lo: rgba(255, 255, 255, 0.05);
  --border-glass: rgba(255, 255, 255, 0.08);
  /* 壁纸遮罩：暗色主题压暗（见下方 .wallpaper-bg::after） */
  --wallpaper-scrim: rgba(12, 9, 16, 0.42);
}
:root[data-theme='light'] {
  color-scheme: light;
  /* 底色与背景：浅蓝紫底 + 几颗**大球体**（对齐效果图局部截图）。
     效果图里不是「一团团亮斑」，而是几颗很大的柔和球体，最可辨的特征是
     **球面那道弧形亮边**。所以单靠 `0% → 透明` 的实心光斑做不出来，必须给
     球面描一圈亮边，五段式才能同时表达「球内雾感」和「球面弧线」：
       0%   中心打底（球内略亮）
       72%  球内一路平缓，制造雾感
       88%  亮度抬起来 —— 弧线开始出现
       95%  弧线最亮处
       99%  快速收边（不能直接归零，否则是一道硬边而不是柔和的球面）
     透明度刻意压在 0.4 上下：效果图那张局部截图整片是**低对比的雾**，
     球面弧线只是隐约浮出来。第一版按 0.66 做，几颗球叠在一起又亮又乱，
     完全不是那个味道。
     半径取 620~700px、只放 3 颗：太多太小会退化成散景光点，也撑不出球面曲率。
     由 body 的 background-attachment: fixed 钉在视口上，滚动时氛围不跟着跑 */
  --bg: #e4ebf8;
  --bg-grad:
    radial-gradient(
      circle 700px at 6% 8%,
      rgba(255, 255, 255, 0.26) 0%,
      rgba(255, 255, 255, 0.16) 72%,
      rgba(255, 255, 255, 0.38) 88%,
      rgba(255, 255, 255, 0.42) 95%,
      rgba(255, 255, 255, 0.12) 99%,
      rgba(255, 255, 255, 0) 100%
    ),
    radial-gradient(
      circle 620px at 104% 34%,
      rgba(240, 246, 255, 0.26) 0%,
      rgba(240, 246, 255, 0.15) 72%,
      rgba(250, 252, 255, 0.38) 88%,
      rgba(255, 255, 255, 0.42) 95%,
      rgba(255, 255, 255, 0.12) 99%,
      rgba(255, 255, 255, 0) 100%
    ),
    radial-gradient(
      circle 680px at 20% 104%,
      rgba(255, 252, 248, 0.24) 0%,
      rgba(255, 252, 248, 0.14) 72%,
      rgba(255, 254, 251, 0.36) 88%,
      rgba(255, 255, 255, 0.4) 95%,
      rgba(255, 255, 255, 0.1) 99%,
      rgba(255, 255, 255, 0) 100%
    ),
    linear-gradient(180deg, #e8eefb 0%, #dee6f6 52%, #e4ebf9 100%);
  /* 卡片底改半透明白：底色能透出球体，配合 backdrop-filter 才成立「毛玻璃」。
     原来是不透明纯白，球体再好看也穿不过去，卡片只会是一块死白。
     0.52 是「透」与「可读」的折中：再往上球面弧线就被压没了，
     再往下正文对比度开始吃紧（底色 #e0e7f6 上正文仍有 ~13:1，稳在 AAA） */
  --card: rgba(255, 255, 255, 0.52);
  --card-hover: rgba(255, 255, 255, 0.8);
  /* 描边走低透明度冷灰蓝：亮色玻璃是白底，纯白描边在白卡上完全看不见、边缘直接
     「虚」掉；冷灰蓝既立得住，也和蓝紫底同色系 */
  --border: rgba(146, 163, 199, 0.22);
  --border-light: rgba(146, 163, 199, 0.34);
  /* 正文与次要文字转冷调石板色（原来是暖棕，压在蓝紫底上发脏）。
     三档都按 AA 校准：正文 ≈13:1、次要 ≈5.4:1、更次要 ≈4.6:1（白卡底） */
  --text: #1c2438;
  --text-dim: #525d7a;
  --text-dimmer: #6a7591;
  --accent: #f97316;
  --accent-light: #ea580c;
  --accent-bg: rgba(249, 115, 22, 0.1);
  --accent-glow: 0 0 20px rgba(249, 115, 22, 0.12);
  /* 亮色下橙 500 在白底只有 ~2.8:1，小字一律用烧橙 700（≈5:1） */
  --accent-text: #c2410c;
  --success: #059669;
  /* 绿色小字专用（同 --accent-text 思路）：翡翠 600 在蓝调壁纸/玻璃底仅 ~3.4:1，
     小字换翡翠 700 才稳住 AA 对比度 */
  --success-text: #047857;
  --warn: #d97706;
  --error: #dc2626;
  /* 阴影转冷：原来偏玫瑰灰，落在蓝紫底上显脏；冷灰蓝 + 更大扩散更接近效果图的柔和悬浮感 */
  --shadow: 0 10px 30px rgba(74, 96, 150, 0.1);
  /* Liquid Glass 玻璃变量组：亮色玻璃定在 0.46。这版刻意比上一版更透——
     效果图局部截图里卡片是能看见背景球面弧线穿过去的，0.58 那档太厚、
     球体透上来只剩一层灰雾。0.46 配上 28px 强模糊，既有通透感又不会让正文发虚 */
  --glass-bg: rgba(255, 255, 255, 0.46);
  /* 玻璃描边给白：卡面是半透明白叠在蓝紫底上、实际呈冷白，纯白描边比它更亮，
     于是边缘形成一圈「玻璃口沿」的高光，正是效果图里那种通透感 */
  --glass-border: rgba(255, 255, 255, 0.7);
  /* hover / 强调态：描边再提亮一档，边缘像被光扫了一下 */
  --glass-border-strong: rgba(255, 255, 255, 0.95);
  --glass-hi: rgba(255, 255, 255, 0.9);
  --glass-lo: rgba(255, 255, 255, 0.24);
  --border-glass: rgba(255, 255, 255, 0.5);
  /* 玻璃投影：原来在 .card 里写死 rgba(0,0,0,...)，亮色下是一层灰雾、暗色下偏死黑。
     抽成变量后两套主题各给各的冷调/深调扩散 */
  --glass-shadow: 0 12px 34px rgba(74, 96, 150, 0.11);
  --glass-shadow-hover: 0 18px 44px rgba(74, 96, 150, 0.17);
  /* 壁纸遮罩：亮色主题提亮（同一层遮罩，方向相反、目的一致） */
  --wallpaper-scrim: rgba(255, 253, 250, 0.5);
}
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
html {
  scroll-behavior: smooth;
}
/* 减少动态偏好：平滑滚动本身也是一种动画，一并退回瞬时跳转
   （JS 里显式传 behavior 的地方用同一个判断，见 app.js 的 SMOOTH） */
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
}
/* 降低透明度偏好：支持系统「减弱透明度」无障碍选项，关闭高耗能毛玻璃模糊并强化底色对比 */
@media (prefers-reduced-transparency: reduce) {
  .card,
  .hero,
  .sidebar,
  .topbar,
  .rail-weather,
  .rail-hot-topics,
  .cat-title,
  .cal-modal-card {
    backdrop-filter: none !important;
    -webkit-backdrop-filter: none !important;
  }
  :root[data-theme='dark'] .card,
  :root[data-theme='dark'] .sidebar,
  :root[data-theme='dark'] .topbar,
  :root[data-theme='dark'] .cat-title {
    background: #191422 !important;
  }
  :root[data-theme='light'] .card,
  :root[data-theme='light'] .sidebar,
  :root[data-theme='light'] .topbar,
  :root[data-theme='light'] .cat-title {
    background: #ffffff !important;
  }
}
/* 统一 emoji 图标（见 emoji.js）：系统 emoji 字体在各平台字形/基线差异过大，
   统一换成本地 SVG 图标后跨设备一致。尺寸跟随字号（1em），基线下移一点与中文对齐 */
.emo {
  width: 1em;
  height: 1em;
  display: inline-block;
  vertical-align: -0.125em;
  object-fit: contain;
  flex-shrink: 0;
  -webkit-user-drag: none;
  user-select: none;
}
body {
  /* 字体栈里为什么要在 PingFang 与雅黑之间插一个 "Noto Sans SC"：
     效果图是 macOS 上截的，中文走 PingFang SC；Windows 上 PingFang 缺失会落到微软雅黑，
     而雅黑的字面明显更重（同一段「每日热榜」23px：雅黑 Bold 墨量 4460、竖笔中位 7.0，
     PingFang Semibold 是 3909 / 6.0）。Noto Sans SC 是 PingFang 最接近的开源替身，
     实测同口径 3736 / 6.0，几乎逐项吻合，所以插在雅黑前面。
     拉丁字符仍由 Segoe UI 负责（Segoe 无 CJK 字形，中文会自动往后落）。 */
  font-family:
    -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Noto Sans SC', 'Source Han Sans SC',
    'Microsoft YaHei', sans-serif;
  background: var(--bg);
  background-image: var(--bg-grad);
  background-attachment: fixed;
  color: var(--text);
  line-height: 1.5;
  /* 不设 -webkit-font-smoothing: antialiased：它会强制灰度抗锯齿、关闭次像素渲染，
     项目大量 11-12px 小字发虚。恢复系统原生渲染（默认 auto）后小字显著更实 */
  transition:
    background var(--dur-base),
    color var(--dur-base);
  min-height: 100vh;
}
a {
  color: var(--accent-text);
  text-decoration: none;
}
a:hover {
  text-decoration: underline;
}

/* ============ 键盘焦点可见性 ============ */
/* 全局 :focus-visible 兜底：项目大量输入框/按钮 outline:none，Tab 走一圈看不到焦点。
   只在键盘导航（focus-visible）时显示橙色描边，鼠标点击不触发，不影响鼠标用户 */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 4px;
}
/* 输入框已有 focus 态的边框高亮，描边贴边即可 */
input:focus-visible,
textarea:focus-visible,
select:focus-visible {
  outline-offset: 0;
}

/* ============ 必应每日壁纸背景 ============ */
/* fixed 全屏层：iOS Safari 不支持 background-attachment: fixed，
   用 fixed 定位层实现等效效果，且不受页面滚动重绘影响 */
.wallpaper-bg {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
  opacity: 0;
  transition: opacity 0.8s ease;
}
.wallpaper-bg.show {
  opacity: 1;
}
/* 壁纸关闭态（顶栏壁纸开关）：透明度归零而非 display:none——
   复用 .wallpaper-bg 自带的 0.8s 过渡，开关都是柔和淡入淡出，不会生硬跳变；
   特异性高于 .wallpaper-bg.show，无需 !important 也能压住「已显示」态。
   开关状态由 html.wallpaper-off 驱动，首屏由 index.html 内联脚本提前打上，避免壁纸闪一下再消失 */
:root.wallpaper-off .wallpaper-bg {
  opacity: 0;
}
/* 壁纸开关图标两态：关闭后换成带斜线的图标并压暗，一眼看出壁纸已关；
   只降透明度不改 color，hover 的 accent 高亮仍照常生效 */
.wallpaper-toggle .wp-off {
  display: none;
}
:root.wallpaper-off .wallpaper-toggle .wp-on {
  display: none;
}
:root.wallpaper-off .wallpaper-toggle .wp-off {
  display: block;
}
:root.wallpaper-off .wallpaper-toggle svg {
  opacity: 0.55;
}
:root.wallpaper-off .wallpaper-toggle:hover svg {
  opacity: 1;
}

/* ============ 氛围背景：大块流动光（壁纸关闭时） ============ */
/* 壁纸关闭是本项目的默认态，此前的背景只有 body 的 --bg-grad：一版静态渐变，
   全屏铺开非常「平」——底色是有的，但既没有景深，也没有任何时间感，
   在 2K 宽屏上尤其像一块没画完的底色板。
   这里补一层独立的氛围层：四颗大尺寸柔光斑在视口里缓慢漂移 + 缩放，
   背景因此始终在动，玻璃卡（backdrop-filter）后面透出来的光也跟着缓慢变化。

   与壁纸层的关系：壁纸开启时整层淡出。用 opacity 过渡而不是 display:none，
   和壁纸层同一套 0.8s 节奏，开了壁纸是「流动光让位」，不是硬切；
   `.aura` 在 DOM 里排在 .wallpaper-bg 之前，两层同 z-index，壁纸天然压在上面。

   性能是这块的主要约束，三条都踩过：
     ① 只动 transform / opacity —— 合成层动画，不触发重排重绘，
        动画中的元素加 will-change: transform 提前升层（四颗而已，不会吃爆显存）；
     ② 光斑用 radial-gradient 的 alpha 收边，**不用 filter: blur()** ——
        在 inset:0 的全屏元素上挂几十 px 的模糊，低端设备与 Safari 上会稳定掉帧；
     ③ 尺寸用 vmax 而不是固定 px：手机上光斑不会被压成一条缝，
        大屏上又跟着放大，「大块」这个观感在任何视口都成立。 */
.aura {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
  opacity: 1;
  transition: opacity 0.8s ease;
}
/* 壁纸开启 → 整层淡出（特异性高于 .aura，无需 !important） */
:root:not(.wallpaper-off) .aura {
  opacity: 0;
}
/* 页面切到后台（标签页离开/最小化）立即挂起动画，GPU/CPU 开销彻底归零 */
:root.page-hidden .aura-b {
  animation-play-state: paused !important;
}

.aura-b {
  position: absolute;
  display: block;
  /* 尺寸封顶：vmax 保证「大屏更大、窄屏不塌成一条」，但 4K 上 68vmax 会到 2600px，
     四张这么大的合成层纹理要吃掉上百 MB 显存（动画层是位图缓存），所以再压一个
     min() 上限——1200px 的光斑在 2560/3840 宽屏上依然是「一大块」，观感不变 */
  width: min(68vmax, 1200px);
  height: min(68vmax, 1200px);
  border-radius: 50%;
  /* 动画参数由各光斑自己给：--dx/--dy 是漂移幅度（百分比相对光斑自身尺寸，
     所以大屏小屏的「相对走位」一致），--s1/--s2 是中途的两个缩放档 */
  animation: auraDrift var(--dur, 42s) ease-in-out infinite;
  animation-delay: var(--delay, 0s);
  will-change: transform;
}
/* 漂移轨迹：出发 → 右上放大 → 左下略收 → 回到出发。
   三个中间点而不是「左上到右下」的直线往返，是为了让四颗错开相位后
   永远处于不同的运动方向，整片背景才像在「流」而不是整体平移 */
@keyframes auraDrift {
  0% {
    transform: translate3d(0, 0, 0) scale(1);
  }
  30% {
    transform: translate3d(var(--dx, 14%), var(--dy, -10%), 0) scale(var(--s1, 1.16));
  }
  62% {
    transform: translate3d(calc(var(--dx, 14%) * -0.7), calc(var(--dy, -10%) * -0.6), 0) scale(var(--s2, 0.94));
  }
  100% {
    transform: translate3d(0, 0, 0) scale(1);
  }
}

/* ---- 亮色主题：冷蓝紫底上的四颗柔光 ----
   亮色底是 #e4ebf8（偏冷的蓝紫），光斑用「更亮更彩」来表达光：
   白亮斑负责高光感、蓝紫/粉/暖橙负责色相。
   透明度刻意给到 0.5 上下 —— 这一版的目标就是「明显」：
   太淡等于没做（旧版静态球体只有 0.4 且半径 620~700px，全屏看基本读不出来），
   再浓正文就该被背景抢了。正文与玻璃卡都在 z-index:1 之上，
   且卡片自身还有 backdrop-filter 与半透明白底兜着，可读性不受影响。 */
.aura-b1 {
  top: -24%;
  left: -18%;
  background: radial-gradient(
    circle,
    rgba(146, 126, 255, 0.52) 0%,
    rgba(146, 126, 255, 0.3) 42%,
    rgba(146, 126, 255, 0) 72%
  );
  --dx: 16%;
  --dy: -12%;
  --s1: 1.18;
  --s2: 0.92;
  --dur: 46s;
  --delay: -6s;
}
.aura-b2 {
  top: -20%;
  right: -22%;
  background: radial-gradient(
    circle,
    rgba(255, 138, 186, 0.46) 0%,
    rgba(255, 138, 186, 0.26) 44%,
    rgba(255, 138, 186, 0) 74%
  );
  --dx: -14%;
  --dy: 14%;
  --s1: 1.12;
  --s2: 0.9;
  --dur: 54s;
  --delay: -22s;
}
.aura-b3 {
  bottom: -30%;
  left: 8%;
  background: radial-gradient(
    circle,
    rgba(255, 176, 112, 0.42) 0%,
    rgba(255, 176, 112, 0.22) 44%,
    rgba(255, 176, 112, 0) 74%
  );
  --dx: 12%;
  --dy: -16%;
  --s1: 1.14;
  --s2: 0.95;
  --dur: 50s;
  --delay: -34s;
}
/* 白亮斑：压在偏中上、尺寸略小，模拟一团「过曝的白光」，
   是四颗里唯一不承载色相的，负责把另外三颗的颜色托起来 */
.aura-b4 {
  top: 12%;
  left: 22%;
  width: min(52vmax, 900px);
  height: min(52vmax, 900px);
  background: radial-gradient(
    circle,
    rgba(255, 255, 255, 0.72) 0%,
    rgba(255, 255, 255, 0.4) 40%,
    rgba(255, 255, 255, 0) 70%
  );
  --dx: -10%;
  --dy: 10%;
  --s1: 1.22;
  --s2: 0.88;
  --dur: 38s;
  --delay: -14s;
}

/* ---- 暗色主题：同一套位置，换成「深底上的发光」 ----
   暗底 #14101a 上不能再加白，否则整片背景会发灰；
   四颗改用高饱和的紫罗兰 / 品红 / 橙 / 青，靠色相而不是明度做「光」。
   透明度比亮色略低，但暗底对颜色的对比本来就强，实际观感同样明显。 */
:root[data-theme='dark'] .aura-b1 {
  background: radial-gradient(
    circle,
    rgba(124, 92, 255, 0.5) 0%,
    rgba(124, 92, 255, 0.26) 44%,
    rgba(124, 92, 255, 0) 74%
  );
}
:root[data-theme='dark'] .aura-b2 {
  background: radial-gradient(
    circle,
    rgba(219, 76, 155, 0.4) 0%,
    rgba(219, 76, 155, 0.2) 44%,
    rgba(219, 76, 155, 0) 74%
  );
}
:root[data-theme='dark'] .aura-b3 {
  background: radial-gradient(
    circle,
    rgba(249, 115, 22, 0.36) 0%,
    rgba(249, 115, 22, 0.18) 44%,
    rgba(249, 115, 22, 0) 74%
  );
}
:root[data-theme='dark'] .aura-b4 {
  background: radial-gradient(
    circle,
    rgba(56, 189, 248, 0.28) 0%,
    rgba(56, 189, 248, 0.14) 44%,
    rgba(56, 189, 248, 0) 74%
  );
}

/* 减弱动态偏好：光斑停在出发点，但保留这层背景（它是「背景」，不是动画特效）；
   同时去掉 will-change，不让四颗全屏元素长期占着合成层 */
@media (prefers-reduced-motion: reduce) {
  .aura-b {
    animation: none;
    will-change: auto;
  }
}

/* ---- 壁纸下的可读性兜底 ----
   壁纸是每日必应图，亮度完全不可控（可能是雪原，也可能是夜景）。
   玻璃底太透时，正文会随当日壁纸明暗漂移；浅色壁纸 + 亮色主题尤其吃亏，
   底一白，正文和次要文字就一起发灰。这里上两道保险：
   ① 壁纸层再压一层遮罩，把亮度收进一个稳定区间（暗色压暗、亮色提亮）；
   ② 玻璃底只在「壁纸开启」时加厚一档——关闭壁纸（默认）仍保持原来的通透观感，
      只有真压上壁纸了才牺牲一点透明度换对比度。 */
.wallpaper-bg::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: var(--wallpaper-scrim);
}
:root[data-theme='dark']:not(.wallpaper-off) {
  --glass-bg: rgba(26, 20, 34, 0.78);
  --glass-border: rgba(255, 255, 255, 0.3);
  --glass-hi: rgba(255, 255, 255, 0.16);
}
:root[data-theme='light']:not(.wallpaper-off) {
  --glass-bg: rgba(255, 255, 255, 0.78);
  /* 描边同步转冷灰蓝（原为暖棕 rgba(88,62,74,...)）：壁纸开启态下玻璃底更厚、
     白描边依旧看不见，仍需要一个比卡面深的冷调描边才立得住 */
  --glass-border: rgba(120, 138, 178, 0.3);
}
/* 壁纸上的「裸文字」（分类标题条这种没有玻璃底的）：补一层极淡的
   方向性阴影把字和背景拉开。幅度刻意做小——只在边缘起作用，不形成描边感 */
:root:not(.wallpaper-off) .cat-title {
  text-shadow: 0 1px 2px rgba(255, 255, 255, 0.6);
}
:root[data-theme='dark']:not(.wallpaper-off) .cat-title {
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.55);
}

.topbar,
.layout,
.main {
  position: relative;
  z-index: 1;
}

/* ============ Header ============ */
/* 顶栏与卡片同一套 Liquid Glass 语言：玻璃底 + 强饱和/提亮折射 + 内高光，
   日/夜主题由 --glass-* 变量组自动切换，视觉与卡片保持一致 */
.topbar {
  position: sticky;
  top: 0;
  z-index: 100;
  background: var(--glass-bg);
  backdrop-filter: blur(28px) saturate(180%) brightness(1.06);
  -webkit-backdrop-filter: blur(28px) saturate(180%) brightness(1.06);
  border-bottom: 1px solid var(--glass-border);
  box-shadow:
    inset 0 1px 0 var(--glass-hi),
    inset 0 -1px 0 var(--glass-lo);
}
/* wrap：宽度不够时「时钟 + 按钮」整组换到第二行，第一行留给 logo + 搜索框。
   不换行的后果是实打实的重叠——时钟 flex-shrink:0 不肯缩，而 header-left 的
   min-width:0 又允许容器缩到比内容更窄，时钟于是越出容器边界、盖在搜索框上 */
.header {
  padding: 12px 24px;
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
  row-gap: 10px;
}
/* 不再 flex:1：那会让它吃掉全部剩余空间、把搜索框推到最右端，
   就做不到「搜索框紧跟一站看完天下事」了。现在它只占 logo 自身宽度 */
.header-left {
  display: flex;
  align-items: center;
  gap: 16px;
  flex: 0 1 auto;
  min-width: 0;
}
/* margin-left:auto 把「时钟 + 按钮」整组推到行尾；放不下时这一组整体换到第二行 */
.header-right {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
  margin-left: auto;
  position: relative;
  z-index: 10;
}
.header-right a,
.header-right button {
  width: 36px;
  height: 36px;
  border-radius: 8px;
  background: var(--card);
  border: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: center;
  /* GitHub 是 <a>、其余是 <button>：UA 默认字号不同(16/13.33)会让两族盒子
     的计算样式对不上；内容都是 SVG 图标，统一继承父级字号拉平 */
  font: inherit;
  cursor: pointer;
  transition:
    border-color var(--dur-fast),
    box-shadow var(--dur-fast);
  flex-shrink: 0;
  text-decoration: none;
}
.header-right a:hover,
.header-right button:hover {
  border-color: var(--accent);
  box-shadow: var(--accent-glow);
}
.header-right svg {
  width: 18px;
  height: 18px;
  color: var(--text-dim);
}
.logo {
  display: flex;
  align-items: center;
  gap: 8px;
  white-space: nowrap;
}
.logo svg {
  width: 24px;
  height: 24px;
  flex-shrink: 0;
  filter: drop-shadow(0 0 6px rgba(249, 115, 22, 0.3));
}
.logo .text {
  font-size: 18px;
  font-weight: 800;
  letter-spacing: -0.5px;
}
.logo .subtitle {
  font-size: 12px;
  font-weight: 400;
  color: var(--text-dim);
  letter-spacing: 0;
}
/* ============ 顶栏搜索 ============ */
/* 与顶栏图标按钮同一套外壳（card 底 + border 描边 + 8px 圆角），
   聚焦时整框转 accent 描边 + 光晕，和输入框/下拉框的全站焦点语言一致。
   紧跟 logo 之后（即「搜索框在前、时钟在后」）。
   flex-basis 必须是 0 而不是具体宽度：顶栏是 flex-wrap 容器，basis 放不下时浏览器
   先换行、后收缩，给个 260px 的 basis 会让它宁可独占一行也不肯变窄。
   basis 0 则永远排得进第一行，宽度全由 grow 分配——宽屏涨到 max-width 为止，
   窄屏就吃剩下的零头，于是任何宽度下都是「logo + 搜索框」同一行 */
.site-search {
  display: flex;
  align-items: center;
  gap: 4px;
  height: 36px;
  padding: 0 5px 0 11px;
  border-radius: 8px;
  background: var(--card);
  border: 1px solid var(--border);
  flex: 1 1 0;
  min-width: 120px;
  max-width: 420px;
  transition:
    border-color var(--dur-fast),
    box-shadow var(--dur-fast);
  position: relative;
  z-index: 30;
}
.site-search:focus-within,
.site-search.suggest-open {
  border-color: var(--accent);
  box-shadow: var(--accent-glow);
  z-index: 200 !important;
}
.ss-glass {
  width: 15px;
  height: 15px;
  flex-shrink: 0;
  color: var(--text-dimmer);
}
.site-search:focus-within .ss-glass {
  color: var(--accent);
}
.ss-input {
  flex: 1;
  min-width: 0;
  height: 100%;
  padding: 0;
  border: none;
  outline: none;
  background: transparent;
  color: var(--text);
  font-size: 13px;
  font-family: inherit;
  -webkit-appearance: none;
  appearance: none;
}
.ss-input::placeholder {
  color: var(--text-dimmer);
}
/* type=search 自带一个清除叉：在深色玻璃上是个突兀的浅色方块，去掉；
   要清空直接全选删除即可 */
.ss-input::-webkit-search-cancel-button,
.ss-input::-webkit-search-decoration {
  -webkit-appearance: none;
  appearance: none;
}
/* ---- 引擎选择：默认只露「当前引擎」一只图标，点开在下方弹出候选列表 ----
   与综合搜索引擎的选法一致：平时不占地方，要换才展开。
   .site-search 兼作菜单的定位基准——菜单锚在它的右下角，也就是按钮正下方 */
.site-search {
  position: relative;
}
.ss-engine {
  display: flex;
  align-items: center;
  gap: 2px;
  flex-shrink: 0;
  height: 26px;
  padding: 0 3px 0 4px;
  border-radius: 7px;
  background: transparent;
  border: 1px solid transparent;
  cursor: pointer;
  color: var(--text-dimmer);
  transition:
    color var(--dur-fast),
    background var(--dur-fast),
    border-color var(--dur-fast),
    box-shadow var(--dur-fast);
  -webkit-tap-highlight-color: transparent;
}
/* 图标切换由按钮上的 data-engine 驱动：两只图标都在 DOM 里，只显示当前那只。
   切换只改一个属性，不重建 DOM，图标不会闪 */
.ss-engine [data-engine-icon] {
  display: none;
}
.ss-engine[data-engine='bing'] [data-engine-icon='bing'],
.ss-engine[data-engine='google'] [data-engine-icon='google'] {
  display: block;
}
.ss-engine-face {
  display: flex;
  align-items: center;
}
.ss-engine-face svg {
  width: 15px;
  height: 15px;
}
/* 小箭头：静态的「可以点开」提示，展开时翻转朝上。9px 是「看得见但不跟图标抢」的尺寸 */
.ss-caret {
  width: 9px;
  height: 9px;
  flex-shrink: 0;
  opacity: 0.7;
  transition: transform var(--dur-fast);
}
/* 展开态：沿用输入框/下拉框的全站焦点语言（accent 描边 + 淡橙底），
   让人一眼看出菜单是从这个按钮出来的 */
.ss-engine[aria-expanded='true'] {
  color: var(--accent);
  background: var(--accent-bg);
  border-color: var(--accent);
}
.ss-engine[aria-expanded='true'] .ss-caret {
  transform: rotate(180deg);
}
/* 候选列表：表面材质来自统一浮层类 dd-pop（与万年历/搜索提示同一套玻璃）。
   定位改由 JS 锚定（fixed + 实时坐标）：浮层挂在 body 直下（见 app.js
   initSiteSearch 的 mountOverlay）——困在 .topbar 内会被它的 backdrop-filter
   限制采样范围，磨不到壁纸，材质与日历不一致。右缘对齐搜索框右端。
   z-index 必须压过顶栏（100）：挂到 body 后不再身处 .site-search 的
   suggest-open 高层级栈里，层级要自己挣——10 会被顶栏整个盖住（回归教训）。
   宽度收窄为内容自适应：下拉只有两三个短条目，固定 168px 会让「名称—勾」
   之间空出一大条，观感松散 */
.ss-menu {
  position: fixed;
  z-index: 300;
  display: flex;
  flex-direction: column;
  gap: 2px;
  width: max-content;
  min-width: 124px;
  max-width: 72vw;
  padding: 5px;
  opacity: 0;
  visibility: hidden;
  transform: translate3d(0, -8px, 0) scale(0.96);
  transform-origin: top right;
  pointer-events: none;
  animation: none !important;
  transition:
    opacity 0.22s cubic-bezier(0.16, 1, 0.3, 1),
    transform 0.22s cubic-bezier(0.16, 1, 0.3, 1),
    visibility 0.22s;
  will-change: transform, opacity;
}
/* 展开时平滑显示与入场 */
.ss-menu.open {
  opacity: 1;
  visibility: visible;
  transform: translate3d(0, 0, 0) scale(1);
  pointer-events: auto;
}
.ss-item {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 36px;
  padding: 0 10px;
  background: transparent;
  border: none;
  border-radius: 10px;
  color: var(--text-dim);
  font-size: 13px;
  font-weight: 600;
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  transition:
    color var(--dur-fast),
    background var(--dur-fast);
  -webkit-tap-highlight-color: transparent;
}
.ss-item-icon {
  display: flex;
  width: 15px;
  height: 15px;
  flex-shrink: 0;
}
.ss-item-icon svg {
  width: 15px;
  height: 15px;
}
.ss-item-name {
  flex: 1;
  min-width: 0;
}
/* 当前引擎：橙勾 + 淡橙底衬。勾用 SVG 图标（文本 ✓ 字形随字体走、粗细不可控，
   观感差），opacity 隐显而不是 display——否则选中项比其他项窄一截，
   切换时整行文字会左右跳 */
.ss-tick {
  display: flex;
  width: 14px;
  height: 14px;
  flex-shrink: 0;
  color: var(--accent);
  opacity: 0;
}
.ss-tick svg {
  width: 100%;
  height: 100%;
}
.ss-item[aria-selected='true'] {
  background: var(--accent-bg);
  color: var(--accent-text);
}
.ss-item[aria-selected='true'] .ss-tick {
  opacity: 1;
}
/* hover 只服务鼠标：触屏没有悬停语义，不包一层会在点完粘住 */
@media (hover: hover) {
  .ss-engine:hover {
    color: var(--accent-text);
    background: var(--accent-bg);
  }
  .ss-item:hover {
    color: var(--accent-text);
    background: var(--accent-bg);
  }
}

/* 清空按钮 */
.ss-clear {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  padding: 0;
  margin-right: 2px;
  border: none;
  border-radius: 50%;
  background: var(--border-light);
  color: var(--text-dimmer);
  font-size: 11px;
  line-height: 1;
  cursor: pointer;
  flex-shrink: 0;
  transition:
    color var(--dur-fast),
    background var(--dur-fast),
    transform var(--dur-fast);
  -webkit-tap-highlight-color: transparent;
}
.ss-clear:hover {
  color: var(--text);
  background: var(--border);
  transform: scale(1.1);
}
.ss-clear[hidden] {
  display: none !important;
}

/* ---- 站内智能检索浮层：左右缘各 -1px 与搜索框描边对齐（坐标由 JS 锚定，
   浮层挂在 body 直下以逃出顶栏的 backdrop root，见 app.js mountOverlay）----
   表面材质来自统一浮层类 dd-pop（与万年历/引擎菜单同一套玻璃） */
.ss-suggest {
  position: fixed;
  max-height: min(480px, calc(100vh - 96px));
  overflow-y: auto;
  overflow-x: hidden;
  overscroll-behavior: contain;
  padding: 6px;
  z-index: 210;
  display: flex;
  flex-direction: column;
  gap: 6px;
  opacity: 0;
  visibility: hidden;
  transform: translate3d(0, -8px, 0) scale(0.98);
  transform-origin: top center;
  pointer-events: none;
  animation: none !important;
  transition:
    opacity 0.22s cubic-bezier(0.16, 1, 0.3, 1),
    transform 0.22s cubic-bezier(0.16, 1, 0.3, 1),
    visibility 0.22s;
  will-change: transform, opacity;
}
.ss-suggest.open {
  opacity: 1;
  visibility: visible;
  transform: translate3d(0, 0, 0) scale(1);
  pointer-events: auto;
}
.ss-suggest[hidden] {
  display: none !important;
}
.ss-suggest::-webkit-scrollbar {
  width: 5px;
}
.ss-suggest::-webkit-scrollbar-thumb {
  background: var(--border-light);
  border-radius: 3px;
}

.ss-group {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.ss-group + .ss-group {
  border-top: 1px solid var(--border);
  padding-top: 6px;
}
.ss-group-title {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 4px 8px 3px;
  font-size: 11px;
  font-weight: 700;
  color: var(--text-dimmer);
  letter-spacing: 0.5px;
}
.ss-group-count {
  font-size: 10px;
  opacity: 0.8;
  font-weight: 600;
}

/* 选项行（统一可键盘导航行） */
.ss-row {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 36px;
  padding: 6px 8px;
  border-radius: 8px;
  cursor: pointer;
  color: var(--text);
  font-size: 13px;
  text-decoration: none;
  user-select: none;
  background: transparent;
  border: none;
  width: 100%;
  box-sizing: border-box;
  text-align: left;
  transition:
    background var(--dur-fast),
    color var(--dur-fast);
  -webkit-tap-highlight-color: transparent;
}
.ss-row:hover,
.ss-row.active {
  background: var(--accent-bg);
  color: var(--accent-text);
}
.ss-row mark {
  background: transparent;
  color: var(--accent);
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.ss-row-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  font-size: 14px;
  flex-shrink: 0;
}
.ss-source-icon {
  width: 16px;
  height: 16px;
  border-radius: 4px;
  flex-shrink: 0;
}
.ss-row-body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.ss-row-title {
  font-size: 13px;
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  color: var(--text);
}
.ss-row.active .ss-row-title,
.ss-row:hover .ss-row-title {
  color: var(--accent-text);
}
.ss-row-sub {
  font-size: 11px;
  color: var(--text-dimmer);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ss-row-badge {
  font-size: 11px;
  font-weight: 600;
  padding: 2px 6px;
  border-radius: 4px;
  background: var(--card-hover);
  color: var(--text-dimmer);
  flex-shrink: 0;
  transition: all var(--dur-fast);
}
.ss-row.active .ss-row-badge,
.ss-row:hover .ss-row-badge {
  background: var(--accent);
  color: #fff;
}
.ss-row-heat {
  font-size: 11px;
  color: var(--accent-text);
  font-weight: 600;
  flex-shrink: 0;
}
.ss-row-jump {
  font-size: 12px;
  color: var(--text-dimmer);
  flex-shrink: 0;
}
.ss-item-tag {
  display: inline-block;
  padding: 1px 4px;
  border-radius: 3px;
  font-size: 10px;
  font-weight: 700;
  background: rgba(239, 68, 68, 0.15);
  color: #f87171;
  flex-shrink: 0;
}

/* 胶囊标签网格（空状态推荐） */
.ss-pills-wrap {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 4px 8px 6px;
}
.ss-kw-pill,
.ss-mod-pill {
  display: inline-flex;
  align-items: center;
  /* 触控下限：suggest 浮层在移动端高频使用，27px 的行高指尖难命中 */
  min-height: 30px;
  gap: 4px;
  padding: 4px 8px;
  border-radius: 6px;
  background: var(--card-hover);
  border: 1px solid var(--border);
  color: var(--text-dim);
  font-size: 12px;
  font-family: inherit;
  cursor: pointer;
  transition: all var(--dur-fast);
  -webkit-tap-highlight-color: transparent;
}
.ss-kw-pill:hover,
.ss-mod-pill:hover {
  background: var(--accent-bg);
  border-color: var(--accent);
  color: var(--accent-text);
  transform: translateY(-1px);
}
.ss-kw-cnt {
  font-size: 10px;
  opacity: 0.8;
  color: var(--accent);
  font-weight: 700;
}
.ss-pill-icon {
  font-size: 13px;
  line-height: 1;
  display: inline-flex;
}
.ss-footer-hint {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 6px 8px 2px;
  margin-top: 2px;
  border-top: 1px solid var(--border);
  font-size: 11px;
  color: var(--text-dimmer);
  white-space: nowrap;
}
.ss-footer-hint span {
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  gap: 3px;
}
.ss-footer-hint kbd {
  display: inline-block;
  padding: 1px 4px;
  font-size: 10px;
  font-family: inherit;
  background: var(--card-hover);
  border: 1px solid var(--border);
  border-radius: 4px;
  box-shadow: 0 1px 0 var(--border);
}

/* ============ Clock ============ */
/* 克制风格：单层 border + card 底，仅保留一颗慢脉冲圆点作为强调色瞬间 */
/* 一日进度填充：从 0:00 起随时间铺满，到 24:00 满格；极淡的 accent 渐变，不压低文字对比度 */
.clock {
  position: relative;
  overflow: hidden;
  display: flex;
  align-items: center;
  gap: 8px;
  white-space: nowrap;
  flex-shrink: 0;
  padding: 8px 12px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--card);
  color: var(--text-dim);
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  user-select: none;
  transition:
    border-color var(--dur-fast),
    box-shadow var(--dur-fast),
    transform var(--dur-fast);
}
.clock:hover {
  border-color: var(--accent);
  box-shadow: 0 0 14px rgba(249, 115, 22, 0.22);
  transform: translateY(-1px);
}
.clock:active {
  transform: translateY(0);
}
/* 一日进度填充：从 0:00 起随时间铺满，到 24:00 满格。
   填充色提亮为 accent→accent-light 实感渐变（原 12%/22% 极淡版几乎看不出进度），
   仍垫在文字下层（z-index 0），正文对比由胶囊底色兜底 */
.clock-fill {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 100%;
  border-radius: inherit;
  z-index: 0;
  pointer-events: none;
  background: linear-gradient(
    90deg,
    color-mix(in srgb, var(--accent) 38%, transparent),
    color-mix(in srgb, var(--accent-light) 68%, transparent)
  );
  opacity: 0.9;
  /* 用 transform: scaleX 代替 width：这条填充每秒更新一次，
     width 变化会触发 layout + paint，scaleX 只走合成层，
     页面常驻打开也不会有每秒一次的布局抖动。origin 固定左侧才能从左往右铺满 */
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 1s linear;
}
:root[data-theme='light'] .clock-fill {
  opacity: 0.95;
}
/* ---- 时间胶囊泡泡：与分组标题同款的装饰泡泡，但只活在「今日进度」的橙色区间里 ----
   进度填充走 scaleX（纯合成、不触发布局），没法直接拿来当裁剪区——泡泡放进去
   会被横向拉扁。所以泡泡单独铺满一层，再用 clip-path 按同一个进度裁出可见范围：
   泡泡形状完全不受影响，可见区域又严格等于橙色渐变那块宽度。
   --day-progress 由 JS 每秒写入（如 72.50%），橙色区随时间变宽，泡泡活动范围同步长大 */
.clock-bubbles {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
  border-radius: inherit;
  clip-path: inset(0 calc(100% - var(--day-progress, 0%)) 0 0);
  /* 与 .clock-fill 的 transform 过渡同节奏，裁剪边界跟着橙色区平滑推进而不是每秒跳一格 */
  transition: clip-path 1s linear;
}
.clock-bubbles .bubble {
  --bubble-bg:
    radial-gradient(circle at 30% 26%, rgba(255, 255, 255, 0.9) 0 9%, transparent 20%),
    radial-gradient(circle at 66% 78%, color-mix(in srgb, var(--accent) 22%, transparent), transparent 46%),
    radial-gradient(circle at 50% 50%, rgba(255, 255, 255, 0.05) 0 52%, rgba(255, 255, 255, 0.38) 66%, transparent 74%);
  display: block;
  position: absolute;
  top: 50%;
  left: 0;
  border-radius: 50%;
  background: var(--bubble-bg);
  animation:
    clockBubbleFloat 13s linear infinite,
    clockBubbleBob 4.4s ease-in-out infinite;
}
/* 胶囊只有 30 多像素高，整组泡泡比标题条那版缩一档；
   负延迟让它们开局就分散在途中的不同位置，不必等一轮才铺开 */
.clock-bubbles .bubble:nth-of-type(1) {
  width: 22px;
  height: 22px;
  margin-top: -11px;
  animation-duration: 12s, 4.1s;
  animation-delay:
    -2s,
    -1s;
}
.clock-bubbles .bubble:nth-of-type(2) {
  width: 15px;
  height: 15px;
  margin-top: -8px;
  animation-duration: 15s, 5s;
  animation-delay:
    -9s,
    -2.2s;
}
.clock-bubbles .bubble:nth-of-type(3) {
  width: 18px;
  height: 18px;
  margin-top: -9px;
  animation-duration: 13.5s, 4.6s;
  animation-delay:
    -5s,
    -3.1s;
}
.clock-bubbles .bubble:nth-of-type(4) {
  width: 11px;
  height: 11px;
  margin-top: -6px;
  animation-duration: 10.5s, 3.8s;
  animation-delay:
    -11s,
    -1.6s;
}
/* 横向走位用独立的 translate 属性，避免和 transform 抢同一个属性。
   行程给足 420px：桌面长胶囊、移动短胶囊都够走出橙色区，越界部分由上面的
   clip-path 裁掉——视觉上就是「从橙色区左端浮现、到右端消失」 */
@keyframes clockBubbleFloat {
  0% {
    translate: -40px 0;
    opacity: 0;
  }
  10% {
    opacity: 0.9;
  }
  90% {
    opacity: 0.9;
  }
  100% {
    translate: 420px 0;
    opacity: 0;
  }
}
@keyframes clockBubbleBob {
  0%,
  100% {
    transform: translateY(2px);
  }
  50% {
    transform: translateY(-3px);
  }
}
/* 减弱动态偏好：静止的泡泡悬在进度条里很突兀，直接不显示 */
@media (prefers-reduced-motion: reduce) {
  .clock-bubbles {
    display: none;
  }
}
.clock-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent);
  flex-shrink: 0;
  animation: clockPulse 3s ease-in-out infinite;
}
.clock-dot,
.clock-date,
.clock-time {
  position: relative;
  z-index: 1;
}
/* 脉冲：谷底无发光、峰底柔光，让“活着”的信号只在一瞬出现 */
@keyframes clockPulse {
  0%,
  100% {
    opacity: 0.4;
    transform: scale(0.85);
    box-shadow: 0 0 0 var(--accent);
  }
  50% {
    opacity: 0.85;
    transform: scale(1.1);
    box-shadow: 0 0 6px var(--accent);
  }
}
.clock .clock-time {
  color: var(--text);
  font-weight: 600;
}
.clock .clock-date {
  color: var(--text-dim);
  font-weight: 500;
}
/* 时钟宽度由内容决定、不参与压缩；日期两态（完整 / 短版）由 JS 按实测宽度切换，
   任何宽度下都不裁切、不出省略号，所以这里不再有 text-overflow / overflow:hidden */
/* 时钟现与按钮同住 .header-right：靠行尾由该容器的 margin-left:auto 负责，
   自己不再需要 margin-left:auto（那会在容器内部把它推到按钮左边） */
.clock-desktop {
  display: flex;
  flex-shrink: 0;
}
.clock-desktop .clock-time {
  flex-shrink: 0;
}
.clock-desktop .clock-date {
  white-space: nowrap;
}
.clock-mobile {
  display: none;
}
/* 窄屏适配的媒体查询统一放在文件末尾（需覆盖上方桌面规则） */

/* theme toggle icon visibility */
.theme-toggle .sun {
  display: none;
}
:root[data-theme='light'] .theme-toggle .moon {
  display: none;
}
:root[data-theme='light'] .theme-toggle .sun {
  display: block;
}

/* ============ Splash 开屏（毛玻璃遮罩 + 火焰图标，首批数据就绪后渐隐） ============ */
.splash {
  position: fixed;
  inset: 0;
  z-index: 999;
  display: flex;
  align-items: center;
  justify-content: center;
  background: color-mix(in srgb, var(--bg) 55%, transparent);
  backdrop-filter: blur(20px) saturate(150%);
  -webkit-backdrop-filter: blur(20px) saturate(150%);
  transition:
    opacity 0.55s ease,
    visibility 0.55s;
}
/* 图标尺寸随屏幕自适应：小屏不局促、大屏不虚胖 */
.splash-icon {
  width: clamp(56px, 14vmin, 128px);
  height: clamp(56px, 14vmin, 128px);
  filter: drop-shadow(0 8px 28px rgba(249, 115, 22, 0.45));
  animation: splashPulse 1.5s ease-in-out infinite;
}
@keyframes splashPulse {
  0%,
  100% {
    transform: scale(1);
  }
  50% {
    transform: scale(1.12);
  }
}
/* 渐隐态：遮罩淡出；图标由 JS 计算坐标飞向左上角 logo（FLIP 变换），
   这里只负责停掉脉冲动画，避免 animation 优先级覆盖 JS 的 transform */
.splash.splash-hide {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}
.splash.splash-hide .splash-icon {
  animation: none;
}
/* 无动画偏好：跳过脉冲，快速淡出 */
@media (prefers-reduced-motion: reduce) {
  .splash-icon {
    animation: none;
  }
  .splash {
    transition:
      opacity var(--dur-fast) ease,
      visibility var(--dur-fast);
  }
}

/* 减少动态偏好全局兜底：除上面 splash 专项外，统一关掉所有无限/装饰动画与过渡——
   时钟脉冲（clockPulse）、骨架屏 shimmer、loading 转圈、卡片淡入、hover 抬升等。
   保留「可见性」相关过渡（.show/.splash-hide），避免遮罩瞬间消失造成闪跳 */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  /* 壁纸淡入与 splash 渐隐保留极短过渡：只求不闪跳，不求动画感 */
  .wallpaper-bg,
  .splash {
    transition-duration: var(--dur-fast) !important;
  }
}

/* ============ Layout（顶栏导航 + 通栏内容，纵向堆叠） ============ */
.layout {
  /* 2026-09 改版：桌面端菜单移到顶部（与移动端同构、大屏适配），
     原为「左侧栏 + 内容区」左右分栏 */
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0;
  max-width: 1600px;
  margin: 0 auto;
  padding: 0 12px;
}

/* ============ Category Nav（顶栏分类导航） ============ */
/* 导航已移出 .layout（见 index.html）。原因：.layout 有 max-width:1600px 且水平居中，
   导航若在它内部，即便用 100vw 撑宽也会被容器的居中偏移带跑——窄窗口下看不出，
   2560px 这类宽屏下内容会偏出约 480px。独立于容器之后宽度直接等于视口，
   不再需要任何 vw / 负边距 / 滚动条补偿技巧。
   内容左边界用 max() 复刻卡片的位置：
     卡片左 = max(0, (可用宽 - 1600) / 2) + 36，36 = .layout 的 12 + .main 的 24
   这里用 100% 而不是 100vw——100% 本身就是不含滚动条的可用宽，天然精确，
   也正因如此不必再引入滚动条宽度变量去补偿 */
.cat-nav {
  display: flex;
  flex-direction: column;
  gap: 0;
  padding: 0 max(36px, calc((100% - 1600px) / 2 + 36px));
  /* 高度与顶栏严格一致：--topbar-h 是 JS 实测的顶栏高度（含底边框），
     内容在其中垂直居中，两块玻璃的上下边线因此完全对齐。
     用 min-height 而非 height——桌面手风琴子菜单展开时导航要能自然变高 */
  min-height: var(--topbar-h, 61px);
  /* safe center：折叠态内容比顶栏矮，居中后上下边线自然对齐；
     展开子菜单内容超高时，「safe」让它退化成 flex-start，
     否则居中会把顶部内容顶到滚动区之外、怎么滚都看不到 */
  justify-content: safe center;
  background: var(--glass-bg);
  backdrop-filter: blur(28px) saturate(180%);
  -webkit-backdrop-filter: blur(28px) saturate(180%);
  /* 去掉卡片边框与圆角，改为与顶栏同款的底边线：两块玻璃上下相接、连成一片 */
  border: none;
  border-bottom: 1px solid var(--glass-border);
  border-radius: 0;
  box-shadow:
    inset 0 1px 0 var(--glass-hi),
    inset 0 -1px 0 var(--glass-lo);
  /* 显式层叠：高于内容区，低于顶栏（100），避免被任何兄弟层叠上下文遮挡 */
  z-index: 50;
  /* sticky 紧贴顶栏下沿（原为 +12px）：内容滚到导航下边即被遮住。
     不用 fixed：fixed 脱离文档流需占位、滚到页脚必然重叠、还会引入层叠与命中检测问题 */
  position: sticky;
  top: var(--topbar-h, 64px);
  /* 展开子菜单时可占高度上限：视口减去顶栏 */
  max-height: calc(100vh - var(--topbar-h, 64px));
  overflow-y: auto;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.cat-nav::-webkit-scrollbar {
  display: none;
}
/* 分类导航两段式：
   .cat-row    外层行容器——还要容纳桌面手风琴子菜单 .cat-sub（靠 flex-basis:100% 换行）
   .cat-scroll 内层——分类 pill 专用：一律不换行，放不下就横向滚动，溢出时两端出现箭头 */
.cat-row {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 6px;
  padding: 0;
}
.cat-scroll {
  flex-basis: 100%;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 6px;
}
.cat-pills {
  display: flex;
  flex-wrap: nowrap;
  gap: 6px;
  flex: 1;
  min-width: 0;
  /* 纵向留 6px：overflow 会把超出的 box-shadow 裁掉，
     不留白激活 pill 的橙色发光会被切平（桌面端原先没有滚动容器，不存在这个问题） */
  padding: 6px 0;
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.cat-pills::-webkit-scrollbar {
  display: none;
}
/* 鼠标拖拽横向滚动时禁用文本选择（触摸端原生支持，不受影响） */
.cat-pills.dragging {
  user-select: none;
  -webkit-user-select: none;
  cursor: grabbing;
}
/* 左右箭头：仅 pill 行溢出时出现，滚到某一端后该侧隐藏。
   注意 .cat-nav button 的基础样式特异性更高，这里的覆盖必须带 .cat-nav 前缀 */
.cat-nav .cat-arrow {
  flex-shrink: 0;
  width: 26px;
  height: 26px;
  padding: 0;
  gap: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  cursor: pointer;
  background: var(--card);
  border: 1px solid var(--border);
  color: var(--text-dim);
  transition:
    color var(--dur-fast),
    border-color var(--dur-fast);
}
.cat-nav .cat-arrow[hidden] {
  display: none;
}
.cat-nav .cat-arrow:hover {
  color: var(--accent);
  border-color: var(--accent);
}
.cat-arrow svg {
  width: 14px;
  height: 14px;
}
/* 移动端模块面板：桌面隐藏（悬浮层样式只存在于窄屏媒体查询内） */
.cat-panel {
  display: none;
}
.cat-nav button {
  padding: 9px 14px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 10px;
  color: var(--text-dim);
  font-size: 13px;
  text-align: left;
  cursor: pointer;
  white-space: nowrap;
  transition:
    color var(--dur-fast),
    background var(--dur-fast),
    border-color var(--dur-fast);
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: 6px;
}
.cat-pills > button {
  /* pill 间距由 .cat-pills 的 gap 承担，不再用按钮下边距 */
  margin-bottom: 0;
}
/* hover 高亮用浅橙底：侧边栏自身即 card 色底，用 card 色高亮会与背景融为一体
   （规则本体在上方 @media (hover:hover) 块内，触屏不粘滞） */
.cat-pills > button.active {
  background: linear-gradient(135deg, var(--accent), var(--accent-light));
  border-color: transparent;
  color: #fff;
  box-shadow: var(--accent-glow);
}
/* 分类计数徽章：紧跟在分类名之后的小胶囊，配方与下方分组标题条 .cat-title .count
   完全一致（实心橙渐变 + 白字）——两处说的是同一件事（这个分类有几个热榜模块），
   风格统一才不会出现两套「数字语言」 */
.cat-pills > button .cnt {
  flex-shrink: 0;
  font-size: 10px;
  font-weight: 700;
  color: #fff;
  background: linear-gradient(135deg, var(--accent), var(--accent-light));
  padding: 2px 8px;
  border-radius: 8px;
  font-variant-numeric: tabular-nums;
}
/* 激活分类的 pill 本身就是同一套橙渐变，橙底徽章会糊在一起分不出形：
   换成白色半透明底（与 pill 右侧开合箭头同配方），保留「徽章」的轮廓 */
.cat-pills > button.active .cnt {
  background: rgba(255, 255, 255, 0.25);
}
/* 目录/面板开合箭头：只在已激活分类上显示——未激活分类的点击语义是「切换」，
   不应暗示展开。方向约定：▸=收起，rotate(90°)→▾=展开；手动折叠时回转回 ▸ */
.cat-pills > button.active[data-cat]:not([data-cat='all'])::after {
  content: '▸';
  margin-left: 4px;
  flex-shrink: 0;
  width: 22px;
  height: 22px;
  line-height: 22px;
  text-align: center;
  border-radius: 7px;
  background: rgba(255, 255, 255, 0.25);
  font-size: 15px;
  font-weight: 700;
  color: #fff;
  transition: transform var(--dur-base) var(--ease);
  transform: rotate(90deg);
}
/* 子菜单被手动折叠（点已激活分类）时，激活分类的箭头必须回到收起朝向 ▸ */
.cat-nav.sub-collapsed .cat-pills > button.active::after {
  transform: rotate(0deg);
}

/* 桌面手风琴子菜单：grid-rows 0fr→1fr 平滑展开/收起，仅当前分类展开。
   不再用「整块淡色面板」——那会和上方分类胶囊行并排成「两块板」，层级糊在一起。
   改成一条两端淡出的细分隔线 + 下面一排小卡片：
   分类胶囊 = 一级（橙渐变激活），目录卡片 = 二级（淡底，激活才变橙） */
.cat-sub {
  display: grid;
  grid-template-rows: 0fr;
  margin: 0 3px;
  padding: 0 6px;
  border-radius: 10px;
  opacity: 0;
  /* 收起态移出 flex 流（绝对定位 + 隐藏）：0fr 只收高度不收宽度，
     目录条 ~1200px 的隐形宽度会把分类 pill 行挤乱 */
  position: absolute;
  visibility: hidden;
  transition:
    grid-template-rows var(--dur-base) var(--ease),
    opacity var(--dur-base) ease,
    margin var(--dur-base),
    padding var(--dur-base);
}
.cat-sub.open {
  position: relative;
  visibility: visible;
  grid-template-rows: 1fr;
  opacity: 1;
  /* 顶栏版式：目录条独占一行排在全部分类 pill 之后（order 99 + basis 100%），
     视觉上成为「第二行模块芯片条」 */
  margin: 8px 0 2px;
  padding: 12px 2px 2px;
  flex-basis: 100%;
  order: 99;
}
/* 与分类胶囊行之间的细分隔线：两端淡出，比通栏实线更轻，
   接手原先整块面板底所承担的「这里进入下一层」的提示 */
.cat-sub.open::before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--border-light) 18%, var(--border-light) 82%, transparent);
}
.cat-nav.sub-collapsed .cat-sub {
  grid-template-rows: 0fr;
  opacity: 0;
  position: absolute;
  visibility: hidden;
}
/* 折叠时同步清零 .open 残留的纵向 margin/padding（transition 已含二者），
   子菜单完全收起不留空隙，与箭头回到 ▸ 的收起态保持一致 */
.cat-nav.sub-collapsed .cat-sub.open {
  margin: 0 3px;
  padding: 0 6px;
}
/* 卡片之间需要呼吸感；内容整体轻微下移淡入，配合高度展开做出「抽屉拉开」的手感 */
.cat-sub-inner {
  min-height: 0;
  overflow: hidden;
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  gap: 8px;
  opacity: 0;
  transform: translateY(-4px);
  transition:
    opacity var(--dur-base) ease 0.05s,
    transform var(--dur-base) var(--ease);
}
.cat-sub.open .cat-sub-inner {
  opacity: 1;
  transform: none;
}
.cat-subitem {
  padding: 5px 10px;
  margin: 0;
  background: transparent;
  border: none;
  border-radius: 8px;
  color: var(--text-dim);
  font-size: 12px;
  font-weight: 500;
  text-align: left;
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition:
    color var(--dur-fast),
    background var(--dur-fast);
}
.cat-subitem .ci {
  margin-right: 5px;
}
.cat-subitem.active {
  color: var(--accent-text);
  background: var(--accent-bg);
  font-weight: 600;
  box-shadow: inset 2px 0 0 var(--accent);
}

/* 桌面侧边栏「本页目录」：图标 + 卡片名，分组卡合并为单条目并带当前标签页徽章。
   高亮单元是卡片（scroll-spy 与点击定位共用），分组成员归到分组条目 */
/* 注意：必须用 .cat-nav 前缀提升特异性，否则 .cat-nav button（0,1,1）的
   9px/14px 内边距与 13px 字号会压过这里的紧凑布局 */
.cat-nav .cat-toc-item {
  display: flex;
  align-items: center;
  gap: 6px;
  width: auto;
  padding: 6px 10px;
  /* 半透明卡片底：有边界感，又不像分类胶囊那样「重」。
     65% 是按明暗两套主题折中的值——再淡亮色下就几乎看不出边界了 */
  background: color-mix(in srgb, var(--card) 65%, transparent);
  border: none;
  border-radius: 9px;
  color: var(--text-dim);
  font-size: 12px;
  font-weight: 500;
  text-align: left;
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  transition:
    background var(--dur-fast),
    color var(--dur-fast),
    box-shadow var(--dur-fast);
}
/* 主内容行：图标 + 标题 */
.cat-toc-item .mm {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  flex: 0 1 auto;
  min-width: 0;
}
.cat-toc-item .ci {
  flex-shrink: 0;
}
.cat-toc-item .nm {
  overflow: hidden;
  text-overflow: ellipsis;
}
.cat-toc-item .tb {
  flex-shrink: 0;
  margin-left: auto;
  max-width: 72px;
  overflow: hidden;
  text-overflow: ellipsis;
  padding: 1px 6px;
  border-radius: 6px;
  font-size: 9px;
  font-weight: 600;
  color: var(--accent-text);
  background: var(--accent-bg);
}
/* 激活态沿用分类胶囊的配方（橙渐变 + 白字 + 柔光）：一眼定位「当前卡片在哪」，
   比原来的橙色文字 + 下划线更有分量，也不会和标题栏的下划线语义打架 */
.cat-toc-item.active {
  background: linear-gradient(135deg, var(--accent), var(--accent-light));
  color: #fff;
  font-weight: 600;
  box-shadow: var(--accent-glow);
}
.cat-toc-item.active .tb {
  color: #fff;
  background: rgba(255, 255, 255, 0.22);
}

/* ============ Main ============ */
/* min-height：内容不足一屏时也撑到接近视口高度，保证始终出现滚动条。
   两点收益：① 各分类滚动条占用宽度一致，网格列数不会因分类切换而跳变；
   ② 页脚到底部的距离在任何分类下都一致。只改高度，不影响宽度与列数 */
.main {
  flex: 1;
  min-width: 0;
  /* 顶部 12px 与上方导航条的 margin-bottom 同值，保持垂直节奏一致 */
  padding: 12px 24px 60px;
  /* 扣掉顶栏高度（含它浮起来后多出的 10px 上方走道，见 .app-shell .topbar）
     与页尾预留，内容不足一屏时也撑到接近视口，保证始终有滚动条 */
  min-height: calc(100vh - var(--topbar-h, 76px) - 110px);
}

/* ============ 页首 Hero 卡 ============ */
/* 这张卡按效果图改成了「实色柔和渐变」，不再走 Liquid Glass：
   效果图里它是一整块淡紫 → 粉 → 橙的品牌色底，背景球体并不透上来，
   和右栏/列表那些玻璃卡是两种材质，这是刻意的（Hero 做成实色高亮块）。
   副作用：卡内文字不能再用 --text / --text-dim——它们会随主题翻转，
   而这张卡的底色永远是浅色，深色模式下一翻就看不见了。
   所以下面用一组 --hero-* 卡内固定色，两套主题共用。

   尺寸换算：效果图是 2× 截图。判据是卡片那条 1px 浅色描边——垂直剖面
   实测亮带 2 个设备像素、文字笔画也 ~2px；1× 截图只会是 1px。
   因此效果图像素 ÷ 2 = CSS px。卡片 1278×251 → 639×125.5。 */
.hero {
  /* 取色前必须先做白点归一化：效果图整张有一个 ×0.965 的色阶压缩——
     全图最白的像素只有 (246,246,246)，橙色图标块里火焰的纯白也是 246，
     而 PNG 里没有 gAMA/sRGB 块，所以这就是图像本身的属性，不是解码问题。
     不归一化的话所有亮色都会系统性偏暗 3.5%（副标语会被读成 #f13704，真值 #fa3904）。
     归一化系数 K = 255/246 = 1.0366；对深色影响很小（0→0，11→11），对亮色影响明显。
     取法：原生分辨率上取「最暗 1.5% 像素的中位色」，再乘 K。
     这个估计量在实现端精确无偏（拿已知的 #4c495a / #15151e / #ef4205 去测逐位相同）。 */
  --hero-ink: #00000b; /* 标题：效果图实测就是近纯黑，带一点蓝 */
  --hero-ink-dim: #8a8495; /* 描述 */
  --hero-accent: #fa3904; /* 副标语 */
  --hero-chip-ink: #545365; /* 芯片内文字（时间戳那一段更深，单独给值） */

  position: relative;
  margin-bottom: 18px;
  padding: 0;
  /* ── 高度：固定 148px，不随窗口宽度变 ──
     2026-09-20 第二次修正。上一版用了 aspect-ratio 让高度跟着卡片宽度走，
     出发点是「卡片变宽就不那么扁」，但结果是**拖窗口时高度一直在变**：
       1600→196  1300→166  1210→148  1181→148   ← 右栏还在，越窄越矮
       1180→196  1080→185   900→196   700→154   ← 右栏消失后卡片突然变宽，高度又跳上去
     用户实测到的正是「从高变低、再从高到低」这两段。装饰性的比例好看，但不能拿
     布局稳定性去换，所以彻底去掉 aspect-ratio / max-height，只留一个固定下限。
     为什么是 148：右栏天气卡就是 148（min-height + 纵向居中），两张卡顶边都在 y=74.5，
     等高才对得齐。桌面端文案区实测 139.4~141.4 高，永远小于 146 的内容盒，所以
     148 就是恒定值，窗口怎么拖都不动。
     min-height 而不是 height：移动端（≤820px）描述会折成两行、芯片折成两行，
     内容真的会超过 148，这时让它自然长高（详见文末移动端媒体查询）。 */
  /* width: 100% 不能省：`.main` 是 shrink-to-fit 容器，`.hero` 的 width 默认是 auto。
     写成确定值，块向尺寸才只由 min-height 和内容决定。 */
  /* container-type 是为了让装饰簇能按「卡片自己的宽度」决定缩不缩。
     不能挂视口断点：右栏 >1180px 才出现，所以 1181~1270 这段主栏反而最窄
     （611~700），而 ≤1180 时右栏消失、卡片反而宽到 876~912 —— 视口宽度和
     卡片宽度是反着走的，写视口断点会得到完全错误的区间。 */
  container-type: inline-size;
  width: 100%;
  min-height: 148px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  /* 裁切：图标块外发光与右侧装饰面板都要被圆角切住 */
  overflow: hidden;
  border-radius: 24px;
  border: 1px solid rgba(255, 255, 255, 0.62);
  /* 这张卡的底不是目视调的，是量出来的。做法：
     ① 把图标块 / 三行文字 / 芯片行 / 三块装饰面板全部遮掉（面板按拟合出的旋转矩形遮，
        外扩 5px），再腐蚀 3px 让抗锯齿不污染，剩下的像素就是纯渐变底；
     ② 逐行取 x15%~45% 的中位色当「竖向底色」，写成 8 个停靠点的 linear-gradient；
     ③ 在底色上按 12×6 网格算残差，残差里剩下的横向结构用径向斑补。
     补的时候用了一个「恒定 RGB + alpha 斜坡」的约束：CSS 渐变色标是在预乘 alpha 空间插值的，
     如果两个色标的 RGB 不同，预乘插值和不预乘结果不一样，手写模拟器容易和 Chrome 差一点；
     让每个斑的 RGB 恒定、只让 alpha 变化，两种插值就等价了，拟合参数搬到 CSS 能逐位复现。
     （为此先在 Python 里写了个渐变模拟器，拿「只有底色」的实测渲染给它定标，
      偏差 |Δ| = 2.85，之后的参数都在这上面调，改一次几秒，不用每轮都开浏览器。）

     量出来的结构是：
       · 底色：顶部紫蓝 #c6c6ee → 中段粉 #efd5dc → y58 #f8dbdb → y75 反而偏橙粉 #fad4cb
         （G 比上下都低）→ 底部奶油 #f9e1dd。所以是 8 个停靠点，不是单调的几段。
       · 右侧 x62%~95% 有一条橙带，最饱和处在 x≈76%，而且**上下两端浓、中间被冲淡**：
         x79 一列从上到下是 (251,145,105) → (249,182,164) → (248,136,101)，
         中间那段 G 高了 37 级。单层径向做不出「两头浓中间淡」，所以拆成上下两个橙核。
       · 橙带左右两翼还有更淡的鲑色（x62 y8 是 (241,189,186)），以及右缘 y58 更饱和的一段
         （x95 y58 是 (244,173,122)），各给一团。
       · 左缘 x0%~5% 在中上部偏冷（x4 y25 是 (200,201,243)，B 比 R 还高），
         x12 一列则整体偏暖偏亮 —— 是两个独立的斑，不是一个。
     最终整卡背景的 |Δ|（24×8 密集网格，Δ=实现−效果图）从原来的 11.1 降到 6.5。

     注意：这张卡是**实色**底，不走 Liquid Glass，背景球体不透上来（刻意如此）。 */
  background:
    /* 橙核上 */
    radial-gradient(
      15% 50% at 76% 2%,
      rgba(251, 145, 105, 1) 0%,
      rgba(251, 145, 105, 0.65) 50%,
      rgba(251, 145, 105, 0) 100%
    ),
    /* 橙核下 */
    radial-gradient(
        15% 55% at 76% 98%,
        rgba(248, 136, 101, 1) 0%,
        rgba(248, 136, 101, 0.65) 50%,
        rgba(248, 136, 101, 0) 100%
      ),
    /* 淡鲑柱 */
    radial-gradient(
        12% 120% at 65% 50%,
        rgba(246, 191, 164, 0.72) 0%,
        rgba(246, 191, 164, 0.47) 50%,
        rgba(246, 191, 164, 0) 100%
      ),
    /* 右上角 */
    radial-gradient(
        13% 36% at 89% 5%,
        rgba(249, 191, 160, 0.95) 0%,
        rgba(249, 191, 160, 0.62) 50%,
        rgba(249, 191, 160, 0) 100%
      ),
    /* 右下角 */
    radial-gradient(
        11% 40% at 88% 95%,
        rgba(248, 177, 132, 1) 0%,
        rgba(248, 177, 132, 0.65) 50%,
        rgba(248, 177, 132, 0) 100%
      ),
    /* 右中橙 */
    radial-gradient(
        12% 60% at 95% 56%,
        rgba(244, 173, 122, 1) 0%,
        rgba(244, 173, 122, 0.65) 50%,
        rgba(244, 173, 122, 0) 100%
      ),
    /* 左冷斑 */
    radial-gradient(
        5% 40% at 0% 34%,
        rgba(170, 182, 251, 0.75) 0%,
        rgba(170, 182, 251, 0.49) 50%,
        rgba(170, 182, 251, 0) 100%
      ),
    /* 左暖柱 */
    radial-gradient(
        8% 110% at 12% 50%,
        rgba(243, 206, 194, 0.6) 0%,
        rgba(243, 206, 194, 0.39) 50%,
        rgba(243, 206, 194, 0) 100%
      ),
    /* 左上亮 */
    radial-gradient(
        5% 14% at 12% 7%,
        rgba(226, 213, 232, 0.85) 0%,
        rgba(226, 213, 232, 0.55) 50%,
        rgba(226, 213, 232, 0) 100%
      ),
    /* 中上亮 */
    radial-gradient(
        9% 26% at 48% 3%,
        rgba(224, 220, 238, 0.7) 0%,
        rgba(224, 220, 238, 0.45) 50%,
        rgba(224, 220, 238, 0) 100%
      ),
    linear-gradient(
      180deg,
      #c8c6eb 0%,
      #ccc7e9 8%,
      #d6cae3 25%,
      #f1d5d9 41%,
      #fadbd8 58%,
      #fcd4c8 75%,
      #fbe1da 91%,
      #fbe1da 100%
    );
  box-shadow: 0 10px 30px rgba(122, 92, 142, 0.16);
}
/* Hero 文案区：图标块在左，标题/描述/数据芯片在右。
   这里的内边距就是整张卡的内边距（.hero 自身零内边距）。
   min-width: 0 是给内部 flex 项兜底：描述是一整段长文本，
   不写它会让 .hero-body 的自动最小尺寸把卡片顶宽、溢出圆角。
   左内边距 26.5 与 gap 18.5 是量出来的：图标块左边距 26.5、文字列起点 113.5 */
.hero-main {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: flex-start;
  gap: 19px;
  /* 左内边距 25.5 与上内边距 24 是量出来的：效果图里橙色图标块的左上角在
     卡片内 (26.5, 25.0)。注意 .hero 自带 1px 描边，而绝对定位/内边距的包含块
    是 padding box，所以 padding 要再减 1 才是图标块的卡片内坐标。
     gap 19：图标块右缘 95.0 + 19 = 文字列起点 114.0，与效果图标题墨迹起点一致。 */
  min-width: 0;
  padding: 24px 30px 20.5px 25.5px;
}
/* 图标块：橙渐变圆角方块 + 白色火焰。
   渐变方向按效果图实测：右上最亮 (#f85600)、右下最深 (#f13802)，
   绿通道从左上的 105 递减到右下的 56，所以是 135°。
   圆角 18px 也是量出来的（沿顶边扫橙色跨度，拟合半径 ≈36 原图px） */
.hero-icon {
  flex-shrink: 0;
  width: 76px;
  height: 76px;
  border-radius: 20px;
  display: flex;
  align-items: center;
  justify-content: center;
  /* 渐变是拟合出来的，不是目视调的：把方块内（避开中央火焰与圆角）按
     colour = a + bu·u + bv·v 做二维最小二乘，G 通道斜率 (bu,bv)=(-39.4,-56.4)。
     比值 56.4/39.4 = 1.43，正好等于 145° 时 cos/sin = 0.819/0.571 = 1.434，
     所以角度是 **145deg** 而不是原来的 135deg。
     端点由 ΔC = bu/(sin145/1.389) 反解：ΔG = -95.9 → G 从 131 走到 35；
     ΔR ≈ -8（R 基本饱和）、ΔB ≈ -10.5。
     得到 #ff830b → #f92300。原值 #f67100 → #f13802 在方块下部明显偏暗偏灰
     （实测 (100,170) 处 效果图 (249,72,2) / 实现 (243,79,2)，G 差 +7~+12）。 */
  background: linear-gradient(145deg, #ff830b 0%, #f92300 100%);
  box-shadow: 0 7px 18px rgba(230, 74, 4, 0.34);
}
/* 火焰本体实测 28×36.5 CSS。path 在 24×24 viewBox 里只占 17.02×22.5
   （0.709×0.9375），所以 SVG 盒子要开到 39px 火焰才是 28 宽 */
.hero-icon svg {
  width: 43px;
  height: 43px;
  display: block;
}
.hero-body {
  flex: 1;
  min-width: 0;
  padding-top: 2.4px;
}
.hero-title {
  display: flex;
  align-items: baseline;
  gap: 13px;
  flex-wrap: wrap;
  /* 600 而不是 800：效果图的标题是 PingFang SC Semibold 那一档。
     在只有 400/700 两档的字体上（微软雅黑）600 会被上取到 700，
     在有可变字重的字体上 600 才落得准，所以 600 比 800 更稳。 */
  /* ── 2026-09-20：整块字号放大一档 ──
     原值（标题 23 / 副标语 14 / 描述 9.3 / 芯片 7.75）是从效果图 2× 截图直接 ÷2 量出来的，
     逐像素确实最像，但 9.3px 的描述和 7.75px 的芯片在真实屏幕上读起来吃力。
     以「可读性优先」整体放大，比例关系保持不变（标题仍是描述的 ~2.1 倍）：
       图标块 68.5 → 76、标题 23 → 26、副标语 14 → 16、描述 9.3 → 12.5、
       芯片 7.75 → 11（高 20 → 26）、芯片图标 10.5 → 14。
     放大量是按「描述行仍能单行放下」反推的上限：12.5px 时整行约 362px，
     而 1210 视口下文字列宽 489.5px，还有余量。 */
  font-size: 26px;
  font-weight: 600;
  line-height: 1.15;
  color: var(--hero-ink);
  letter-spacing: 0;
}
.hero-sub {
  font-size: 16px;
  font-weight: 700;
  color: var(--hero-accent);
  letter-spacing: 0;
}
/* 标题→描述、描述→芯片 的两段间距是按「以图标块上沿为基准」的像素差对齐的：
   效果图里描述行顶距图标块上沿 76px、芯片行顶 119px；初版分别是 66/108，
   差了 5 个 CSS px（不是按卡片顶对齐——卡片顶在效果图里被圆角描边糊掉 1~2px，不可靠） */
.hero-desc {
  /* 6 而不是 6.5：试过把描述行（连带芯片行）下移 0.5 CSS 去凑整块互相关的
     dy=-1，结果反而更差（描述行窗口 MAE 15.70 → 17.48，全卡 11.30 → 11.44）。
     原因是 0.5px 的位移把文字推到了另一个子像素相位，抗锯齿变差，
     收益（约 -0.02 全卡 MAE）远小于这一档相位损失。
     结论：这类 0.5 CSS 的偏差属于「跨平台文字渲染」的噪声底，不要再追。 */
  margin: 6px 0 0;
  /* 字号 9.3 是按「整行墨迹总宽」定的，不要再用字距中位数去反解——
     那个口径只有 0.5 原图px 的分辨率，而且在混合文本（含拉丁字母 B 和全角标点）上
     连续段之间的斜率能差到 1.7~19.0，线性拟合在副标语上自检就偏了 1%。
     整行墨迹总宽最稳：效果图 114.0..398.0 = 284.0 CSS，
     实现 9.25px 时是 113.5..396.0 = 282.5 CSS，按线性外推 → 9.30px。
     （另注：测量时窗口起点不能设成内容起点，否则会把窗口边界当成墨迹起点——
     之前正是这么把效果图描述行误测成 277 CSS，得出「宽了 2.5%」的错误结论。） */
  font-size: 12px;
  line-height: 1.55;
  color: var(--hero-ink-dim);
}
.hero-stats {
  display: flex;
  flex-wrap: wrap;
  gap: 7.5px;
  margin-top: 12px;
}
/* 芯片：纯白药丸 + 极淡投影，没有描边（效果图里芯片和底色之间只有明度差）。
   高度 20、字号 7.75 都是量出来的（芯片白区 40 原图px，内文 CJK advance 15.2 原图px）。
   左右内边距 6.75 是用「药丸左右边缘」对比定的：实现端 DOM 实测药丸1 x114.0 w123.55，
   效果图同口径量到 x≈114.0 w≈121.65，起点一致、宽度差 1.9（来自字重的抗锯齿差，见下）。
   间距 7.3 是重新量过的：在药丸垂直中部行（y184..196，此处药丸投影还没落下来）
   按「药丸亮度 / 间隙底色 的中点」读边缘——
     效果图 药丸1右 469.54、药丸2左 484.55 → 空隙 15.0 原图px = 7.5 CSS；
            药丸2右 643.48、药丸3左 658.34 → 空隙 14.86 原图px = 7.43 CSS。
     实现端同口径读出来是 6.41 / 6.38（真值 6.25，说明这个口径本身有 +0.15 的偏差），
     扣掉偏差 → 效果图真值 ≈ 7.3。原来写 6.25 时三颗药丸整体比效果图窄 4 CSS。 */
.hero-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 26px;
  padding: 0 8px;
  font-size: 11px;
  line-height: 1;
  color: var(--hero-chip-ink);
  /* 药丸是半透明白，不是纯白——这点很容易做错。
     反解方法：同一 x 上取药丸正上/正下方的背景求平均当底色，再解
     pill = bg·(1-α) + 255·α。三个药丸压在完全不同的底色上
     （背景归一化后分别是 #f7dddc / #f6dbd7 / #f8d5c2），
     解出的 α 却是 0.607 / 0.595 / 0.620 —— 高度一致，反过来也验证了白点归一化是对的。
     原来写 #fff（α=1）会让药丸在橙底上明显过亮、过硬。 */
  background: rgba(255, 255, 255, 0.61);
  border-radius: 999px;
  box-shadow: 0 2px 5px rgba(126, 84, 92, 0.12);
  /* 这里刻意不写 font-variant-numeric: tabular-nums。
     效果图里数字的 advance 实测 8.0 原图px = 4.00 CSS（在 "2026" 上取相邻空隙中心间距），
     开了 tabular-nums 会到 ≈4.58 CSS，19 个字符的时间戳就宽出 12.5 CSS——
     这正是 chip1 原来比效果图宽 10.5 CSS 的主因（CJK 段「更新于」两边都是 31.0，完全一致）。 */
}
/* 时间戳 / 数字：效果图里这一段是**粗体**，不是「只把颜色调深」。
   判据用的是「墨量加权等效色」里的**总墨量**（同一区域、同一底色口径下，粗细只影响总量）：
     芯片1 时间戳   效果图 131896 / 实现 83143  → 效果图多 59%
     芯片2 标签     效果图  74399 / 实现 64910  → 只多 15%
     芯片1 标签     效果图  36375 / 实现 35182  → 只多  3%
     时钟图标       效果图  18248 / 实现 17426  → 只多  5%
   也就是说只有「数字」这一段显著变粗，其余都只是字体栅格化差异（效果图是 macOS 的
   PingFang/SF Pro，本机是 Microsoft YaHei/Segoe UI，描边本来就粗 3~15%）。
   另外注意峰值墨量两边几乎一样（229 vs 216）——这说明变粗的是**笔画宽度**而不是颜色，
   单看「最暗像素」会漏掉这个结论。
   负字距 -0.35 是为了把粗体多出来的宽度压回去：Segoe UI 的数字比效果图的 SF Pro 宽，
   19 个字符的时间戳加粗后会宽出 ≈4.9 CSS。 */
.hero-chip b {
  color: #212137;
  font-weight: 700;
  letter-spacing: -0.35px;
}
/* 芯片里「图标 + 文案」要各自成组（见 app.js 里的 .hc-t 包裹层）：
   .hero-chip 是 inline-flex，gap 会作用在**任意两个 flex 子元素之间**。
   chip1 原本是 [图标, "更新于 ", <b>时间戳</b>] 三个 flex 项，
   于是「更新于」和时间戳之间被多加了一个 flex gap（DOM 实测文本节点右缘 410 → b 盒左缘 415，
   正好等于当时的 gap 值），而效果图那里只有 2.5 CSS（就是那个空格本身的宽度）。
   把文案和 <b> 包进同一个 span 后，两者回到普通行内流，空格恢复正常。
   注意：不要给这个包裹层加 white-space: pre —— 正常空白模式下，
   文本与行内元素之间的单个空格本来就不会被折叠，加了反而容易踩换行坑。 */
/* 芯片内的小图标：全部内联 SVG，尺寸随字号走 */
/* 0.2px 的模糊是量出来的，不是随手加的：效果图里**装饰性图形**的边缘是软的，
   而文字和卡片描边是锐的（卡片 1px 上边框的过渡只跨 2 个 device px，说明是原生 2× 渲染），
   所以软的是图形本身，不是整张图被缩放。
   芯片3 图标横剖：效果图从底色到墨色跨 ~3 个 device px，实现只跨 ~1 个。
   0.2 CSS 在 4× 下是 0.8 device px，降采样回 2× 后 σ≈0.4 device px，方向对上了。
   试过 0.4：全卡 MAE 只再降 0.01（11.24 → 11.23），但 14× 放大后底部那个 V 缺口被糊平，
   形状反而比效果图更不清楚，所以取 0.2 这个「软而不糊」的值。 */
.hc-i {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
  display: block;
  filter: blur(0.2px);
}

/* ---- Hero 右侧装饰面板 ----
   效果图里是三块磨砂玻璃圆角方块呈扇形叠放：火焰块在左后、上扬箭头块在右后、
   柱状图块在最前。
   位置/尺寸/旋转角不是目视估的——磨砂玻璃压在浅色渐变上对比度极低，
   目视读角点误差能到 4° 以上（同一个矩形的上边和左边会算出相反的旋转方向）。
   最后用的是「周长响应最大化」的改进版：在矩形周长上采 240 个点，
   目标函数取「高通值超阈值的采样点占比」（命中率）而不是均值——
   这样被前面面板压住的那几条边只是不计分，不会把拟合结果往遮挡方向拉。
   再做分阶段坐标下降，并把矩形画回参考图核对。最终命中率 A 65.8% / B 55.4% / C 84.6%
   （A、B 各有一段被压住，命中率天然上不去；C 在最前面，基本整圈可见）。
   三块面板尺寸并不相同，效果图里确实是这样：火焰块 65.35×59.48、上扬块 63.5×62、
   柱状块 82.3×59.15（明显更扁）。
   纯装饰，aria-hidden；卡片不够宽时整块隐藏（见文末的窄屏媒体查询）。 */
.hero-art {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  /* ── 2026-09-20：整簇缩小并贴向右缘 ──
     字号放大之后，文案与装饰抢地方了。实测量（卡片内 x，卡片 640 宽）：
       描述行墨迹右缘 503.7、最后一颗芯片右缘 501.3、装饰簇原尺寸左缘 451.4
     —— 描述最后几个字直接压在火焰面板上（放大看过，字和面板左边缘叠在一起）。
     这不是「再挪 2px」能解决的：效果图那套「小字 + 大装饰」的比例本身就要求
     描述行只有 284 宽（9.3px）。字号提到 12/11 之后单行就是 367，怎么挪都撞。
     所以让装饰让步（它是纯装饰，aria-hidden），文案不让。分三档，阈值两套版式各一份
     （见文件下方）：桌面 隐藏/缩小/原尺寸 的分界在 635、700，移动在 577、636。
     用「整簇一起缩放」而不是逐块缩：逐块缩会保住三块的中心距，簇反而摊得更开
     （实测按各自中心缩 0.75 后簇宽 129，右缘会顶出卡片 3px）。
     缩放同时把圆角 8.5→6.6、描边 1→0.78、backdrop 模糊 6→4.7 一起等比带走，
     这正是一整簇「变小」该有的样子。
     transform-origin 取 100% calc(50% + 16.6px)：横向上以卡片右缘为基准，
     纵向上以装饰簇自身的中心（= 卡片中心 +16.6）为基准，
     这样缩放不会把整簇往上抬（用 50% 的话会平白上移 4px）。 */
  transform-origin: 100% calc(50% + 16.6px);
}
/* 装饰簇按「卡片自己的宽度」分三档让位（理由见 .hero-art 的注释）。
   装饰簇原尺寸时左缘 = 卡片宽 − 188.6、缩到 0.76 并右移 13 后 = 卡片宽 − 130.3
   （188.6 = 右缘留白 40.9 + 簇宽 147.7；缩 0.76 后右缘留白 31.1、簇宽 112.3，再右移 13）。
   文案占到哪里是实测的：桌面 121.5..492.3、移动 84..434.8，两边都留 12 净空：
     · 桌面版式（≥821px）：原尺寸需 ≥692.9（取 700）；0.76 需 ≥634.6（取 635）
     · 移动版式（≤820px）：原尺寸需 ≥635.2（取 636）；0.76 需 ≥576.9（取 577）
   桌面与移动的文案宽度差 58（两套字号：26/16/12.5/11 与 20.5/13/11.5/10），
   所以两套阈值必须分开写。合成一条（取交集）会让移动端在 577~634 这段白缩一档、
   把本来放得下的装饰整块藏掉——610 宽时净空还有 45，却什么都不显示。
   635 这条线**必须 ≤640**——1210px 视口下卡片正好 640，那是基准版式，
   装饰面板必须还在（缩到 0.76 时左缘 509.4，与描述行留 17.1 净空）。
   一开始把线画在 641，结果 640 宽时整块被隐藏，基准视口上装饰直接没了。 */
@media (min-width: 821px) {
  @container (max-width: 634px) {
    .hero-art {
      display: none;
    }
  }
  @container (min-width: 635px) and (max-width: 700px) {
    .hero-art {
      transform: translateX(13px) scale(0.76);
    }
  }
}
@media (max-width: 820px) {
  @container (max-width: 576px) {
    .hero-art {
      display: none;
    }
  }
  @container (min-width: 577px) and (max-width: 635px) {
    .hero-art {
      transform: translateX(13px) scale(0.76);
    }
  }
}
.ha-card {
  position: absolute;
  overflow: hidden;
  /* 圆角实测 ≈8.5：在逆旋转图上取左上角轮廓点拟合圆，得 r≈16 原图 px（=8 CSS）；
     再对照 8× 放大的角点截图校正到 8.5 */
  border-radius: 8.5px;
  /* 磨砂玻璃：半透明白 + 描边高光 + 背景模糊。
     白色叠加强度必须用「白点归一化之后」的对比来定，否则会得出反向结论：
     效果图整张有 ×0.965 的色阶压缩，不补偿的话效果图的玻璃看起来偏暗，
     于是会误判成「实现的玻璃太白」，把白纱越调越弱。
     ── 2026-09-20 修正 ──
     上一版（135° 左上最强 0.26/0.15/0.10 + 底纱 0.23，描边 0.52）是在
     「LANCZOS 降采样 + 旧背景渐变」两个前提下定出来的，现在两个都不成立了：
       ① 实现端截图是 640 CSS 的 **4×** 渲染（2560px），降到 2× 必须用 BOX（面积平均）。
          用 LANCZOS 会在边缘产生振铃，把文字/边界的最暗像素从 89（= CSS 的 #545365）压到 61，
          凭空多出对比，结论自然反过来。
       ② 卡片背景渐变后来整体重做过，面板底下透出来的底色已经不同了。
     按 BOX 口径重测面板区域（MAE / 实现−效果图 偏色）：
       A 19.8 (-3,+17,+30)   B 11.4 (-5,+7,+15)   C 13.0 (-3,-6,-7)
     即左上那块（A）白纱过强、右下那块（C）反而略弱 —— 方向和旧版正好相反，
     所以把 135° 改成 315°（右下最强）。
     描边同时从 0.52 降到 0.28：沿面板 A 左边界取横向剖线，效果图的边缘高光只比
     内部亮 +3，实现是 +17；面板 C 同理 +11 vs +22 —— 旧值明显过硬。
     5× 放大对照也印证：效果图里玻璃块的可见性主要来自那圈细高光，
     内部更接近「磨砂去饱和」而不是「加白提亮」。
     ── 再者：三块面板的白纱**不能共用同一个值** ──
     改完描边和方向后再测，A 仍然偏亮（+9,+20）、C 反而偏暗（-12,-15）。
     按 Δα ≈ Δ通道 /(255−该处底色) 反解：A ≈ -0.21、B ≈ +0.05、C ≈ +0.19，
     即等效 α 要分别落在 0.0 / 0.30 / 0.55 附近。这其实也符合层次逻辑：
     最前面的柱状图块最「实」，后面的火焰块最「透」。
     所以放弃共用渐变，改成每块各给一个平铺白纱。 */
  background: rgba(255, 255, 255, 0.21);
  border: 1px solid rgba(255, 255, 255, 0.28);
  /* 效果图里玻璃是「被磨白」的——饱和度略降而不是升，所以 saturate 给到 1 附近 */
  backdrop-filter: blur(6px) saturate(100%);
  -webkit-backdrop-filter: blur(6px) saturate(100%);
  box-shadow: 0 8px 20px rgba(190, 110, 70, 0.16);
}
/* 定位约定：right/top 定的是「面板中心」到卡片右边/顶边的距离，
   靠 translate(50%, -50%) 把中心对齐过去，后面再接旋转。
   注意卡片自己带 1px 描边，绝对定位的包含块是 padding box（比 border box 内缩 1px），
   所以这里的值 = 效果图量到的值 − 1。
   （实测：写 right:152 时 getBoundingClientRect 量到 fromRight = 153。） */
/* 火焰块（左后）：65.35×59.48，中心距右 152、距卡片竖向中心 +1.5，逆时针 8.19° */
.ha-flame {
  right: 151px;
  top: calc(50% + 1.5px);
  width: 65.35px;
  height: 59.48px;
  transform: translate(50%, -50%) rotate(-8.19deg);
  /* 最后面那块最透：效果图里它几乎只剩一圈细高光，内部和底色差 ≈+2 级 */
  background: rgba(255, 255, 255, 0.03);
}
/* 面板内的火焰：22×26.5，中心在面板中心偏左 11.5、偏下 2.25
   （效果图里它确实不居中——被柱状图块压掉右边一块后视觉上才「居中」）。
   viewBox 收紧到火焰路径自身的包围盒，再配 preserveAspectRatio="none"，
   这样量到的 22×26.5 就是渲染尺寸，不受路径内空白影响。
   注意 left/top 要再减 1：卡片带 1px 描边，绝对定位的包含块是 padding box */
.ha-flame svg {
  position: absolute;
  left: 9.18px;
  top: 17.74px;
  width: 22px;
  height: 26.5px;
}
/* 上扬箭头块（右后）：63.5×62，中心距右 78.55、距卡片竖向中心 −11.04，顺时针 12.4° */
.ha-trend {
  right: 77.55px;
  top: calc(50% - 11.04px);
  width: 63.5px;
  height: 62px;
  transform: translate(50%, -50%) rotate(12.4deg);
}
/* SVG 自带 63.5×62 的 viewBox，1 单位 = 1 CSS px。
   这里必须写死尺寸：用 width/height:100% 的话百分比是相对「内容盒」解析的，
   会被卡片那 1px 描边各吃一点，整幅图缩到 97.6% 并整体偏移 1px
   （实测 svg 渲染成 61.5×60，坐标全错）。left/top 用 −1px 抵消描边。 */
.ha-trend svg {
  position: absolute;
  left: -1px;
  top: -1px;
  display: block;
  width: 63.5px;
  height: 62px;
}
/* 柱状图块（最前）：82.3×59.15，中心距右 101.05、距卡片竖向中心 +27.94，顺时针 12.2°。
   压在最上层，所以投影给重一点，制造层次 */
.ha-chart {
  right: 100.05px;
  top: calc(50% + 27.94px);
  width: 82.3px;
  height: 59.15px;
  transform: translate(50%, -50%) rotate(12.2deg);
  /* 最前面那块最实 */
  background: rgba(255, 255, 255, 0.52);
  box-shadow: 0 10px 24px rgba(186, 106, 66, 0.22);
}
.ha-chart svg {
  position: absolute;
  left: -1px;
  top: -1px;
  display: block;
  width: 82.3px;
  height: 59.15px;
}

/* ============ 页首 Hero 卡 · 夜间模式 ============ */
/* 上面那张卡是「实色柔和渐变」，底色固定为浅色，卡内文字用的是 --hero-* 这组
   **不随主题翻转**的固定色 —— 白天没问题，夜间就变成一块刺眼的浅色高光块：
   浅紫粉底压在深紫壁纸上像是漏光的白斑，与周围玻璃卡完全不是一个体系。
   这里补一套夜间配色，思路是「同色相、压暗提纯」，而不是简单把整块调灰或降低不透明度：
     · 底：保留「紫 → 玫红 → 烧橙」的横向走向，整体压到 #2a2340 → #63302a，
       这样它仍是页面里最亮的那块品牌色，但明度落回暗色主题该有的区间；
     · 装饰斑：白天的橙色带是「加白提亮」，夜间反转为「加纯色提亮」——
       用低透明度的橙 / 品红 / 紫罗兰径向光斑重新点出来，
       否则纯压暗会把效果图里那条最有辨识度的橙色带压成一块脏褐；
     · 卡内固定色：--hero-* 一并换成浅色版，标题近白、副标语用亮橙
       （纯 #fa3904 压在深紫底上只有 ~2.5:1，小字直接糊掉，必须提亮到 #ff8f5e）。
   注意 specificity：这组选择器 (0,2,0) 高于上面的 .hero / .hero-chip / .ha-card，
   所以不依赖书写顺序；但 .ha-flame / .ha-chart 的 background 原写在 .ha-card 之后，
   被这里的 .ha-card 规则盖住后要各自补回来。 */
:root[data-theme='dark'] .hero {
  --hero-ink: #f6eff0; /* 标题：白天是近纯黑，夜间翻成暖白 */
  --hero-ink-dim: #c5b6c8; /* 描述：中段玫红底上 ≈5.5:1，稳在 AA */
  --hero-accent: #ff8f5e; /* 副标语：烧橙在深底上不够看，提亮到亮橙 */
  --hero-chip-ink: #ded6e6; /* 芯片文字 */

  /* 描边从白 0.62 收到 0.14：白天那圈白边是为了在浅色卡上「立口沿」，
     夜间底已经足够深，再用 0.62 会白得发虚 */
  border-color: rgba(255, 255, 255, 0.14);
  background:
    /* 右上暖核 */
    radial-gradient(18% 55% at 77% 2%, rgba(255, 126, 74, 0.42) 0%, rgba(255, 126, 74, 0) 100%),
    /* 右下暖核 */ radial-gradient(20% 60% at 80% 98%, rgba(255, 110, 64, 0.38) 0%, rgba(255, 110, 64, 0) 100%),
    /* 中段玫红，补上 58%~75% 那段粉色的位置 */
    radial-gradient(34% 70% at 58% 48%, rgba(226, 110, 150, 0.2) 0%, rgba(226, 110, 150, 0) 100%),
    /* 左缘冷紫，对应白天那块偏蓝的冷斑 */
    radial-gradient(14% 60% at 4% 36%, rgba(122, 106, 214, 0.3) 0%, rgba(122, 106, 214, 0) 100%),
    linear-gradient(180deg, #2a2340 0%, #2f2646 30%, #3f2848 55%, #522c3c 78%, #63302a 100%);
  /* 白天是带紫的柔和投影；夜间投影只在深底上「压出层次」，颜色去掉以免泛紫 */
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.42);
}
/* 橙色图标块本身不受主题影响（橙渐变 + 白火焰在深底上更醒目），
   只把外发光换成一档更亮的橙，免得光晕在小尺寸下糊在暗底上看不见 */
:root[data-theme='dark'] .hero-icon {
  box-shadow: 0 7px 22px rgba(249, 96, 16, 0.45);
}
/* 芯片：白天是「半透明白药丸」（解出的 α≈0.61），压在深底上会变成三块刺眼的白糖。
   翻成低透明度白纱——与全站暗色玻璃同一思路，靠描边级的明度差而不是纯白来成形。
   0.12 是「看得见是颗药丸」与「不抢正文」之间的折中：再低就和底分不开，
   再高整行会亮成一条白带，把上面的标题都压下去。
   阴影同步转黑（白天那层 rgba(126,84,92,.12) 是暖灰，深底上等于没有） */
:root[data-theme='dark'] .hero-chip {
  background: rgba(255, 255, 255, 0.12);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
}
/* 芯片里的「值」（时间戳 / 数字）：白天是 #212137 深墨，夜间必须是整行最亮的一档 */
:root[data-theme='dark'] .hero-chip b {
  color: #fff;
}
/* 芯片内联图标：这两个颜色是 SVG 里写死的（时钟的 stroke #646b84、
   「大分类」实心块的 fill #646b84），深底上只剩一团灰影，需要各自提亮。
   两个选择器分开写是因为它们的着色方式不同：
     · 时钟用 stroke（属性写在 svg 上，CSS 的 stroke 会覆盖表现属性并向下继承），
       用 svg[stroke] 精确命中，避免误伤两根柱状图标（它们没有 stroke 属性）；
     · 「大分类」用实心 path 的 fill，同理只取没有 stroke 的那两个图标里的 path
       ——柱状图标的色块是 rect，不会被这条命中，它们的绿/紫/青在深底上本来就够亮。 */
:root[data-theme='dark'] .hero-chip svg[stroke] {
  stroke: #a9b2d0;
}
:root[data-theme='dark'] .hero-chip svg:not([stroke]) path {
  fill: #b7bdd8;
}
/* 右侧装饰玻璃簇：白天靠「白纱叠在浅色渐变上」成形，夜间同样的白纱会亮成三块塑料板。
   按全站暗色玻璃的口径换算——底色变深后白纱要降到 0.09 左右才等价，
   描边同步收一档，投影转黑并加重（暗底上的投影是唯一能拉开前后层次的线索）。
   三块仍保持「后透前实」的层次：火焰块最透、柱状块最实 */
:root[data-theme='dark'] .ha-card {
  background: rgba(255, 255, 255, 0.09);
  border-color: rgba(255, 255, 255, 0.2);
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.32);
}
:root[data-theme='dark'] .ha-flame {
  background: rgba(255, 255, 255, 0.04);
}
:root[data-theme='dark'] .ha-chart {
  background: rgba(255, 255, 255, 0.16);
  box-shadow: 0 10px 24px rgba(0, 0, 0, 0.42);
}

/* 今日天气卡：右栏第一张卡（>1180px）／左侧抽屉里深色模式开关之上（≤900px）。
   背景由 JS 按天气写入天空渐变，
   圆角/裁切/投影与右栏其它 .rail-card 保持同一档，只是底色换成天空色而非玻璃。
   两种落点都是竖排小卡：城市 → 大温度 → 区间，居中对齐。
   卡片本身由 app.js 在两个宿主之间搬运，同一时刻只在一处；
   900~1180px 这段两端都没有它的位置（右栏已隐藏、抽屉还没出现），整段不显示 */
.hero-weather {
  position: relative;
  /* 独立卡片：自己承担圆角、裁切与投影（原先这些都由 .hero 代劳） */
  border-radius: 18px;
  overflow: hidden;
  box-shadow: var(--glass-shadow);
  /* 两个宿主（.rail 与 .sb-foot）都是纵向 flex/block，卡片默认就会被拉满宽度；
     写死 100% 是为了不依赖父级——宿主的 display 是 contents，卡片直接挂到
     .rail / .sb-foot 上，两处的盒模型不同，交给父级反而会算错 */
  width: 100%;
  /* 高度下限：只显示「城市 + 温度」的加载骨架时不至于塌成一条 */
  min-height: 148px;
  /* 对称内边距。原来那套 88px 左内距 + 104px 左缘渐隐是给「Hero 右半边」
     写的——天空色要与左侧玻璃区无缝接上才需要渐隐，现在它是一张独立的卡，
     四边都是自己的天空色，渐隐和为它预留的左内距都没有存在意义 */
  padding: 16px 18px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  color: #fff;
  background: var(--glass-lo);
  transition: background var(--dur-slow) ease;
}
/* hidden 会被上面的 display:flex 覆盖，必须显式兜住（定位失败时整个区不占位） */
.hero-weather[hidden] {
  display: none;
}
/* 暗罩：无论天空底色深浅，白字都稳在可读对比度上 */
.hero-weather::before {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(180deg, rgba(12, 20, 38, 0.12), rgba(12, 20, 38, 0.3));
}
.hero-weather > * {
  position: relative;
  z-index: 1;
}
/* ==================== 天气装饰层 ====================
   背景渐变只表达「天空的色彩」，这里补上「看得见的天气符号」：
   晴出太阳/月亮、多云出云、雨雪雾雷各有其形。
   全部为纯装饰——pointer-events:none 不挡点击，aria-hidden 不进读屏；
   用 .hero-weather > .hw-fx 提升特异性，把它压到文字之下（上面那条
   `.hero-weather > *` 会给出 z-index:1，0,2,0 > 0,1,1 所以能覆盖）。
   装饰集中在右上角与边缘，避开居中的城市/温度文字；
   雨丝与雪花是铺满的低透明度氛围层，不抢文字可读性 */
.hero-weather > .hw-fx {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
  transition: opacity var(--dur-fast) ease;
}
.hw-fx i {
  position: absolute;
  display: block;
}
/* 编辑态隐藏：编辑层要盖住整块区域，背后有漂移的云会干扰输入 */
.hero-weather.editing .hw-fx {
  opacity: 0;
}

/* ---- 晴（白天）：大太阳 + 双层光芒反向旋转 + 光晕呼吸 + 耀斑 ----
   本体 92px、光芒扫到 ~230px，从右上角「溢出去」一半，
   构图有张力，这块天空也才真的像晴天 */
.hw-fx-sun i:nth-of-type(1) {
  top: -20px;
  right: -10px;
  width: 92px;
  height: 92px;
  border-radius: 50%;
  /* 内芯到外缘四段色：白热 → 暖黄 → 金 → 橙，边缘不发灰 */
  background: radial-gradient(circle at 40% 36%, #fffef6 0 20%, #fff6cf 34%, #ffe083 56%, #fbbf24 78%, #f59e0b 100%);
  box-shadow:
    0 0 22px rgba(255, 236, 170, 0.95),
    0 0 60px rgba(255, 208, 96, 0.6),
    0 0 120px rgba(251, 191, 36, 0.32);
  animation: hwSunBreath 6.5s ease-in-out infinite;
}
/* 内层光芒：16 道细扇区，60s 一转；上下两处 mask 收边，只从太阳外缘射出 */
.hw-fx-sun i:nth-of-type(1)::before {
  content: '';
  position: absolute;
  left: 50%;
  top: 50%;
  width: 190px;
  height: 190px;
  margin: -95px 0 0 -95px;
  border-radius: 50%;
  background: conic-gradient(from 0deg, rgba(255, 244, 200, 0.72) 0 2.2deg, transparent 2.2deg 22.5deg);
  -webkit-mask-image: radial-gradient(circle, transparent 0 34%, #000 43%, #000 62%, transparent 86%);
  mask-image: radial-gradient(circle, transparent 0 34%, #000 43%, #000 62%, transparent 86%);
  animation: hwSpin 60s linear infinite;
}
/* 外层光芒：8 道长扇区，反向 95s 慢转、更淡更长，两层错开转速才有「闪耀」感 */
.hw-fx-sun i:nth-of-type(1)::after {
  content: '';
  position: absolute;
  left: 50%;
  top: 50%;
  width: 232px;
  height: 232px;
  margin: -116px 0 0 -116px;
  border-radius: 50%;
  background: conic-gradient(from 12deg, rgba(255, 250, 230, 0.5) 0 3.5deg, transparent 3.5deg 45deg);
  -webkit-mask-image: radial-gradient(circle, transparent 0 40%, #000 50%, #000 66%, transparent 92%);
  mask-image: radial-gradient(circle, transparent 0 40%, #000 50%, #000 66%, transparent 92%);
  animation: hwSpin 95s linear infinite reverse;
}
/* 耀斑：几个错位的小光点缓慢明灭，模拟镜头光斑 */
.hw-fx-sun i:nth-of-type(2) {
  top: 46px;
  right: 42px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: radial-gradient(circle, #fff 0 40%, transparent 72%);
  box-shadow:
    -30px 34px 0 -1px rgba(255, 255, 255, 0.75),
    18px 52px 0 -2px rgba(255, 250, 220, 0.6),
    -52px 12px 0 -2px rgba(255, 255, 255, 0.5);
  animation: hwTwinkle 5.2s ease-in-out infinite;
}
@keyframes hwSunBreath {
  0%,
  100% {
    transform: scale(1);
  }
  50% {
    transform: scale(1.045);
  }
}
@keyframes hwSpin {
  to {
    transform: rotate(360deg);
  }
}

/* ---- 晴（夜间）：大弯月 + 月晕 + 两簇错落闪烁的星 ---- */
.hw-fx-moon i:nth-of-type(1) {
  top: -16px;
  right: -6px;
  width: 84px;
  height: 84px;
  border-radius: 50%;
  /* 内阴影偏移挖出月牙：比叠两个圆省事，也不依赖天空底色 */
  box-shadow: inset -23px -8px 0 0 #fff9e2;
  filter: drop-shadow(0 0 16px rgba(255, 248, 214, 0.8));
  transform: rotate(-28deg);
}
/* 月晕：一圈极淡的大圆，让月亮「发光」而不是贴在天空上 */
.hw-fx-moon i:nth-of-type(3) {
  top: -56px;
  right: -44px;
  width: 164px;
  height: 164px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(255, 250, 224, 0.32) 0 26%, rgba(255, 250, 224, 0.11) 46%, transparent 70%);
  animation: hwMoonHalo 8s ease-in-out infinite;
}
/* 星：两簇大小不一的点，错开闪烁周期，避免整片同步的机械感 */
.hw-fx-moon i:nth-of-type(2) {
  top: 10px;
  right: 112px;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: #fff;
  box-shadow:
    24px 22px 0 -1px rgba(255, 255, 255, 0.85),
    46px 6px 0 -1.5px rgba(255, 255, 255, 0.6),
    12px 46px 0 -1.5px rgba(255, 255, 255, 0.5);
  animation: hwTwinkle 4.6s ease-in-out infinite;
}
.hw-fx-moon i:nth-of-type(4) {
  top: 56px;
  right: 58px;
  width: 2.5px;
  height: 2.5px;
  border-radius: 50%;
  background: #fff;
  box-shadow:
    16px -36px 0 -0.5px rgba(255, 255, 255, 0.7),
    -22px 8px 0 -0.5px rgba(255, 255, 255, 0.55);
  animation: hwTwinkle 6.4s ease-in-out 1.4s infinite;
}
@keyframes hwTwinkle {
  0%,
  100% {
    opacity: 0.3;
    transform: scale3d(0.9, 0.9, 1);
  }
  50% {
    opacity: 1;
    transform: scale3d(1.15, 1.15, 1);
  }
}
@keyframes hwMoonHalo {
  0%,
  100% {
    opacity: 0.72;
    transform: scale3d(1, 1, 1);
  }
  50% {
    opacity: 0.96;
    transform: scale3d(1.08, 1.08, 1);
  }
}

/* ---- 云体：多云 / 阴 / 雨 / 雪 / 雷共用 ----
   实现是两段内联 SVG（见 app.js 的 HW_CLOUD_SVG）：整朵云只有**一条闭合外轮廓**
   加一层竖向渐变。旧版是「横向大椭圆 + 三团实心圆」靠 box-shadow 拼的，
   每团各带自己的渐变与透明度，交叠处会露出圆与圆的接缝，放大后就是一道硬边——
   「云画碎了」的根源就在这里；单路径没有内部边界，任何尺寸都干净。
   两朵：主云从右上方压过来（占约半个天气区），副云更小更淡、在左前方反向漂移撑出纵深。
   z-index 2：压在雨滴 / 雪花（z-index 1）之上，雨雪看起来才是「从云后落下」 */
.hw-cloud {
  position: absolute;
  display: block;
  z-index: 2;
  /* SVG 默认按视口裁剪，不放开的话那层柔和投影会被切掉一圈 */
  overflow: visible;
  filter: drop-shadow(0 14px 26px rgba(96, 128, 170, 0.26));
}
.hw-cloud-shape {
  fill: url(#hwCloudGrad);
}
/* 云身三段色：止于 CSS，换天气只改色值。白顶 → 冷调中段 → 灰蓝底，给出体积感 */
.hw-cloud-s1 {
  stop-color: #ffffff;
}
.hw-cloud-s2 {
  stop-color: #f4f9fe;
}
.hw-cloud-s3 {
  stop-color: #dbe7f4;
}
/* 阴天：整体转灰、去掉高光，和「多云」的亮白云拉开区别 */
.hw-fx-overcast .hw-cloud-s1 {
  stop-color: #f1f4f9;
}
.hw-fx-overcast .hw-cloud-s2 {
  stop-color: #e2e9f2;
}
.hw-fx-overcast .hw-cloud-s3 {
  stop-color: #c9d5e4;
}
/* 主云：84×56 是 viewBox 24×16 的等比尺寸（宽高比必须守住，否则云会被拉变形）。
   ⚠️ 这朵云必须整朵完整地落在右上角，不能像原来那样「往右上溢出卡片」——
   云的可辨识部分（起伏的云顶）在 y 4~12，而卡片只有 148px 高、文字占掉中间，
   一旦让它溢出顶边，露出来的就只剩平坦的云底，看上去是块圆角方块而不是云
   （实测裁到 y 0~58 时就是这个结果）。完整放下后云占 y 6~62，
   而「多云」二字的字形从 y≈72 起、温度那行的「°C」最右只到 x≈162，
   横竖都错开，白字不会再压在白云上。
   尺寸从 190×127 收到 84×56：天气卡不再是 Hero 的右半边（约 340px 宽），
   而是右栏里 286px 宽的独立卡，原尺寸会横跨 2/3 张卡、把文字糊掉 */
.hw-cloud-main {
  top: 6px;
  right: 10px;
  width: 84px;
  height: 56px;
  animation: hwCloudDriftMain 19s ease-in-out infinite;
}
/* 副云：更小更淡，反向漂移，撑出前后两层。
   位置从「右侧靠内」改到「左下角」：原位置（right:152px，即距左 34px 起）
   正好盖住居中的温度大字，而原先挡住它的是 Hero 右半区那条 104px 左缘渐隐——
   改成独立卡后渐隐没有了，副云就整个露出来压在白字底下。
   挪到左下角后与文字各行都不相交（文字最左到 x≈70，副云右缘只到 x≈54） */
.hw-cloud-sub {
  top: 102px;
  right: auto;
  left: -30px;
  width: 84px;
  height: 56px;
  opacity: 0.62;
  animation: hwCloudDriftSub 27s ease-in-out infinite;
}
.hw-fx-overcast .hw-cloud-sub {
  opacity: 0.62;
}
@keyframes hwCloudDriftMain {
  0%,
  100% {
    transform: translate3d(0, 0, 0) scale(1);
  }
  35% {
    transform: translate3d(-7px, -2px, 0) scale(1.02);
  }
  70% {
    transform: translate3d(-14px, 1.5px, 0) scale(1.01);
  }
}
@keyframes hwCloudDriftSub {
  0%,
  100% {
    transform: translate3d(0, 0, 0) scale(1);
  }
  40% {
    transform: translate3d(9px, 2px, 0) scale(0.98);
  }
  75% {
    transform: translate3d(14px, -1.5px, 0) scale(1.015);
  }
}

/* ---- 降水层（雨 / 雷雨 / 雪）----
   图块本身——雨滴或雪花的**随机分布**，以及按降水强度（小/中/大/暴）的疏密分档——
   由 app.js 的 hwPrecipTile 生成，这里不再写 background-image。
   分工是刻意的：尺寸、位移、动画这些「布局」留在 CSS；
   每颗雨滴落在哪、有多少颗这种「内容」归 JS。两边各管各的才不会互相打架，
   也才可能在 JS 里用 Math.random 排出真正的随机分布。

   两个槽位沿用装饰层的约定：i3 = 近处（大、亮、快），i6 = 远处（小、淡、慢）。
   下落位移必须**严格等于图块高度**（两层都是 160）才能无缝循环；
   图块里的雨滴被约束在图块内不越界（见 app.js），所以循环点上不会出现半颗雨滴。
   ⚠️ background-size 与下方 keyframes 的位移量是一对，改一处必须改另一处；
   改高度还要回头核对 app.js 里 HW_PRECIP 的 h（图块画布高度），三处必须一致。

   z-index 低于云：视觉上雨雪是「从云后面落下来」的，不会穿过云体 */

/* 雨天 / 雷雨：迎风微倾角（-10°~-12°），雨滴斜落且与修长雨丝朝向保持严密一致 */
.hw-fx-rain i:nth-of-type(3),
.hw-fx-thunder i:nth-of-type(3) {
  inset: -30px -40px;
  z-index: 1;
  background-repeat: repeat;
  background-size: 360px 160px;
  transform: rotate(-10deg);
  animation: hwPrecipFall 7s linear infinite;
}
.hw-fx-rain i:nth-of-type(6),
.hw-fx-thunder i:nth-of-type(6) {
  inset: -30px -40px;
  z-index: 1;
  background-repeat: repeat;
  background-size: 300px 160px;
  transform: rotate(-8deg);
  animation: hwPrecipFall2 15.1s linear infinite;
}
/* 雷雨强对流：风势更急，倾角稍大 */
.hw-fx-thunder i:nth-of-type(3) {
  transform: rotate(-12deg);
}
.hw-fx-thunder i:nth-of-type(6) {
  transform: rotate(-10deg);
}

/* 雪天：下落同时叠加横向双层微风飘摇（S 型曲线飘落） */
.hw-fx-snow i:nth-of-type(3) {
  inset: -20px -30px;
  z-index: 1;
  background-repeat: repeat;
  background-size: 360px 160px;
  animation-name: hwPrecipFall, hwSnowSwayFront;
  animation-timing-function: linear, ease-in-out;
  animation-iteration-count: infinite, infinite;
  animation-duration: 12s, 8s;
}
.hw-fx-snow i:nth-of-type(6) {
  inset: -20px -30px;
  z-index: 1;
  background-repeat: repeat;
  background-size: 300px 160px;
  animation-name: hwPrecipFall2, hwSnowSwayBack;
  animation-timing-function: linear, ease-in-out;
  animation-iteration-count: infinite, infinite;
  animation-duration: 22s, 13s;
}
@keyframes hwSnowSwayFront {
  0%,
  100% {
    transform: translate3d(-14px, 0, 0) rotate(-2deg);
  }
  50% {
    transform: translate3d(14px, 0, 0) rotate(2.5deg);
  }
}
@keyframes hwSnowSwayBack {
  0%,
  100% {
    transform: translate3d(10px, 0, 0) rotate(1.5deg);
  }
  50% {
    transform: translate3d(-10px, 0, 0) rotate(-2deg);
  }
}

@keyframes hwPrecipFall {
  from {
    background-position: 0 0;
  }
  to {
    background-position: 0 160px;
  }
}
@keyframes hwPrecipFall2 {
  from {
    background-position: 0 0;
  }
  to {
    background-position: 0 160px;
  }
}

/* ---- 雾 / 霾 / 沙尘：四条薄纱雾气，多重呼吸弥散与微幅浮沉 ----
   两端渐隐到透明（告别圆角硬边胶囊感），四层具有错落的 Y 轴微浮沉与透明度起伏 */
.hw-fx-fog i:nth-of-type(-n + 4) {
  left: -20%;
  right: -20%;
  border-radius: 999px;
  background: linear-gradient(
    90deg,
    transparent 0%,
    rgba(255, 255, 255, 0.16) 18%,
    rgba(255, 255, 255, 0.32) 50%,
    rgba(255, 255, 255, 0.16) 82%,
    transparent 100%
  );
  filter: blur(9px);
}
.hw-fx-fog i:nth-of-type(1) {
  top: 10%;
  height: 28px;
  animation: hwFogShift1 16s ease-in-out infinite;
}
.hw-fx-fog i:nth-of-type(2) {
  top: 34%;
  height: 36px;
  animation: hwFogShift2 22s ease-in-out infinite;
}
.hw-fx-fog i:nth-of-type(3) {
  top: 58%;
  height: 32px;
  animation: hwFogShift3 18s ease-in-out 1.5s infinite;
}
.hw-fx-fog i:nth-of-type(4) {
  top: 80%;
  height: 24px;
  animation: hwFogShift4 26s ease-in-out 0.8s infinite;
}
@keyframes hwFogShift1 {
  0%,
  100% {
    transform: translate3d(-18px, 0, 0) scaleY(1);
    opacity: 0.8;
  }
  50% {
    transform: translate3d(18px, -2px, 0) scaleY(1.08);
    opacity: 0.55;
  }
}
@keyframes hwFogShift2 {
  0%,
  100% {
    transform: translate3d(20px, 0, 0) scaleY(0.95);
    opacity: 0.6;
  }
  50% {
    transform: translate3d(-20px, 3px, 0) scaleY(1.06);
    opacity: 0.9;
  }
}
@keyframes hwFogShift3 {
  0%,
  100% {
    transform: translate3d(-14px, 2px, 0) scaleY(1.04);
    opacity: 0.85;
  }
  50% {
    transform: translate3d(16px, -2px, 0) scaleY(0.94);
    opacity: 0.5;
  }
}
@keyframes hwFogShift4 {
  0%,
  100% {
    transform: translate3d(16px, -2px, 0) scaleY(0.96);
    opacity: 0.5;
  }
  50% {
    transform: translate3d(-16px, 1.5px, 0) scaleY(1.05);
    opacity: 0.85;
  }
}

/* ---- 雷雨：乌云 + 疾风斜雨 + 真实闪击 + 天光泛白 ----
   云体渐变三段色保持乌云质感，闪电与天光由机械单闪升级为富有张力的双闪爆发 */
.hw-fx-thunder .hw-cloud-s1 {
  stop-color: #dbe4f0;
}
.hw-fx-thunder .hw-cloud-s2 {
  stop-color: #c8d4e4;
}
.hw-fx-thunder .hw-cloud-s3 {
  stop-color: #a9b8cc;
}
.hw-fx-thunder i:nth-of-type(4) {
  /* 云底约 52px，闪电 34 起、高 54，向下伸出云外 */
  top: 34px;
  right: 34px;
  width: 28px;
  height: 54px;
  background: linear-gradient(180deg, #ffffff 0%, #fff6d0 38%, #ffd84d 70%, #ff9800 100%);
  clip-path: polygon(56% 0, 10% 50%, 42% 50%, 24% 100%, 96% 38%, 56% 38%);
  filter: drop-shadow(0 0 10px rgba(255, 255, 255, 0.98)) drop-shadow(0 0 28px rgba(255, 218, 80, 0.8));
  animation: hwFlash 8.5s steps(1, end) infinite;
}
/* 天光泛白：闪电瞬间从右上角向整片天空扩散，配合微弱的先兆闷闪与余晖 */
.hw-fx-thunder i:nth-of-type(5) {
  inset: 0;
  background: radial-gradient(
    circle at 74% 20%,
    rgba(255, 255, 255, 0.9) 0 16%,
    rgba(255, 255, 255, 0.35) 45%,
    transparent 78%
  );
  opacity: 0;
  mix-blend-mode: screen;
  animation: hwSkyFlash 8.5s steps(1, end) infinite;
}
@keyframes hwFlash {
  0%,
  82.5%,
  100% {
    opacity: 0;
  }
  83% {
    opacity: 0.15;
  } /* 远空暗芒先兆 */
  83.4% {
    opacity: 0;
  }
  85.5% {
    opacity: 1;
  } /* 第一道裂空主击 */
  86.2% {
    opacity: 0.2;
  } /* 瞬间微歇 */
  86.7% {
    opacity: 0.95;
  } /* 第二道强力回闪 */
  87.4% {
    opacity: 0.45;
  }
  88.2% {
    opacity: 0.1;
  }
  88.8% {
    opacity: 0;
  }
}
@keyframes hwSkyFlash {
  0%,
  82.5%,
  100% {
    opacity: 0;
  }
  83% {
    opacity: 0.22;
  } /* 远空云层先亮 */
  83.4% {
    opacity: 0;
  }
  85.5% {
    opacity: 0.9;
  } /* 强击瞬间整片天空透白 */
  86.2% {
    opacity: 0.25;
  }
  86.7% {
    opacity: 0.82;
  } /* 第二次天光高亮 */
  87.4% {
    opacity: 0.35;
  }
  88.2% {
    opacity: 0.12;
  }
  88.8% {
    opacity: 0;
  }
}

/* 减弱动态偏好：装饰保留（它承载天气信息）但全部静止 */
@media (prefers-reduced-motion: reduce) {
  .hw-fx i,
  .hw-fx i::before,
  .hw-cloud {
    animation: none !important;
  }
}
/* 天气区文字阴影：底色是 JS 按天气写入的天空渐变（浅蓝晴空 / 深蓝夜空 / 灰白阴天），
   上面还飘着云、雨、雪、雾等装饰层，白字很容易被吃掉。
   原先一律只有一层大模糊（0.2 不透明度、6~12px）：它只能把背景整体压暗一点，
   笔画与背景的边界依旧发虚，细笔画的 hw-sub / hw-unit 更是完全没有阴影。
   改用「近距离小模糊 + 远距离大模糊」双层——前者贴着笔画勾出轮廓、提升边缘分离度，
   后者负责在明亮天空上压出整体对比。
   不设在 .hero-weather 上靠继承，是因为编辑层与这些文字类是兄弟节点，
   设在容器上会被它一并继承，而编辑层自带深色玻璃底、不需要阴影 */
.hw-city,
.hw-unit,
.hw-cond {
  text-shadow:
    0 1px 2px rgba(0, 0, 0, 0.4),
    0 2px 10px rgba(0, 0, 0, 0.24);
}
.hw-city {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 15px;
  font-weight: 600;
  letter-spacing: 0.5px;
}
.hw-city-text {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.hw-city-name {
  display: inline-block;
}
.hw-sep {
  opacity: 0.65;
  font-size: 13px;
  margin: 0 4px;
}
.hw-cond {
  font-size: 15px;
  font-weight: 600;
  letter-spacing: 0.5px;
  opacity: 0.95;
}
/* 编辑符号与默认角标：绝对定位到文本右侧，不参与居中宽度计算，
   确保「无锡 · 雾」本身在几何与视觉上完全绝对居中，无任何左拉偏移 */
.hw-city-extra {
  position: absolute;
  left: 100%;
  top: 50%;
  transform: translateY(-50%);
  margin-left: 8px;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  white-space: nowrap;
}
/* 「默认」角标：定位失败、兜底城市时提示用户这不是他所在的城市 */
.hw-def {
  padding: 0 4px;
  border-radius: 4px;
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0;
  background: rgba(255, 255, 255, 0.18);
}

/* ---- 点击天气区展开「重新定位 / 手动输入城市」编辑层 ---- */
.hero-weather {
  cursor: pointer;
}
/* 城市名旁的修改入口（✎）：可悬停设备 hover 才浮现，保持干净展示态 */
.hw-tip {
  font-size: 11px;
  opacity: 0.55;
  transition: opacity var(--dur-fast);
}
@media (hover: hover) {
  .hw-tip {
    opacity: 0;
  }
  .hero-weather:hover .hw-tip {
    opacity: 0.85;
  }
}
.hero-weather.editing .hw-tip {
  opacity: 0 !important;
}
/* 编辑态不缩卡：卡片现在两种落点都是竖排卡（右栏 286×148、抽屉 232×148），
   竖排单列布局约需 89px，148px 本来就有余量。
   这里必须跟基础高度取同一个值——低于它会变成一条「编辑就变矮」的规则：
   min-height 是下限而非高度，卡片实际高 = max(下限, 内容高)，
   下限调到 112 就等于把卡片从 148 压到内容高的 ~126px，点一下卡就跳一截 */
.hero-weather.editing {
  min-height: 148px;
}

.hw-edit {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 12px;
  background: rgba(14, 20, 34, 0.72);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  opacity: 0;
  transform: translate3d(0, 6px, 0) scale(0.96);
  transition:
    opacity 0.22s cubic-bezier(0.16, 1, 0.3, 1),
    transform 0.22s cubic-bezier(0.16, 1, 0.3, 1);
  will-change: transform, opacity;
}
.hw-edit.show {
  opacity: 1;
  transform: translate3d(0, 0, 0) scale(1);
}
.hw-edit[hidden] {
  display: none !important;
}
.hw-input {
  width: 100%;
  max-width: 150px;
  padding: 6px 10px;
  border-radius: 8px;
  border: 1px solid rgba(255, 255, 255, 0.35);
  background: rgba(255, 255, 255, 0.16);
  color: #fff;
  font-size: 13px;
  text-align: center;
  outline: none;
}
.hw-input::placeholder {
  color: rgba(255, 255, 255, 0.6);
}
.hw-input:focus {
  border-color: #fff;
  background: rgba(255, 255, 255, 0.24);
}
.hw-err {
  max-width: 160px;
  font-size: 11px;
  line-height: 1.3;
  color: #ffd0cc;
  text-align: center;
}
.hw-edit-actions {
  display: flex;
  gap: 6px;
}
/* 触屏按钮统一口径：屏蔽系统默认高亮（Android 蓝框），按压反馈交给 :active 贴合形状。
   尺寸在文件末尾的 (pointer: coarse) 块里放大到 44px 触控目标 */
.hw-btn {
  padding: 5px 10px;
  border-radius: 7px;
  cursor: pointer;
  font-size: 12px;
  color: #fff;
  border: 1px solid rgba(255, 255, 255, 0.35);
  background: rgba(255, 255, 255, 0.14);
  transition:
    background var(--dur-fast),
    border-color var(--dur-fast),
    transform var(--dur-fast);
  -webkit-tap-highlight-color: transparent;
}
.hw-btn:active {
  transform: scale(0.96);
  background: rgba(255, 255, 255, 0.34);
}
.hw-btn-go {
  background: rgba(255, 255, 255, 0.92);
  border-color: transparent;
  color: #1b2430;
  font-weight: 600;
}
.hw-btn-go:active {
  background: #fff;
}
/* 「取消」默认隐藏：桌面端空间宽裕，用右上角那个 × 关闭更省地方；
   触摸端才把它显出来与另两个按钮并排（见文件末尾 pointer:coarse 块） */
.hw-btn-cancel {
  display: none;
}
.hw-edit-close {
  position: absolute;
  top: 5px;
  right: 7px;
  display: grid;
  place-items: center; /* 图标改 SVG 后不再靠 line-height 居中，交给 grid 精准定位 */
  width: 22px;
  height: 22px;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: rgba(255, 255, 255, 0.8);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.hw-edit-close svg {
  width: 13px;
  height: 13px;
}
.hw-edit-close:active {
  background: rgba(255, 255, 255, 0.26);
}
/* hover 属「纯增强」：必须包在 (hover:hover) 里——触摸设备没有悬停语义，
   点完 :hover 会一直粘着不消失（与全站 hover 规则同一约定） */
@media (hover: hover) {
  .hw-btn:hover {
    background: rgba(255, 255, 255, 0.26);
    border-color: #fff;
  }
  .hw-btn-go:hover {
    background: #fff;
  }
  .hw-edit-close:hover {
    background: rgba(255, 255, 255, 0.2);
    color: #fff;
  }
}
/* 方案 A：纯粹极简大温度，居中对称无多余挂件 */
.hw-temp {
  margin: 4px 0 2px;
  display: inline-flex;
  align-items: flex-start;
  justify-content: center;
  font-size: 56px;
  font-weight: 800;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  /* 56px 大字笔画粗壮，双层柔和阴影在明暗天空上均具高对比度 */
  text-shadow:
    0 2px 4px rgba(0, 0, 0, 0.34),
    0 4px 20px rgba(0, 0, 0, 0.26);
}
.hw-unit {
  font-size: 22px;
  font-weight: 600;
  margin-left: 1px;
  margin-top: 4px;
  opacity: 0.9;
}
.hw-sub {
  margin-top: 5px;
  font-size: 13px;
  opacity: 0.85;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  /* 13px 小字笔画最细，又叠了 .85 的透明度（opacity 会连阴影一起衰减），
     是整块里最先糊掉的一行，所以给最强的一档：近距离那层提到 0.5 */
  text-shadow:
    0 1px 2px rgba(0, 0, 0, 0.5),
    0 2px 9px rgba(0, 0, 0, 0.3);
}
/* Hero 文案在移动模式整体收一档（与下方 ≤900px 的顶栏两行 / 侧栏抽屉同步）。
   注意这里只管字号与内边距：天气区搬走后 Hero 只剩「图标 + 文案」一块，
   横排竖排的分岔随之消失，原先那条 flex-direction:column 已无意义 */
@media (max-width: 820px) {
  /* 高度沿用桌面那条 min-height: 148px，一个字都不改——**全站就这一个高度**。
     这里只调字号/内边距，让内容在 148 里装得下：
     375px 视口（卡片 351）时描述折两行、芯片折两行，是内容最高的那一档。
     逐项实测并压到刚好合适（内容盒 146，实占 145.1）：
       标题 20.5×1.15 = 23.6 + 描述外边距 5 + 两行描述 2×11.5×1.45 = 33.4
       + 芯片行外边距 9 + 两行芯片 2×21+6 = 48  →  119.0；加内边距 24 = 143
     比原来（内边距 32、行高 1.55、芯片高 24）省下约 20px，靠的是行高与内边距，
     不是字号——描述 11.5 / 芯片 10 这两个「能读的下限」一个都没动。 */
  .hero {
    margin-bottom: 14px;
    border-radius: 18px;
  }
  .hero-main {
    padding: 12px 16px 12px 15px;
    gap: 13px;
  }
  .hero-icon {
    width: 54px;
    height: 54px;
    border-radius: 14px;
  }
  .hero-icon svg {
    width: 31px;
    height: 31px;
  }
  .hero-title {
    font-size: 20.5px;
  }
  .hero-sub {
    font-size: 13px;
  }
  .hero-desc {
    font-size: 11.5px;
    line-height: 1.45;
    margin-top: 5px;
  }
  .hero-stats {
    gap: 6px;
    margin-top: 9px;
  }
  .hero-chip {
    height: 21px;
    padding: 0 8px;
    font-size: 10px;
    gap: 5px;
  }
  .hc-i {
    width: 13px;
    height: 13px;
  }
}
/* 装饰面板的隐藏/缩小已改为按「卡片自己的宽度」判断（容器查询，见 .hero-art 附近）。
   原来那条 @media (max-width: 620px) 是按视口宽度隐藏的，现在不成立：
   1181~1270 这段视口下卡片只有 611~700 宽，比 620 视口时的 596 还挤，
   按视口判断会把最需要隐藏的那一段漏掉。 */

/* ---- 天气卡「通栏横排条」版式已退役 ----
   原先 ≤1180px 时右栏会退化成一条整页宽的通栏横带、只留天气卡，卡内随之从竖排改横排。
   现在这条横带没有了：≤1180px 右栏整体隐藏，天气卡改为搬进左侧抽屉
   （结构见 index.html 的 #sbWeather 与 app.js 的 placeWeatherCard）。
   抽屉里卡片是 232px 宽的竖排卡，用的就是上面这套桌面竖排样式，
   于是横排条那一整块（字号 clamp、装饰缩档、云缩到 78×52、闪电下移、雾带压薄）
   连同它的 1180px 断点一起删掉——留着只会是没人命中的死规则 */

/* 触摸屏上的天气编辑层：两行「输入框 + 关闭键」/「等宽双按钮」。
   天气卡在触摸设备上只有一种形态——右栏的 286px 竖卡，或抽屉里的 232px 竖卡，
   两种都窄。把三个控件强压进一行会让输入框只剩 ~40% 宽（抽屉里约 196px、
   右栏约 258px），长城市名根本看不全，按钮也被挤成贴身小目标；
   改成两行后每一行都是完整可点区域，触控目标与可视性才都达标。
   输入框必须 ≥16px——iOS Safari 对字号 <16px 的 input 聚焦时会自动放大整个页面 */
@media (pointer: coarse) {
  /* 这里不再把 min-height 归零。原来归零是因为横条版式的基础高度只有 65px、
     编辑态要抬到 140px，需要一条「0 → 具体像素」的可插值起点。
     现在卡片两种落点都是竖排卡（右栏 286×148、抽屉 232×148），
     基础高度就是 148px，而两行大控件实测只需 138px，148px 本来就有余量——
     再归零反而会让卡片在进入编辑态时塌掉一截，「卡片形状保持不变」就没了 */
  .hero-weather {
    min-height: 148px;
    transition:
      background var(--dur-slow) ease,
      min-height 0.24s ease;
  }
  /* 编辑态保持 148px 不缩：两行 52px 控件实测需 138px（padding 24 + 52 + 行间距 10 + 52），
     基础高度已经够用。必须显式写出来——基础规则只压住「无编辑态」，
     而文件上方那条 .hero-weather.editing{min-height:148px} 虽然同值，
     但它排在更前面；同值时靠这条保证触屏分支不被别的规则抢先 */
  .hero-weather.editing {
    min-height: 148px;
  }
  .hw-edit {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
    padding: 12px 14px;
  }
  /* order 重排：输入框在左、关闭键在右。HTML 里关闭键排在最前，
     用 order 换位即可，不必为纯视觉顺序去动 DOM（也免得影响桌面端） */
  .hw-input {
    order: 1;
    flex: 1 1 auto;
    width: auto;
    max-width: none;
    min-width: 0;
    font-size: 16px;
    padding: 0 16px;
    min-height: 52px;
    text-align: left;
    border-radius: 12px;
  }
  /* 聚焦光晕：移动端输入时页面被键盘顶起，这圈高亮让「光标此刻在哪」一目了然 */
  .hw-input:focus {
    border-color: rgba(255, 255, 255, 0.9);
    background: rgba(255, 255, 255, 0.26);
    box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.14);
  }
  /* 关闭入口改走第二行的「取消」按钮：原先那个 52px 圆形 × 挤在通栏输入框右侧，
     两者贴在一起像「输入框里长了个按钮」，视觉重心很乱；
     并进按钮行之后三个按钮等宽对齐，关系清楚、也更好点 */
  .hw-edit-close {
    display: none;
  }
  .hw-btn-cancel {
    display: block;
  }
  /* 报错占满一行另起一排；正常态不占位，布局仍是两行 */
  .hw-err {
    order: 3;
    flex-basis: 100%;
    max-width: none;
    margin: 0;
  }
  /* flex-basis 100% 强制操作行独占第二行（比让浏览器自行折行更稳定），
     内部再 flex:1 均分——两个按钮等宽，不再一大一小 */
  .hw-edit-actions {
    order: 4;
    flex: 1 1 100%;
    display: flex;
    gap: 10px;
  }
  .hw-btn {
    flex: 1 1 0;
    width: 100%;
    min-height: 52px;
    padding: 0 10px;
    font-size: 15px;
    border-radius: 12px;
  }
}
.cat-section {
  margin-bottom: 32px;
}
/* 分组标题：与卡片同款圆角玻璃横条——18px 圆角、卡片级 Liquid Glass 配方
   （强饱和/提亮 backdrop-filter + 多层内高光），通栏铺满与上方 Hero 卡一致。
   relative + overflow:hidden：内部泡泡装饰被裁在圆角条内滚动 */
.cat-title {
  font-size: 14px;
  font-weight: 800;
  color: var(--text);
  letter-spacing: 0.3px;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 14px;
  margin-bottom: 14px;
  background: var(--glass-bg);
  border: 1px solid var(--glass-border);
  border-radius: 18px;
  backdrop-filter: blur(28px) saturate(180%) brightness(1.06);
  -webkit-backdrop-filter: blur(28px) saturate(180%) brightness(1.06);
  position: relative;
  overflow: hidden;
  box-shadow:
    inset 0 1px 0 var(--glass-hi),
    inset 0 -1px 0 var(--glass-lo),
    inset 0 0 24px var(--glass-lo);
}
/* 标题前橙色小竖条：呼应激活分类 pill 的橙色渐变，给分组一个视觉锚点 */
.cat-title::before {
  content: '';
  flex-shrink: 0;
  width: 3px;
  height: 14px;
  border-radius: 2px;
  background: linear-gradient(180deg, var(--accent), var(--accent-light));
}
/* 数量徽章：实心橙渐变 + 白字（同激活分类 pill 配方），玻璃胶囊内足够跳出易读 */
.cat-title .count {
  font-size: 10px;
  font-weight: 700;
  color: #fff;
  background: linear-gradient(135deg, var(--accent), var(--accent-light));
  padding: 2px 8px;
  border-radius: 8px;
  font-variant-numeric: tabular-nums;
}

/* ---- 标题条泡泡动画：四颗大小不一的玻璃泡泡在留白内不停流转 ----
   泡泡是标题模板注入的 .bubble 装饰元素（app.js TITLE_BUBBLES），
   绝对定位不占布局；负 z-index 落在玻璃底之上、标题文字之下，
   越出 18px 圆角的部分被裁掉。尺寸/时长/负延迟伪随机错开，
   打开页面即在途中，任意时刻都有泡泡在流转 */
.cat-title .bubble {
  --bubble-bg:
    radial-gradient(circle at 30% 26%, rgba(255, 255, 255, 0.9) 0 9%, transparent 20%),
    radial-gradient(circle at 66% 78%, color-mix(in srgb, var(--accent) 22%, transparent), transparent 46%),
    radial-gradient(circle at 50% 50%, rgba(255, 255, 255, 0.05) 0 52%, rgba(255, 255, 255, 0.38) 66%, transparent 74%);
  display: block;
  position: absolute;
  top: 50%;
  left: 0;
  z-index: -1;
  border-radius: 50%;
  background: var(--bubble-bg);
  animation:
    titleBubbleFloat 16s linear infinite,
    titleBubbleBob 5s ease-in-out infinite;
}
.cat-title .bubble:nth-of-type(1) {
  width: 58px;
  height: 58px;
  margin-top: -29px;
  animation-duration: 15s, 4.6s;
  animation-delay:
    -3s,
    -1.2s;
}
.cat-title .bubble:nth-of-type(2) {
  width: 34px;
  height: 34px;
  margin-top: -17px;
  animation-duration: 19s, 5.6s;
  animation-delay:
    -11s,
    -2.6s;
}
.cat-title .bubble:nth-of-type(3) {
  width: 46px;
  height: 46px;
  margin-top: -23px;
  animation-duration: 17.5s, 5.1s;
  animation-delay:
    -6.5s,
    -3.8s;
}
.cat-title .bubble:nth-of-type(4) {
  width: 22px;
  height: 22px;
  margin-top: -11px;
  animation-duration: 13s, 4.3s;
  animation-delay:
    -16s,
    -var(--dur-slow);
}
/* 横向位移改用独立的 translate 属性，纵向 bob 继续用 transform ——
   两者是叠加关系而非互相覆盖（同一个 transform 会被后写的动画吃掉）。
   关键是横向绝对不能再动 right：right 每帧都要重算布局，
   4 个泡泡 × 16s 无限循环会让每个分组标题条持续抖动 */
@keyframes titleBubbleFloat {
  0% {
    translate: -90px 0;
    opacity: 0;
  }
  8% {
    opacity: 0.95;
  }
  92% {
    opacity: 0.95;
  }
  100% {
    translate: calc(100vw - 48px) 0;
    opacity: 0;
  }
}
@keyframes titleBubbleBob {
  0%,
  100% {
    transform: translateY(3px);
  }
  50% {
    transform: translateY(-4px);
  }
}
/* 减弱动态偏好：泡泡直接不显示（静止的大泡泡悬在条内显得突兀） */
@media (prefers-reduced-motion: reduce) {
  .cat-title .bubble {
    display: none;
  }
}

/* ============ Grid ============ */
.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
  gap: 16px;
  /* dense：span-2 锚点卡片跨列后，后续小卡片回填空位，避免网格空洞 */
  grid-auto-flow: dense;
}

/* ============ 锚点卡片通栏（EPS 注册项带 span:2，如 60s 早报 / Hacker News） ============ */
/* 独占整行（原为跨 2 列）：这类卡片的内容区为多栏排布特意不限高，实测高度能到
   600px 上下，比普通卡片的设计上限（卡片头 + 内容区 460px）高出近百像素。
   若让它与普通卡片共享一行，grid 会把整行拉高到它的高度，而普通卡片的内容区
   卡在 460px 上，底部就空出一截（改前「历史上的今天」「微博热搜」下方那 92px）。
   独占一行后两种卡片互不影响：行内高度一致、底部齐平，卡片内部也没有空白 */
.card.span-2 {
  grid-column: 1 / -1;
}
/* 收起态（默认 Top 10）：通栏后按「栏宽」而非「栏数」分栏——column-width 让每栏
   宽度与普通卡片对齐（约 340px），宽屏自动排成 3~4 栏、窄屏自动回退单栏，
   不必为每个断点单独指定栏数；
   必须解除 max-height 限高——多列容器若有固定高度，溢出内容会横向生成新列而非纵向滚动 */
.card.span-2:not(.expanded) .card-body {
  max-height: none;
  overflow: visible;
  column-width: 340px;
  column-gap: 28px;
}
.card.span-2 .item,
.card.span-2 .news-item {
  break-inside: avoid;
}
.card.span-2 .news-header,
.card.span-2 .news-tip {
  column-span: all;
}
/* 展开态：回退与其他卡片一致的单列 + 内部滚动（column-width 需一并复位，
   否则它会与 column-count: 1 同时生效、把栏数决策交回给宽度） */
.card.span-2.expanded .card-body {
  column-count: 1;
  column-width: auto;
}

/* 「60 秒读懂世界」的专属标识（方案②：标题色带）
   —— 它是唯一一张条目没有外部来源可点、又固定每天第一眼看到的大卡。
   醒目度靠两处叠加，都取自站内既有的强调语言（.cat-subitem.active 用的是
   「左侧品牌色竖条 + 淡橙底 + 橙字」）：
     ① 卡片头一条左浓右淡的橙带，把标题区从玻璃底里托出来；
     ② 标题文字转 --accent-text（亮色深烧橙、暗色亮橙，两套都为 AA 对比度调过）。
   只作用于这一张卡（#card-60s），同为锚点卡的 Hacker News 不受影响。
   渐变终点写成 rgba(橙色, 0) 而不是 transparent：后者在部分浏览器里会被当成
   「透明黑」，橙→黑→无 的过渡会让色带尾巴发灰 */
#card-60s .card-head {
  --c60-rgb: 249, 115, 22; /* 橙 500（品牌色） */
  --c60-a: 0.24;
  background: linear-gradient(
    90deg,
    rgba(var(--c60-rgb), var(--c60-a)),
    rgba(var(--c60-rgb), calc(var(--c60-a) * 0.3)) 58%,
    rgba(var(--c60-rgb), 0)
  );
}
#card-60s .card-title {
  color: var(--accent-text);
}
/* 暗色下橙 500 叠在深紫玻璃上会偏暗褐，换成更亮的橙 400；
   浓度也略降——深底对颜色的对比本来就强，不需要那么浓 */
:root[data-theme='dark'] #card-60s .card-head {
  --c60-rgb: 251, 146, 60;
  --c60-a: 0.22;
}

/* ============ Card（Liquid Glass 液态玻璃） ============ */
.card {
  /* 半透明玻璃底 + 强饱和/提亮的 backdrop-filter，让穿透的壁纸呈现
     柔和的彩色氛围而非噪点；多层内高光与内泛光模拟玻璃厚度与边缘折射 */
  position: relative;
  background: var(--glass-bg);
  backdrop-filter: blur(28px) saturate(180%) brightness(1.06);
  -webkit-backdrop-filter: blur(28px) saturate(180%) brightness(1.06);
  /* 描边统一走 --glass-border（与顶栏/侧栏/hero 同一档「实边」） */
  border: 1px solid var(--glass-border);
  border-radius: 18px;
  overflow: hidden;
  /* 只过渡必要属性：transition:all 会让无关属性变化也触发样式计算 */
  transition:
    border-color var(--dur-fast),
    box-shadow var(--dur-base),
    transform var(--dur-base);
  display: flex;
  flex-direction: column;
  /* 不做懒渲染（content-visibility:auto）：视口外卡片连玻璃模糊层一起被跳过，
     滚入时合成器重建 backdrop-filter 层的头 1-2 帧模糊未就绪，卡片闪现半透明
     底色——玻璃越通透（低不透明度）越明显。全量实渲染换取玻璃稳定，
     定位逻辑里的 .locate-force 类保留（无样式定义时是空操作，不报错） */
  /* 桌面吸顶栈高度：顶栏 + 顶栏导航条 ≈ 180px（scrollIntoView/键盘聚焦用；
     卡片定位主路径走 JS 的 scrollDockTop() 动态测量，不依赖此值） */
  scroll-margin-top: 180px;
  box-shadow:
    var(--glass-shadow),
    inset 0 1px 0 var(--glass-hi),
    inset 0 -1px 0 var(--glass-lo),
    inset 0 0 32px var(--glass-lo);
}
/* 定位高亮：脉冲动画放在 ::after 覆盖层，卡片本体 animation 始终只有 fadeIn，
   移除本类时不会改变卡片的 animation-name，从而避免 fadeIn 被重新播放（整卡淡出再淡入） */
/* 减少动态偏好：hover 不抬升 */
@media (prefers-reduced-motion: reduce) {
  .card:hover {
    transform: none;
  }
}
.card.locate-flash {
  position: relative;
  border-color: var(--accent);
  box-shadow: var(--accent-glow);
}
.card.locate-flash::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  animation: locateFlash 1s ease 2;
}
@keyframes locateFlash {
  0%,
  100% {
    box-shadow: inset 0 0 0 0 transparent;
  }
  50% {
    box-shadow: inset 0 0 0 5px var(--accent-bg);
  }
}
/* hover 触屏不粘滞：触屏没有悬停语义，点击后 :hover 会粘住不消失；
   所有「纯增强型」hover 规则用 (hover:hover) 包裹，触摸设备直接跳过。
   注意：不包裹 .card-actions button:hover 等深色反馈（触屏长按/点击态仍有价值），
   与 :active 态配合的规则也不受影响 */
@media (hover: hover) {
  .card:hover {
    transform: translateY(-2px);
    /* 悬停加深描边对比（暗色更亮 / 亮色更深），边缘比默认更「实」 */
    border-color: var(--glass-border-strong);
    box-shadow:
      var(--glass-shadow-hover),
      inset 0 1px 0 var(--glass-hi),
      inset 0 -1px 0 var(--glass-lo),
      inset 0 0 36px var(--glass-lo);
  }
  /* 「:not(.active)」：激活 pill 必须整条排除在通用 hover 之外，两个原因——
     ① 它自己就是橙渐变 + 白字，紧跟其后的计数徽章与开合箭头也都是「白字 + 白底」，
        被拉回浅橙底会一起糊掉（亮色模式几乎看不见，暗色下退化成橙字压橙底）；
     ② pill 正下方就是导航条的毛玻璃，任何背景变化都要连带重算 backdrop-filter。
        之前给激活项写了「反转移变方向」的 hover（同 .cat-toc-item.active），
        悬停移开时每帧重画渐变，会肉眼可见地闪一下。
     激活项本就是「已选中」，不再需要 hover 二次提示，保持零变化最干净 */
  .cat-pills > button:hover:not(.active) {
    color: var(--accent-text);
    background: var(--accent-bg);
  }
  .cat-subitem:hover {
    color: var(--accent-text);
    background: var(--accent-bg);
  }
  .cat-toc-item:hover {
    color: var(--accent-text);
    background: var(--accent-bg);
  }
  /* 激活项不能被上面的通用 hover 拉回浅橙底——白字会糊在浅底上，
     这里只做渐变方向的反转，保持「已选中」的同时给一点按压反馈 */
  .cat-nav .cat-toc-item.active:hover {
    background: linear-gradient(135deg, var(--accent-light), var(--accent));
    color: #fff;
  }
  .clock:hover {
    border-color: var(--border-light);
  }
}
.card-head {
  padding: 11px 16px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  border-bottom: 1px solid var(--border-glass);
  flex-shrink: 0;
}
/* ⚠️ 预览实验：标题与图标放大（14→20 / 15→22 / 16→24），未定稿 */
.card-title {
  font-size: 20px;
  font-weight: 600;
  display: flex;
  align-items: center;
  gap: 10px;
}
.card-title .icon {
  font-size: 22px;
  display: inline-flex;
  align-items: center;
}
/* 品牌 Logo（Simple Icons 单色 SVG / 官网 favicon）：与 emoji 字号同高，
   flex 垂直居中不被基线顶歪；卡片标题与菜单目录/模块子项共用 */
.card-logo {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
}
/* ⚠️ 预览实验：仅卡片标题内的 Logo 放大，菜单/目录保持 16px */
.card-title .card-logo {
  width: 24px;
  height: 24px;
}
/* 相对时间徽标（卡片标题右侧）：浅色小字，与标题拉开层级 */
.card-title .rel-time {
  font-size: 11px;
  font-weight: 400;
  color: var(--text-dimmer);
  margin-left: 2px;
  white-space: nowrap;
  flex-shrink: 0;
}
@media (max-width: 480px) {
  .card-title .rel-time {
    font-size: 10px;
  }
}
.card-actions {
  display: flex;
  gap: 5px;
}
/* 与顶栏图标按钮（.icon-btn / .header-right button）同款：
   同款 8px 圆角、同款 card 底 + border 描边、同款 hover 光晕。
   尺寸仍取 28px 而非顶栏的 36px——卡片头的预算注释写着
   「头部 = 按钮 28 + 上下内边距 22 ≈ 50」，放大按钮会把每张卡的头部一起撑高，
   所以统一的是观感，不是尺寸 */
.card-actions button {
  width: 28px;
  height: 28px;
  border-radius: 8px;
  background: var(--card);
  border: 1px solid var(--border);
  color: var(--text-dim);
  font-size: 11px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition:
    border-color var(--dur-fast),
    color var(--dur-fast),
    box-shadow var(--dur-fast);
}
.card-actions button:hover {
  border-color: var(--accent);
  color: var(--accent);
  box-shadow: var(--accent-glow);
}
/* 刷新图标已由文本 ↻ 换成与顶栏同款的 SVG：
   顶栏是 18px 图标配 36px 按钮，这里按同比例取 14px 配 28px */
.card-actions button svg {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
}

/* .card-pane 与 .card-body 共用内容区规则（分组卡片的标签页面板） */
.card-body,
.card-pane {
  padding: 12px 16px;
  flex: 1;
  overflow-y: auto;
  max-height: 460px;
  scrollbar-width: thin;
  scrollbar-color: var(--border-light) transparent;
}
/* 带数据源下拉的卡片（分组卡片，如猫眼电影榜）：下拉行本身占约 40px，内容区相应
   减去同等高度，让整卡高度与不带下拉的卡片一致。否则两者同行时，grid 会把
   整行对齐到较高的那个，较矮卡片的内容区到上限就停住、底部空出一小截（实测约 35px）。
   用 :has() 判断，不支持的旧浏览器会跳过这条规则，效果退化为原来的 35px 空白，无功能影响 */
.card:has(.card-select-row) .card-body {
  max-height: 420px;
}
.card-body::-webkit-scrollbar,
.card-pane::-webkit-scrollbar {
  width: 6px;
}
.card-body::-webkit-scrollbar-track,
.card-pane::-webkit-scrollbar-track {
  background: transparent;
  margin: 4px 0;
}
.card-body::-webkit-scrollbar-thumb,
.card-pane::-webkit-scrollbar-thumb {
  background: var(--border);
  border-radius: 3px;
  border: 2px solid transparent;
  background-clip: padding-box;
}
.card-body::-webkit-scrollbar-thumb:hover,
.card-pane::-webkit-scrollbar-thumb:hover {
  background: var(--border-light);
  background-clip: padding-box;
}

/* ============ 分组卡片：数据源下拉 ============ */
/* 分组内的数据源从 2 个到 60+ 个不等（网易云榜单有六十多个），胶囊标签行放不下会
   换行或横向滚动，下拉一屏就能选完，也更省纵向空间 */
.card-select-row {
  padding: 10px 16px 0;
  border-bottom: 1px solid var(--border-glass);
}
.card-select {
  width: 100%;
  padding: 6px 30px 6px 10px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text);
  font-size: 12px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  outline: none;
  transition: border-color var(--dur-fast);
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  /* 自绘下拉箭头（与移动端 .input-row select 同款；色值在明暗两套主题下都清晰） */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%237a8094' stroke-width='2'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
}
.card-select:hover,
.card-select:focus {
  border-color: var(--accent);
}
.card-pane[hidden] {
  display: none;
}

/* Input row */
.input-row {
  display: flex;
  gap: 8px;
  margin-bottom: 12px;
  flex-wrap: wrap;
}
.input-row input,
.input-row select {
  flex: 1;
  min-width: 60px;
  padding: 7px 10px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--text);
  font-size: 12px;
  outline: none;
  transition: border var(--dur-fast);
}
.input-row input:focus {
  border-color: var(--accent);
}
.input-row .go {
  padding: 7px 16px;
  background: var(--accent);
  border: none;
  border-radius: 6px;
  color: #fff;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--dur-fast);
}
.input-row .go:hover {
  background: var(--accent-light);
}

/* Fanyi (translation) layout */
.fanyi-textarea {
  width: 100%;
  min-height: 80px;
  resize: vertical;
  margin-bottom: 12px;
  padding: 10px 12px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text);
  font-size: 14px;
  font-family: inherit;
  outline: none;
  transition: border var(--dur-fast);
  line-height: 1.6;
  -webkit-appearance: none;
  appearance: none;
}
.fanyi-textarea:focus {
  border-color: var(--accent);
  box-shadow: var(--accent-glow);
}
.fanyi-lang-row {
  display: flex;
  gap: 8px;
  align-items: center;
  margin-bottom: 12px;
}
.fanyi-lang-row select {
  flex: 1;
  min-width: 0;
  padding: 8px 28px 8px 10px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text);
  font-size: 13px;
  outline: none;
  cursor: pointer;
  transition: border var(--dur-fast);
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%237a8094' stroke-width='2'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 8px center;
}
.fanyi-lang-row select:focus {
  border-color: var(--accent);
}
.fanyi-lang-row .arrow {
  color: var(--text-dim);
  font-size: 16px;
  flex-shrink: 0;
  user-select: none;
  cursor: pointer;
  padding: 4px;
  border-radius: 4px;
  transition:
    color var(--dur-fast),
    background var(--dur-fast);
}
.fanyi-lang-row .arrow:hover {
  color: var(--accent);
  background: var(--accent-bg);
}
.fanyi-submit {
  width: 100%;
  padding: 11px 16px;
  background: var(--accent);
  border: none;
  border-radius: 8px;
  color: #fff;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  transition:
    background var(--dur-fast),
    transform var(--dur-fast);
  margin-bottom: 12px;
  -webkit-appearance: none;
  appearance: none;
  -webkit-tap-highlight-color: transparent;
}
.fanyi-submit:hover {
  background: var(--accent-light);
}
.fanyi-submit:active {
  transform: scale(0.98);
}

/* Loading & placeholder */
.loading {
  text-align: center;
  padding: 24px;
  color: var(--text-dimmer);
  font-size: 12px;
}
.loading::after {
  content: '';
  display: inline-block;
  width: 14px;
  height: 14px;
  border: 2px solid var(--border);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
  margin-left: 8px;
  vertical-align: middle;
}
@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}
.placeholder {
  color: var(--text-dimmer);
  font-size: 12px;
  padding: 6px 0;
}
.badge {
  display: inline-block;
  padding: 1px 6px;
  border-radius: 4px;
  font-size: 10px;
  font-weight: 600;
}
.badge.fail {
  background: rgba(248, 113, 113, 0.12);
  color: var(--error);
}

/* ============ Skeleton Shimmer ============ */
.skeleton {
  padding: 4px 0;
}
.card-body .skeleton {
  min-height: 400px;
  padding: 10px 4px;
  box-sizing: border-box;
}
.skeleton-line {
  position: relative;
  overflow: hidden;
  height: 15px;
  border-radius: 5px;
  margin-bottom: 14px;
  background: var(--border);
}
/* 扫光交给伪元素的 translateX：background-position 动画每帧都要重绘整条，
   translateX 只走合成层。骨架屏常常一屏十几条，这个差别很直观 */
.skeleton-line::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, transparent, var(--border-light), transparent);
  transform: translateX(-100%);
  animation: shimmer 1.5s ease-in-out infinite;
}
.skeleton-line-short {
  width: 55%;
}
.skeleton-line:last-child {
  margin-bottom: 0;
}
@keyframes shimmer {
  to {
    transform: translateX(100%);
  }
}

/* ============ Retry Button ============ */
.retry-btn {
  display: inline-block;
  margin-left: 6px;
  padding: 2px 8px;
  background: transparent;
  border: 1px solid var(--error);
  border-radius: 4px;
  color: var(--error);
  font-size: 11px;
  cursor: pointer;
  transition:
    background var(--dur-fast),
    color var(--dur-fast);
}
.retry-btn:hover {
  background: var(--error);
  color: #fff;
}

/* ============ 榜单展开/收起（Top N 折叠） ============ */
.list-toggle {
  display: block;
  width: 100%;
  margin-top: 8px;
  padding: 6px 0;
  background: transparent;
  border: 1px dashed var(--border);
  border-radius: 8px;
  color: var(--text-dim);
  font-size: 12px;
  cursor: pointer;
  transition:
    color var(--dur-fast),
    border-color var(--dur-fast);
  -webkit-tap-highlight-color: transparent;
}
.list-toggle:hover {
  color: var(--accent);
  border-color: var(--accent);
}
/* span-2 宽卡片收起态为多列布局，按钮必须横跨所有列 */
.card.span-2:not(.expanded) .list-toggle {
  column-span: all;
}

/* ============ 数据源不可用的友好提示 ============ */
.card-unavailable {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  padding: 16px 8px;
  text-align: left;
}
.card-unavailable .un-icon {
  font-size: 18px;
  line-height: 1;
}
.card-unavailable .un-text {
  color: var(--text-dim);
  font-size: 12px;
}
.card-unavailable .un-detail {
  color: var(--text-dimmer);
  font-size: 11px;
}
.card-unavailable .retry-btn {
  margin-left: 0;
}

/* ============ List item (ranked) ============ */
.item {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 9px 0;
  border-bottom: 1px solid color-mix(in srgb, var(--border) 50%, transparent);
  font-size: 13px;
}
.item:last-child {
  border-bottom: none;
}
/* ---- 名次角标（全站统一 · 唯一来源） ----
   所有信息卡片的序号共用同一套形制：22px 圆角方块，前三名是「红 / 橙 / 黄」三色渐变
   加同色系柔光，第四名起换成浅灰底 + 深灰数字。
   这一组选择器覆盖全部使用场景——分类页各类榜单（普通行 + 海报行）、流媒体榜、
   首页聚合榜（.hl-rank）、右侧热门话题（.rt-item .rn）、新闻型卡片（.news-item .num）。
   此前这几处各写了一套（分类页是「橙→橙→琥珀」实心角标、首页是「红→橙→黄」、
   新闻卡是橙色纯文字），同一屏里会同时看到好几种编号风格；收到这一处后，
   换颜色就等于全站换一次「数字语言」。

   注意：新闻卡的序号是「第几条」而不是名次，所以不参与前三名配色，只共用这套形制 */
.item .rank,
.item.with-poster .body-wrap .rank,
.simkl-body .rank,
.hl-rank,
.rt-item .rn,
.news-item .num {
  flex-shrink: 0;
  /* 用 min-width 而不是固定 width：三位数序号（展开全部的长榜）能自己撑开，
     一位数时仍是正方的 22px */
  min-width: 22px;
  height: 22px;
  padding: 0 3px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 7px;
  /* 4 名之后的浅灰底：色调从正文色派生，明暗两套主题各自成立 */
  background: color-mix(in srgb, var(--text) 8%, transparent);
  color: var(--text-dim);
  font-size: 12px;
  font-weight: 800;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
/* 列表行首的角标：flex-start 对齐时数字看着偏高，做一个 1px 的视觉校正 */
.item .rank {
  margin-top: 1px;
}
/* 行内形态（海报标题行 / 流媒体榜）：角标跟文字基线走，不再独占一行 */
.item.with-poster .body-wrap .rank,
.simkl-body .rank {
  display: inline-block;
  width: auto;
  text-align: center;
  line-height: 22px;
  vertical-align: middle;
  margin-right: 4px;
}
/* 前三名：红 / 橙 / 黄，与首页聚合榜、热门话题同一组色值；
   柔光取同色系低透明度阴影，做出图中那种「浮起来」的角标 */
.item .rank.top1,
.item.with-poster .body-wrap .rank.top1,
.simkl-body .rank.top1,
.hl-item.top1 .hl-rank,
.rt-item.top1 .rn {
  background: linear-gradient(135deg, #ff5a5f, #d92d33);
  color: #fff;
  box-shadow: 0 3px 10px -2px rgba(217, 45, 51, 0.55);
}
.item .rank.top2,
.item.with-poster .body-wrap .rank.top2,
.simkl-body .rank.top2,
.hl-item.top2 .hl-rank,
.rt-item.top2 .rn {
  background: linear-gradient(135deg, #ff9f43, #f76707);
  color: #fff;
  box-shadow: 0 3px 10px -2px rgba(247, 103, 7, 0.5);
}
.item .rank.top3,
.item.with-poster .body-wrap .rank.top3,
.simkl-body .rank.top3,
.hl-item.top3 .hl-rank,
.rt-item.top3 .rn {
  background: linear-gradient(135deg, #ffd43b, #f0a500);
  color: #fff;
  box-shadow: 0 3px 10px -2px rgba(240, 165, 0, 0.5);
}
.item .body {
  flex: 1;
  min-width: 0;
}
.item .body a,
.item .body .t {
  color: var(--text);
  text-decoration: none;
  display: block;
  word-break: break-word;
  line-height: 1.5;
}
.item .body a:hover {
  color: var(--accent-light);
}
.item .meta {
  font-size: 11px;
  color: var(--text-dim);
  margin-top: 3px;
}
.item .hot {
  color: var(--warn);
  font-weight: 500;
}
.item .desc {
  font-size: 11.5px;
  color: var(--text-dim);
  margin-top: 3px;
  line-height: 1.5;
}

/* 带海报的列表项（豆瓣周榜 / 百度电视剧榜等复用） */
.item.with-poster .poster {
  width: 40px;
  height: 58px;
  border-radius: 6px;
  object-fit: cover;
  flex-shrink: 0;
  background: var(--bg);
  border: 1px solid var(--border);
}
.item.with-poster .body-wrap {
  flex: 1;
  min-width: 0;
}
.item.with-poster .body-wrap a,
.item.with-poster .body-wrap .t {
  color: var(--text);
  text-decoration: none;
  display: block;
  word-break: break-word;
  line-height: 1.5;
}
.item.with-poster .body-wrap a:hover {
  color: var(--accent-light);
}
/* 海报行里的 .rank 样式见文件开头的「名次角标（全站统一）」 */
.item.with-poster .body-wrap .meta {
  font-size: 11px;
  color: var(--text-dim);
  margin-top: 3px;
}
.item.with-poster .body-wrap .hot {
  color: var(--warn);
  font-weight: 500;
}
.item.with-poster .body-wrap .desc {
  font-size: 11.5px;
  color: var(--text-dim);
  margin-top: 3px;
  line-height: 1.5;
}

/* 方形封面变体（音乐榜专辑封面，与竖版电影海报区分） */
.item.with-poster.with-cover .poster.cover-square {
  width: 40px;
  height: 40px;
}

/* News (60s) */
.news-item {
  padding: 7px 0;
  border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent);
  font-size: 13px;
  display: flex;
  gap: 8px;
  line-height: 1.6;
}
.news-item:last-child {
  border: none;
}
/* .news-item .num 的样式见「名次角标（全站统一）」：形状与其它卡片一致，
   但它表示「第几条」而非名次，因此不带前三名的彩色配色 */
.news-item a,
.news-item .nt {
  color: var(--text);
  text-decoration: none;
}
.news-item a:hover {
  color: var(--accent-light);
}
.news-tip {
  margin: 10px 0 12px;
  padding: 10px 12px;
  background: var(--accent-bg);
  border-left: 2px solid var(--accent);
  border-radius: 4px;
  font-size: 12.5px;
  color: var(--text-dim);
  line-height: 1.6;
}
.news-header {
  font-size: 11px;
  color: var(--text-dim);
  margin-bottom: 8px;
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}
.news-header span {
  background: var(--bg);
  padding: 3px 9px;
  border-radius: 6px;
  border: 1px solid var(--border);
}

/* 历史上的今天（时间轴） */
.hist-head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 6px 12px;
  margin-bottom: 10px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--border);
}
.hist-date {
  font-size: 15px;
  font-weight: 700;
  color: var(--accent-text);
}
.hist-legend {
  font-size: 11px;
  color: var(--text-dimmer);
}
.hist-legend b {
  color: var(--text-dim);
}
.hist-item {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 8px 0;
  border-bottom: 1px solid color-mix(in srgb, var(--border) 40%, transparent);
}
.hist-item:last-child {
  border-bottom: none;
}
/* 事件徽章（默认橙）：底色混入 card 做成实色，与 birth/death 徽章统一，
   透明橙在蓝调壁纸玻璃底上会透色发虚 */
.hist-year {
  flex-shrink: 0;
  min-width: 46px;
  box-sizing: border-box;
  padding: 0 7px;
  height: 22px;
  line-height: 22px;
  text-align: center;
  font-size: 11px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--accent-text);
  background: color-mix(in srgb, var(--accent) 12%, var(--card));
  border-radius: 6px;
}
/* 出生徽章：文字用 --success-text（亮色下深翡翠保 AA）；底色混入 card 而非
   transparent——实色浅绿不透壁纸，蓝色壁纸下绿色年份/标签不再发虚 */
.hist-year.birth {
  color: var(--success-text);
  background: color-mix(in srgb, var(--success) 14%, var(--card));
}
.hist-year.death {
  color: var(--text-dimmer);
  background: var(--bg);
  border: 1px solid var(--border);
}
.hist-body {
  flex: 1;
  min-width: 0;
}
.hist-title {
  font-size: 13px;
  line-height: 1.5;
}
.hist-title a,
.hist-title .t {
  color: var(--text);
  text-decoration: none;
  font-weight: 600;
  word-break: break-word;
}
.hist-title a:hover {
  color: var(--accent-light);
}
.hist-tag {
  display: inline-block;
  margin-right: 6px;
  padding: 0 6px;
  height: 16px;
  line-height: 16px;
  font-size: 10px;
  font-weight: 600;
  border-radius: 4px;
  vertical-align: 2px;
  color: var(--accent-text);
  background: color-mix(in srgb, var(--accent) 12%, var(--card));
}
.hist-tag.birth {
  color: var(--success-text);
  background: color-mix(in srgb, var(--success) 14%, var(--card));
}
.hist-tag.death {
  color: var(--text-dim);
  background: var(--bg);
  border: 1px solid var(--border);
}
.hist-desc {
  font-size: 11.5px;
  color: var(--text-dim);
  margin-top: 3px;
  line-height: 1.55;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* KV */
.kv {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.kv-row {
  display: flex;
  gap: 8px;
  font-size: 13px;
  padding: 3px 0;
}
.kv-row .k {
  color: var(--text-dim);
  min-width: 72px;
  flex-shrink: 0;
  font-size: 12px;
}
.kv-row .v {
  color: var(--text);
  word-break: break-word;
}
.kv-row .v.mono {
  font-family: 'SF Mono', Consolas, monospace;
  font-size: 11.5px;
}

/* Color */
.swatch {
  width: 100%;
  height: 50px;
  border-radius: 8px;
  margin-bottom: 8px;
  border: 1px solid var(--border);
}
.palette-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 6px;
}
.palette-chip {
  text-align: center;
  font-size: 9px;
}
.palette-chip .c {
  width: 40px;
  height: 40px;
  border-radius: 6px;
  border: 1px solid var(--border);
  margin-bottom: 2px;
}

/* QR */
.qr-wrap {
  text-align: center;
}
.qr-frame {
  display: inline-block;
  padding: 10px;
  background: #ffffff;
  border-radius: 14px;
  box-shadow:
    0 4px 16px rgba(0, 0, 0, 0.18),
    0 0 0 1px var(--border);
}
.qr-frame img {
  display: block;
  width: 200px;
  max-width: 100%;
  border-radius: 4px;
}
.qr-tip {
  margin-top: 10px;
  font-size: 11px;
  color: var(--text-dimmer);
  letter-spacing: 1px;
}
.qr-download {
  display: block;
  width: fit-content;
  margin: 8px auto 0;
  padding: 7px 18px;
  border-radius: 17px;
  background: var(--accent-bg);
  color: var(--accent-text);
  font-size: 12px;
  font-weight: 600;
  transition:
    background var(--dur-fast),
    color var(--dur-fast);
}
.qr-download:hover {
  background: var(--accent);
  color: #fff;
  text-decoration: none;
}

/* Image */
.img-wrap {
  margin-bottom: 8px;
}
.img-wrap img {
  max-width: 100%;
  border-radius: 8px;
  border: 1px solid var(--border);
}
/* 图片加载前预留宽高比空间，避免加载完成把下方内容顶下去（子菜单定位依赖高度稳定） */
.img-wrap.ratio-banner {
  aspect-ratio: 16 / 9;
  overflow: hidden;
}
.img-wrap.ratio-banner img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* ============ 必应壁纸：看大图 + 双尺寸下载按钮 ============ */
/* 点图看大图：图片本身是 <a target="_blank">，href 指向 4K 原图。
   display 必须显式写 block——<a> 默认是 inline，而 .ratio-banner 靠 aspect-ratio
   占位，inline 下 aspect-ratio 不生效，图片加载完会把下方内容顶下去
   （子菜单定位也依赖高度稳定）。position: relative 供右下角提示浮层定位 */
.bing-open {
  display: block;
  position: relative;
  text-decoration: none;
  cursor: zoom-in;
}
.bing-open-hint {
  position: absolute;
  right: 10px;
  bottom: 10px;
  pointer-events: none;
  padding: 5px 11px;
  border-radius: 999px;
  background: rgba(12, 20, 38, 0.62);
  color: #fff;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.2px;
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.18);
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease);
}
/* 悬停在「整张卡片」上就淡入，而不是只悬停在图片上——提示要传达的是
   「这张图能点」，而鼠标多半落在卡片任意处。只有壁纸卡带 .bing-open-hint，
   所以 .card:hover 不会波及别的卡片。
   触屏没有悬停语义，按下时亮一下作反馈 */
@media (hover: hover) {
  .card:hover .bing-open-hint {
    opacity: 1;
  }
}
.bing-open:active .bing-open-hint {
  opacity: 1;
}

/* 双尺寸下载按钮：原先两个都是「白底 + 灰边框」的等权外观，在玻璃卡里太素、
   也分不出主次。改成按用途分档——4K 原图是多数人要的，给品牌橙实底（主），
   1080P 给橙描边（次），一眼就知道该点哪个 */
.bing-dl {
  display: flex;
  gap: 8px;
  margin-bottom: 10px;
}
.bing-dl-btn {
  flex: 1;
  min-height: 38px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 9px;
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
  border: 1.5px solid var(--accent);
  background: var(--accent-bg);
  color: var(--accent-text);
  transition:
    transform var(--dur-fast),
    box-shadow var(--dur-fast),
    filter var(--dur-fast);
  -webkit-tap-highlight-color: transparent;
}
.bing-dl-btn.primary {
  border-color: transparent;
  color: #fff;
  background: linear-gradient(135deg, var(--accent), var(--accent-light));
  box-shadow: var(--accent-glow);
}
.bing-dl-btn:hover {
  filter: brightness(1.06);
  box-shadow: 0 3px 14px rgba(249, 115, 22, 0.32);
}
.bing-dl-btn:active {
  transform: scale(0.98);
}
.bing-dl-btn:disabled {
  opacity: 0.6;
  cursor: wait;
  filter: none;
  transform: none;
}
/* 移动端触控目标加大，与其余触屏控件一致（窄屏 ≤820 或触摸设备） */
@media (max-width: 820px), (pointer: coarse) {
  .bing-dl-btn {
    min-height: 44px;
    font-size: 14px;
  }
}

/* ============ 工具卡通用：数据瓦片（健康/IP/WHOIS/密码 共用） ============ */
.ht-tiles {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 6px;
}
.ht-tile {
  display: flex;
  flex-direction: column;
  gap: 2px;
  line-height: 1.3;
  min-width: 0;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 7px 10px;
}
.ht-tile .k {
  color: var(--text-dim);
  font-size: 10px;
}
.ht-tile .v {
  font-weight: 600;
  font-size: 12px;
  word-break: break-word;
  font-variant-numeric: tabular-nums;
}

/* ============ 健康计算器 ============ */
.ht-bmi {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 14px;
  margin-bottom: 10px;
}
.ht-bmi-num {
  display: flex;
  flex-direction: column;
  align-items: center;
  line-height: 1;
}
.ht-bmi-num b {
  font-size: 40px;
  font-weight: 800;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}
.ht-bmi-num span {
  font-size: 11px;
  color: var(--text-dim);
  margin-top: 2px;
  letter-spacing: 1px;
}
.ht-bmi-info {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}
.ht-bmi-info p {
  font-size: 12px;
  color: var(--text);
  line-height: 1.5;
}
.ht-bmi-info p.dim {
  color: var(--text-dim);
  font-size: 11px;
}
.ht-chip {
  display: inline-block;
  margin-left: auto;
  flex-shrink: 0;
  align-self: center;
  white-space: nowrap;
  padding: 4px 14px;
  border-radius: 12px;
  background: var(--accent-bg);
  color: var(--accent-text);
  font-size: 13px;
  font-weight: 700;
}
.ht-gauge {
  margin-bottom: 12px;
}
.ht-gauge-track {
  position: relative;
  height: 10px;
  border-radius: 5px;
  background: linear-gradient(90deg, #60a5fa 0 17.5%, #34d399 17.5% 45%, #fbbf24 45% 65%, #f87171 65% 100%);
}
.ht-gauge-track i {
  position: absolute;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--card);
  border: 3px solid var(--text);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
}
.ht-gauge-scale {
  display: flex;
  justify-content: space-between;
  margin-top: 4px;
  font-size: 9.5px;
  color: var(--text-dimmer);
}
.ht-bigs {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 6px;
  margin-bottom: 10px;
}
.ht-big {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  background: var(--accent-bg);
  border-radius: 10px;
  padding: 10px 6px;
  text-align: center;
}
.ht-big .ic {
  font-size: 16px;
}
.ht-big .k {
  font-size: 10px;
  color: var(--text-dim);
}
.ht-big b {
  font-size: 13px;
  font-weight: 800;
  color: var(--accent-text);
  font-variant-numeric: tabular-nums;
}
.ht-sec {
  margin-top: 12px;
}
.ht-sec-t {
  font-size: 12px;
  font-weight: 700;
  color: var(--text);
  margin-bottom: 6px;
}
.ht-bf {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 12px;
  color: var(--text-dim);
  margin-bottom: 8px;
}
.ht-bf-bar {
  flex: 1;
  height: 8px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 4px;
  overflow: hidden;
}
.ht-bf-bar i {
  display: block;
  height: 100%;
  background: linear-gradient(90deg, var(--accent), var(--accent-light));
  border-radius: 4px;
}
.ht-bf b {
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.ht-details {
  margin-top: 10px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--bg);
}
.ht-details summary {
  padding: 9px 12px;
  font-size: 12px;
  font-weight: 600;
  color: var(--text-dim);
  cursor: pointer;
  list-style: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}
.ht-details summary::before {
  content: '▸ ';
  color: var(--accent);
}
.ht-details[open] summary::before {
  content: '▾ ';
}
.ht-details summary:hover {
  color: var(--accent);
}
.ht-details > .ht-tiles,
.ht-details > .ht-tip,
.ht-details > div:last-child {
  margin: 0 12px 10px;
}
.ht-tip {
  font-size: 12px;
  color: var(--text-dim);
  line-height: 1.7;
}
.ht-details .ht-tiles {
  margin-top: 0;
}

/* ============ 密码生成 ============ */
.pwd-hero {
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 12px 14px;
}
.pwd-text {
  flex: 1;
  font-family: 'SF Mono', Consolas, monospace;
  font-size: 14px;
  font-weight: 700;
  word-break: break-all;
  line-height: 1.5;
  color: var(--accent-text);
}
.pwd-copy {
  flex-shrink: 0;
  padding: 6px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
  font-size: 11px;
  font-weight: 600;
  cursor: pointer;
  transition: background var(--dur-fast);
  -webkit-tap-highlight-color: transparent;
}
.pwd-copy:hover {
  background: var(--accent-light);
}
.pwd-copy.done {
  background: var(--success);
  border-color: var(--success);
}
.pwd-strength {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 10px 0 12px;
}
.pwd-strength-bar {
  flex: 1;
  height: 8px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 4px;
  overflow: hidden;
}
.pwd-strength-bar i {
  display: block;
  height: 100%;
  border-radius: 4px;
  transition: width var(--dur-slow);
}
.pwd-strength-badge {
  flex-shrink: 0;
  min-width: 44px;
  text-align: center;
  padding: 3px 12px;
  border-radius: 10px;
  color: #fff;
  font-size: 13px;
  font-weight: 800;
  line-height: 1.5;
}

/* ============ 密码强度检测 ============ */
.pwchk-hero {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 10px;
}
.pwchk-score {
  display: flex;
  align-items: baseline;
  gap: 3px;
  line-height: 1;
}
.pwchk-score b {
  font-size: 42px;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
}
.pwchk-score span {
  font-size: 12px;
  color: var(--text-dim);
}
.pwchk-side {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
}
.pwchk-strength {
  display: inline-block;
  margin-left: auto;
  flex-shrink: 0;
  align-self: center;
  padding: 3px 12px;
  border-radius: 10px;
  color: #fff;
  font-size: 13px;
  font-weight: 800;
}
.pwchk-crack {
  font-size: 12px;
  color: var(--text-dim);
}
.pwchk-crack b {
  color: var(--text);
  font-weight: 700;
}
.pwchk-bar {
  height: 10px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 5px;
  overflow: hidden;
  margin-bottom: 10px;
}
.pwchk-bar i {
  display: block;
  height: 100%;
  border-radius: 5px;
  transition: width var(--dur-slow) var(--ease);
}
.pwchk-sets {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  margin-top: 10px;
}
.pwchk-set {
  padding: 3px 10px;
  border-radius: 12px;
  font-size: 11px;
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--text-dimmer);
}
.pwchk-set.on {
  background: var(--accent-bg);
  border-color: transparent;
  color: var(--accent-text);
  font-weight: 600;
}
.pwchk-recs {
  margin-top: 10px;
  display: flex;
  flex-direction: column;
  gap: 5px;
}
.pwchk-rec {
  font-size: 12px;
  color: var(--text-dim);
  line-height: 1.6;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 7px 10px;
}

/* ============ 随机颜色 ============ */
.clr-hero {
  border-radius: 12px;
  padding: 26px 16px;
  margin-bottom: 8px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
}
.clr-name {
  font-size: 12px;
  opacity: 0.85;
}
.clr-hero b {
  font-size: 26px;
  font-weight: 800;
  letter-spacing: 1px;
  font-variant-numeric: tabular-nums;
}
.clr-bright {
  font-size: 10px;
  opacity: 0.7;
}
.clr-rows {
  display: flex;
  flex-direction: column;
  gap: 5px;
}
.clr-row {
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 7px 10px;
}
.clr-row .k {
  flex-shrink: 0;
  width: 42px;
  color: var(--text-dim);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.5px;
}
.clr-row code {
  flex: 1;
  min-width: 0;
  font-family: 'SF Mono', Consolas, monospace;
  font-size: 11.5px;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.clr-copy {
  flex-shrink: 0;
  padding: 3px 9px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--card);
  color: var(--text-dim);
  font-size: 10px;
  cursor: pointer;
  transition:
    color var(--dur-fast),
    border-color var(--dur-fast);
}
.clr-copy:hover {
  color: var(--accent);
  border-color: var(--accent);
}

/* ============ 哈希加密 ============ */
.hash-src {
  font-size: 12px;
  color: var(--text-dim);
  margin-bottom: 8px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 7px 10px;
}
.hash-src code {
  color: var(--text);
  word-break: break-all;
}
.hash-rows {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.hash-row {
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 8px 10px;
}
.hash-alg {
  flex-shrink: 0;
  width: 58px;
  font-size: 10px;
  font-weight: 700;
  color: var(--accent-text);
  letter-spacing: 0.5px;
}
.hash-val {
  flex: 1;
  min-width: 0;
  font-family: 'SF Mono', Consolas, monospace;
  font-size: 10.5px;
  color: var(--text);
  word-break: break-all;
  line-height: 1.5;
}
.hash-copy {
  flex-shrink: 0;
  padding: 3px 9px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--card);
  color: var(--text-dim);
  font-size: 10px;
  cursor: pointer;
  transition:
    color var(--dur-fast),
    border-color var(--dur-fast);
}
.hash-copy:hover {
  color: var(--accent);
  border-color: var(--accent);
}

/* ============ WHOIS ============ */
.whois-dom {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
}
.whois-name {
  font-size: 17px;
  font-weight: 800;
  word-break: break-all;
}
.whois-dnssec {
  flex-shrink: 0;
  padding: 2px 8px;
  border-radius: 8px;
  font-size: 9px;
  font-weight: 700;
  background: var(--accent-bg);
  color: var(--accent-text);
  letter-spacing: 0.5px;
}
.whois-exp {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 8px;
  padding: 8px 12px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-left: 3px solid var(--border);
  border-radius: 8px;
  font-size: 12px;
}
.whois-exp .k {
  color: var(--text-dim);
}
.whois-exp b {
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.whois-remain {
  margin-left: auto;
  padding: 2px 10px;
  border-radius: 10px;
  border: 1px solid;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.whois-ns {
  margin-top: 8px;
  font-size: 12px;
}
.whois-ns .k {
  display: block;
  color: var(--text-dim);
  font-size: 11px;
  margin-bottom: 4px;
}
.whois-ns div {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
}
.whois-ns code {
  padding: 2px 8px;
  border-radius: 6px;
  font-size: 10.5px;
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--text-dim);
}
.whois-status {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  margin-top: 8px;
}
.whois-pill {
  padding: 2px 9px;
  border-radius: 10px;
  font-size: 10px;
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--text-dimmer);
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ============ IP 查询 ============ */
.ip-hero {
  margin-bottom: 10px;
  padding: 14px 16px;
  background: var(--accent-bg);
  border-radius: 12px;
  text-align: center;
}
.ip-label {
  font-family: 'SF Mono', Consolas, monospace;
  font-size: 20px;
  font-weight: 800;
  color: var(--accent-text);
  letter-spacing: 0.5px;
  word-break: break-all;
}
.ip-map {
  display: block;
  margin-top: 8px;
  padding: 9px 12px;
  text-align: center;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--accent-text);
  font-size: 12px;
  font-weight: 600;
  transition: border-color var(--dur-fast);
}
.ip-map:hover {
  border-color: var(--accent);
  text-decoration: none;
}

/* ============ OG 分享预览卡 ============ */
.og-card {
  border: 1px solid var(--border);
  border-radius: 12px;
  overflow: hidden;
  background: var(--bg);
}
.og-img {
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: var(--card);
}
.og-img img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.og-body {
  padding: 12px 14px;
}
.og-title {
  font-size: 14px;
  font-weight: 700;
  color: var(--text);
  line-height: 1.5;
  margin-bottom: 5px;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.og-desc {
  font-size: 12px;
  color: var(--text-dim);
  line-height: 1.7;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.og-host {
  margin-top: 8px;
  font-size: 11px;
  color: var(--text-dimmer);
}

/* Text */
.text-block {
  white-space: pre-wrap;
  word-break: break-word;
  font-size: 13px;
  line-height: 1.8;
}

/* 唱鸭 */
.changya-user {
  display: flex;
  align-items: center;
  gap: 10px;
}
.changya-avatar {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  object-fit: cover;
  flex-shrink: 0;
}

/* 答案之书 */
/* ============ 答案之书：神谕卡（牌面质感 + 双线金边 + 逐字渐显） ============ */
.answer-card {
  position: relative;
  text-align: center;
  padding: 26px 18px 18px;
  border-radius: 14px;
  background:
    radial-gradient(ellipse at 50% 0%, color-mix(in srgb, var(--accent) 10%, transparent), transparent 60%), var(--bg);
  border: 1px solid color-mix(in srgb, #f59e0b 45%, var(--border));
  box-shadow:
    inset 0 0 0 4px var(--bg),
    inset 0 0 0 5px color-mix(in srgb, #f59e0b 35%, var(--border));
  overflow: hidden;
}
/* 四角 L 形角饰 */
.ans-frame {
  position: absolute;
  inset: 10px;
  pointer-events: none;
}
.ans-frame::before,
.ans-frame::after,
.answer-card::before,
.answer-card::after {
  content: '';
  position: absolute;
  width: 14px;
  height: 14px;
  border-color: color-mix(in srgb, #f59e0b 55%, var(--border));
  border-style: solid;
  border-width: 0;
}
.ans-frame::before {
  top: 0;
  left: 0;
  border-top-width: 1.5px;
  border-left-width: 1.5px;
}
.ans-frame::after {
  top: 0;
  right: 0;
  border-top-width: 1.5px;
  border-right-width: 1.5px;
}
.answer-card::before {
  bottom: 0;
  left: 0;
  border-bottom-width: 1.5px;
  border-left-width: 1.5px;
}
.answer-card::after {
  bottom: 0;
  right: 0;
  border-bottom-width: 1.5px;
  border-right-width: 1.5px;
}

.ans-title {
  font-family: Georgia, serif;
  font-size: 9px;
  letter-spacing: 3px;
  color: color-mix(in srgb, #f59e0b 80%, var(--text-dim));
  padding-bottom: 10px;
  margin-bottom: 6px;
  border-bottom: 1px solid color-mix(in srgb, #f59e0b 30%, var(--border));
  width: fit-content;
  margin-left: auto;
  margin-right: auto;
}
.ans-zh {
  font-family: Georgia, 'Songti SC', 'SimSun', serif;
  font-size: 22px;
  font-weight: 700;
  line-height: 1.7;
  letter-spacing: 3px;
  margin: 14px 0 4px;
  word-break: break-word;
  color: var(--text);
}
.answer-card.vertical .ans-zh {
  writing-mode: vertical-rl;
  text-orientation: upright;
  letter-spacing: 6px;
  margin: 6px auto 4px;
  min-height: 150px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.ans-ch {
  display: inline-block;
  opacity: 0;
  animation: ansIn var(--dur-slow) ease forwards;
}
@keyframes ansIn {
  from {
    opacity: 0;
    transform: translateY(6px);
    filter: blur(3px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
    filter: blur(0);
  }
}
.ans-en {
  font-family: Georgia, serif;
  font-style: italic;
  font-size: 12px;
  color: var(--text-dim);
  margin-top: 4px;
  letter-spacing: 0.5px;
}
.answer-card.vertical .ans-en {
  letter-spacing: 2px;
}
.ans-sep {
  margin-top: 14px;
  font-size: 8px;
  letter-spacing: 6px;
  color: color-mix(in srgb, #f59e0b 60%, var(--border));
}
.ans-seal {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 52px;
  height: 52px;
  margin-top: 10px;
  border: 1.5px solid color-mix(in srgb, #f59e0b 50%, var(--border));
  border-radius: 50%;
  font-family: Georgia, serif;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 1px;
  color: color-mix(in srgb, #f59e0b 75%, var(--text-dim));
  box-shadow:
    inset 0 0 0 3px var(--bg),
    inset 0 0 0 4px color-mix(in srgb, #f59e0b 25%, var(--border));
}

/* 金句卡片（一言/段子/KFC/发病文案等） */
.quote-card {
  position: relative;
  text-align: center;
  padding: 20px 16px 14px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 10px;
  overflow: hidden;
}
.quote-card::before {
  content: '';
  position: absolute;
  inset: 0;
  background: radial-gradient(ellipse at 50% -20%, var(--accent-bg), transparent 65%);
  pointer-events: none;
}
.quote-mark {
  font-family: Georgia, serif;
  font-size: 34px;
  color: var(--accent);
  line-height: 1;
  height: 18px;
}
.quote-text {
  position: relative;
  font-family: Georgia, 'Songti SC', 'SimSun', serif;
  font-size: 14px;
  line-height: 2;
  letter-spacing: 0.5px;
  white-space: pre-wrap;
  word-break: break-word;
  margin-top: 6px;
}
.quote-card.long {
  text-align: left;
}
.quote-card.long .quote-text {
  font-family: inherit;
  font-size: 13px;
  letter-spacing: 0;
  line-height: 1.9;
}
.quote-card.long .quote-mark {
  height: 14px;
  font-size: 26px;
}
.quote-meta {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  font-size: 10px;
  color: var(--text-dimmer);
  margin-top: 14px;
}
.quote-meta i {
  width: 26px;
  height: 1px;
  background: var(--border-light);
}
.quote-card.long .quote-meta {
  justify-content: flex-start;
}

/* 梗百科卡片 */
.geng-card {
  padding: 14px 16px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 10px;
}
.geng-title {
  font-size: 16px;
  font-weight: 700;
  color: var(--accent-text);
  margin-bottom: 8px;
}
.geng-content {
  font-size: 13px;
  color: var(--text);
  line-height: 1.8;
  word-break: break-word;
}
.geng-meta {
  font-size: 10px;
  color: var(--text-dimmer);
  margin-top: 12px;
  text-align: right;
}

/* 每日一句英语：中英对照 + 朗读按钮 */
.daily-eng {
  padding: 14px 16px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 10px;
  position: relative;
  overflow: hidden;
}
.daily-eng::before {
  content: '';
  position: absolute;
  inset: 0;
  background: radial-gradient(ellipse at 50% -20%, var(--accent-bg), transparent 65%);
  pointer-events: none;
}
.de-en {
  position: relative;
  font-family: Georgia, 'Times New Roman', serif;
  font-size: 16px;
  font-weight: 600;
  color: var(--accent-text);
  line-height: 1.6;
  word-break: break-word;
}
.de-zh {
  position: relative;
  font-size: 13px;
  color: var(--text);
  line-height: 1.8;
  margin-top: 10px;
}
.de-tts {
  position: relative;
  margin-top: 12px;
  padding: 6px 14px;
  background: var(--accent);
  border: none;
  border-radius: 16px;
  color: #fff;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  transition:
    background var(--dur-fast),
    transform var(--dur-fast),
    box-shadow var(--dur-fast);
  -webkit-tap-highlight-color: transparent;
}
.de-tts:hover {
  background: var(--accent-light);
}
.de-tts:active {
  transform: scale(0.96);
}
.de-tts.playing {
  background: var(--accent-light);
  box-shadow: var(--accent-glow);
}
.de-meta {
  position: relative;
  font-size: 10px;
  color: var(--text-dimmer);
  margin-top: 12px;
  text-align: right;
}

/* 汇率卡：基准头 + 旗帜瓷片格 + 换算计算器 */
.ex-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.ex-base {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 10px;
  border-radius: 10px;
  font-size: 12px;
  background: var(--accent-bg);
  color: var(--accent-text);
}
.ex-base b {
  font-weight: 800;
}
.ex-upd {
  font-size: 10px;
  color: var(--text-dimmer);
}
/* 瓷片 3 列自适应：10 个常用币种 4 行排完，整卡内容控制在限高内免滚动 */
.ex-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(92px, 1fr));
  gap: 5px;
  margin-top: 6px;
}
.ex-tile {
  display: flex;
  align-items: center;
  gap: 6px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 6px 8px;
}
.ex-tile-tx {
  display: flex;
  flex-direction: column;
  line-height: 1.25;
  min-width: 0;
  flex: 1;
}
.ex-code {
  color: var(--text-dim);
  font-size: 10px;
  letter-spacing: 0.5px;
}
.ex-val {
  font-weight: 700;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* 换算计算器：金额行 → 互换钮 → 结果行 */
.ex-calc {
  margin-top: 8px;
  padding: 10px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 10px;
}
.ex-calc-row {
  display: flex;
  gap: 6px;
  align-items: stretch;
}
.ex-amount {
  flex: 1;
  min-width: 0;
  padding: 7px 10px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text);
  font-size: 14px;
  outline: none;
  transition: border var(--dur-fast);
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}
.ex-amount:focus {
  border-color: var(--accent);
}
.ex-from,
.ex-to {
  flex-shrink: 0;
  padding: 7px 8px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text);
  font-size: 12px;
  outline: none;
  cursor: pointer;
  transition: border var(--dur-fast);
}
.ex-from:focus,
.ex-to:focus {
  border-color: var(--accent);
}
.ex-sym {
  flex-shrink: 0;
  width: 28px;
  height: 28px;
  line-height: 28px;
  text-align: center;
  border-radius: 8px;
  background: var(--accent-bg);
  color: var(--accent-text);
  font-size: 12px;
  font-weight: 700;
}
.ex-swap {
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 5px auto;
  width: 30px;
  height: 30px;
  border: 1px solid var(--border);
  border-radius: 50%;
  background: var(--accent-bg);
  color: var(--accent);
  cursor: pointer;
  transition:
    transform var(--dur-base) var(--ease),
    background var(--dur-fast),
    color var(--dur-fast);
  -webkit-tap-highlight-color: transparent;
}
.ex-swap svg {
  display: block;
}
.ex-swap:hover {
  background: var(--accent);
  color: #fff;
  transform: rotate(180deg);
}
.ex-swap:active {
  transform: rotate(180deg) scale(0.92);
}
.ex-result {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 7px 10px;
  background: var(--accent-bg);
  border-radius: 8px;
  overflow: hidden;
}
.ex-res-num {
  font-size: 15px;
  font-weight: 800;
  color: var(--accent-text);
  font-variant-numeric: tabular-nums;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ex-res-code {
  font-size: 12px;
  font-weight: 600;
  color: var(--accent-text);
}

/* ============ 金价卡：主价格 + 区间条 + 趋势曲线 + 品种瓷片 ============ */
.gold-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  font-size: 12px;
  color: var(--text-dim);
}
.gold-upd {
  font-size: 10px;
  color: var(--text-dimmer);
}
.gold-hero {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 6px 0 2px;
}
.gold-price {
  font-size: 34px;
  font-weight: 800;
  color: #f59e0b;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.gold-unit {
  font-size: 13px;
  font-weight: 500;
  color: var(--text-dim);
  margin-left: 2px;
}
.gold-name {
  font-size: 12px;
  color: var(--text-dim);
}
.gold-range {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 8px;
  font-size: 11px;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}
.gold-range-bar {
  position: relative;
  flex: 1;
  height: 6px;
  border-radius: 3px;
  background: var(--bg);
  border: 1px solid var(--border);
}
.gold-range-bar i {
  position: absolute;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: #f59e0b;
  border: 2px solid var(--card);
  box-shadow: 0 0 0 3px rgba(245, 158, 11, 0.2);
}
.gold-chart-wrap {
  margin-top: 10px;
  padding: 8px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 10px;
}
.gold-chart {
  display: block;
  width: 100%;
  height: auto;
}
.gold-chart-tip {
  margin-top: 10px;
  padding: 12px 8px;
  text-align: center;
  font-size: 11px;
  color: var(--text-dimmer);
  background: var(--bg);
  border: 1px dashed var(--border);
  border-radius: 10px;
}
.gold-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 6px;
  margin-top: 10px;
}
.gold-tile {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 7px 10px;
  font-size: 12px;
}
.gold-tile-name {
  color: var(--text-dim);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.gold-tile-val {
  font-weight: 700;
  color: var(--text);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* ============ 摸鱼日历：日期状态头 + 倒计时瓷片 + 假期条 + 进度条 + 语录 ============ */
.moyu-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  flex-wrap: wrap;
}
.moyu-date {
  display: flex;
  flex-direction: column;
  line-height: 1.3;
}
.moyu-date b {
  font-size: 20px;
  font-weight: 800;
}
.moyu-date span {
  font-size: 11px;
  color: var(--text-dim);
}
.moyu-badge {
  padding: 4px 12px;
  border-radius: 14px;
  font-size: 12px;
  font-weight: 700;
  white-space: nowrap;
}
.moyu-badge.rest {
  background: var(--accent-bg);
  color: var(--accent-text);
}
.moyu-badge.work {
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--text-dim);
}
.moyu-badge.holiday {
  background: linear-gradient(135deg, #f97316, #fb7185);
  color: #fff;
  box-shadow: var(--accent-glow);
}
.moyu-tiles {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 6px;
  margin-top: 10px;
}
.moyu-tile {
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 8px 10px;
}
.moyu-tile .ic {
  font-size: 16px;
  line-height: 1;
  flex-shrink: 0;
}
.moyu-tile .tx {
  display: flex;
  flex-direction: column;
  line-height: 1.3;
  min-width: 0;
}
.moyu-tile .k {
  color: var(--text-dim);
  font-size: 10px;
}
.moyu-tile .v {
  font-weight: 700;
  font-size: 14px;
  font-variant-numeric: tabular-nums;
}
.moyu-tile .v i {
  font-style: normal;
  font-size: 10px;
  font-weight: 500;
  color: var(--text-dim);
}
.moyu-next {
  margin-top: 10px;
  padding: 8px 12px;
  font-size: 12px;
  color: var(--text-dim);
  background: var(--bg);
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: 8px;
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}
.moyu-next b {
  color: var(--accent-text);
  font-weight: 800;
}
.moyu-progress {
  margin-top: 9px;
}
.moyu-progress-label {
  display: flex;
  justify-content: space-between;
  font-size: 11px;
  color: var(--text-dim);
  margin-bottom: 4px;
  font-variant-numeric: tabular-nums;
}
.moyu-progress-bar {
  height: 8px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 4px;
  overflow: hidden;
}
.moyu-progress-bar i {
  display: block;
  height: 100%;
  border-radius: 4px;
  background: linear-gradient(90deg, var(--accent), var(--accent-light));
  transition: width var(--dur-slow) var(--ease);
}
.moyu-quote {
  margin-top: 12px;
  padding: 10px 12px;
  font-size: 12.5px;
  line-height: 1.8;
  color: var(--text-dim);
  background: var(--accent-bg);
  border-radius: 10px;
  position: relative;
}

/* ============ JS 题目：题干 + 代码块（可复制）+ 可点击答题 ============ */
.jsq-q {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  margin-bottom: 10px;
}
.jsq-no {
  flex-shrink: 0;
  padding: 2px 9px;
  border-radius: 9px;
  background: var(--accent);
  color: #fff;
  font-size: 11px;
  font-weight: 800;
}
.jsq-qt {
  font-size: 13px;
  font-weight: 600;
  line-height: 1.6;
  word-break: break-word;
}
.jsq-code {
  position: relative;
  margin-bottom: 10px;
}
.jsq-code pre {
  margin: 0;
  padding: 12px 12px;
  overflow-x: auto;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 10px;
  font-family: 'SF Mono', Consolas, monospace;
  font-size: 11.5px;
  line-height: 1.65;
  color: var(--text);
  tab-size: 2;
}
.jsq-copy {
  position: absolute;
  top: 6px;
  right: 6px;
  padding: 2px 9px;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--text-dim);
  font-size: 10px;
  cursor: pointer;
  transition:
    color var(--dur-fast),
    border-color var(--dur-fast);
}
.jsq-copy:hover {
  color: var(--accent);
  border-color: var(--accent);
}
.jsq-opts {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.jsq-opt {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  width: 100%;
  text-align: left;
  padding: 9px 12px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 10px;
  color: var(--text);
  font-size: 12.5px;
  line-height: 1.6;
  cursor: pointer;
  transition:
    border-color var(--dur-fast),
    background var(--dur-fast),
    transform var(--dur-fast);
  -webkit-tap-highlight-color: transparent;
}
.jsq-opt:hover:not(:disabled) {
  border-color: var(--accent);
}
.jsq-opt:active:not(:disabled) {
  transform: scale(0.99);
}
.jsq-opt:disabled {
  cursor: default;
  opacity: 0.75;
}
.jsq-opt-no {
  flex-shrink: 0;
  width: 20px;
  height: 20px;
  line-height: 20px;
  text-align: center;
  border-radius: 6px;
  background: var(--card);
  border: 1px solid var(--border);
  color: var(--text-dim);
  font-size: 10px;
  font-weight: 700;
}
.jsq-opt.correct {
  background: rgba(52, 211, 153, 0.12);
  border-color: var(--success);
  color: var(--success);
  opacity: 1;
  font-weight: 600;
}
.jsq-opt.correct .jsq-opt-no {
  background: var(--success);
  border-color: var(--success);
  color: #fff;
}
.jsq-opt.wrong {
  background: rgba(248, 113, 113, 0.1);
  border-color: var(--error);
  color: var(--error);
  opacity: 1;
  animation: jsqShake var(--dur-base) ease;
}
.jsq-opt.wrong .jsq-opt-no {
  background: var(--error);
  border-color: var(--error);
  color: #fff;
}
@keyframes jsqShake {
  0%,
  100% {
    transform: translateX(0);
  }
  25% {
    transform: translateX(-4px);
  }
  75% {
    transform: translateX(4px);
  }
}
.jsq-verdict {
  margin-top: 8px;
  font-size: 13px;
  font-weight: 700;
}
.jsq-verdict.ok {
  color: var(--success);
}
.jsq-verdict.bad {
  color: var(--error);
}
.jsq-exp {
  margin-top: 8px;
  padding: 10px 12px;
  font-size: 12px;
  line-height: 1.8;
  color: var(--text-dim);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 10px;
}
.jsq-exp-t {
  display: block;
  margin-bottom: 4px;
  font-weight: 700;
  color: var(--accent-text);
}

/* SIMKL 流媒体热门榜（字号/字重/分隔线与 .item 系保持一致；
   注意 .meta 必须在此显式声明——通用 .meta 规则只作用于 .item 后代） */
.simkl-item {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 9px 0;
  border-bottom: 1px solid color-mix(in srgb, var(--border) 50%, transparent);
  font-size: 13px;
}
.simkl-item:last-child {
  border-bottom: none;
}
.simkl-poster {
  width: 40px;
  height: 58px;
  border-radius: 6px;
  object-fit: cover;
  flex-shrink: 0;
  background: var(--border);
  border: 1px solid var(--border);
}
.simkl-body {
  flex: 1;
  min-width: 0;
}
.simkl-body a,
.simkl-body .t {
  color: var(--text);
  text-decoration: none;
  display: block;
  word-break: break-word;
  line-height: 1.5;
}
.simkl-body a:hover {
  color: var(--accent-light);
}
.simkl-body .meta {
  font-size: 11px;
  color: var(--text-dim);
  margin-top: 3px;
}
/* 流媒体榜的 .rank 样式见文件开头的「名次角标（全站统一）」 */
.simkl-badge {
  display: inline-block;
  margin-top: 4px;
  padding: 1px 8px;
  border-radius: 8px;
  font-size: 10px;
  font-weight: 600;
  color: var(--accent-text);
  background: var(--accent-bg);
}

/* JSON */
.json-view {
  font-family: 'SF Mono', Consolas, monospace;
  font-size: 11px;
  background: var(--bg);
  padding: 10px;
  border-radius: 6px;
  overflow-x: auto;
  white-space: pre;
  max-height: 400px;
  overflow-y: auto;
  line-height: 1.5;
  scrollbar-width: thin;
  scrollbar-color: var(--border-light) transparent;
}
.json-view::-webkit-scrollbar {
  width: 6px;
  height: 6px;
}
.json-view::-webkit-scrollbar-track {
  background: transparent;
}
.json-view::-webkit-scrollbar-thumb {
  background: var(--border);
  border-radius: 3px;
  border: 2px solid transparent;
  background-clip: padding-box;
}
.json-view::-webkit-scrollbar-thumb:hover {
  background: var(--border-light);
  background-clip: padding-box;
}

/* Progress bar (moyu) */
.progress-bar {
  height: 6px;
  background: var(--bg);
  border-radius: 3px;
  overflow: hidden;
  margin-top: 2px;
}
.progress-fill {
  height: 100%;
  background: var(--accent);
  border-radius: 3px;
  transition: width var(--dur-slow);
}

/* Game card */
/* 免费游戏卡（Epic / Steam 共用）：字号字重与 .item 系统一，
   描述/截止/原价/领取均用 class 管理而非内联样式（.desc 通用规则只作用于 .item 后代） */
.game-card {
  margin-bottom: 12px;
  padding-bottom: 10px;
  border-bottom: 1px solid color-mix(in srgb, var(--border) 50%, transparent);
  font-size: 13px;
}
.game-card:last-child {
  border: none;
}
.game-card img {
  border-radius: 6px;
}
.game-title {
  font-size: 13px;
  font-weight: 600;
  margin-top: 6px;
  line-height: 1.5;
}
.game-card .desc {
  font-size: 11.5px;
  color: var(--text-dim);
  margin-top: 3px;
  line-height: 1.5;
}
.game-free {
  display: inline-block;
  padding: 1px 7px;
  background: rgba(52, 211, 153, 0.15);
  color: var(--success);
  border-radius: 4px;
  font-size: 10px;
  font-weight: 600;
  margin-top: 3px;
}
.game-end {
  font-size: 10px;
  color: var(--warn);
}
.game-orig {
  font-size: 10px;
  color: var(--text-dim);
  text-decoration: line-through;
}
/* 领取按钮：成功色渐变胶囊，与「免费」徽标同色系呼应，
   和全站橙色主色形成对比，让领取入口一眼可见 */
.game-claim {
  margin-top: 8px;
}
.game-claim a {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 6px 16px;
  border-radius: 999px;
  background: linear-gradient(135deg, var(--success), #10b981);
  color: #fff;
  font-size: 12px;
  font-weight: 700;
  text-decoration: none;
  box-shadow: 0 2px 8px rgba(52, 211, 153, 0.3);
  transition:
    transform var(--dur-fast),
    box-shadow var(--dur-fast),
    filter var(--dur-fast);
  -webkit-tap-highlight-color: transparent;
}
.game-claim a:hover {
  filter: brightness(1.08);
  box-shadow: 0 3px 12px rgba(52, 211, 153, 0.45);
  transform: translateY(-1px);
}
.game-claim a:active {
  transform: translateY(0) scale(0.97);
}

/* Steam 头图胶囊比例（header 460x215），约束高度避免原始尺寸撑爆卡片 */
.img-wrap.ratio-capsule {
  aspect-ratio: 460 / 215;
  overflow: hidden;
}
.img-wrap.ratio-capsule img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* 卡片空态（免费游戏等）：居中图标 + 主文案 + 副说明 */
.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  padding: 26px 12px;
  text-align: center;
}
.empty-state .es-icon {
  font-size: 30px;
  line-height: 1;
}
.empty-state .es-text {
  color: var(--text-dim);
  font-size: 13px;
  font-weight: 600;
}
.empty-state .es-sub {
  color: var(--text-dimmer);
  font-size: 11px;
}

/* 搜索无结果空态：复用 empty-state 语言（图标+主文案+副说明），比一行灰字更友好 */
.search-empty {
  padding: 48px 12px;
}

/* Weather */
.wx-loc {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-size: 13px;
  font-weight: 600;
  margin-bottom: 8px;
}
.wx-upd {
  font-size: 10px;
  font-weight: 400;
  color: var(--text-dimmer);
}
.wx-alert {
  display: flex;
  align-items: center;
  gap: 6px;
  background: rgba(248, 113, 113, 0.1);
  color: var(--error);
  border: 1px solid rgba(248, 113, 113, 0.3);
  border-radius: 8px;
  padding: 5px 10px;
  font-size: 12px;
  font-weight: 600;
  margin-bottom: 8px;
}
.wx-main {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 6px 0 10px;
}
.wx-icon {
  width: 52px;
  height: 52px;
  flex-shrink: 0;
}
.wx-temp {
  font-size: 38px;
  font-weight: 800;
  line-height: 1;
}
.wx-cond {
  font-size: 13px;
  font-weight: 500;
  color: var(--text-dim);
  margin-top: 4px;
}
.wx-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 6px;
}
.wx-stat {
  display: flex;
  align-items: center;
  gap: 8px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 7px 10px;
  font-size: 12px;
}
.wx-stat-ic {
  font-size: 15px;
  line-height: 1;
  flex-shrink: 0;
}
.wx-stat-tx {
  display: flex;
  flex-direction: column;
  line-height: 1.3;
  min-width: 0;
}
.wx-stat-tx .k {
  color: var(--text-dim);
  font-size: 10px;
}
.wx-stat-tx .v {
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* 空气质量面板：左侧质量徽标（质量+AQI 数值），中间污染物，右侧全国排名胶囊 */
.wx-aqi-panel {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 8px;
  padding: 8px 10px;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 10px;
  font-size: 12px;
}
.wx-aqi-badge {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: 0;
  padding: 3px 12px;
  border-radius: 10px;
  color: #fff;
  line-height: 1.25;
  flex-shrink: 0;
}
.wx-aqi-badge b {
  font-size: 16px;
  font-weight: 800;
}
.wx-aqi-vals {
  display: flex;
  gap: 10px;
  color: var(--text-dim);
}
.wx-aqi-vals b {
  color: var(--text);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.wx-aqi-rank {
  margin-left: auto;
  padding: 2px 9px;
  border-radius: 10px;
  background: var(--accent-bg);
  color: var(--accent-text);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.wx-aqi-rank i {
  font-style: normal;
  color: var(--accent-text);
  font-weight: 500;
}
.wx-sun {
  margin-top: 8px;
  font-size: 12px;
  color: var(--text-dim);
  display: flex;
  gap: 14px;
}
.wx-life {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  margin-top: 10px;
}
.wx-chip {
  font-size: 10px;
  padding: 2px 8px;
  border-radius: 8px;
  background: var(--accent-bg);
  color: var(--accent-text);
  border: 1px solid transparent;
  cursor: help;
  white-space: nowrap;
}
.wx-chip:hover {
  border-color: var(--accent);
}
.wx-chart {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 4px 8px;
}
.wx-chart-head,
.wx-chart-foot {
  display: grid;
}
.wxc-col {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  font-size: 10px;
  color: var(--text-dim);
  padding: 0 1px;
}
.wxc-col img {
  width: 26px;
  height: 26px;
}
.wx-chart-svg {
  margin-top: 4px;
}
.wx-chart-svg svg {
  display: block;
}
.wxc-day {
  text-align: center;
  font-size: 11px;
  color: var(--text-dim);
  font-weight: 600;
  margin-top: 2px;
}
.wxc-day.today {
  color: var(--accent-text);
}
.wx-hourly-title {
  font-size: 11px;
  color: var(--text-dim);
  margin: 12px 0 6px;
  font-weight: 600;
}
.wx-hourly {
  display: flex;
  gap: 4px;
  overflow-x: auto;
  padding-bottom: 4px;
  scrollbar-width: thin;
}
.wx-hour {
  flex-shrink: 0;
  width: 56px;
  text-align: center;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 6px 2px;
}
.wx-hour-t {
  font-size: 10px;
  color: var(--text-dim);
}
.wx-hour img {
  width: 26px;
  height: 26px;
  margin: 2px 0;
}
.wx-hour-temp {
  font-size: 13px;
  font-weight: 700;
}
.wx-hour-c {
  font-size: 10px;
  color: var(--text-dimmer);
  white-space: nowrap;
}

/* ===== 油价模块 ===== */
.fuel-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: 6px;
}
.fuel-region {
  font-size: 14px;
  font-weight: 700;
  color: var(--text);
}
.fuel-updated {
  font-size: 11px;
  color: var(--text-dimmer);
}
.fuel-trend {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  padding: 5px 10px;
  border-radius: 10px;
  margin-bottom: 8px;
  font-size: 11.5px;
  border: 1px solid;
}
.fuel-trend .dir {
  font-weight: 700;
}
.fuel-trend .desc {
  color: var(--text-dim);
}
.fuel-trend .date {
  margin-left: auto;
  color: var(--text-dimmer);
  font-size: 11px;
}
.fuel-trend.up {
  background: rgba(239, 68, 68, 0.08);
  border-color: rgba(239, 68, 68, 0.25);
}
.fuel-trend.up .dir {
  color: #ef4444;
}
.fuel-trend.down {
  background: rgba(16, 185, 129, 0.08);
  border-color: rgba(16, 185, 129, 0.25);
}
.fuel-trend.down .dir {
  color: #10b981;
}
.fuel-trend.flat {
  background: var(--accent-bg);
  border-color: var(--border);
}
.fuel-trend.flat .dir {
  color: var(--text-dim);
}
.fuel-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 6px;
  margin-bottom: 8px;
}
.fuel-card {
  position: relative;
  padding: 7px 8px 6px 11px;
  border-radius: 10px;
  background: var(--card-hover);
  border: 1px solid var(--border);
  overflow: hidden;
}
.fuel-card::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 3px;
  background: var(--c);
  border-radius: 3px;
}
.fuel-card .name {
  display: block;
  font-size: 10.5px;
  color: var(--text-dim);
  margin-bottom: 1px;
}
.fuel-card .price {
  font-size: 16px;
  font-weight: 800;
  color: var(--text);
  letter-spacing: -0.5px;
}
.fuel-card .price small {
  font-size: 9px;
  font-weight: 400;
  color: var(--text-dimmer);
  margin-left: 2px;
}
.fuel-chart {
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 10px;
  background: var(--card-hover);
}
.fuel-chart-title {
  font-size: 12px;
  font-weight: 600;
  color: var(--text-dim);
  margin-bottom: 6px;
}
.fuel-chart-svg {
  width: 100%;
  height: auto;
  display: block;
}
.fl-grid {
  stroke: var(--border);
  stroke-width: 0.5;
  stroke-dasharray: 2 3;
}
.fl-ylabel {
  font-size: 8.5px;
  fill: var(--text-dimmer);
}
.fl-xlabel {
  font-size: 8.5px;
  fill: var(--text-dimmer);
}
.fl-last {
  font-size: 9px;
  font-weight: 700;
  fill: #f59e0b;
}
.fuel-legend {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: 5px;
  font-size: 10.5px;
  color: var(--text-dim);
}
.fuel-legend span {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.fuel-legend i {
  width: 12px;
  height: 3px;
  border-radius: 2px;
  display: inline-block;
}
.fuel-link {
  margin-top: 6px;
  font-size: 11px;
  text-align: right;
}
.fuel-link a {
  color: var(--text-dim);
  text-decoration: none;
}
.fuel-link a:hover {
  color: var(--accent);
}
@media (max-width: 560px) {
  /* 窄屏：价格格 4 列太挤，改为 2x2 */
  .fuel-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

/* ============ P3: Card fade-in + keyboard focus ============ */
/* 入场动画的 two traps，都是这一条 fadeIn 带来的：

   ① `fill-mode: both` 会让结束态（opacity:1 / translateY(0)）**永久**参与层叠，
      而动画效果在层叠里高于普通声明 —— 于是下面 .card:hover 的
      `transform: translateY(-2px)` 永远不会生效（实测悬停时 transform 恒为
      matrix(1,0,0,1,0,0)）。悬停抬升其实一直是死的。

   ② 任何把 animation 置回 none 的规则（.card:fullscreen / .card.fs-fake /
      .vt-run .card）都会「销毁 + 重建」这条动画。带 fill-mode:both 与正的
      内联 animation-delay（卡片 staggering）时，重建意味着回到 before-phase：
      先按 from 帧渲染（opacity:0 + translateY(8px)），等完延迟再淡入 250ms。
      退出全屏时肉眼就是「卡片闪一下 → 整卡上滑 8px 淡入」，也就是用户报的抖动。

   所以入场动画跑完由 JS 把它摘成内联 `animation: none`（见 app.js 的
   animationend 委托）：结束态不再残留 → 悬停抬升恢复；animation-name 不再变化
   → 全屏 / 伪全屏 / View Transition 来回切换都不会重建动画。
   @keyframes 本身保持不动。 */
@keyframes fadeIn {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}
.card {
  animation: fadeIn var(--dur-base) ease both;
}
.card.kb-focus {
  border-color: var(--accent);
  box-shadow: var(--accent-glow);
}

/* ============ 响应式适配（置于末尾以正确覆盖上方规则） ============ */
/* 移动端布局（窄屏 ≤820px）：侧边栏折叠回顶部横向滚动 pill 栏，内容纵向单列；
   与 app.js 的 MQ_MOBILE 同口径，避免大屏触控设备（Surface/触摸笔记本等）被误判掉回移动端样式 */
@media (max-width: 820px) {
  /* 移动端 GPU 与发热降载：光斑保留静态渐变景深，停用无限漂移动画与合成层提升，彻底消除移动端每秒数十次的 GPU 复合图层重绘 */
  .aura-b {
    animation: none;
    will-change: auto;
  }
  /* header：时钟切移动版、布局换行，避免挤压 */
  .clock-desktop {
    display: none;
  }
  .clock-mobile {
    display: flex;
    /* 与 4 个按钮同住 .header-right（第二行），靠行尾对齐由该容器的
       justify-content:flex-end 统一负责；这里不再用 margin-left:auto——
       那会把它顶到容器最右、把按钮挤到它左边去 */
    /* 不收缩也不拉伸：宽度完全由内容决定，放不放得下交给 JS 实测判定 */
    flex: 0 0 auto;
    min-width: 0;
  }
  .header {
    padding: 10px 14px;
    flex-wrap: wrap;
    gap: 10px;
  }
  /* 第一行 = logo + 搜索框：两者同行，剩余宽度由搜索框吃掉。
     max-width 放开成 none，否则稍宽的机型上它会停在 420px、右侧空一截 */
  .header-left {
    flex: 0 0 auto;
    gap: 10px;
  }
  .site-search {
    max-width: none;
  }
  /* 引擎选择：按钮放大到 30px（仍在 36px 搜索框内），菜单项给足 40px 触控目标；
     菜单宽一点更好点，右对齐后不会顶出窄屏 */
  .ss-engine {
    height: 30px;
    padding: 0 4px 0 5px;
  }
  .ss-engine-face svg,
  .ss-item-icon svg {
    width: 16px;
    height: 16px;
  }
  /* 菜单宽度已在基础规则改为内容自适应（max-content + min-width 124px），
     移动端条目同样是两三个短名，固定 176px 的旧覆盖已删 */
  .ss-item {
    min-height: 40px;
  }
  /* 移动端同样保留搜索框——第一行 logo + 搜索框、第二行 时钟 + 4 按钮，仍是两行。
     （上一版把它整个隐藏，是因为当时时钟还在第一行、第二行只放按钮；现在时钟已归入
     .header-right，第二行本来就是它俩。过窄时的时间胶囊降级交给 fitClocks，
     不靠隐藏控件腾地方） */
  /* 第二行 = 时间胶囊 + GitHub/主题/壁纸/刷新 4 按钮，必须锁死在同一行：
     容器 nowrap + 两侧都不收缩，宽度不够时由 fitClocks 把日期降级成短版。
     这里不能用 wrap —— 一旦允许换行，刷新按钮就会被挤到第三行（已踩过这个坑） */
  .header-right {
    flex: 1 1 100%;
    justify-content: flex-end;
    flex-wrap: nowrap;
    gap: 6px;
    min-width: 0;
  }
  .header-right a,
  .header-right button {
    width: 32px;
    height: 32px;
    flex-shrink: 0;
  }
  /* 时间不裁：秒数跳动时宽度恒定，优先保证完整可读 */
  .clock-mobile .clock-time {
    flex-shrink: 0;
  }
  /* 日期一定完整：显示完整版还是短版由 fitClocks 实测决定，
     两态都是整段文本，不存在被裁成半截或出现省略号的可能 */
  .clock-mobile .clock-date {
    white-space: nowrap;
  }
  .logo .subtitle {
    display: none;
  }

  /* 布局纵向：分类栏移到顶部、内容在下方 */
  .layout {
    flex-direction: column;
    padding: 0;
  }
  /* 移动端分类导航：cat-nav 只是吸顶包装器，cat-pills 才是横向滚动 pill 行 */
  .cat-nav {
    flex-direction: column;
    gap: 0;
    width: 100%;
    /* 移动端吸顶栏与桌面同为 Liquid Glass：玻璃底 + 强饱和/提亮折射 */
    margin: 0;
    padding: 0;
    /* 复位桌面那两条为「与顶栏等高 / 复刻卡片限宽」写的规则：
       移动端顶栏是两行（明显更高），导航高度该由 pill 行自己决定；
       内容位置也不必套用限宽公式（移动端 .layout 无内边距，
       卡片左边界就是 .main 的 14px），交给 pill 行自身的横向内边距承担 */
    min-height: 0;
    border: none;
    border-radius: 0;
    border-bottom: 1px solid var(--glass-border);
    /* 吸顶：紧贴顶栏下方，下滑时分类栏始终可见可切换；
       top 由 JS 按顶栏实际高度设置 --topbar-h，回退值 96px 适配两行 header */
    position: sticky;
    top: var(--topbar-h, 96px);
    z-index: 90;
    background: var(--glass-bg);
    backdrop-filter: blur(28px) saturate(180%);
    -webkit-backdrop-filter: blur(28px) saturate(180%);
    max-height: none;
    align-self: auto;
    margin: 0;
    overflow: visible;
  }
  /* pill 行内边距（不换行 / 横向滚动 / 拖拽规则已在基础样式里，桌面移动共用）。
     横向取 14px 与 .main 的内边距一致（≤480px 时两边同步收成 10px）——
     移动块也会命中「宽屏触屏」设备，此时 .cat-nav 的 padding 是 0，
     卡片左边界由 .main 决定，pill 不跟着对齐就会与卡片错开一截 */
  .cat-pills {
    padding: 6px 14px;
  }
  .cat-sub {
    display: none !important;
  }
  /* 触摸设备用原生滑动就够，箭头只服务鼠标场景，隐藏以免占掉本就紧张的行宽 */
  .cat-nav .cat-arrow {
    display: none;
  }
  /* 模块面板：点击已激活分类 pill 开合的悬浮目录（EPS 级，分组成员直达标签页）。
     fixed 挂在 body 下（JS 控制 top 贴吸顶栏底边）——不能做 .cat-nav 的子元素：
     吸顶栏自身带 backdrop-filter 会形成 backdrop root，其后代的毛玻璃只能采样
     root 内部已绘制内容，悬浮在壁纸上时采样为空，模糊失效退化成纯半透明。
     面板外点击 / Esc / 定位跳转关闭 */
  .cat-panel {
    position: fixed;
    top: 96px;
    left: 0;
    right: 0;
    z-index: 89;
    display: none;
    grid-template-columns: 1fr 1fr;
    gap: 6px;
    padding: 10px 12px 12px;
    /* 折叠菜单面板：与卡片同款 Liquid Glass（玻璃底 + 强饱和/提亮折射 + 多层内高光） */
    background: var(--glass-bg);
    backdrop-filter: blur(28px) saturate(180%) brightness(1.06);
    -webkit-backdrop-filter: blur(28px) saturate(180%) brightness(1.06);
    border-bottom: 1px solid var(--glass-border);
    box-shadow:
      0 14px 30px rgba(0, 0, 0, 0.2),
      inset 0 1px 0 var(--glass-hi),
      inset 0 -1px 0 var(--glass-lo),
      inset 0 0 24px var(--glass-lo);
    max-height: min(55vh, 420px);
    overflow-y: auto;
    overscroll-behavior: contain;
  }
  .cat-panel.open {
    display: grid;
    animation: fadeIn var(--dur-fast) ease;
  }
  .cat-panel .cat-subitem {
    display: flex;
    align-items: center;
    min-width: 0;
    min-height: 40px;
    padding: 0 12px;
    /* 玻璃面板内的子项：半透明玻璃 chip（不叠 blur，避免多层 backdrop-filter 开销） */
    border: 1px solid var(--glass-border);
    border-radius: 12px;
    background: var(--glass-bg);
    font-size: 12px;
    -webkit-tap-highlight-color: transparent;
  }
  .cat-panel .cat-subitem:active {
    background: var(--accent-bg);
    color: var(--accent-text);
  }
  .cat-panel .cat-subitem.active {
    background: var(--accent);
    border-color: transparent;
    color: #fff;
    font-weight: 600;
    box-shadow: var(--accent-glow);
  }
  /* pill 上的面板开合箭头：只在已激活分类显示，方向约定与桌面一致
     ▸=面板收起、rotate(90°)→▾=面板展开（toc-open） */
  .cat-pills > button.active[data-cat]:not([data-cat='all'])::after {
    content: '▸';
    margin-left: 4px;
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 16px;
    font-weight: 700;
    color: #fff;
    transition: transform var(--dur-base) var(--ease);
    transform: rotate(0deg);
  }
  .cat-nav.toc-open .cat-pills > button.active::after {
    transform: rotate(90deg);
  }
  .cat-pills > button {
    /* 固定高度（含上下 border），垂直方向由 flex 居中，与模块 chip 等高 */
    height: 42px;
    padding: 0 15px;
    text-align: center;
    font-size: 13px;
    border-radius: 17px;
    white-space: nowrap;
    flex-shrink: 0;
    margin-bottom: 0;
    /* 屏蔽浏览器触屏默认高亮，改用 :active 状态贴合胶囊形状 */
    -webkit-tap-highlight-color: transparent;
  }
  .cat-pills > button:active {
    background: var(--card);
  }
  .cat-pills > button.active:active {
    background: var(--accent-light);
  }
  .cat-pills > button.active {
    background: var(--accent);
    box-shadow: none;
  }

  /* 移动端首张卡片需紧贴吸顶分类栏：收紧上内边距（原 14px 在 sticky 导航下显空） */
  .main {
    padding: 6px 14px 48px;
  }
  .grid {
    grid-template-columns: 1fr;
    gap: 12px;
  }
  /* 移动端单列：锚点卡片回退通栏，宽卡片内部双栏也回退单列 */
  .card.span-2 {
    grid-column: span 1;
  }
  .card.span-2:not(.expanded) .card-body {
    column-count: 1;
    column-width: auto;
    column-gap: normal;
  }
  /* 展开全部/收起按钮：44px 触控目标，与其余触屏控件一致 */
  .list-toggle {
    min-height: 44px;
    padding: 10px 0;
  }
  /* 计数徽章仅桌面显示：移动 pill 空间优先给名称 */
  .cat-pills > button .cnt {
    display: none;
  }
  /* 子菜单定位跳转偏移：吸顶顶栏 + 分类 pill 单行（模块面板是悬浮层不占位）。
     +66 → +76：顶栏浮起来后上方多出 10px 走道（见 .app-shell .topbar），
     停靠线整体下移，否则定位到的卡片会被顶栏多盖住 10px */
  .card {
    scroll-margin-top: calc(var(--topbar-h, 96px) + 76px);
  }
  /* 移动端性能：backdrop-filter 逐帧模糊是最大的 GPU 消耗，
     单列布局卡片通栏宽，模糊面积巨大——改用高不透明度纯色底，
     视觉上仍保留轻微通透感，合成开销大幅下降 */
  .card {
    /* 移动端与桌面同为 Liquid Glass：玻璃底 + 强饱和/提亮的 backdrop-filter */
    background: var(--glass-bg);
    backdrop-filter: blur(28px) saturate(180%) brightness(1.06);
    -webkit-backdrop-filter: blur(28px) saturate(180%) brightness(1.06);
  }
  /* 健康卡三列大数字在窄屏收紧字号防换行错位 */
  .ht-big {
    padding: 8px 4px;
  }
  .ht-big b {
    font-size: 11px;
  }
  .ht-big .k {
    font-size: 9px;
  }

  /* 卡片内输入行/下拉：触屏适配——44px 触控目标 + 16px 字号防 iOS 聚焦缩放 */
  .input-row input,
  .input-row select {
    font-size: 16px;
    padding: 10px 12px;
    min-height: 44px;
    border-radius: 8px;
  }
  .input-row select {
    -webkit-appearance: none;
    appearance: none;
    padding-right: 34px;
    /* 去掉原生外观后自绘下拉箭头（与 fanyi 语言下拉同款） */
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%237a8094' stroke-width='2'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 12px center;
  }
  .input-row .go {
    min-height: 44px;
    padding: 10px 18px;
    font-size: 14px;
  }
  /* 卡片内部内容不滚动：合并进页面滚动，便于触屏连续阅读 */
  .card-body,
  .card-pane {
    max-height: none;
    overflow-y: visible;
  }
  /* 数据源下拉：触屏给足触控高度，横向内边距与卡片内容区对齐 */
  .card-select-row {
    padding: 10px 14px 0;
  }
  .card-select {
    min-height: 40px;
    font-size: 13px;
  }
  .json-view {
    max-height: none;
    overflow: visible;
  }
}

/* 手机小屏：进一步收紧触控与留白。
   注意特异性：后方 ≤1100/≤900 块里是 .app-shell .header / .app-shell .main 这类
   两级选择器，且在文件里更靠后——这里的小屏收紧若用单类名会被静默压掉
   （此前 .header/.main 两条就是这样变成死代码的），必须按同链路提升特异性 */
@media (max-width: 480px) {
  /* (0,3,0) 压过 ≤900 的 .app-shell .header（8px 14px）：小屏侧边距收成 12px，
     与 12px 走道基准对齐 */
  .app-shell .topbar .header {
    padding: 8px 12px;
    gap: 8px;
  }
  /* cat-nav 不加内边距：吸顶分类栏上缘必须与顶栏底边贴合，
     行内留白由 pill 行自身的 padding 提供 */
  .cat-pills {
    padding: 6px 10px;
  }
  .cat-pills > button {
    padding: 0 13px;
    font-size: 13px;
  }
  /* 原 ≤480 的 .main 收边规则已删：内容列走道由 .app-shell .layout 的 12px
     统一承担、.app-shell .main 横向内边距恒为 0，小屏再收会让卡片与
     顶栏/吸顶分类栏错位；底部 48px 预留也已由 .app-shell .main 接管 */
  /* 触控区域加大，防误触（通过伪元素扩展最小 44x44px 点击热区） */
  .card-actions button {
    width: 32px;
    height: 32px;
    position: relative;
  }
  .card-actions button::after {
    content: '';
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    min-width: 44px;
    min-height: 44px;
    width: 100%;
    height: 100%;
  }
  .item {
    padding: 11px 0;
  }
  /* 触屏把角标放大到 26px：字号与圆角一并等比放大，形状比例与桌面一致 */
  .item .rank {
    min-width: 26px;
    height: 26px;
    font-size: 13px;
    border-radius: 8px;
  }
  .card-head {
    padding: 12px 14px;
  }
  .card-body,
  .card-pane {
    padding: 10px 14px;
  }
  /* 文字卡片留白收紧 */
  .quote-card {
    padding: 14px 12px 10px;
  }
  .quote-text {
    font-size: 13px;
    letter-spacing: 0;
    line-height: 1.9;
  }
  .quote-card.long .quote-text {
    font-size: 12.5px;
  }
  /* 神谕卡移动端收紧 */
  .answer-card {
    padding: 22px 12px 14px;
  }
  .ans-zh {
    font-size: 19px;
    letter-spacing: 2px;
  }
  .answer-card.vertical .ans-zh {
    min-height: 130px;
    letter-spacing: 5px;
  }
  .ans-en {
    font-size: 11px;
  }
  .ans-seal {
    width: 46px;
    height: 46px;
    font-size: 11px;
  }
  /* 时钟内边距收紧：纵向随 ≤1100 两行版式统一为 5px，横向收成 10px */
  .clock {
    padding: 5px 10px;
    font-size: 12.5px;
    gap: 6px;
  }
}

/* 原「≤430px 隐藏 .hw-sub」已移除：气温区间属于必要信息。
   当时靠 .hero-weather 的 flex-wrap 折到第二行兜住，但横排条版式已退役，
   现在卡片在两种落点（右栏 286px / 抽屉 232px）都是竖排卡，
   区间本来就在自己的一行上，不需要再折行 */

/* ≤380px：控件整体收紧一档，尽量把「日期 + 时间 + 4 按钮」留在同一行。
   这只是「省出空间」的优化，不是截断开关——真放不下时由 fitClocks
   把时间胶囊换到独立一行继续显示，日期绝不会被隐藏或截断 */
@media (max-width: 380px) {
  .clock-mobile {
    padding: 5px 8px;
    font-size: 12px;
    gap: 4px;
  }
  .clock-mobile .clock-dot {
    width: 5px;
    height: 5px;
  }
  /* (0,2,0) 压过 ≤1100 的 .header-right { gap: 8px }（它在文件里更靠后） */
  .app-shell .header-right {
    gap: 4px;
  }
  .header-right a,
  .header-right button {
    width: 30px;
    height: 30px;
  }
}

/* ≤340px：再收紧一档，让 320px 这类小屏也能把「短版日期 + 时间 + 4 按钮」锁在同一行。
   这里只缩尺寸、绝不隐藏日期——日期始终完整，顶栏永远是两行 */
@media (max-width: 340px) {
  /* (0,2,0)：纵向 4px 要压过 ≤1100 的 .clock 纵向 5px（后写会赢），窄屏收得更紧 */
  .header-right .clock-mobile {
    padding: 4px 6px;
    font-size: 11.5px;
    gap: 3px;
  }
  .clock-mobile .clock-dot {
    width: 4px;
    height: 4px;
  }
  .app-shell .header-right {
    gap: 3px;
  }
  .header-right a,
  .header-right button {
    width: 28px;
    height: 28px;
  }
}

/* ============ 万年历（生活信息 / calendar） ============ */
.cal-head {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 10px;
}
.cal-title {
  flex: 1;
  text-align: center;
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
}
.cal-nav {
  width: 28px;
  height: 28px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--card);
  color: var(--text);
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}
.cal-nav:hover {
  border-color: var(--accent);
  color: var(--accent-light);
}
.cal-today {
  padding: 5px 10px;
  border-radius: 8px;
  border: none;
  background: var(--accent);
  color: #fff;
  font-size: 11.5px;
  cursor: pointer;
}
.cal-today:hover {
  filter: brightness(1.1);
}
.cal-weeks {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 4px;
  margin-bottom: 4px;
}
.cal-weeks span {
  text-align: center;
  font-size: 11px;
  color: var(--text-dim);
  padding: 2px 0;
}
.cal-weeks span.wk {
  color: var(--warn);
}
.cal-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 4px;
}
.cal-cell {
  position: relative;
  aspect-ratio: 0.9;
  min-width: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  padding: 3px 2px;
  border-radius: 8px;
  background: color-mix(in srgb, var(--card) 60%, transparent);
  border: 1px solid color-mix(in srgb, var(--border) 50%, transparent);
}
.cal-cell.blank {
  background: transparent;
  border: none;
}
.cal-cell.wk {
  background: color-mix(in srgb, var(--warn) 7%, transparent);
}
.cal-cell.today {
  background: var(--accent-bg);
  border-color: var(--accent);
}
.cal-d {
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
  line-height: 1.2;
}
.cal-cell.wk .cal-d {
  color: var(--warn);
}
.cal-cell.today .cal-d {
  color: var(--accent-text);
}
.cal-mark {
  position: absolute;
  top: 2px;
  right: 3px;
  font-style: normal;
  font-size: 9px;
  font-weight: 700;
  line-height: 1.4;
  padding: 0 3px;
  border-radius: 4px;
}
.cal-mark.rest {
  background: var(--warn);
  color: #fff;
}
.cal-mark.work {
  background: var(--text-dim);
  color: var(--bg);
}
.cal-l {
  font-size: 10px;
  color: var(--text-dim);
  line-height: 1.2;
  max-width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.cal-l.sp {
  color: var(--accent-light);
  font-weight: 600;
}
.cal-cell.today .cal-l.sp {
  color: var(--accent-text);
}
.cal-tip {
  margin-top: 10px;
  font-size: 11px;
  color: var(--text-dim);
}

/* ============ 统一下拉浮层 .dd-pop ============ */
/* 全站「点控件弹出的浮层」共用这一套视觉语言，基准是万年历弹窗（此前三个浮层
   各写一份玻璃参数、明暗主题还要各自补 override，越长越歪）。已接入：
   .ss-menu（搜索引擎选择）/ .ss-suggest（搜索提示）/ .cal-modal-card（万年历）。
   以后新增同类浮层：HTML 挂 class="dd-pop"，CSS 只写几何与布局——
   定位（absolute/fixed + top/left/right）、尺寸（width/max-width/max-height）、
   padding、display 开合与内容排版；表面材质（底色/模糊/描边/圆角 20/投影/
   入场动画）一律从这里继承，不要重写 */
/* 磨砂质感的配方（上一次 0.94/0.96 修可读性时把底色提到了近实底，玻璃感没了）：
   真正的磨砂 = 「强模糊化掉字形 + 半透底染色 + 噪点颗粒」，三件事各管一段——
   ① blur 拉到 64px：后景榜单文字的笔画被完全化开（28px 还能辨出残影），
      可读性问题在模糊端根治，底色就不必再背 0.94 的锅；
   ② 底色回到 0.62/0.66：让磨砂层后透出的光与色真正参与观感，
      文字对比度由「模糊已把后景变成色块」来保证，不再靠高不透明度硬压；
   ③ ::after 噪点层：磨砂与「纯半透明」的观感分水岭就在颗粒，
      内联 SVG feTurbulence 生成 128×128 平铺噪声，opacity 0.05（暗）/0.035（亮），
      pointer-events:none 不挡交互，mix-blend-mode:overlay 让颗粒在明暗两面都自然 */
.dd-pop {
  box-sizing: border-box;
  border: 1px solid var(--glass-border);
  border-radius: 20px;
  background: rgba(255, 255, 255, 0.62);
  backdrop-filter: blur(64px) saturate(175%) brightness(1.06);
  -webkit-backdrop-filter: blur(64px) saturate(175%) brightness(1.06);
  box-shadow:
    0 24px 60px rgba(0, 0, 0, 0.28),
    inset 0 1px 0 var(--glass-hi),
    inset 0 -1px 0 var(--glass-lo),
    inset 0 0 32px var(--glass-lo);
  animation: ddPopIn var(--dur-base) var(--ease);
}
/* 噪点颗粒：dd-pop 本体必须有定位上下文（三个使用方都已是 absolute/fixed/relative），
   ::after 铺满本体、圆角随本体裁切 */
.dd-pop::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='128' height='128'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='128' height='128' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 128px 128px;
  opacity: 0.035;
  mix-blend-mode: overlay;
  z-index: 0;
}
:root[data-theme='dark'] .dd-pop {
  background: rgba(30, 24, 40, 0.66);
  backdrop-filter: blur(64px) saturate(175%) brightness(1.03);
  -webkit-backdrop-filter: blur(64px) saturate(175%) brightness(1.03);
  box-shadow:
    0 24px 64px rgba(0, 0, 0, 0.65),
    inset 0 1px 0 var(--glass-hi),
    inset 0 -1px 0 var(--glass-lo),
    inset 0 0 32px rgba(255, 255, 255, 0.03);
}
:root[data-theme='dark'] .dd-pop::after {
  opacity: 0.05;
}
@keyframes ddPopIn {
  from {
    opacity: 0;
    transform: scale(0.95) translateY(10px);
  }
  to {
    opacity: 1;
    transform: scale(1) translateY(0);
  }
}

/* ============ 万年历 / 农历 / 节假日弹窗 ============ */
.cal-modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  background: rgba(12, 8, 18, 0.52);
  backdrop-filter: blur(14px) saturate(150%);
  -webkit-backdrop-filter: blur(14px) saturate(150%);
}
.cal-modal-overlay[hidden] {
  display: none !important;
}
.cal-modal-overlay:not(.is-closing):not([hidden]) {
  animation: calOverlayEnter 0.28s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
.cal-modal-overlay.is-closing {
  animation: calOverlayExit 0.2s cubic-bezier(0.4, 0, 0.2, 1) forwards;
}
@keyframes calOverlayEnter {
  from {
    opacity: 0;
    backdrop-filter: blur(0px);
    -webkit-backdrop-filter: blur(0px);
  }
  to {
    opacity: 1;
    backdrop-filter: blur(14px) saturate(150%);
    -webkit-backdrop-filter: blur(14px) saturate(150%);
  }
}
@keyframes calOverlayExit {
  from {
    opacity: 1;
    backdrop-filter: blur(14px) saturate(150%);
    -webkit-backdrop-filter: blur(14px) saturate(150%);
  }
  to {
    opacity: 0;
    backdrop-filter: blur(0px);
    -webkit-backdrop-filter: blur(0px);
  }
}

.cal-modal-card {
  position: relative;
  width: 100%;
  max-width: 480px;
  max-height: min(92vh, 740px);
  /* 强制父容器裁切：100% 杜绝原生滚动条顶角，确保 4 个大圆角永远完整无损 */
  overflow: hidden !important;
  padding: 0;
  display: flex;
  flex-direction: column;
  transform-origin: center 25%;
  will-change: transform, opacity;
}
.cal-modal-overlay:not(.is-closing):not([hidden]) .cal-modal-card {
  animation: calCardEnter 0.32s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
.cal-modal-overlay.is-closing .cal-modal-card {
  animation: calCardExit 0.2s cubic-bezier(0.4, 0, 0.2, 1) forwards;
}
@keyframes calCardEnter {
  0% {
    opacity: 0;
    transform: translate3d(0, -22px, 0) scale(0.92);
  }
  100% {
    opacity: 1;
    transform: translate3d(0, 0, 0) scale(1);
  }
}
@keyframes calCardExit {
  0% {
    opacity: 1;
    transform: translate3d(0, 0, 0) scale(1);
  }
  100% {
    opacity: 0;
    transform: translate3d(0, -14px, 0) scale(0.94);
  }
}
@media (prefers-reduced-motion: reduce) {
  .cal-modal-overlay,
  .cal-modal-card {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

.cal-modal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 20px 14px;
  border-bottom: 1px solid var(--glass-border);
  flex-shrink: 0;
}
.cal-modal-title {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 16px;
  font-weight: 700;
  color: var(--text);
}
.cal-modal-icon {
  font-size: 18px;
  line-height: 1;
}
.cal-modal-close {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  border: 1px solid var(--glass-border);
  background: var(--card-bg, rgba(255, 255, 255, 0.45));
  color: var(--text-dim);
  font-size: 14px;
  cursor: pointer;
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  transition: all var(--dur-fast);
}
.cal-modal-close:hover {
  color: var(--text);
  background: var(--card-hover);
  border-color: var(--accent);
  transform: scale(1.08);
}

/* 弹窗内容滚动区：自带内边距与极细悬浮圆角滚动条，绝不溢出到外层圆角 */
.cal-modal-body {
  flex: 1 1 auto;
  overflow-y: auto;
  overflow-x: hidden;
  padding: 16px 20px 18px;
  display: flex;
  flex-direction: column;
  gap: 13px;
  scrollbar-width: thin;
  scrollbar-color: rgba(160, 160, 175, 0.35) transparent;
}
.cal-modal-body::-webkit-scrollbar {
  width: 5px;
}
.cal-modal-body::-webkit-scrollbar-track {
  background: transparent;
  margin: 6px 0;
}
.cal-modal-body::-webkit-scrollbar-thumb {
  background: rgba(160, 160, 175, 0.35);
  border-radius: 999px;
  transition: background var(--dur-fast);
}
.cal-modal-body::-webkit-scrollbar-thumb:hover {
  background: var(--accent);
}

/* 今日农历概览卡（增强毛玻璃质感） */
.cal-today-box {
  background: rgba(249, 115, 22, 0.08);
  border: 1px solid rgba(249, 115, 22, 0.22);
  border-radius: 14px;
  padding: 12px 14px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.35);
}
:root[data-theme='dark'] .cal-today-box {
  background: rgba(249, 115, 22, 0.12);
  border-color: rgba(249, 115, 22, 0.28);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.08);
}
.cal-today-top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 6px;
}
.cal-today-solar {
  font-size: 17px;
  font-weight: 800;
  color: var(--accent);
}
.cal-today-lunar {
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
}
.cal-today-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.cal-badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 8px;
  border-radius: 6px;
  font-size: 11px;
  font-weight: 600;
  background: rgba(255, 255, 255, 0.5);
  border: 1px solid var(--glass-border);
  color: var(--text-dim);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}
:root[data-theme='dark'] .cal-badge {
  background: rgba(255, 255, 255, 0.07);
}
.cal-badge.highlight {
  background: rgba(249, 115, 22, 0.15);
  border-color: rgba(249, 115, 22, 0.35);
  color: var(--accent-text);
}
.cal-badge.rest {
  background: rgba(239, 68, 68, 0.15);
  border-color: rgba(239, 68, 68, 0.3);
  color: #ef4444;
}

/* 宜忌条 */
.cal-taboo-row {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  font-size: 12px;
  line-height: 1.5;
}
.cal-taboo-label {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border-radius: 4px;
  font-size: 11px;
  font-weight: 700;
  flex-shrink: 0;
  margin-top: 1px;
}
.cal-taboo-label.yi {
  background: #10b981;
  color: #fff;
}
.cal-taboo-label.ji {
  background: #ef4444;
  color: #fff;
}
.cal-taboo-val {
  color: var(--text-dim);
  flex: 1;
  min-width: 0;
}

/* 弹窗内的日历网格微调 */
.cal-modal-card .cal {
  margin-top: 0;
}
.cal-modal-card .cal-title {
  font-size: 15px;
  font-weight: 700;
}
.cal-modal-card .cal-nav {
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.45);
  border: 1px solid var(--glass-border);
}
:root[data-theme='dark'] .cal-modal-card .cal-nav {
  background: rgba(255, 255, 255, 0.07);
}
.cal-modal-card .cal-cell {
  aspect-ratio: 1;
  border-radius: 9px;
  transition: all var(--dur-fast);
}
.cal-modal-card .cal-cell:not(.blank):hover {
  transform: translateY(-1px);
  border-color: var(--accent);
  background: var(--card-hover);
}

/* ============ 2048 小游戏（趣味内容 / game2048） ============ */
/* 结构：board（背景/容器） > cells（16 个静态底格）+ layer（持久 tile 元素层）；
   tile 外层只管定位（--r/--c 驱动 transform 滑动），内层 .ti 管视觉与弹出动画 */
.g2048 {
  user-select: none;
  -webkit-user-select: none;
  --ggap: 8px;
  --g-pad: 8px;
}
/* 侧栏 + 棋盘左右布局：卡片网格最窄 340px（内宽约 308），侧栏压到 84px 保棋盘尺寸；
   margin-top 与卡片顶部 hint 提示条隔开，避免两个 accent-bg 色块视觉粘连 */
.g2048-main {
  display: flex;
  align-items: stretch;
  gap: 8px;
  width: min(100%, 460px);
  margin: 10px auto 0;
}
.g2048-side {
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: 84px;
  flex-shrink: 0;
}
.g2048-side [data-g2048-fs] {
  margin-top: auto;
} /* 分数在上、全屏/撤销/重开按钮贴底 */
.g2048-scorebox {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 8px 4px;
  border-radius: 8px;
  background: var(--accent-bg);
  font-size: 11px;
  color: var(--text-dim);
  line-height: 1.3;
  white-space: nowrap;
  overflow: visible;
}
.g2048-scorebox b {
  font-size: 16px;
  color: var(--text);
}
.g-add {
  position: absolute;
  left: 50%;
  top: -4px;
  transform: translate(-50%, 0);
  font-size: 13px;
  font-weight: 700;
  color: var(--accent-light);
  pointer-events: none;
  animation: g-float 0.7s ease-out forwards;
}
/* 上浮收在 14px 内：穿过 10px 间距 + hint 底部 padding 区即淡出，不扫到提示文字 */
@keyframes g-float {
  from {
    transform: translate(-50%, 0);
    opacity: 1;
  }
  to {
    transform: translate(-50%, -14px);
    opacity: 0;
  }
}
.g2048-btn {
  padding: 7px 4px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--card);
  color: var(--text);
  font-size: 12px;
  cursor: pointer;
  white-space: nowrap;
}
.g2048-btn:hover:not(:disabled) {
  border-color: var(--accent);
  color: var(--accent-light);
}
.g2048-btn:disabled {
  opacity: 0.45;
  cursor: default;
}
/* touch-action:none：让滑动手势只作用于棋盘，不被页面滚动劫持；
   棋盘占据侧栏以外的剩余宽度（封顶 ~368px），操作提示在卡片顶部 hint，无底部提示行 */
.g2048-wrap {
  position: relative;
  touch-action: none;
  flex: 1;
  min-width: 0;
}
.g2048-board {
  position: relative;
  width: 100%;
  aspect-ratio: 1;
  padding: var(--g-pad);
  border-radius: 12px;
  background: var(--bg);
  border: 1px solid var(--border);
  outline: none;
  container-type: inline-size;
}
.g2048-board:focus-visible {
  border-color: var(--accent);
}
.g2048-cells {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--ggap);
  width: 100%;
  height: 100%;
}
.g-cell {
  border-radius: 8px;
  background: color-mix(in srgb, var(--card) 80%, transparent);
}
.g2048-layer {
  position: absolute;
  inset: var(--g-pad);
}
.g-tile {
  position: absolute;
  left: 0;
  top: 0;
  z-index: 2;
  width: calc((100% - 3 * var(--ggap)) / 4);
  height: calc((100% - 3 * var(--ggap)) / 4);
  transform: translate(calc(var(--c) * (100% + var(--ggap))), calc(var(--r) * (100% + var(--ggap))));
  transition: transform 130ms ease-in-out;
}
.g-tile.ghost {
  z-index: 1;
} /* 被合并的块垫底滑行 */
.g-tile.merged {
  z-index: 3;
} /* 合并产物浮在上层弹出 */
.g-tile .ti {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  border-radius: 8px;
  font-weight: 700;
  line-height: 1;
  background: color-mix(in srgb, var(--card) 80%, transparent);
  color: var(--text-dim);
}
/* 字号按位数分档，cqw 随棋盘（容器）宽度缩放，小屏大屏都不溢出 */
.g-tile.d1 .ti {
  font-size: 11cqw;
}
.g-tile.d2 .ti {
  font-size: 9.5cqw;
}
.g-tile.d3 .ti {
  font-size: 8cqw;
}
.g-tile.d4 .ti {
  font-size: 6.6cqw;
}
/* 新块延迟淡入（等滑行动画近尾声），合并块弹跳 */
.g-tile.new .ti {
  animation: g-spawn 160ms 80ms ease-out backwards;
}
.g-tile.merged .ti {
  animation: g-pop 190ms ease;
}
@keyframes g-spawn {
  from {
    opacity: 0;
    transform: scale(0.3);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}
@keyframes g-pop {
  0% {
    transform: scale(1);
  }
  45% {
    transform: scale(1.2);
  }
  100% {
    transform: scale(1);
  }
}
/* 亮色主题：原版 2048 暖色系（基础规则） */
.g-tile.t2 .ti {
  background: #eee4da;
  color: #776e65;
}
.g-tile.t4 .ti {
  background: #ede0c8;
  color: #776e65;
}
.g-tile.t8 .ti {
  background: #f2b179;
  color: #fff;
}
.g-tile.t16 .ti {
  background: #f59563;
  color: #fff;
}
.g-tile.t32 .ti {
  background: #f67c5f;
  color: #fff;
}
.g-tile.t64 .ti {
  background: #f65e3b;
  color: #fff;
}
.g-tile.t128 .ti {
  background: #edcf72;
  color: #fff;
}
.g-tile.t256 .ti {
  background: #edcc61;
  color: #fff;
}
.g-tile.t512 .ti {
  background: #edc850;
  color: #fff;
}
.g-tile.t1024 .ti {
  background: #edc53f;
  color: #fff;
}
.g-tile.t2048 .ti {
  background: #edc22e;
  color: #fff;
  box-shadow: 0 0 12px rgba(237, 194, 46, 0.5);
}
.g-tile.tx .ti {
  background: #3c3a32;
  color: #fff;
  box-shadow: 0 0 10px rgba(60, 58, 50, 0.35);
}
/* 暗色主题：同色相压暗的专属配色，避免亮米色块在暗背景下突兀 */
:root[data-theme='dark'] .g-tile.t2 .ti {
  background: #3e3a33;
  color: #d8cec2;
}
:root[data-theme='dark'] .g-tile.t4 .ti {
  background: #4a4339;
  color: #e8dcc8;
}
:root[data-theme='dark'] .g-tile.t8 .ti {
  background: #8a6540;
  color: #fff;
}
:root[data-theme='dark'] .g-tile.t16 .ti {
  background: #9c5e30;
  color: #fff;
}
:root[data-theme='dark'] .g-tile.t32 .ti {
  background: #ad4c25;
  color: #fff;
}
:root[data-theme='dark'] .g-tile.t64 .ti {
  background: #bd3b19;
  color: #fff;
}
:root[data-theme='dark'] .g-tile.t128 .ti {
  background: #9a8b2c;
  color: #fff;
}
:root[data-theme='dark'] .g-tile.t256 .ti {
  background: #a69524;
  color: #fff;
}
:root[data-theme='dark'] .g-tile.t512 .ti {
  background: #b2a21c;
  color: #fff;
}
:root[data-theme='dark'] .g-tile.t1024 .ti {
  background: #bfb00f;
  color: #fff;
}
:root[data-theme='dark'] .g-tile.t2048 .ti {
  background: #d4c404;
  color: #fff;
  box-shadow: 0 0 14px rgba(212, 196, 4, 0.45);
}
:root[data-theme='dark'] .g-tile.tx .ti {
  background: #3d6b8f;
  color: #fff;
  box-shadow: 0 0 12px rgba(61, 107, 143, 0.5);
}
.g2048-over {
  position: absolute;
  inset: 0;
  z-index: 5;
  border-radius: 12px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  background: color-mix(in srgb, var(--bg) 82%, transparent);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  opacity: 0;
  pointer-events: none;
  transform: translate3d(0, 10px, 0) scale(0.94);
  transition:
    opacity 0.28s cubic-bezier(0.16, 1, 0.3, 1),
    transform 0.28s cubic-bezier(0.16, 1, 0.3, 1);
  will-change: transform, opacity;
}
.g2048-over.show {
  opacity: 1;
  pointer-events: auto;
  transform: translate3d(0, 0, 0) scale(1);
}
.g2048-over[hidden] {
  display: none !important;
}
.go-title {
  font-size: 20px;
  font-weight: 700;
  color: var(--text);
}
.go-score {
  font-size: 13px;
  color: var(--text-dim);
}
.go-score b {
  color: var(--text);
}
.go-row {
  display: flex;
  gap: 8px;
}
.go-btn {
  padding: 8px 16px;
  border-radius: 8px;
  border: none;
  background: var(--accent);
  color: #fff;
  font-size: 13px;
  cursor: pointer;
}
.go-btn:hover {
  filter: brightness(1.1);
}
.go-btn.ghost {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--text);
}
/* 窄屏（手机）：侧栏再收窄，把宽度让给棋盘 */
@media (max-width: 480px) {
  .g2048-side {
    width: 68px;
    gap: 6px;
  }
  .g2048-main {
    gap: 6px;
  }
  .g2048-scorebox b {
    font-size: 14px;
  }
}

/* ============ 电子木鱼（趣味内容 / muyu） ============ */
/* 结构：muyu-main 左侧计分/按钮 + 右侧 muyu-stage（点击区，内嵌 SVG 木鱼）；
   动画全在 SVG 内：.my-mallet 绕柄尾挥摆，.my-fish 受击挤压回弹，.my-spark 金星弹出 */
.muyu {
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
}
.muyu-main {
  display: flex;
  align-items: stretch;
  gap: 8px;
  width: min(100%, 460px);
  margin: 10px auto 0;
}
.muyu-side {
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: 84px;
  flex-shrink: 0;
}
.muyu-btn.mt {
  margin-top: auto;
}
.muyu-scorebox {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 8px 4px;
  border-radius: 8px;
  background: var(--accent-bg);
  font-size: 11px;
  color: var(--text-dim);
  line-height: 1.3;
  white-space: nowrap;
}
.muyu-scorebox b {
  font-size: 16px;
  color: var(--text);
}
.muyu-btn {
  padding: 7px 4px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: var(--card);
  color: var(--text);
  font-size: 12px;
  cursor: pointer;
  white-space: nowrap;
}
.muyu-btn:hover {
  border-color: var(--accent);
  color: var(--accent-light);
}
/* 点击区：touch-action:manipulation 防移动端双击缩放，保证连点跟手 */
.muyu-stage {
  position: relative;
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  touch-action: manipulation;
  cursor: pointer;
  border-radius: 12px;
  background: var(--bg);
  border: 1px solid var(--border);
  outline: none;
}
.muyu-stage:focus-visible {
  border-color: var(--accent);
}
.muyu-svg {
  width: 100%;
  max-width: 350px;
  height: auto;
  display: block;
}
/* 挥槌：绕柄尾快速下摆触鱼后回弹 */
.muyu-svg .my-mallet {
  transform-box: view-box;
  transform-origin: 308px 142px;
}
.muyu-svg.hit .my-mallet {
  animation: my-swing 0.24s cubic-bezier(0.36, 0.07, 0.19, 0.97);
}
@keyframes my-swing {
  0% {
    transform: rotate(0deg);
  }
  34% {
    transform: rotate(-22deg);
  }
  58% {
    transform: rotate(-16deg);
  }
  100% {
    transform: rotate(0deg);
  }
}
/* 受击挤压：木鱼以底部为支点压扁再弹性回弹（squash & stretch） */
.muyu-svg .my-fish {
  transform-box: fill-box;
  transform-origin: 50% 100%;
}
.muyu-svg.hit .my-fish {
  animation: my-squash 0.32s cubic-bezier(0.22, 1.6, 0.36, 1);
}
@keyframes my-squash {
  0% {
    transform: scale(1, 1);
  }
  35% {
    transform: scale(1.05, 0.94);
  }
  70% {
    transform: scale(0.985, 1.015);
  }
  100% {
    transform: scale(1, 1);
  }
}
/* 木鱼被敲击时：额头爆出暴怒青筋（怒りマーク 💢，紧贴脑门上方） */
.muyu-svg .my-angry {
  opacity: 0;
  transform-origin: 0 0;
  pointer-events: none;
  transition: opacity 0.25s ease;
}
.muyu-svg.hit .my-angry {
  animation: my-angry-pop 0.52s cubic-bezier(0.18, 0.89, 0.32, 1.28);
}
.muyu-svg.is-angry:not(.hit) .my-angry {
  opacity: 1;
  animation: my-angry-throb 0.5s ease-in-out infinite alternate;
}
@keyframes my-angry-pop {
  0% {
    opacity: 0;
    transform: scale(0.2) rotate(-12deg);
  }
  18% {
    opacity: 1;
    transform: scale(1.25) rotate(6deg);
  }
  35% {
    transform: scale(0.92) rotate(-6deg);
  }
  55% {
    transform: scale(1.15) rotate(4deg);
  }
  75% {
    opacity: 1;
    transform: scale(1) rotate(0deg);
  }
  90% {
    opacity: 0.95;
    transform: scale(1) rotate(0deg);
  }
  100% {
    opacity: 0;
    transform: scale(0.8) rotate(0deg);
  }
}
@keyframes my-angry-throb {
  0% {
    transform: scale(0.92) rotate(-4deg);
  }
  100% {
    transform: scale(1.08) rotate(4deg);
  }
}
/* 受击金星：默认隐藏，敲击时从木鱼四周依次弹出并淡出 */
.muyu-svg .my-spark {
  opacity: 0;
  transform-box: fill-box;
  transform-origin: center;
}
.muyu-svg.hit .my-spark {
  animation: my-spark 0.45s ease-out backwards;
}
.muyu-svg.hit .my-spark.s2 {
  animation-delay: 0.04s;
}
.muyu-svg.hit .my-spark.s3 {
  animation-delay: 0.08s;
}
.muyu-svg.hit .my-spark.s4 {
  animation-delay: 0.12s;
}
@keyframes my-spark {
  0% {
    opacity: 0;
    transform: scale(0.2) rotate(0deg);
  }
  30% {
    opacity: 1;
  }
  100% {
    opacity: 0;
    transform: scale(1.2) rotate(35deg);
  }
}
/* 功德飘字：华文楷体 + 典雅金光 + 灵动升华动效 */
.m-add {
  position: absolute;
  top: 14%;
  transform: translateX(-50%);
  font-family: 'STKaiti', 'KaiTi', 'Kaiti SC', 'Source Han Serif SC', 'Noto Serif SC', 'SimSun', serif;
  font-size: 20px;
  font-weight: 700;
  letter-spacing: 2px;
  color: #b45309;
  white-space: nowrap;
  text-shadow:
    0 0 12px rgba(245, 158, 11, 0.45),
    0 1px 2px rgba(255, 255, 255, 0.9),
    0 2px 4px rgba(146, 64, 14, 0.2);
  pointer-events: none;
  animation: my-add 0.95s cubic-bezier(0.22, 1, 0.36, 1) forwards;
}
.m-add .m-add-num {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
  font-weight: 800;
  margin-left: 2px;
  letter-spacing: 0;
}
:root[data-theme='dark'] .m-add {
  color: #fbbf24;
  text-shadow:
    0 0 16px rgba(251, 191, 36, 0.7),
    0 0 3px rgba(0, 0, 0, 0.8),
    0 2px 6px rgba(245, 158, 11, 0.4);
}
@keyframes my-add {
  0% {
    opacity: 0;
    transform: translate(-50%, 8px) scale(0.75);
  }
  16% {
    opacity: 1;
    transform: translate(-50%, -6px) scale(1.15);
  }
  32% {
    transform: translate(-50%, -14px) scale(1);
  }
  70% {
    opacity: 0.95;
    transform: translate(-50%, -36px) scale(1.02);
  }
  100% {
    opacity: 0;
    transform: translate(-50%, -58px) scale(1.08);
  }
}
/* 敲击点涟漪：落在槌头触鱼位置（约 55%, 45%） */
.m-rip {
  position: absolute;
  left: 55%;
  top: 45%;
  width: 22px;
  height: 22px;
  margin: -11px 0 0 -11px;
  border-radius: 50%;
  border: 2px solid rgba(220, 110, 45, 0.7);
  pointer-events: none;
  animation: my-rip 0.55s ease-out forwards;
}
@keyframes my-rip {
  from {
    transform: scale(0.6);
    opacity: 0.9;
  }
  to {
    transform: scale(4.6);
    opacity: 0;
  }
}
@media (max-width: 480px) {
  .muyu-side {
    width: 68px;
    gap: 6px;
  }
  .muyu-main {
    gap: 6px;
  }
  .muyu-scorebox b {
    font-size: 14px;
  }
  .m-add {
    font-size: 17px;
    letter-spacing: 1.5px;
  }
}

/* ============ 卡片全屏（2048 / 电子木鱼） ============ */
/* 进入 / 退出两个图标（app.js 的 ICON_FS_ENTER / ICON_FS_EXIT，内联 SVG）同占一个位置：
   平时只显示「四向展开」，全屏时原位互换为「✕」（纯 CSS :fullscreen 驱动）；
   全屏按屏幕真实方向自然渲染（竖屏设备竖屏全屏、横屏设备横屏全屏）。
   尺寸沿用上面 .card-actions button svg 的 14px，颜色走 color/currentColor，
   所以 hover 变橙、全屏下的配色都和刷新按钮一致。
   （原来这里是 ⛶ 字符 + font-size/font-weight 两行，换成矢量图标后不再需要字号档） */
.card-actions .btn-fs-exit {
  display: none;
}
/* 游戏侧栏按钮（2048 / 木鱼）里的同一枚图标：按钮是 inline 文本流而不是 flex，
   SVG 得自己跟字号走并用 vertical-align 把基线和「全屏 / 退出」对上。
   1.1em ≈ 13px，与旁边 12px 的文字同高、不抢行高 */
.g2048-btn svg,
.muyu-btn svg {
  width: 1.1em;
  height: 1.1em;
  flex-shrink: 0;
  vertical-align: -0.18em;
}
/* fs:1 卡片的布局单元：头部（✕/↻）+ 内容区包在一起，全屏时作为 flex 布局宿主；
   非全屏时是普通静态容器，无任何视觉影响 */
.fs-unit {
  display: block;
}
/* 全屏通用精简（原生 :fullscreen 与伪全屏共用）：只保留游戏本体 + 侧栏菜单，
   右上角只留 ✕ 退出、↻ 刷新；标题、💡提示条一律隐藏 */
.card:fullscreen .card-title,
.card.fs-fake .card-title,
.card:fullscreen .card-body > .news-tip,
.card.fs-fake .card-body > .news-tip {
  display: none;
}
/* 标题隐藏后头部只剩操作按钮，靠右放置 */
.card:fullscreen .card-head,
.card.fs-fake .card-head {
  justify-content: flex-end;
}
/* 头部预算 = 按钮 44 + 上下内边距 28 ≈ 72（非全屏是 28+22≈50） */
.card:fullscreen .card-head,
.card.fs-fake .card-head {
  padding-block: 14px;
  /* 顶部下移：小屏/刘海屏的地址栏、状态栏容易压住贴顶的小按钮，
     有安全区时再让出一截（桌面 env()=0，等价 14px） */
  padding-top: max(14px, env(safe-area-inset-top));
}
/* 全屏时右上角按钮放大：28px 太小又贴顶，44px 热区更易点中、更醒目 */
.card:fullscreen .card-actions button,
.card.fs-fake .card-actions button {
  width: 44px;
  height: 44px;
}
.card:fullscreen .card-actions button svg,
.card.fs-fake .card-actions button svg {
  width: 18px;
  height: 18px;
}
/* 这里**不能**写 animation: none —— 详见 .card 那条 fadeIn 的注释：
   把 animation 置回 none 会「销毁再重建」卡片的入场动画，退出全屏时
   卡片先隐身（内联 animation-delay 期）再淡入 250ms，同时整卡上滑 8px，
   就是用户看到的那一下闪抖。入场动画现在跑完会由 JS 摘成内联 animation:none，
   全屏态与常态的 animation 值因此完全一致，切换时没有任何动画被重建。 */
.card:fullscreen {
  width: 100vw;
  height: 100vh;
  max-height: none;
  border-radius: 0;
  border: none;
  /* 全屏卡片不接受 hover 抬升：2px 位移会在底部露出一条缝，
     而且进出全屏时 :hover 命中状态变化会让 transform 走 250ms 过渡 */
  transform: none;
  background: var(--bg);
  overflow: hidden;
}
.card:fullscreen .btn-fs,
.card.fs-fake .btn-fs {
  display: none;
}
.card:fullscreen .btn-fs-exit,
.card.fs-fake .btn-fs-exit {
  display: flex;
}
/* 全屏布局宿主是 .fs-unit（卡片本体被 UA transform 锁死，布局发生在内层） */
.card:fullscreen .fs-unit,
.card.fs-fake .fs-unit {
  display: flex;
  flex-direction: column;
  width: 100%;
  height: 100%;
}
.card:fullscreen .card-head,
.card.fs-fake .card-head {
  flex-shrink: 0;
}
/* 全屏内容区：去掉限高，flex 纵向布局 */
.card:fullscreen .card-body,
.card.fs-fake .card-body {
  max-height: none;
  overflow: auto;
  display: flex;
  flex-direction: column;
  align-items: stretch;
}
/* 游戏主体（content 容器）占满行宽，auto margin 垂直居中——
   溢出时 auto margin 自动归零退化为顶对齐滚动，不会裁掉上半部分 */
.card:fullscreen .card-body > [id^='content-'],
.card.fs-fake .card-body > [id^='content-'] {
  width: 100%;
  margin-block: auto;
}
/* 放开非全屏时对内部主体的 460px 限宽，让游戏区吃满上面的宽度约束 */
.card:fullscreen .g2048-main,
.card:fullscreen .muyu-main,
.card.fs-fake .g2048-main,
.card.fs-fake .muyu-main {
  width: 100%;
  gap: clamp(10px, 1.8vw, 18px);
  gap: clamp(10px, 2cqw, 20px);
  margin-top: clamp(8px, 1.5vh, 16px);
}
/* 2048：棋盘随视口放大；棋盘为正方形，宽度同时受视口高度约束防止纵向溢出
   （预算 = 头部 72 + 上下内边距 20 + 主体 margin 10 ≈ 102，留少量余量） */
.card:fullscreen .g2048,
.card.fs-fake .g2048 {
  width: min(100%, 860px, calc(100dvh - 112px));
  margin-inline: auto;
  container-type: inline-size;
  container-name: game;
}
/* 木鱼：整体加宽；viewBox 高宽比 260/360 ≈ 0.72，宽度按可用高度反推 */
.card:fullscreen .muyu,
.card.fs-fake .muyu {
  width: min(100%, 760px, calc((100dvh - 132px) / 0.72));
  margin-inline: auto;
  container-type: inline-size;
  container-name: game;
}
.card:fullscreen .muyu-svg,
.card.fs-fake .muyu-svg {
  max-width: none;
}
/* 不支持 dvh 的老浏览器退化 vh */
@supports not (height: 100dvh) {
  .card:fullscreen .g2048,
  .card.fs-fake .g2048 {
    width: min(100%, 860px, calc(100vh - 112px));
  }
  .card:fullscreen .muyu,
  .card.fs-fake .muyu {
    width: min(100%, 760px, calc((100vh - 132px) / 0.72));
  }
}

/* ============ 全屏下游戏侧栏与按钮等比例自适应缩放 ============ */
/* 解决全屏下游戏棋盘/画布放大至 ~800px，而侧栏锁定 84px、字号仅 11/12px 的失调问题。
   以容器查询 cqw（结合 vw/vh 兜底）实现桌面、平板、手机等不同视口与设备下的黄金比例缩放 */
.card:fullscreen .g2048-side,
.card.fs-fake .g2048-side,
.card:fullscreen .muyu-side,
.card.fs-fake .muyu-side {
  width: clamp(84px, 15vw, 160px);
  width: clamp(84px, 18.5cqw, 160px);
  gap: clamp(8px, 1.4vh, 14px);
  gap: clamp(8px, 1.6cqw, 14px);
}
/* 计分框 / 功德信息框比例放大与卡片化微质感 */
.card:fullscreen .g2048-scorebox,
.card.fs-fake .g2048-scorebox,
.card:fullscreen .muyu-scorebox,
.card.fs-fake .muyu-scorebox {
  padding: clamp(9px, 1.4vh, 15px) clamp(6px, 1vw, 12px);
  padding: clamp(9px, 1.6cqw, 15px) clamp(6px, 1.2cqw, 12px);
  border-radius: clamp(9px, 1.3cqw, 13px);
  gap: clamp(2px, 0.5cqw, 5px);
  border: 1px solid color-mix(in srgb, var(--accent) 18%, transparent);
  box-shadow: 0 2px 6px color-mix(in srgb, var(--accent) 8%, transparent);
}
.card:fullscreen .g2048-scorebox span,
.card.fs-fake .g2048-scorebox span,
.card:fullscreen .muyu-scorebox span,
.card.fs-fake .muyu-scorebox span {
  font-size: clamp(12px, 1.2vw, 15px);
  font-size: clamp(12px, 1.5cqw, 15px);
  font-weight: 500;
  letter-spacing: 0.5px;
}
.card:fullscreen .g2048-scorebox b,
.card.fs-fake .g2048-scorebox b,
.card:fullscreen .muyu-scorebox b,
.card.fs-fake .muyu-scorebox b {
  font-size: clamp(18px, 2.5vw, 30px);
  font-size: clamp(18px, 3.2cqw, 30px);
  font-weight: 700;
  letter-spacing: 0.3px;
  line-height: 1.15;
}
/* 操作按钮比例放大、易点击热区与触感反馈 */
.card:fullscreen .g2048-btn,
.card.fs-fake .g2048-btn,
.card:fullscreen .muyu-btn,
.card.fs-fake .muyu-btn {
  min-height: clamp(36px, 4.2vh, 48px);
  min-height: clamp(36px, 4.8cqw, 48px);
  padding: clamp(8px, 1vh, 12px) clamp(8px, 1vw, 16px);
  padding: clamp(8px, 1.1cqw, 12px) clamp(8px, 1.3cqw, 16px);
  border-radius: clamp(8px, 1.2cqw, 12px);
  font-size: clamp(12.5px, 1.3vw, 15.5px);
  font-size: clamp(12.5px, 1.6cqw, 15.5px);
  font-weight: 500;
  line-height: 1.35;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
  transition:
    transform 0.15s ease,
    border-color 0.15s ease,
    color 0.15s ease,
    box-shadow 0.15s ease;
}
.card:fullscreen .g2048-btn:hover:not(:disabled),
.card.fs-fake .g2048-btn:hover:not(:disabled),
.card:fullscreen .muyu-btn:hover,
.card.fs-fake .muyu-btn:hover {
  transform: translateY(-1.5px);
  box-shadow: 0 3px 8px rgba(0, 0, 0, 0.08);
}
.card:fullscreen .g2048-btn:active:not(:disabled),
.card.fs-fake .g2048-btn:active:not(:disabled),
.card:fullscreen .muyu-btn:active,
.card.fs-fake .muyu-btn:active {
  transform: translateY(0) scale(0.98);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
}
.card:fullscreen .g2048-btn svg,
.card.fs-fake .g2048-btn svg,
.card:fullscreen .muyu-btn svg,
.card.fs-fake .muyu-btn svg {
  width: 1.15em;
  height: 1.15em;
  vertical-align: -0.16em;
}
/* 全屏下动态增量数字与功德字样放大 */
.card:fullscreen .g-add,
.card.fs-fake .g-add {
  font-size: clamp(14px, 2.2cqw, 20px);
  top: -6px;
}
.card:fullscreen .m-add,
.card.fs-fake .m-add {
  font-size: clamp(22px, 3.8cqw, 34px);
}
/* 2048 全屏游戏结束弹层适配放大 */
.card:fullscreen .go-title,
.card.fs-fake .go-title {
  font-size: clamp(20px, 3.2cqw, 28px);
}
.card:fullscreen .go-score,
.card.fs-fake .go-score {
  font-size: clamp(13px, 1.8cqw, 16px);
}
.card:fullscreen .go-btn,
.card.fs-fake .go-btn {
  padding: clamp(8px, 1.2cqw, 12px) clamp(16px, 2.5cqw, 24px);
  font-size: clamp(13px, 1.8cqw, 15px);
  border-radius: clamp(8px, 1.2cqw, 10px);
}

/* 窄屏手机（纵向）全屏响应：边距适度收窄，保留棋盘最大尺寸与舒适触控 */
@media (max-width: 480px) {
  .card:fullscreen .card-body,
  .card.fs-fake .card-body {
    padding-inline: 8px;
  }
  .card:fullscreen .g2048-side,
  .card.fs-fake .g2048-side,
  .card:fullscreen .muyu-side,
  .card.fs-fake .muyu-side {
    width: clamp(74px, 21vw, 88px);
    gap: 6px;
  }
  .card:fullscreen .g2048-main,
  .card.fs-fake .g2048-main,
  .card:fullscreen .muyu-main,
  .card.fs-fake .muyu-main {
    gap: 8px;
  }
  .card:fullscreen .g2048-scorebox,
  .card.fs-fake .g2048-scorebox,
  .card:fullscreen .muyu-scorebox,
  .card.fs-fake .muyu-scorebox {
    padding: 7px 4px;
    gap: 2px;
    border-radius: 8px;
  }
  .card:fullscreen .g2048-scorebox span,
  .card.fs-fake .g2048-scorebox span,
  .card:fullscreen .muyu-scorebox span,
  .card.fs-fake .muyu-scorebox span {
    font-size: 11px;
  }
  .card:fullscreen .g2048-scorebox b,
  .card.fs-fake .g2048-scorebox b,
  .card:fullscreen .muyu-scorebox b,
  .card.fs-fake .muyu-scorebox b {
    font-size: 16px;
  }
  .card:fullscreen .g2048-btn,
  .card.fs-fake .g2048-btn,
  .card:fullscreen .muyu-btn,
  .card.fs-fake .muyu-btn {
    min-height: 34px;
    padding: 6px 4px;
    font-size: 12px;
    border-radius: 8px;
  }
}

/* 矮屏设备（手机横屏全屏）：紧凑高度自适应，防止纵向溢出滚动 */
@media (max-height: 520px) {
  .card:fullscreen .g2048-side,
  .card.fs-fake .g2048-side,
  .card:fullscreen .muyu-side,
  .card.fs-fake .muyu-side {
    gap: 4px;
    width: clamp(78px, 15vw, 110px);
  }
  .card:fullscreen .g2048-scorebox,
  .card.fs-fake .g2048-scorebox,
  .card:fullscreen .muyu-scorebox,
  .card.fs-fake .muyu-scorebox {
    padding: 4px 5px;
    gap: 1px;
  }
  .card:fullscreen .g2048-scorebox span,
  .card.fs-fake .g2048-scorebox span,
  .card:fullscreen .muyu-scorebox span,
  .card.fs-fake .muyu-scorebox span {
    font-size: 10px;
  }
  .card:fullscreen .g2048-scorebox b,
  .card.fs-fake .g2048-scorebox b,
  .card:fullscreen .muyu-scorebox b,
  .card.fs-fake .muyu-scorebox b {
    font-size: 15px;
  }
  .card:fullscreen .g2048-btn,
  .card.fs-fake .g2048-btn,
  .card:fullscreen .muyu-btn,
  .card.fs-fake .muyu-btn {
    min-height: 28px;
    padding: 4px 6px;
    font-size: 11px;
  }
}

/* ============ 伪全屏（iOS Safari 无 Fullscreen API 的回退） ============ */
/* 卡片固定铺满视口，视觉与 :fullscreen 一致；html.fs-fake-on 锁背景滚动。
   高度用 100dvh 随地址栏伸缩（不支持的老浏览器退化 100vh） */
.card.fs-fake {
  position: fixed;
  inset: 0;
  z-index: 999;
  width: 100vw;
  max-width: none;
  height: 100dvh;
  max-height: none;
  border-radius: 0;
  border: none;
  /* 与 .card:fullscreen 同理：不碰 animation（会重建入场动画导致闪抖），
     也不接受 hover 抬升。原先这里写的 animation: none + animation-delay: 0s
     其实并没有生效——内联 animation-delay 的优先级高于类规则，
     真正让动画停住的是 animation-name: none；而它正是闪抖的成因。 */
  transform: none;
  background: var(--bg);
  overflow: hidden;
}

/* 伪全屏只留游戏本体（与原生 :fullscreen 行为对齐）：
   卡片的 z-index:999 被祖先层叠上下文钳制，压不过 sticky 顶栏（z-80）
   和悬浮目录按钮（z-150），它们会浮在全屏卡片上——索性直接隐藏。
   挂在 html.fs-fake-on 上，进/出全屏由 JS 增删该类，自动还原 */
html.fs-fake-on .app-shell .topbar,
html.fs-fake-on .sb-toc {
  display: none;
}
/* 层级修复：.main 的 z-index:1 会把满屏卡片（z-999 落在 .main 层叠上下文内）
   整体钳到 1，压不过顶栏（z-100）/侧栏这些 body 级 UI——全屏期间把 .main
   抬高一档，保证卡片永远是最高层（顶栏/目录按钮另有 display:none 兜底） */
html.fs-fake-on .main {
  z-index: 1000;
}

/* ============ 分类/搜索切换过渡（View Transitions） ============ */
/* render() 用 startViewTransition 包装后，新 #main 快照淡入覆盖旧快照。
   旧快照必须保持完全不透明、不做淡出：若新旧同时半透明，合成会出现透明缺口，
   露出文档画布底色（日间 --bg-grad 近白 / 夜间近黑），用户会感知到
   「切换分类全屏闪一下」。只淡入不淡出，视觉等效交叉溶解且无闪帧 */
::view-transition-new(root) {
  animation: vtFadeIn var(--dur-base) ease;
}
@keyframes vtFadeIn {
  from {
    opacity: 0;
  }
}
/* VT 生效期间关掉卡片自身的入场上浮：整块快照已经在淡入，
   卡片再各自 translateY(8px) 淡入属于双重动画，叠加后会互相干扰显得「抖」 */
.vt-run .card {
  animation: none;
}

/* ============ 顶栏图标按钮（全部刷新） ============ */
/* 与 theme-toggle 同规格（36px 圆角方块），图标为 SVG，
   尺寸/默认色由 .header-right svg（18px、text-dim）统一控制 */
.icon-btn {
  width: 36px;
  height: 36px;
  border-radius: 8px;
  flex-shrink: 0;
  background: var(--card);
  border: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition:
    border-color var(--dur-fast),
    box-shadow var(--dur-fast);
}
.icon-btn:hover {
  border-color: var(--accent);
  box-shadow: var(--accent-glow);
  color: var(--accent);
}
/* svg 自带显式 color（.header-right svg），hover 提亮需单独覆盖 */
.icon-btn:hover svg {
  color: var(--accent);
}
/* 全部刷新进行中：图标转圈提示 + 防重复点击 */
.icon-btn.busy {
  pointer-events: none;
  animation: iconSpin 1s linear infinite;
}
@keyframes iconSpin {
  to {
    transform: rotate(360deg);
  }
}

/* ============ 下拉刷新指示器 ============ */
.ptr-indicator {
  position: fixed;
  top: 0;
  left: 50%;
  z-index: 200;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--card);
  border: 1px solid var(--border);
  box-shadow: var(--shadow);
  opacity: 0;
  pointer-events: none;
  transform: translateY(0);
  transition: opacity var(--dur-fast) ease;
}
.ptr-indicator .ptr-spin {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 2px solid var(--border-light);
  border-top-color: var(--accent);
  animation: iconSpin 0.8s linear infinite;
}
/* 拉满就绪态：描边高亮提示「松手刷新」 */
.ptr-indicator.ready {
  border-color: var(--accent);
  box-shadow: var(--accent-glow);
}

/* ============ 空态/错误态视觉升级 ============ */
/* 复用 empty-state 的居中卡片式布局（原先 search-empty 专用），错落有致：
   图标 32px + 主文案 + 副说明 + 重试按钮，替代原来的一行灰字 */
.card-unavailable {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 26px 16px;
  text-align: center;
}
.card-unavailable .un-icon {
  font-size: 30px;
  line-height: 1;
  filter: grayscale(0.2);
}
.card-unavailable .un-text {
  color: var(--text-dim);
  font-size: 13px;
}
.card-unavailable .un-detail {
  color: var(--text-dimmer);
  font-size: 11px;
}
.card-unavailable .retry-btn {
  margin-top: 4px;
  padding: 6px 18px;
  border-radius: 999px;
  background: var(--accent-bg);
  border: 1px solid var(--accent);
  color: var(--accent-text);
  font-size: 12px;
  cursor: pointer;
  transition:
    box-shadow var(--dur-fast),
    border-color var(--dur-fast);
}
.card-unavailable .retry-btn:hover {
  box-shadow: var(--accent-glow);
}

/* ================================================================
   三栏骨架：左侧固定导航栏 + 中间内容区 + 右侧信息栏
   ----------------------------------------------------------------
   排版取向：左侧栏用 position:sticky 做「视觉固定」而不是 overflow 容器滚动，
   整页滚动机制（window.scrollTo / 下拉刷新 / 卡片定位校正 / scroll-spy）
   因此完全不受影响——这是这套改版最重要的一条约束。
   下面所有规则统一带 .app-shell / .sidebar 前缀提升特异性，
   以便稳定覆盖前面为基础版式写的 .layout / .cat-nav / .main 规则。
   ================================================================ */

/* --shell-end-gap：页面滚到底时三块内容的**公共基准线**——
   侧栏面板底边、主栏最后一张卡底边、右栏最后一张卡底边都停在这条线上
   （= 页面底边往上 10px），滚动到这里就到底，收尾是齐的。
   改之前三处各写各的（侧栏 10 / 主栏 56 / 右栏 40），实测滚到底时三条底边
   分别在 890 / 844 / 860（视口 900），差出十几到四十多像素，看着很散。
   抽成一个变量，以后要调只改这一处：
     · 侧栏 —— margin-bottom + height 的扣减；
     · 主栏 / 右栏 —— 各自的 padding-bottom。 */
.app-shell {
  display: flex;
  align-items: flex-start;
  min-height: 100vh;
  --shell-end-gap: 10px;
}
.app-main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

/* ---------------- 左侧导航栏 ---------------- */
/* ③ 侧栏卡化：原来是「贴左缘、通高、只在朝内容那侧一条竖线」的玻璃条，
   与顶栏/页脚相接处必然是直角（用户标注的那两处）。
   现在改成浮在背景光之上的一张圆角玻璃面板，三块玻璃之间留出 18px 走道，
   直角从结构上消失，「玻璃浮在光上」的语言也和卡片/hero 统一了。

   ⚠️ 取舍：外缘（左边）也留 18px，而不是只圆个角。
   18px 是 .app-shell .header / .layout 的内边距，选它当统一走道宽度是为了
   对齐：面板左缘、顶栏左缘、卡片左缘、页脚文字左缘全部落在同一条竖线上，
   同时 .app-shell 的可用宽也没变（232 + 18 = 原来的 0 + 232 + 18 卡片左界）。

   ⚠️ top/height：面板与视口等高会把圆角顶出屏幕，所以上下各让 10px
   （height: calc(100vh - 20px) 配 sticky top: 10px —— 静止与吸顶位置重合，
   滚动时不会有一下跳动）。

   ⚠️ ≤900px 的抽屉态要把这套改写成「贴左/上/下缘、只圆右侧」：
   留边会让抽屉看起来没贴边，但圆角本身保留（见文件末尾的媒体查询） */
.sidebar {
  position: sticky;
  top: 10px;
  z-index: 70;
  flex: 0 0 232px;
  width: 232px;
  /* 高度 = 视口 − 上走道 10 − 下基准 --shell-end-gap：
     面板底边因此正好停在公共基准线上，它就是「三块底沿平齐」的依据 */
  height: calc(100vh - 10px - var(--shell-end-gap));
  margin: 10px 0 var(--shell-end-gap) 18px;
  display: flex;
  flex-direction: column;
  padding: 16px 10px 14px;
  /* 菜单行的两级灰阶：半透明中性灰，深色主题下是「提亮面板」、
     浅色主题下是「浅灰底」，一组值同时覆盖明暗两套皮肤 */
  --sb-menu-bg: rgba(148, 152, 168, 0.1);
  --sb-menu-bg-hover: rgba(148, 152, 168, 0.18);
  background: var(--glass-bg);
  backdrop-filter: blur(28px) saturate(180%);
  -webkit-backdrop-filter: blur(28px) saturate(180%);
  /* 四周同一条描边（原为只在右侧一条竖线——那是通栏贴边的画法）；
     投影取全站玻璃那一档，面板才浮得起来 */
  border: 1px solid var(--glass-border);
  border-radius: 18px;
  box-shadow:
    var(--glass-shadow),
    inset 0 1px 0 var(--glass-hi);
  overflow-y: auto;
  overscroll-behavior: contain;
}
.sidebar::-webkit-scrollbar {
  width: 0;
  height: 0;
}

/* 亮色下菜单行提亮一档：原来那组中性灰压在蓝紫底上会显出一层「灰壳」，
   换成半透明白才像玻璃浮在底色之上（对齐效果图）；暗色仍用原灰阶 */
:root[data-theme='light'] .sidebar {
  /* 玻璃底降到 0.46 后，菜单行若仍用 0.5 白就跟侧栏底几乎同色、整片糊在一起；
     提到 0.6 才重新拉开「一行行」的层级 */
  --sb-menu-bg: rgba(255, 255, 255, 0.6);
  --sb-menu-bg-hover: rgba(255, 255, 255, 0.85);
}

.sb-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 2px 8px 16px;
}
.sidebar .sb-head .logo {
  display: flex;
  align-items: center;
  gap: 9px;
  min-width: 0;
}
.sidebar .sb-head .logo > svg {
  width: 26px;
  height: 26px;
  flex-shrink: 0;
}
.sb-brand {
  display: flex;
  flex-direction: column;
  line-height: 1.25;
  min-width: 0;
}
.sidebar .sb-head .logo .text {
  font-size: 16px;
  font-weight: 800;
  color: var(--text);
  letter-spacing: 0.2px;
}
.sidebar .sb-head .logo .subtitle {
  display: block;
  font-size: 11px;
  font-style: normal;
  color: var(--text-dimmer);
}

.sb-close,
.sb-toggle,
.sb-toc {
  display: none;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  flex-shrink: 0;
  padding: 0;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--card);
  color: var(--text-dim);
  cursor: pointer;
  transition:
    color var(--dur-fast),
    border-color var(--dur-fast);
}
.sb-close:hover,
.sb-toggle:hover,
.sb-toc:hover {
  color: var(--accent);
  border-color: var(--accent);
}
.sb-close svg,
.sb-toggle svg,
.sb-toc svg {
  width: 17px;
  height: 17px;
}

/* 窄屏目录面板：桌面/宽视口永不出现（面板完整样式都在 ≤900px 媒体查询里） */
.toc-panel {
  display: none;
}

/* 侧边栏底部的主题开关与版权 */
.sb-foot {
  margin-top: auto;
  padding: 12px 6px 0;
  border-top: 1px solid var(--border);
}
.sb-theme {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  height: 40px;
  padding: 0 10px;
  margin-bottom: 10px;
  border: none;
  border-radius: 11px;
  background: transparent;
  color: var(--text-dim);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition:
    color var(--dur-fast),
    background var(--dur-fast);
}
.sb-theme:hover {
  color: var(--accent-text);
  background: var(--accent-bg);
}
.sb-theme svg {
  width: 17px;
  height: 17px;
  flex-shrink: 0;
}
.sb-theme .sun {
  display: none;
}
:root[data-theme='light'] .sb-theme .moon {
  display: none;
}
:root[data-theme='light'] .sb-theme .sun {
  display: block;
}
.sb-theme-label {
  flex: 1;
  text-align: left;
}
/* 开关滑块：纯装饰，状态由 data-theme 驱动，避免再维护一套 JS 状态 */
.sb-switch {
  flex-shrink: 0;
  width: 34px;
  height: 19px;
  border-radius: 999px;
  background: var(--border-light);
  position: relative;
  transition: background var(--dur-fast);
}
.sb-switch::after {
  content: '';
  position: absolute;
  top: 2.5px;
  left: 2.5px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: #fff;
  transition: transform var(--dur-base) var(--ease);
}
:root[data-theme='dark'] .sb-switch {
  background: var(--accent);
}
:root[data-theme='dark'] .sb-switch::after {
  transform: translateX(15px);
}
/* 底部版权：抽屉/常驻侧栏共用同一段（.sb-foot 里主题开关之下）。
   字号取全站最小档，靠 --text-dimmer 压到最淡，不跟菜单项抢注意力；
   行高 1.7 让两行之间松一点，10.5px 的行距否则会挤成一坨。
   .sb-foot 的 margin-top:auto 把它整体推到侧栏底部，
   .sb-theme 的 margin-bottom:10px 就是它和主题开关之间的那道间距。
   第二行是「作者主页 · 数据来源」两个外链：原先它们和版权一起躺在页底那条
   通栏玻璃条（.foot）里，页脚整块删掉后并到这里，所以链接样式也得跟着搬过来——
   比版权行亮一档（--text-dim vs --text-dimmer）表明可点，hover 走品牌橙
   （全局 a:hover 的下划线保留，和站内其它链接一致） */
.sb-copy {
  margin: 0;
  font-size: 10.5px;
  line-height: 1.7;
  color: var(--text-dimmer);
  text-align: center;
}
.sb-copy a {
  color: var(--text-dim);
  transition: color var(--dur-fast);
}
.sb-copy a:hover {
  color: var(--accent-text);
}
/* ---- 侧栏与抽屉里的天气卡（≤1180px）：243×96 的紧凑横条 ----
   宿主用 display:contents，卡片因此是 .sidebar 的直接 flex 子项：
   居于 Logo 正下方与分类菜单之间。
   >1180px 卡片搬去右栏；≤1180px 无论是桌面中间视口（900~1180px）还是移动抽屉（≤900px），
   均放置在 #sbWeather 内展示紧凑天气条。宽度跟菜单自动对齐，不用写死数值。 */
.sb-weather {
  display: contents;
}
/* 高度取「菜单行高的两倍」：抽屉里菜单行是 48px（见下方 ≤900px 那段的
   .sidebar .cat-nav button），两行正好 96px——视觉上是「比菜单项大一号的一格」，
   而不是原来那样顶掉半个屏幕。
   版式换成横排两行：左列「城市 温度 天气」+ 第二行「区间」，
   右端留 56px 净空给装饰。文字左对齐而不是居中，就是为了让出这条净空：
   243px 宽的卡里居中的文字会一直顶到右端的云 / 太阳上。
   下边距 14px 与菜单拉开，上边距靠 .sb-head 自己的 16px 下内边距 */
.sb-weather .hero-weather {
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
  align-content: center;
  justify-content: center;
  gap: 6px 10px;
  flex: 0 0 auto;
  height: 96px;
  min-height: 96px;
  padding: 12px 16px;
  margin-bottom: 14px;
  text-align: center;
  border-radius: 14px;
}
/* 子项不收缩：城市、温度、区间都是不可断行的完整内容，
   允许收缩只会把它们压得互相重叠，反而不触发 flex-wrap 折行 */
.sb-weather .hero-weather > * {
  flex-shrink: 0;
}
/* 城市 + 状况与温度统一按中线（水平中心轴）严格对齐 */
.sb-weather .hw-city {
  position: relative;
  display: inline-flex;
  align-items: center;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.3px;
  line-height: 1;
}
.sb-weather .hw-city-text {
  display: inline-flex;
  align-items: center;
}
.sb-weather .hw-sep {
  opacity: 0.65;
  font-size: 12px;
  margin: 0 2px;
}
.sb-weather .hw-cond {
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.3px;
  line-height: 1;
  opacity: 0.95;
}
.sb-weather .hw-city-extra {
  position: static;
  transform: none;
  margin-left: 3px;
  display: inline-flex;
  align-items: center;
}
.sb-weather .hw-tip {
  font-size: 11px;
  line-height: 1;
}
.sb-weather .hw-temp {
  margin: 0;
  display: inline-flex;
  align-items: flex-start;
  font-size: 30px;
  font-weight: 800;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
.sb-weather .hw-unit {
  font-size: 15px;
  font-weight: 600;
  margin-top: 1px;
  margin-left: 1px;
  opacity: 0.9;
}
/* 区间独占第二行并整体水平居中（flex-basis:100% 强制换行） */
.sb-weather .hw-sub {
  flex-basis: 100%;
  width: 100%;
  margin: 0;
  text-align: center;
  font-size: 12px;
  line-height: 1.3;
  opacity: 0.85;
}

/* 装饰缩到紧凑档并竖排居中在右端。桌面那套 92px 太阳 / 84px 云 / 164px 月晕
   是给 286×148 写的，在 96px 高的小格里会直接溢出被裁 */
.sb-weather .hw-cloud-main {
  top: 33px;
  right: 10px;
  width: 44px;
  height: 29px;
}
/* 副云在这么窄的格里只会和主云糊成一团 */
.sb-weather .hw-cloud-sub {
  display: none;
}
.sb-weather .hw-fx-sun i:nth-of-type(1) {
  top: 26px;
  right: 10px;
  width: 44px;
  height: 44px;
}
/* 两圈光芒环（桌面 190/232px 的 conic 扇区）在紧凑卡里直接去掉：
   圆心距卡片右缘只有 32px，而可见光芒的最外半径分别是 39.6px 和 51.5px，
   右侧 7.6px / 19.5px 会被 overflow:hidden 切掉——扇形被切出一道硬边白楔，
   比桌面版「挂在角外」的效果难看太多。日盘自带的三层 box-shadow 已经是
   足够柔和的光晕，小尺寸下不需要再叠光芒 */
.sb-weather .hw-fx-sun i:nth-of-type(1)::before,
.sb-weather .hw-fx-sun i:nth-of-type(1)::after {
  display: none;
}
/* 耀斑是一簇散开的小光点，格子里放不下 */
.sb-weather .hw-fx-sun i:nth-of-type(2) {
  display: none;
}
.sb-weather .hw-fx-moon i:nth-of-type(1) {
  top: 28px;
  right: 12px;
  width: 40px;
  height: 40px;
  box-shadow: inset -11px -4px 0 0 #fff9e2;
}
/* 星与第二簇星：点位是照 286px 宽算的，缩到 244px 会落到文字上 */
.sb-weather .hw-fx-moon i:nth-of-type(2),
.sb-weather .hw-fx-moon i:nth-of-type(4) {
  display: none;
}
/* 月晕：桌面那版是 164px 挂在右上角外、故意被裁的（月亮本身也在角外）。
   紧凑卡里太阳和云都收进了卡内，月亮也收进来了，所以月晕不能再溢出——
   否则月亮完整、光晕却被右缘切出一道直边，看着像渲染出错。
   64px 且圆心对准月亮（月亮 right:12 + 40/2 = 距右 32px；月晕 right:0 + 64/2 = 32px），
   径向渐变到 70% 就透明，实际可见半径约 22px，最右到距右缘 10px 处，完全在卡内 */
.sb-weather .hw-fx-moon i:nth-of-type(3) {
  top: 16px;
  right: 0;
  width: 64px;
  height: 64px;
}
.sb-weather .hw-fx-thunder i:nth-of-type(4) {
  top: 30px;
  right: 26px;
  width: 13px;
  height: 26px;
}
/* 雾带压薄并按 96px 重新分布（桌面那套 26/34/30/20 是按 148px 配的） */
.sb-weather .hw-fx-fog i:nth-of-type(-n + 4) {
  height: 8px;
  filter: blur(4px);
}
.sb-weather .hw-fx-fog i:nth-of-type(1) {
  top: 16%;
}
.sb-weather .hw-fx-fog i:nth-of-type(2) {
  top: 40%;
  height: 10px;
}
.sb-weather .hw-fx-fog i:nth-of-type(3) {
  top: 62%;
  height: 9px;
}
.sb-weather .hw-fx-fog i:nth-of-type(4) {
  top: 82%;
  height: 7px;
}
/* 编辑态要把高度让出来：两行 52px 控件（输入框 + 按钮组）实测需 138px，96px 塞不下。
   height 必须一起复位成 auto，否则内容会从卡片里溢出被裁 */
.sb-weather .hero-weather.editing {
  height: auto;
  min-height: 138px;
}
/* 编辑态在触屏下多一个「取消」，三个按钮等宽均分 215px 内容宽 → 每个只有约 66px。
   通用那条 .hw-btn 是 padding 0 10px / font-size 15px，「重新定位」四个字要 60px，
   加内边距 80px 放不下，会折成两行（实测截图里就是这个样子）。
   收紧到 padding 0 4px / 13.5px 后文字宽 54px + 8px = 62px < 66px，一行放下，
   且保持三键等宽、不破坏触控目标高度（min-height:52px 来自通用规则，没动）。
   只加粗指针分支：细指针（窄桌面窗口）是两键一行，原尺寸本来就够，
   跟着改反而会把按钮压得又扁又小 */
@media (pointer: coarse) {
  .sb-weather .hw-edit-actions {
    gap: 8px;
  }
  .sb-weather .hw-btn {
    padding: 0 4px;
    font-size: 13.5px;
  }
}
/* ---------------- 侧边栏内的分类导航：横向 pill 行 → 纵向菜单列表 ----------------
   JS 逻辑（分类切换 / 手风琴目录 / 定位跳转 / 滚动高亮）原样复用，这里只改排布与皮。
   视觉取自设计稿：
     一级菜单 = 浅灰圆角行（左图标色块 + 名称 + 计数徽章 + 展开箭头）
     子目录   = 缩进的小字列表
     选中项   = 橙色渐变实心 + 白字（徽章/图标底同步转半透明白）
   深浅两套主题共用同一组灰阶（半透明中性灰），自动浮在各自底色之上 */
.sidebar .cat-nav {
  display: block;
  position: static;
  top: auto;
  z-index: auto;
  flex: 0 0 auto;
  min-height: 0;
  max-height: none;
  margin: 0;
  padding: 0;
  overflow: visible;
  background: none;
  border: none;
  box-shadow: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
.sidebar .cat-row {
  display: block;
  flex-wrap: nowrap;
  gap: 0;
  padding: 0;
}
.sidebar .cat-scroll {
  display: block;
  flex-basis: auto;
}
.sidebar .cat-nav .cat-arrow {
  display: none;
}
.sidebar .cat-pills {
  display: flex;
  flex-direction: column;
  flex-wrap: nowrap;
  /* 间距改用按钮下边距而不是容器 gap：每个分类按钮后面都跟着一个收起的
     .cat-sub（0 高、留在文档流里撑展开动画），它同样是 flex 子项，容器 gap
     会照常在「按钮→cat-sub→按钮」之间插两份——分类按钮之间成了双倍间距，
     而「首页按钮→第一个分类」之间没有 cat-sub 只有单倍，第一段明显偏短。
     （负 margin 抵消方案实测无效：Chrome 把负 margin 子项的外尺寸钳到 0，
     不会少吃 gap。）margin 方案里 0 高的 cat-sub 天然不占间距，全部等距。
     间距值做成变量：触摸端媒体查询会把行距放大到 6px，两处必须联动 */
  --sb-pills-gap: 4px;
  gap: 0;
  padding: 0;
  flex: none;
  min-width: 0;
  overflow: visible;
}
/* 只给一级按钮加下边距；最后的 cat-sub 收尾不需要（0 高无影响，与原 gap 行为一致） */
.sidebar .cat-pills > button {
  margin-bottom: var(--sb-pills-gap);
}
/* 一级菜单行：浅灰圆角块 + 深色粗体名称。
   尺寸/圆角/内边距必须写全——窄屏那套 42px/17px 的胶囊底（.cat-pills > button）
   特异性更低但会影响抽屉里的这一份，不写就会被它串味 */
.sidebar .cat-nav button {
  width: 100%;
  height: 44px;
  padding: 0 10px;
  border-radius: 12px;
  justify-content: flex-start;
  /* text-align 必须显式写：窄屏那套 pill 样式把它设成了 center，
     抽屉里照抄就会变成「图标 + 居中标题」的怪样子 */
  text-align: left;
  gap: 10px;
  font-size: 14px;
  font-weight: 700;
  color: var(--text);
  background: var(--sb-menu-bg);
  white-space: nowrap;
  overflow: hidden;
}
.sidebar .cat-nav button:hover {
  background: var(--sb-menu-bg-hover);
}
/* 触屏按下反馈（抽屉里 :active 优先于 hover） */
.sidebar .cat-nav button:active {
  background: var(--sb-menu-bg-hover);
}
/* 图标色块：emoji 自带颜色，方块底只负责「把图标圈成一个标记」 */
.sidebar .cat-nav button .mi {
  flex-shrink: 0;
  width: 26px;
  height: 26px;
  border-radius: 8px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 14px;
  line-height: 1;
  background: var(--sb-menu-bg-hover);
}
/* 各分类的图标底给一点区分度（低透明度 pastel，深浅主题都成立） */
.sidebar .cat-pills > button[data-view='home'] .mi {
  background: rgba(249, 115, 22, 0.16);
}
.sidebar .cat-pills > button[data-cat='news'] .mi {
  background: rgba(59, 130, 246, 0.16);
}
.sidebar .cat-pills > button[data-cat='tech'] .mi {
  background: rgba(139, 92, 246, 0.16);
}
.sidebar .cat-pills > button[data-cat='ent'] .mi {
  background: rgba(236, 72, 153, 0.16);
}
.sidebar .cat-pills > button[data-cat='tools'] .mi {
  background: rgba(20, 184, 166, 0.18);
}
.sidebar .cat-pills > button[data-cat='life'] .mi {
  background: rgba(245, 158, 11, 0.18);
}
.sidebar .cat-pills > button[data-cat='fun'] .mi {
  background: rgba(132, 204, 22, 0.18);
}
.sidebar .cat-pills > button[data-cat='trans'] .mi {
  background: rgba(99, 102, 241, 0.16);
}
/* 名称占满中间，把计数徽章与箭头一起推向行尾（长名称省略号收尾） */
.sidebar .cat-nav button .mn {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* 选中项：橙色渐变实心 + 白字，与全局「选中 = 品牌橙」的语言一致 */
.sidebar .cat-pills > button.active {
  background: linear-gradient(135deg, var(--accent), var(--accent-light));
  color: #fff;
  border-color: transparent;
  box-shadow: var(--accent-glow);
}
.sidebar .cat-pills > button.active .mi {
  background: rgba(255, 255, 255, 0.22);
}
/* 计数徽章：行尾的淡橙小胶囊。窄屏基础样式为「pill 空间优先给名称」把它整个
   display:none 了，但侧边栏抽屉有 264px 宽、放得下，桌面端本来就显示——
   这里恢复显示，两端保持同一套菜单外观 */
.sidebar .cat-pills > button .cnt {
  display: inline-block;
  flex-shrink: 0;
  padding: 2px 8px;
  border-radius: 8px;
  font-size: 11px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--accent-text);
  background: var(--accent-bg);
}
.sidebar .cat-pills > button.active .cnt {
  color: #fff;
  background: rgba(255, 255, 255, 0.26);
}
/* 展开箭头：行尾的 V 形（.mn 的 flex:1 已把它和徽章推到右边），
   两条 1.8px 描边拼成，跟着 currentColor 走，深浅主题都不用另配颜色。
   方向约定：∧=子目录已展开、∨=已收起，点已激活分类即可切换。

   ★ 默认必须是 ∨（收起），只有「已激活 + 目录真的展开」才翻成 ∧。
   反过来写（默认 ∧、sub-collapsed 时才 ∨）会踩两个坑：
     ① **首次进入/刷新后第一次展开不播动画。** 页面加载时 nav 上还没有 sub-collapsed，
        箭头一开始就停在 ∧，而目录其实是收起的；第一次点分类走的是
        nav.classList.remove('sub-collapsed') —— 移除一个本来就不存在的类，
        transform 前后完全一样，transition 根本不会被创建。
        用 document.getAnimations() 直接对比过（见 caretanim.mjs）：
          第一次点击的动画列表里只有 ::before|opacity，**没有 ::before|transform**；
          第二次点击才出现 6 个 ::before|transform|250。
        也就是说「图标不跟着开合动」不是没写 transition，而是那一帧没有状态差可过渡。
     ② **一次开合会让全部 7 个分类按钮的 ::before 一起起 transform 过渡**，
        而其中只有 1 个是可见的（其余 opacity .5，只在 hover 时才露脸）。
        改成「默认 ∨」之后，切换 sub-collapsed 只改变激活那一个按钮的样式，
        实测同一帧的 ::before|transform 过渡从 7 个降到 1 个。
   顺带把语义也摆正了：未激活的分类本来就该显示 ∨ ——「点我会展开」。 */
.sidebar .cat-nav .cat-pills > button[data-cat]::before {
  content: '';
  order: 9;
  flex-shrink: 0;
  margin-left: 3px;
  width: 7px;
  height: 7px;
  border-radius: 1px;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  opacity: 0.5;
  transform: translateY(-1px) rotate(45deg);
  transition:
    transform var(--dur-base) var(--ease),
    opacity var(--dur-fast);
}
.sidebar .cat-nav:not(.sub-collapsed) .cat-pills > button.active[data-cat]::before {
  transform: translateY(-2px) rotate(225deg);
}
/* 悬停与「已展开」都把箭头提到高对比：展开态是白字行，currentColor 即纯白；
   未展开行同样加浓，让「这一支能点开」这件事一眼可见 */
.sidebar .cat-pills > button[data-cat]:hover::before,
.sidebar .cat-pills > button.active[data-cat]::before {
  opacity: 0.95;
}
/* 基础版式为「只在已激活分类上显示箭头」写了一条 ::after（'▸' 字符箭头 + 白底方块，
   移动端吸顶分类栏在用），特异性高达 (0,5,1)——侧边栏里必须原样废掉，
   否则会和上面这枚 V 形箭头叠在一起 */
.sidebar .cat-nav .cat-pills > button.active[data-cat]:not([data-cat='all'])::after {
  content: none;
}

/* ---- 展开的分类子目录（一级分类下的数据源清单） ----
   这里必须用 .sidebar .cat-nav .cat-toc-item 三级前缀：基础样式里的
   .cat-nav button（0,2,1）会把 40px 高、11px 圆角、0 12px 内边距套到子项上，
   一串看下来就是十几个「小按钮」堆在一起，又重又碎。
   改成紧凑列表项 + 整块内缩，信息密度提上来、层级也更清楚 */

/* 展开/折叠动画：只动高度，元素始终留在文档流里。
   基础版式为「横向 pill 行」写了一套规避（收起时切 position:absolute + visibility:hidden，
   因为目录的隐形宽度会把 pill 行挤乱）。但位置切换不可过渡：收起瞬间元素就被移出流，
   下方内容立刻弹上去，而高度动画还在跑——这就是抽屉里折叠「一顿」的根源。
   侧边栏是纵向列表，宽度天然由列决定，不需要那套规避，于是这里全部退回纯高度动画。 */
.sidebar .cat-nav .cat-sub {
  position: relative;
  visibility: hidden;
  opacity: 1;
  display: grid;
  grid-template-rows: 0fr;
  border-radius: 10px;
  /* 收起时高度为 0：容器已弃用 gap（改按钮 margin 承担间距，见 .sidebar .cat-pills），
     这里上下 margin 保持 0，0 高子项就不会在任何两行之间贡献额外空隙 */
  margin: 0 0 0 14px;
  padding: 0 0 0 8px;
  /* visibility 不参与视觉过渡，只负责把收回的内容移出焦点链：
     0s + 延迟到高度收完那一刻才翻成 hidden，所以它不会打断动画；
     opacity 则完全交给高度——高度已经在做显隐，再叠一层淡入会糊成一团 */
  transition:
    grid-template-rows var(--dur-base) var(--ease),
    margin-top var(--dur-base) var(--ease),
    margin-bottom var(--dur-base) var(--ease),
    visibility 0s linear var(--dur-base);
}
.sidebar .cat-nav .cat-sub.open {
  flex-basis: auto;
  order: 0;
  grid-template-rows: 1fr;
  visibility: visible;
  /* 只保留缩进、不加左侧引导线：层级靠内缩本身就看得懂，多一条竖线反而碍眼。
     上下间距按 margin 方案对账：上方 = 按钮 mb(gap) + mt，与原 gap+mt 相同；
     下方原为 mb + 容器 gap，gap 没了就并进 mb 里保持视觉不变 */
  margin-top: 3px;
  margin-bottom: calc(7px + var(--sb-pills-gap));
  transition:
    grid-template-rows var(--dur-base) var(--ease),
    margin-top var(--dur-base) var(--ease),
    margin-bottom var(--dur-base) var(--ease),
    visibility 0s;
}
/* 手动折叠（点已激活分类）与「未展开」是同一状态。
   必须显式写一遍：基础版式那两条 .cat-nav.sub-collapsed .cat-sub（0,3,0 / .open 0,4,0）
   会把元素重新拽回 absolute + hidden，动画立刻断掉 */
.sidebar .cat-nav.sub-collapsed .cat-sub,
.sidebar .cat-nav.sub-collapsed .cat-sub.open {
  position: relative;
  visibility: hidden;
  opacity: 1;
  grid-template-rows: 0fr;
  /* 四边写全：基础版式那条 (.cat-nav.sub-collapsed .cat-sub.open，0,4,0) 用的是
     margin/padding 简写，只覆盖上下两边的写法会被它把左右也一起改掉，
     收起过程中整块目录会横移十来像素。上下归零同收起态（间距由按钮 margin 承担） */
  margin: 0 0 0 14px;
  padding: 0 0 0 8px;
}
.sidebar .cat-nav .cat-sub.open::before {
  display: none;
}
/* 目录项：列布局 + 更紧的行距；内容整体先淡出/后淡入，
   方向感交给位移（收起时快速淡出，避免文字在收拢过程中被裁切） */
.sidebar .cat-sub-inner {
  flex-direction: column;
  flex-wrap: nowrap;
  gap: 1px;
  opacity: 0;
  transform: translateY(-6px);
  transition:
    opacity 120ms ease,
    transform 140ms var(--ease);
}
.sidebar .cat-nav .cat-sub.open .cat-sub-inner {
  opacity: 1;
  transform: none;
  /* 内容比高度晚 60ms 起步：先「拉开抽屉」再「内容落位」，两段动画有先后就顺了 */
  transition:
    opacity 200ms ease 60ms,
    transform 260ms var(--ease) 60ms;
}
.sidebar .cat-nav .cat-toc-item {
  width: 100%;
  height: auto;
  min-height: 32px;
  padding: 4px 8px;
  border-radius: 8px;
  gap: 8px;
  font-size: 12.5px;
  font-weight: 500;
  background: transparent;
  color: var(--text-dim);
  transition:
    color var(--dur-fast),
    background var(--dur-fast);
}
.sidebar .cat-nav .cat-toc-item:hover {
  background: var(--sb-menu-bg);
  color: var(--text);
}
/* 当前卡片：与父级分类同为橙色实心。
   原先的「浅橙底 + 橙字」在玻璃底上太轻，目录一展开几乎看不出现在停在哪张卡；
   层级改由缩进 + 行高 + 字号三档差来承担（子项更窄、更矮、字更小），
   两块橙色上下相邻也不会糊成一整块 */
.sidebar .cat-nav .cat-toc-item.active {
  background: linear-gradient(135deg, var(--accent), var(--accent-light));
  color: #fff;
  font-weight: 700;
  box-shadow: var(--accent-glow);
}
/* 触屏按下反馈：抽屉里没有 hover，靠 :active 给一次即时回执 */
.sidebar .cat-nav .cat-toc-item:active {
  background: var(--sb-menu-bg-hover);
}
.sidebar .cat-nav .cat-toc-item.active:active {
  background: var(--accent-light);
}
.sidebar .cat-toc-item .ci {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
}
.sidebar .cat-toc-item .ci .card-logo {
  width: 16px;
  height: 16px;
  border-radius: 4px;
}
.sidebar .cat-toc-item .tb {
  font-size: 9px;
  padding: 1px 5px;
  border-radius: 5px;
}

/* ---------------- 主区（顶栏 + 内容 + 页脚） ---------------- */
/* ② 顶栏卡化：从「通栏贴边玻璃条」改成浮在背景光上的圆角玻璃条。
   左侧那 18px 走道就是它与侧栏面板之间的缝——原来顶栏底边与侧栏竖线
   顶死的那个直角（用户标注的第一处）不复存在。

   sticky 的 top 从 0 改成 10px：浮起来之后圆角上边缘不能再贴视口顶，
   否则圆角外会漏出背后滚动的卡片。至于走道里穿过的滚动内容——
   交给下面那条 .app-main::before 顶部遮罩「化掉」，这里只管位置。
   ⚠️ --topbar-h 由 JS 实测 .topbar 的**高度**（不含 margin），
   所以按它算吸顶偏移的地方都要 +10：.rail 的 sticky top 与 .card 的
   scroll-margin-top（移动端）两处，见对应规则 */
.app-shell .topbar {
  position: sticky;
  top: 10px;
  z-index: 80;
  /* 上下 10、左右 18：横向走道与 .layout 的内边距同值，
     顶栏左缘因此与卡片左缘（以及下面页脚的文字）落在同一条竖线上 */
  margin: 10px 18px 0;
  background: var(--glass-bg);
  backdrop-filter: blur(28px) saturate(180%);
  -webkit-backdrop-filter: blur(28px) saturate(180%);
  /* 四周一圈描边（覆盖基础版那条 border-bottom）+ 玻璃投影。
     不能再沿用「只在朝内容那侧描边」的画法——那是通栏条的语言，
     浮起来之后四边都得有边，否则圆角处会缺一截 */
  border: 1px solid var(--glass-border);
  border-radius: 16px;
  box-shadow:
    var(--glass-shadow),
    inset 0 1px 0 var(--glass-hi),
    inset 0 -1px 0 var(--glass-lo);
}
/* ---- 顶部遮罩：让内容在进顶栏之前就「化掉」 ----
   顶栏浮起来之后，上方那条 10px 走道（以及顶栏两个 16px 圆角处缺出来的角）
   会让滚动内容**清晰地**穿过去，比原来的通栏条更脏。做法是压一条通栏遮罩：
   顶部完全不透明（内容到这里就没了），到底边渐隐，再叠一点 backdrop 模糊
   让过渡更软——内容不是被硬切，而是「化」进顶栏区域。

   高度 26 = 走道 10 + 顶栏圆角 16，是个常量：
     · 0~10px 就是那条走道，必须盖住；
     · 10~26px 是顶栏圆角缺出来的两个角，也得盖住；
     · 再往下就全在顶栏背后了，那里**不能盖**——内容透在毛玻璃后面是
       顶栏自己的 backdrop-filter 效果，正是要保留的，所以不需要跟随
       --topbar-h（跟随反而会把顶栏整块糊死）。
   层叠取 60：内容层（.layout / .main 是 z-index 1）之上，
   侧栏 70、顶栏 80 之下 —— 遮罩只压内容，两块玻璃自己的磨砂不受影响
   （它们照常采样遮罩之下的背景，玻璃该透的还透）。 */
.app-main::before {
  content: '';
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 26px;
  z-index: 60;
  pointer-events: none;
  background: var(--bg);
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  /* 底边 12px 内淡出，免得在走道两侧留下一条硬边。
     mask 会把 backdrop-filter 的结果一起渐隐（Chrome / Safari 都支持，
     也是「渐进模糊」的常规写法），所以模糊与底色是同步收边的 */
  mask-image: linear-gradient(180deg, #000 0 14px, transparent 26px);
  -webkit-mask-image: linear-gradient(180deg, #000 0 14px, transparent 26px);
}

/* 顶栏 / 内容区 / 页脚一律铺满可用宽度：
   三栏版式下左侧栏已经占掉一截，内容再套 max-width 居中就会在左边缘留出
   一条几十到上百像素的空白带（窗口越宽越明显），这正是「内容没铺满」的来源。
   这里不再限宽，只保留一圈窄内边距，让卡片从侧边栏右侧直接铺到右栏。 */
.app-shell .header {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 10px 18px;
}

.app-shell .layout {
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  gap: 16px;
  padding: 0 18px;
  /* 必须显式打掉基础版式留下的 max-width + margin:auto：
     那是给「顶栏导航 + 通栏内容」写的限宽，三栏下窗口一旦超过
     （1600 + 侧边栏）就触发居中，左边缘凭空多出几十像素空白带。
     max-width 只在窄窗口不生效，所以这个问题宽屏才看得见 */
  max-width: none;
  margin: 0;
}
/* 下内边距 = 公共基准线 --shell-end-gap：这样滚到底时最后一张卡的底边
   正好与侧栏面板底边（以及右栏最后一张卡，见下）齐平，滚动就此停住。
   原来是 56px，最后一张卡会比侧栏底边高出 46px，收尾是「断开」的 */
.app-shell .main {
  flex: 1;
  min-width: 0;
  padding: 16px 0 var(--shell-end-gap);
}
/* 主栏最后一个直接子元素不许带下外边距，否则基准线会被它整体抬起来。
   踩的就是分类页：每个分组 .cat-section 带 32px 下外边距（分组之间要的间距），
   但它同时也是最后一个元素——实测 tech 分类滚到底时最后一排卡片底边在 857.5、
   侧栏底边在 889.5，差的正好是这 32px。
   首页没有分组（主栏下只有一个 .home 包裹层），所以首页一直是对齐的，
   这也是「只有首页对齐」的来源。
   写成 :last-child 而不是写死 .cat-section：以后主栏结尾换成别的容器也一样管用 */
.main > :last-child {
  margin-bottom: 0;
}

/* 右栏吸顶固定：滚动时右栏留在视口里不动，只有左侧内容滚动。
   top 取「顶栏高度」而不是「顶栏高度 + 内边距」——sticky 一旦生效，
   右栏顶边正好贴在顶栏下沿，与它未滚动时的文档流位置完全重合，
   从静止切到吸顶状态时不会有一下视觉跳动。
   padding-top 与 .main 的 16px 严格一致，保证未滚动时右栏第一张卡片
   与左栏头图顶边是齐平的（这是这里要的效果）。
   注意：sticky 只能保证「初始位置」对齐，一旦滚动，左栏内容上移而右栏停住，
   两者必然错开——这是吸顶的固有代价，本版式选择接受 */
.app-shell .rail {
  flex: 0 0 286px;
  width: 286px;
  /* +10 是顶栏浮起来之后多出的那条上方走道：顶栏吸顶在 10px、
     高度 --topbar-h，右栏要贴在它正下方就得从 10 + topbar-h 起算，
     这样吸顶后右栏第一张卡片与未吸顶时位置一致，不会有跳动 */
  position: sticky;
  top: calc(var(--topbar-h, 64px) + 10px);
  display: flex;
  flex-direction: column;
  gap: 14px;
  /* 下内边距同样取公共基准线：滚到底时右栏最后一张卡的底边与侧栏底边齐平。
     前提是右栏「够高」——sticky 元素到容器底部时会被顶住（bottom 贴住 .layout
     的底边），此时卡片底 = 容器底 − 这里的内边距；右栏比视口矮、没被顶到
     容器底部时（超高的窗口），它的底边会停在半途，这是吸顶的固有代价。
     实测 900 高视口下正好被顶住（右栏内容 868 + 吸顶线 71 > 900）。 */
  padding: 16px 0 var(--shell-end-gap);
}
.app-shell .rail[hidden] {
  display: none;
}
/* 天气卡在右栏的宿主。display:contents 让卡片成为 .rail 的直接 flex 子项：
   宿主自己若也是一个 flex 项，卡片处于 hidden 时 .rail 的 14px gap 会在它身上
   多算一次，右栏顶部会凭空多出一条空档（display:none 的卡片不占位，但宿主占） */
.rail-weather-slot {
  display: contents;
}

/* 页脚（.foot）已整块删除：站点信息收进侧栏底部的版权行（.sb-copy）。
   原来它作为一条通栏玻璃条沉在页底，卡化之后与顶栏形态重复、
   又在左下角与侧栏面板形成第二条竖线，不如把这点信息并进侧栏更干净 */

/* ================================================================
   「今日热榜」聚合首页
   ================================================================ */

/* 首页头图已改用分类页那套 Hero 卡（见 app.js 的 buildHome / buildHero）：
   站点简介 + 数据统计 + 按访客 IP 定位的今日天气。原先的橙色渐变 banner 已退役，
   样式一并移除，避免留下没人用的死规则 */

/* 平台筛选：一律不换行，放不下就横向滚动 + 两端箭头（与分类 pill 行同口径）。
   换行会把头图到榜单之间的高度撑成两行甚至三行，列表被推得忽上忽下 */
.home-filter-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 14px;
}
.home-filter {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-wrap: nowrap;
  gap: 8px;
  overflow-x: auto;
  overflow-y: hidden;
  /* 纵向留白：overflow 会裁掉激活 pill 的橙色外发光，不留白就被切平 */
  padding: 4px 0;
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.home-filter::-webkit-scrollbar {
  display: none;
}
.home-filter.dragging {
  user-select: none;
  -webkit-user-select: none;
  cursor: grabbing;
}

.hf-arrow {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 34px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--card);
  color: var(--text-dim);
  cursor: pointer;
  transition:
    color var(--dur-fast),
    border-color var(--dur-fast);
}
.hf-arrow:hover {
  color: var(--accent);
  border-color: var(--accent);
}
.hf-arrow svg {
  width: 15px;
  height: 15px;
}
.hf-arrow[hidden] {
  display: none;
}

.hf-pill {
  /* 不收缩：pill 被压扁会让文字截断，宁可让容器滚动 */
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 34px;
  padding: 0 14px;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--card);
  color: var(--text-dim);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition:
    color var(--dur-fast),
    background var(--dur-fast),
    border-color var(--dur-fast);
}
.hf-pill:hover {
  color: var(--accent-text);
  border-color: var(--accent);
}
.hf-pill.active {
  background: var(--accent);
  border-color: transparent;
  color: #fff;
  box-shadow: var(--accent-glow);
}
/* 抓取失败的平台：标签保留但降低存在感（悬停有 title 说明），
   不直接移除——标签行长度随抓取成败变化会让人以为功能坏了 */
.hf-pill-off {
  opacity: 0.45;
}
.hf-pill-off:not(.active):hover {
  border-color: var(--border);
  color: var(--text-dim);
}
.hf-pill img {
  width: 16px;
  height: 16px;
  border-radius: 4px;
}
/* 「综合」标签的2×2四宫格：内联 SVG + fill=currentColor。
   颜色跟着 pill 的文字走——选中态（橙底白字）自动是白色，不用另配一套图 */
.hf-pill .hf-grid {
  width: 15px;
  height: 15px;
  flex-shrink: 0;
}

/* ---- 分类页的数据源便签行（版式与首页平台筛选完全一致） ---- */
.cat-filter-row {
  margin-top: 12px;
}
/* 便签里的数据源图标：品牌 logo 用 16px 方图，没有 logo 的数据源退回 emoji 文本 */
.hf-pill .ci {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
}
.hf-pill .ci .card-logo {
  width: 16px;
  height: 16px;
  border-radius: 4px;
}

/* 榜单流 */
.home-list {
  /* 与 .card / .hero 同一套毛玻璃材质：玻璃底 + 背景模糊 + 玻璃描边 + 内高光。
     原来是「不透明卡片底 + 实描边」，光斑根本透不过来，毛玻璃不成立 */
  background: var(--glass-bg);
  backdrop-filter: blur(28px) saturate(180%) brightness(1.06);
  -webkit-backdrop-filter: blur(28px) saturate(180%) brightness(1.06);
  border: 1px solid var(--glass-border);
  border-radius: 18px;
  padding: 6px 4px;
  box-shadow:
    var(--glass-shadow),
    inset 0 1px 0 var(--glass-hi),
    inset 0 -1px 0 var(--glass-lo);
}
/* 统一行高：各平台数据丰俭不一（头条只有标题、百度带摘要），
   min-height + 垂直居中后，有无摘要的行高差被拉平，节奏一致 */
.hl-item {
  display: flex;
  align-items: center;
  gap: 14px;
  min-height: 64px;
  padding: 11px 16px;
  border-radius: 12px;
  transition: background var(--dur-fast);
}
/* 整行可点（点击委托见 app.js「榜单条目整行可点」）：有主链接的行给 pointer
   手势，:has 不支持的旧浏览器只是不显示手型，行为不受影响 */
.hl-item:has(a[href]),
.item:has(a[href]) {
  cursor: pointer;
}
.hl-item + .hl-item {
  border-top: 1px solid var(--border);
}
.hl-item:hover {
  background: var(--card-hover);
}
/* 聚合榜的序号角标 .hl-rank 与前三名配色，全部来自文件开头的「名次角标（全站统一）」 */
/* 榜单缩略图槽位：桌面端固定 64px 宽度，确保缩略图全列绝对垂直对齐 */
.hl-thumb-slot {
  flex: 0 0 64px;
  width: 64px;
  height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.hl-thumb-link {
  flex-shrink: 0;
  display: block;
  border-radius: 8px;
  line-height: 0;
  overflow: hidden;
  width: 64px;
  height: 44px;
}
.hl-thumb {
  width: 64px;
  height: 44px;
  border-radius: 8px;
  object-fit: cover;
  background: var(--border);
  transition:
    transform var(--dur-fast),
    filter var(--dur-fast);
}
.hl-thumb-link:hover .hl-thumb {
  transform: scale(1.05);
  filter: brightness(1.06);
}
.hl-body {
  flex: 1;
  min-width: 0;
}
.hl-title {
  font-size: 15px;
  font-weight: 700;
  line-height: 1.45;
  color: var(--text);
  /* 知乎这类站内问题标题很长：最多两行，超出省略，保证每条的节奏一致 */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.hl-title a {
  color: inherit;
  text-decoration: none;
}
.hl-title a:hover {
  color: var(--accent);
}
.hl-desc {
  margin-top: 3px;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--text-dimmer);
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 1;
  -webkit-box-orient: vertical;
}

/* 右侧元信息列：固定宽度排布，确保 缩略图、来源、热度 纵向全列绝对对齐 */
.hl-meta {
  flex: 0 0 196px;
  width: 196px;
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.hl-meta-left {
  flex: 0 0 112px;
  width: 112px;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.hl-tag-slot {
  flex: 0 0 22px;
  width: 22px;
  height: 18px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.hl-tag {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 1px 5px;
  border-radius: 5px;
  font-size: 10px;
  font-weight: 700;
  line-height: 1.3;
  background: rgba(249, 115, 22, 0.16);
  color: var(--accent-text);
  flex-shrink: 0;
}
.hl-source {
  flex: 0 0 84px;
  width: 84px;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  overflow: hidden;
  font-size: 12px;
  color: var(--text-dimmer);
  white-space: nowrap;
}
.hl-source img {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  border-radius: 4px;
}
.hl-hot {
  flex: 0 0 74px;
  width: 74px;
  text-align: left;
  display: inline-flex;
  align-items: center;
  justify-content: flex-start;
  font-size: 13px;
  font-weight: 800;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* 热度徽标前的小火焰：CSS 伪元素里塞不进 <img>，改用等价的内联 SVG 图标
   （与 .emo 同一套资源，见 emoji.js），保证与其他 emoji 图标观感一致 */
.hl-hot:not(:empty)::before {
  content: '';
  display: inline-block;
  width: 12px;
  height: 12px;
  margin-right: 3px;
  background: url('/emoji/1f525.svg') center / contain no-repeat;
}
/* 折叠/展开：整行宽的按钮，视觉上接在榜单末尾，与「已经到底了」同一位置互换 */
.hl-toggle {
  display: block;
  width: 100%;
  margin-top: 2px;
  padding: 12px 0;
  border: none;
  border-radius: 10px;
  background: transparent;
  color: var(--accent-text);
  font-size: 12.5px;
  font-weight: 700;
  cursor: pointer;
  transition:
    background var(--dur-fast),
    color var(--dur-fast);
}
.hl-toggle:hover {
  background: var(--accent-bg);
}
.hl-end {
  padding: 18px 0 10px;
  text-align: center;
  font-size: 12px;
  color: var(--text-dimmer);
}

/* ---------------- 右侧信息栏 ---------------- */
/* 右栏第一张卡是「今日天气」（.hero-weather，由 app.js 的 placeWeatherCard
   搬进 #railWeather 宿主）。它的全部样式在文件上方「今日天气卡」一节，这里不再重复。
   它不挂 .rail-card：材质是同档的圆角 + 投影，但底色是 JS 按天气写入的天空渐变，
   不是玻璃，套上 .rail-card 的玻璃底会把它盖掉。
   ≤1180px 右栏整体隐藏，天气卡改住进左侧抽屉（#sbWeather），见文件末尾「响应式」 */

.rail-card {
  /* 同 .home-list：右栏卡片也换成毛玻璃，与左侧榜单同一材质 */
  background: var(--glass-bg);
  backdrop-filter: blur(28px) saturate(180%) brightness(1.06);
  -webkit-backdrop-filter: blur(28px) saturate(180%) brightness(1.06);
  border: 1px solid var(--glass-border);
  border-radius: 18px;
  padding: 14px;
  box-shadow:
    var(--glass-shadow),
    inset 0 1px 0 var(--glass-hi),
    inset 0 -1px 0 var(--glass-lo);
}
.rail-title {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 12px;
  font-size: 13px;
  font-weight: 800;
  color: var(--text);
}
.rail-title .rt-sub {
  font-size: 11px;
  font-weight: 500;
  color: var(--text-dimmer);
}
.rail-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 6px;
}
.rail-plat {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 8px 2px;
  border-radius: 10px;
  border: 1px solid transparent;
  background: transparent;
  cursor: pointer;
  transition:
    background var(--dur-fast),
    border-color var(--dur-fast);
}
.rail-plat:hover {
  background: var(--card-hover);
}
.rail-plat.active {
  border-color: var(--accent);
  background: var(--accent-bg);
}
.rail-plat img {
  width: 30px;
  height: 30px;
  border-radius: 8px;
}
.rail-plat span {
  font-size: 10.5px;
  color: var(--text-dim);
  white-space: nowrap;
}
/* ---- 右侧栏：全网实时洞察（突发飙升速报 + 全网热词词云） ---- */
.rail-insights {
  display: flex;
  flex-direction: column;
}
.rail-title .rt-label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
/* 突发红点呼吸动效 */
.rt-pulse {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #ef4444;
  box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.7);
  animation: rtPulse 2s infinite cubic-bezier(0.4, 0, 0.6, 1);
}
@keyframes rtPulse {
  0% {
    transform: scale(0.95);
    box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.7);
  }
  70% {
    transform: scale(1.1);
    box-shadow: 0 0 0 6px rgba(239, 68, 68, 0);
  }
  100% {
    transform: scale(0.95);
    box-shadow: 0 0 0 0 rgba(239, 68, 68, 0);
  }
}

/* 突发飙升列表 */
.rail-surging {
  display: flex;
  flex-direction: column;
  gap: 7px;
}
.rs-item {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 8px 10px;
  border-radius: 12px;
  background: var(--card-bg, rgba(255, 255, 255, 0.04));
  border: 1px solid var(--glass-border);
  text-decoration: none;
  transition:
    transform var(--dur-fast),
    background var(--dur-fast),
    border-color var(--dur-fast);
}
.rs-item:hover {
  background: var(--card-hover);
  transform: translateY(-1px);
  border-color: var(--accent);
}
.rs-header {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}
.rs-tag {
  flex-shrink: 0;
  padding: 1px 5px;
  border-radius: 4px;
  font-size: 10.5px;
  font-weight: 700;
  line-height: 1.3;
  color: #fff;
}
.rs-tag.tag-bao {
  background: linear-gradient(135deg, #ef4444, #dc2626);
}
.rs-tag.tag-fei {
  background: linear-gradient(135deg, #f97316, #ea580c);
}
.rs-tag.tag-xin {
  background: linear-gradient(135deg, #0ea5e9, #0284c7);
}
.rs-tag.tag-hot {
  background: linear-gradient(135deg, #f43f5e, #e11d48);
}
.rs-tag.tag-top {
  background: linear-gradient(135deg, #f59e0b, #d97706);
}
.rs-title {
  flex: 1;
  min-width: 0;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rs-meta {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 11px;
  color: var(--text-dimmer);
}
.rs-meta img {
  width: 13px;
  height: 13px;
  border-radius: 3px;
  object-fit: contain;
}

/* 玻璃质感分割线 */
.rail-divider {
  margin: 14px 0 12px;
  border-top: 1px solid var(--glass-border);
}

/* 全网热词词云 */
.rail-wordcloud {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-content: flex-start;
}
/* 热度分级只走「字号+字重+配色」的阶梯，高度统一 28px：
   此前高度随字号 25→29 连续变化，相邻档差异过小，视觉上像没对齐而不是分级 */
.wc-tag {
  display: inline-flex;
  align-items: center;
  min-height: 28px;
  gap: 4px;
  padding: 3px 9px;
  border-radius: 999px;
  border: 1px solid var(--glass-border);
  background: var(--card-bg, rgba(255, 255, 255, 0.05));
  color: var(--text-dim);
  cursor: pointer;
  user-select: none;
  transition: all var(--dur-fast) ease;
}
.wc-tag:hover {
  transform: translateY(-1.5px);
  border-color: var(--accent);
  background: var(--accent-bg);
  color: var(--accent);
}
.wc-tag.active {
  border-color: var(--accent);
  background: var(--accent);
  color: #fff;
  box-shadow: 0 2px 8px rgba(234, 88, 12, 0.35);
}
.wc-tag.active .wc-count {
  background: rgba(255, 255, 255, 0.25);
  color: #fff;
}
.wc-tag.wc-t1 {
  font-size: 13.5px;
  font-weight: 700;
  border-color: rgba(234, 88, 12, 0.35);
  background: var(--accent-bg);
  color: var(--accent);
}
.wc-tag.wc-t2 {
  font-size: 12.5px;
  font-weight: 600;
}
.wc-tag.wc-t3 {
  font-size: 11.5px;
  font-weight: 500;
  opacity: 0.88;
}
.wc-count {
  font-size: 10px;
  line-height: 1;
  padding: 1px 4px;
  border-radius: 999px;
  background: rgba(125, 125, 125, 0.15);
  color: var(--text-dimmer);
}
.wc-clear-inline {
  color: var(--accent);
  cursor: pointer;
  text-decoration: underline;
  margin-left: 2px;
}

/* 首页主榜热词筛选状态提示条 */
.hl-filter-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 14px;
  margin-bottom: 12px;
  border-radius: 12px;
  background: var(--accent-bg);
  border: 1px solid var(--accent);
  color: var(--text);
  font-size: 13px;
}
.hl-filter-info {
  display: inline-flex;
  align-items: center;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.hl-filter-tag {
  display: inline-block;
  padding: 2px 6px;
  margin-right: 8px;
  border-radius: 4px;
  background: var(--accent);
  color: #fff;
  font-size: 11px;
  font-weight: 700;
  flex-shrink: 0;
}
.hl-filter-clear {
  padding: 4px 10px;
  border-radius: 8px;
  flex-shrink: 0;
  border: 1px solid var(--glass-border);
  background: var(--glass-bg);
  color: var(--text-dim);
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  transition:
    background var(--dur-fast),
    color var(--dur-fast);
}
.hl-filter-clear:hover {
  background: var(--card-hover);
  color: var(--text);
}

/* 加载骨架（首页复用卡片骨架观感） */
.home-skeleton {
  padding: 8px 4px;
}

/* 抽屉遮罩：窄屏侧边栏展开时铺满内容区 */
.sb-mask {
  position: fixed;
  inset: 0;
  z-index: 190;
  background: rgba(0, 0, 0, 0.45);
  opacity: 0;
  transition: opacity var(--dur-base) ease;
}
.sb-mask[hidden] {
  display: none;
}
.sb-mask.show {
  opacity: 1;
}
body.sb-locked {
  overflow: hidden;
}

/* ================================================================
   响应式：先收右栏，再收侧边栏（抽屉化）
   ================================================================ */
/* 天气卡搬进抽屉后，右栏在窄屏已经没有任何要显示的内容——热搜平台九宫格与
   热门话题在窄屏属于重复信息（顶部已有平台筛选行、正文已有主榜单），
   天气卡又改住在抽屉里，于是这里可以退回最直接的做法：整栏隐藏。
   原先那条「右栏退化成通栏横带、只留天气卡、order:-1 提到内容之前」的写法
   连同它的一串补偿（align-items 改 stretch、负外边距抵消 gap、取消吸顶）一并删掉。
   ⚠️ 结构断点仍是 1180px：它必须与 app.js 里 weatherHost() 的取值一致——
   那里以 window.innerWidth > 1180 判断卡片该不该落在右栏 */
@media (max-width: 1180px) {
  .app-shell .rail {
    display: none;
  }
}

/* 顶栏自适应两行模式：当桌面端窗口缩窄（≤1100px，如半屏/分屏浏览时），
   单行不足以容纳舒适宽度的搜索框（避免被压缩到 <350px 导致弹窗变形与文字折行），
   顶栏提前优雅切入两行模式：
   - 第一行：搜索框独占通栏（或与抽屉按钮同行），横向舒展铺满；
   - 第二行：时钟胶囊与操作按钮靠右对齐；
   彻底根治弹窗窄长摩天大楼与文字挤压变形。 */
@media (max-width: 1100px) {
  .app-shell .header {
    flex-wrap: wrap;
    gap: 8px;
    padding: 8px 18px;
  }
  .site-search {
    flex: 1 1 100%;
    max-width: none;
  }
  .header-right {
    flex: 1 1 100%;
    justify-content: flex-end;
    gap: 8px;
  }
  /* 两行版式下第二行整体右对齐，时钟左侧会在搜索框正下方留出一大片空带
     （窗口越宽越明显），观感即「两行之间空白很大」。让时钟锚到行首、
     按钮组留在行尾，两行各成一条整齐的内容带；窄屏放不下时
     margin-right:auto 自然收敛为 0，退化成原来的紧凑布局 */
  .header-right .clock {
    margin-right: auto;
  }
  /* 胶囊上下内边距 8→5px：两行版式下胶囊内圈的空气会把行间视觉间距放大
     到近两倍（形式 row-gap 只有 8px），收紧后第二行明显更贴第一行；
     泡泡进度层按胶囊实际盒子裁切，高度变化不影响动画 */
  .clock {
    padding-top: 5px;
    padding-bottom: 5px;
  }
}

@media (max-width: 900px) {
  /* 侧边栏改为抽屉：默认移出视口，点顶栏 ☰ 展开。
     桌面那条「浮动面板」的**边距**必须还原：抽屉是从屏幕左缘滑出的整块，
     留边会让它「没贴边」。但**圆角保留**，只是换成只圆右侧——
       · 左侧/上/下都要贴屏幕边，圆了会在屏幕边缘露出一道弧形缝；
       · 朝内容那侧的上下两个角圆成 20px，和顶栏/页脚/卡片的圆角语言统一，
         收起态 translateX(-100%) 之后整块在屏外，也不会漏出圆角亮边。
     描边仍只留朝内容侧那一条竖线（贴边的一侧画描边会跟屏幕边缘打架） */
  .sidebar {
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    z-index: 200;
    width: 264px;
    flex: none;
    height: 100dvh;
    margin: 0;
    border: none;
    border-right: 1px solid var(--glass-border);
    border-radius: 0 20px 20px 0;
    transform: translateX(-100%);
    transition: transform var(--dur-base) var(--ease);
    box-shadow: 0 0 42px rgba(0, 0, 0, 0.34);
    padding-top: max(16px, env(safe-area-inset-top));
  }
  .app-shell.sidebar-open .sidebar {
    transform: none;
  }
  .sb-close,
  .sb-toggle {
    display: flex;
  }
  /* 汉堡按钮与左下目录 FAB 是同一族入口（都从左缘展开抽屉），统一尺寸语言：
     44px 触控目标（iOS 44pt 下限；48 会把顶栏第一行撑到 68px，太厚）、
     圆角按 FAB 的 15/48 比例取 13、图标 17→20；
     材质对齐 FAB 的 Liquid Glass（半透明玻璃底 + 玻璃描边 + 内高光，
     backdrop-filter 由下方小控件统一规则提供 blur 20，底色透明后即生效） */
  .sb-toggle {
    width: 44px;
    height: 44px;
    border-radius: 13px;
    background: var(--glass-bg);
    border-color: var(--glass-border);
    box-shadow: inset 0 1px 0 var(--glass-hi);
  }
  .sb-toggle svg {
    width: 20px;
    height: 20px;
  }
  /* 目录按钮：仍是左下角悬浮玻璃 fab（汉堡已回归顶栏、搜索框左边）。
     按钮本体挂在 body 直下（见 index.html），fixed 才相对视口定位；
     尺寸取触屏下限 48px、留 safe-area 避开全面屏手势条；层叠压在遮罩(190)/
     抽屉(200)之下——抽屉展开时它随内容一起被压暗，关闭走面板自己的 ✕ / 遮罩点击。
     首页视图由 JS 写 hidden 收起，display:flex 会盖掉 hidden 的默认隐藏，末尾补一条。
     left/bottom 取 12px：与移动端走道基准（.app-shell .layout 内边距 12px，
     内容列即从 12px 起）同源，与顶栏 ☰ 才不会各漂各的 */
  .sb-toc {
    position: fixed;
    left: max(12px, env(safe-area-inset-left));
    bottom: max(12px, env(safe-area-inset-bottom));
    z-index: 150;
    display: flex;
    width: 48px;
    height: 48px;
    border-radius: 15px;
    background: var(--glass-bg);
    backdrop-filter: blur(28px) saturate(180%);
    -webkit-backdrop-filter: blur(28px) saturate(180%);
    border: 1px solid var(--glass-border);
    box-shadow:
      var(--glass-shadow),
      inset 0 1px 0 var(--glass-hi);
    color: var(--text);
    transition:
      color var(--dur-fast),
      border-color var(--dur-fast),
      transform var(--dur-fast);
  }
  .sb-toc svg {
    width: 22px;
    height: 22px;
  }
  .sb-toc:active {
    transform: scale(0.92);
  }
  .sb-toc[hidden] {
    display: none;
  }
  .app-shell .layout {
    padding: 0 12px;
  }
  /* 移动端走道收成 12px：桌面那 18px 是按 .layout 的内边距定的对齐基准，
     窄屏 .layout 的内边距是 12px，顶栏必须跟着收，否则左缘又会错开 6px */
  .app-shell .topbar {
    margin-left: 12px;
    margin-right: 12px;
  }
  /* 抽屉版式下侧栏是浮层，不存在「与它底边对齐」，所以这里不套 --shell-end-gap；
     但底部安全区得有人接——原来由页脚（.foot）的
     padding-bottom: max(14px, env(safe-area-inset-bottom)) 负责，页脚删掉后
     全面屏手势条会压住最后一张卡，挪到这里兜住 */
  .app-shell .main {
    padding: 12px 0 max(16px, env(safe-area-inset-bottom));
  }
  /* 顶栏窄屏版式：第一行 ☰ + 搜索框，第二行 时钟 + 按钮。
     flex-wrap 必须显式重设——上方 .app-shell .header 的新版式把它覆盖掉了 */
  .app-shell .header {
    flex-wrap: wrap;
    gap: 8px;
    padding: 8px 14px;
  }
  /* ≤1100px 的版式把搜索框压成 flex-basis:100%（给「logo 独占一行」用的），
     断点内 ☰ 已回归顶栏，必须恢复 basis 0 才能和 ☰ 排进同一行——
     否则就是 ☰ / 搜索框 / 时钟按钮 各占一行的三行版式（已踩坑）。
     本块在文件里位于 1100px 规则之后，同特异性靠后者胜 */
  .site-search {
    flex: 1 1 0;
  }
  /* 抽屉里的菜单按触屏尺寸整体重排：桌面那套 44/32px 的行高是给鼠标的，
     指尖点下去容易点偏、误触相邻项。
     行高取平台下限（iOS 44pt / Android 48dp）：一级行 48px、子项 44px，
     行间距一并放到 6px，两侧各让出误触缓冲区；图标与徽章同步放大，
     保证「48px 的行」里视觉重心也够大，不是一行空旷的长条 */
  .sidebar .cat-pills {
    /* 行距放大到 6px：间距由 .sidebar .cat-pills > button 的下边距承担（gap 方案
       会被按钮后面的 0 高 cat-sub 吃掉一份，造成首段间距偏短），这里只抬变量 */
    --sb-pills-gap: 6px;
  }
  .sidebar .cat-nav button {
    height: 48px;
    gap: 12px;
    font-size: 14.5px;
  }
  .sidebar .cat-nav button .mi {
    width: 30px;
    height: 30px;
    border-radius: 9px;
    font-size: 16px;
  }
  .sidebar .cat-pills > button .cnt {
    padding: 3px 9px;
    font-size: 12px;
  }
  .sidebar .cat-nav .cat-pills > button[data-cat]::before {
    width: 8px;
    height: 8px;
    margin-left: 4px;
    border-right-width: 2px;
    border-bottom-width: 2px;
  }
  /* 子目录：行高 44px、行间 2px，父级 48px 的两级节奏在指尖下也不挤。
     选择器要带 .cat-nav 才够压过桌面那条 (0,4,0)，否则这 4/8px 根本落不下去 */
  .sidebar .cat-nav .cat-sub.open {
    margin-top: 4px;
    /* 下方原为 mb + 容器 gap(6px)，gap 已弃用，并进 mb 保持视觉不变 */
    margin-bottom: calc(8px + var(--sb-pills-gap));
  }
  .sidebar .cat-sub-inner {
    gap: 2px;
  }
  .sidebar .cat-nav .cat-toc-item {
    min-height: 44px;
    padding: 6px 10px;
    gap: 10px;
    font-size: 13.5px;
    border-radius: 10px;
  }
  /* 抽屉右上角的关闭按钮同样要够大：34px 在指尖下偏小 */
  .sb-close {
    width: 40px;
    height: 40px;
  }
  .sb-close svg {
    width: 19px;
    height: 19px;
  }

  /* ===== 目录抽屉（左下角目录按钮拉出，只列当前分类的数据源标签）=====
     与侧边栏抽屉同款侧滑语言：贴左/上/下缘、只圆右侧、整块从 -100% 滑入。
     窄一档（264 → 252px）：里面只有一列数据源条目，不需要抽屉那个宽度；
     菜单灰阶变量同步带一份——条目 :active 要用，别去 .sidebar 身上借 */
  .toc-panel {
    --sb-menu-bg-hover: rgba(148, 152, 168, 0.18);
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    z-index: 200;
    display: flex;
    flex-direction: column;
    width: min(76vw, 252px);
    background: var(--glass-bg);
    backdrop-filter: blur(28px) saturate(180%);
    -webkit-backdrop-filter: blur(28px) saturate(180%);
    border: none;
    border-right: 1px solid var(--glass-border);
    border-radius: 0 20px 20px 0;
    transform: translateX(-100%);
    transition: transform var(--dur-base) var(--ease);
    box-shadow: 0 0 42px rgba(0, 0, 0, 0.34);
    padding: max(14px, env(safe-area-inset-top)) 8px max(10px, env(safe-area-inset-bottom));
  }
  .toc-panel.open {
    transform: none;
  }
  /* 面板里的 ✕ 收回桌面档的 34px：全局窄屏规则把它放大到 40px，
     在这份紧凑列表的头部显得笨重 */
  .toc-head .sb-close {
    width: 34px;
    height: 34px;
  }
  .toc-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0 2px 8px 8px;
  }
  .toc-title {
    font-size: 14px;
    font-weight: 700;
    color: var(--text);
  }
  .toc-body {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    display: flex;
    flex-direction: column;
    gap: 2px;
    /* 与 .sidebar 同款处理：触摸/滚轮都能滚，但不画原生滚动条——
     那条灰色轨道挤在玻璃面板右缘，是整块面板里最脏的元素 */
    scrollbar-width: none;
  }
  .toc-body::-webkit-scrollbar {
    width: 0;
    height: 0;
  }
  /* 条目与抽屉菜单同语言但更紧凑：37px 窄行 + 18px 小图标，一屏多看几条。
     类名复用 .cat-toc-item 换全局高亮同步，样式在这里自成一档（不带 .sidebar 前缀） */
  .toc-panel .cat-toc-item {
    display: flex;
    align-items: center;
    width: 100%;
    min-height: 37px;
    padding: 4px 10px;
    border-radius: 9px;
    font-size: 13px;
    font-weight: 500;
    background: transparent;
    border: none;
    color: var(--text-dim);
    cursor: pointer;
    transition:
      color var(--dur-fast),
      background var(--dur-fast);
  }
  .toc-panel .cat-toc-item .mm {
    display: flex;
    align-items: center;
    gap: 10px;
    flex: 1;
    min-width: 0;
  }
  .toc-panel .cat-toc-item .nm {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .toc-panel .cat-toc-item .ci {
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
  }
  .toc-panel .cat-toc-item .ci .card-logo {
    width: 18px;
    height: 18px;
    border-radius: 5px;
  }
  .toc-panel .cat-toc-item.active {
    background: linear-gradient(135deg, var(--accent), var(--accent-light));
    color: #fff;
    font-weight: 700;
    box-shadow: var(--accent-glow);
  }
  .toc-panel .cat-toc-item:active {
    background: var(--sb-menu-bg-hover);
  }
  .toc-panel .cat-toc-item.active:active {
    background: var(--accent-light);
  }
}

@media (max-width: 640px) {
  .hl-item {
    flex-wrap: wrap;
    gap: 8px 12px;
    padding: 12px 12px;
  }
  .hl-thumb-slot {
    width: auto;
    height: auto;
    flex: 0 0 auto;
  }
  .hl-thumb-slot:empty {
    display: none;
  }
  .hl-thumb-link {
    width: 56px;
    height: 40px;
  }
  .hl-thumb {
    width: 56px;
    height: 40px;
  }
  .hl-title {
    font-size: 14px;
  }
  /* 窄屏：右侧列不再固定宽度，整行落到标题下方，两端对齐 */
  .hl-meta {
    flex: 1 0 100%;
    width: auto;
    justify-content: space-between;
    gap: 8px;
    padding-left: 34px;
  }
  .hl-meta-left {
    width: auto;
    flex: 0 0 auto;
    gap: 6px;
  }
  .hl-tag-slot {
    width: auto;
    flex: 0 0 auto;
  }
  .hl-tag-slot:empty {
    display: none;
  }
  .hl-source {
    width: auto;
    flex: 0 0 auto;
  }
  .hl-hot {
    width: auto;
    flex: 0 0 auto;
    min-width: 0;
  }
  .hf-pill {
    height: 32px;
    padding: 0 12px;
    font-size: 12.5px;
  }
  .hf-arrow {
    width: 28px;
    height: 32px;
  }
  .hf-arrow svg {
    width: 14px;
    height: 14px;
  }
}

/* 恢复被移动端媒体查询用 !important 关掉的手风琴目录。
   侧边栏（桌面常驻 + 窄屏抽屉）都要能就地展开子目录，与宽度断点无关——
   原来只在 ≤820px / 粗指针下还原，821~900px 的鼠标窗口打开抽屉会看不到子菜单 */
.sidebar .cat-sub {
  display: grid !important;
}

/* ============ 毛玻璃统一：小控件补齐背景模糊 ============ */
/* .card / .hero / .topbar / .sidebar / .home-list / .rail-card 这些「大面」各自写了
   backdrop-filter，但搜索框、顶栏图标按钮、卡片头按钮、平台 pill 这些「小控件」
   只有半透明底（--card 已改成半透明白），缺了模糊层就会把底下的光斑直接透出来、
   显得脏。这里统一补上。
   模糊半径取 20px 而非大面的 28px：控件面积小，28px 会把背后的色斑糊成一片纯色，
   反而丢掉玻璃的通透感 */
.site-search,
.header-right a,
.header-right button,
.card-actions button,
.sb-close,
.sb-toggle,
.hf-pill,
.hf-arrow {
  backdrop-filter: blur(20px) saturate(170%);
  -webkit-backdrop-filter: blur(20px) saturate(170%);
}

/* 分组标题条补上与卡片同一档冷调投影：它此前只有内高光没有外投影，
   夹在一堆浮起来的毛玻璃卡片中间会明显「轻」一档 */
.cat-title {
  box-shadow:
    var(--glass-shadow),
    inset 0 1px 0 var(--glass-hi),
    inset 0 -1px 0 var(--glass-lo),
    inset 0 0 24px var(--glass-lo);
}

/* ============ 键盘可达性：表单控件焦点态 ============ */
/* 表单控件此前无可见焦点态，Tab 导航找不到焦点在哪。
   :focus-visible 只在键盘聚焦时显示描边，鼠标点击不打扰；
   搜索框（.ss-input）已有 :focus-within 的整框描边，显式排除避免双框 */
.ss-input:focus-visible {
  outline: none;
}
.input-row input:focus-visible,
.input-row select:focus-visible,
.hw-input:focus-visible,
#main input:focus-visible,
#main select:focus-visible,
#main textarea:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
