/* ============================================================
 * ui.css —— 通用界面增强层（全站加载）
 * ============================================================
 * 职责（只放**全站通用、且不修改 style.css 既有组件**的增强）：
 *   §1 `.to-top`        回到顶部按钮（滚动超过阈值才浮现）
 *   §2 `.read-progress` 顶部阅读进度条（仅详情页 / 子页面）
 *   §3 尊重 prefers-reduced-motion
 *   §4 骨架屏 `.sk*`     （第 11 轮 B3）
 *   §5 页面入场过渡      （第 11 轮 B6）
 *   §6 列表错峰淡入      （第 11 轮 D1）
 *
 * 🔴 边界：本文件**只新增 class**。要改 style.css 里某个既有组件的观感，
 *    一律去 style-v2.css（覆盖层）或 theme-light.css（浅色覆盖），别写这里 ——
 *    否则「不满意整文件删掉回滚」这条回滚路径就断了。
 *
 * 深色默认值写在这里；浅色覆盖在 theme-light.css 的 §20 / §21（成对，别拆）。
 *
 * 加载位置：所有基础样式之后（放最后即可，本文件不参与任何既有选择器）
 * 注入方式：§1/§2 由 js/ui.js 动态创建 DOM（PHP 模板零改动）；
 *           §4 由 app.js 在各 render 函数**发请求之前**同步插入。
 *
 * 🔴 约束：
 *   · 尺寸一律 px（`html{font-size:14px}` 会把 rem 算错）
 *   · **子页面不加载 style-v2.css** → 那里 `--brand-*` / `--line-strong` 是
 *     未定义的，所以本文件所有 var() 都必须带兜底值，否则整条声明失效
 *     （这不是防御性编程，是本项目的真实差异，已核对 _inc/head.php）
 *   · 进度条的值通过 CSS 变量 `--p` 从 JS 传入，**不在 JS 里直接写 transform**
 *     —— 本项目规律：JS 写过 style.xxx 的属性，CSS 侧要 !important 才能覆盖。
 *     让 CSS 持有 transform，覆盖关系才可控。
 *   · §5 页面过渡**只做 opacity，绝不做 transform** —— `.page` 里含
 *     `position: sticky` 的 `.header`，给祖先加 transform 会新造一个包含块，
 *     动画期间 sticky 会失效。这条是硬约束，别为了"加个位移更好看"去改。
 *   · 🔴 **注释里绝不能出现「星号紧跟斜杠」这两个字符连写**（连在代码片段里也不行）。
 *     CSS 注释在**第一个** 星号+斜杠 处就结束，写 `.stagger > * ` 后面接 `/is-enter`
 *     这类片段，会把注释**提前闭合**，后半段注释变成"垃圾 CSS"，
 *     解析器的错误恢复会**静默吞掉**后面的一整条 at-rule。
 *     💥 2026-09-28 踩过：§3 注释里把 `.stagger > *` 和 `.is-enter` 写成
 *     `.stagger > *` 与 `/is-enter` 连着的一串，导致整个
 *     `@media (prefers-reduced-motion: reduce)` 块**从未生效**
 *     （CSSOM 里根本没有它，`getComputedStyle` 也看不出异常，
 *     只有逐条枚举 `document.styleSheets[].cssRules` 才能发现）。
 *     要写这类片段请**用空格或反引号拆开**，别让那两个字符相邻。
 * ============================================================ */

/* ------------------------------------------------------------
 * §1 回到顶部
 * ------------------------------------------------------------
 * 位置：右侧固定。`env(safe-area-inset-bottom)` 让开 iPhone 底部小白条，
 *       否则按钮会被手机的 Home 指示条压住点不到。
 * 与 `.toast`（bottom:80px）错开，避免两者叠在一起。
 * 层级：150 —— 压过 .header(100) 与 .sub-tabs(20)，
 *            但在 .lightbox(999) / .notice-modal(1000) 之下，
 *            所以弹窗打开时按钮自动被盖住，不需要额外处理。
 * ---------------------------------------------------------- */
.to-top {
  position: fixed;
  right: 16px;
  bottom: calc(24px + env(safe-area-inset-bottom, 0px));
  z-index: 150;

  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;

  padding: 0;
  margin: 0;
  border: 1px solid var(--line-strong, rgba(255, 255, 255, .12));
  border-radius: 50%;

  /* 真的是半透明 → 这里的 blur 是真生效的
     （对比：style.css 里 .header 那处 blur 是空转，已在第 10 轮删掉） */
  background: rgba(26, 26, 46, .82);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  color: #fff;

  cursor: pointer;
  -webkit-tap-highlight-color: transparent;

  opacity: 0;
  pointer-events: none;
  transform: translateY(10px) scale(.9);
  transition:
    opacity .25s ease,
    transform .25s cubic-bezier(.2, 0, 0, 1),
    background-color .2s ease,
    border-color .2s ease,
    color .2s ease;
}

.to-top.show {
  opacity: 1;
  pointer-events: auto;
  transform: none;
}

.to-top svg {
  width: 20px;
  height: 20px;
  display: block;
}

/* ⚠️ 顺序要紧：`.to-top:active` 与 `.to-top.show` 特异性相同(0,2,0)，
   写在后面才能让「按下时缩小」压过 `transform: none`。别调换。 */
.to-top:active { transform: scale(.9); }

/* 键盘可达性：与 .theme-toggle / style-v2 的焦点环保持同一套语言 */
.to-top:focus-visible {
  outline: 2px solid rgba(99, 102, 241, .85);
  outline-offset: 2px;
}

/* 指针设备 hover */
@media (hover: hover) and (pointer: fine) {
  .to-top { background: rgba(26, 26, 46, .72); }
  .to-top:hover {
    background: rgba(99, 102, 241, .9);
    border-color: transparent;
    color: #fff;
  }
}

/* ------------------------------------------------------------
 * §2 阅读进度条
 * ------------------------------------------------------------
 * 只在「阅读模式」页面创建（详情页 / 子页面），由 ui.js 判断 ——
 * 首页和列表页是网格导航，不是阅读，不需要进度条。
 *
 * 层级 200：压在 .header(100) 的顶边上 —— 这正是想要的效果，
 *           进度条骑在顶栏上沿；同时低于弹窗，弹窗时不露出来。
 * ---------------------------------------------------------- */
.read-progress {
  position: fixed;
  left: 0;
  top: 0;
  z-index: 200;

  width: 100%;
  height: 3px;

  pointer-events: none;
  background: transparent;
}

.read-progress > i {
  display: block;
  width: 100%;
  height: 100%;

  transform-origin: 0 50%;
  /* --p 由 ui.js 写入（0~1）。兜底 0 → 未滚动时完全不可见 */
  transform: scaleX(var(--p, 0));

  /* 品牌粉紫渐变（--brand-* 兜底：#6366f1 → #8b5cf6，与 style-v2 同源） */
  background: linear-gradient(90deg,
              var(--brand-1, #6366f1),
              var(--brand-2, #8b5cf6));

  /* 只做 transform 的过渡，代价极低；再加一道右下投影让它在深底上看得清 */
  transition: transform .1s linear;
  box-shadow: 0 0 6px rgba(99, 102, 241, .55);
}

/* ------------------------------------------------------------
 * §3 尊重「减少动效」（2026-09-28 第 11 轮起改为**全局**，C4）
 * ------------------------------------------------------------
 * 改之前：全站只有这一处（只覆盖 .to-top / .read-progress）尊重
 * prefers-reduced-motion。而 style.css / subpage.css 里到处是
 * `transition: all .2s` 的位移缩放反馈 —— `.cat-item:active` 缩放、
 * `.art-item:active` 缩放、`.detail-body img:active` 缩放、
 * `.cover-carousel img` 的 .6s 交叉淡入……开了「减少动效」的用户
 * 在这些地方照样被晃。所以这里换成一条**全站生效**的压制规则。
 *
 * 做法上刻意**不用** `animation: none !important` 那种最粗的写法：
 *  · `animation-duration: .01ms` + `iteration-count: 1` 会把动画"瞬间走完"，
 *    配合 `animation-fill-mode: both` 正好停在**终态** ——
 *    对 `.stagger > *` 与 `.page.is-enter`（终态 opacity:1）、
 *    `.sk::after`（终态不可见）都是正确结果，
 *    比 `none` 更不容易留下"卡在 from 态"的残影。
 *  · `transition-duration: .01ms` 而不是 `transition: none` ——
 *    保留 transition 的存在，只是瞬间完成，避免把依赖 transitionend
 *    的逻辑一起干掉（本项目暂无，但这是更稳的写法）。
 *
 * 🔴 唯一必须单独救回的是**加载指示器**：一刀切会让 `.spinner` 冻住不动，
 *    而静止的转圈会被误读成"卡死了"（本项目已有此教训）。
 *    这里改成**整体呼吸** —— 仍然明显"在动"，但不做旋转位移
 *    （旋转正是前庭不适最主要的那类运动）。
 * ---------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    transition-duration: .01ms !important;
    transition-delay: 0ms !important;
    animation-duration: .01ms !important;
    animation-delay: 0ms !important;
    animation-iteration-count: 1 !important;
  }

  /* 救回加载指示器：换成呼吸，不旋转。
     ⚠️ 环色走 `--lmw-ring` 自定义属性而不是写死颜色 —— 浅色主题下
        写死的半透明白环 = 隐形（浅色覆盖见 theme-light.css §21）。 */
  .spinner {
    animation: lmw-pulse 1.5s ease-in-out infinite !important;
    animation-duration: 1.5s !important;
    animation-iteration-count: infinite !important;
    border-color: var(--lmw-ring, rgba(255, 255, 255, .28)) !important;
    border-top-color: transparent !important;
  }
}

/* 加载指示器在「减少动效」下的环色（深色默认值；浅色覆盖见 theme-light.css §21）。
   放在媒体查询外，保证两态都能解析到。 */
.spinner { --lmw-ring: rgba(255, 255, 255, .28); }

@keyframes lmw-pulse {
  0%, 100% { opacity: .35; }
  50%      { opacity: 1; }
}

/* ------------------------------------------------------------
 * §4 骨架屏（2026-09-28 第 11 轮，B3）
 * ------------------------------------------------------------
 * 改之前：等接口时显示一个转圈 + 「加载中...」。转圈不告诉用户
 * 「接下来会出现什么」，而且列表页要等 2 列封面区，转圈一消失就是
 * 一次大幅布局跳动。
 *
 * 骨架屏的形状**照着真实内容做**：分类瓦片是「方块图标 + 一行文字」，
 * 文章卡是「4:3 封面 + 两行文字」，详情页是「一大段文字块 + 若干行」。
 * 这样数据到位时是「填充」而不是「重排」。
 *
 * 🔴 颜色走 CSS 变量 `--sk-base` / `--sk-hi`，浅色覆盖在 theme-light.css §21。
 *    深色那套（半透明白）直接写进浅色主题就是**完全隐形**——这正是
 *    theme-light.css §20 顶部警告过的那类坑。
 * ---------------------------------------------------------- */
.sk {
  --sk-base: rgba(255, 255, 255, .06);
  --sk-hi:   rgba(255, 255, 255, .11);
  position: relative;
  overflow: hidden;
  background: var(--sk-base);
  border-radius: 8px;
}

/* 高光扫过（1.25s 一循环）。用一个窄亮带横穿，比整体呼吸更"在加载"。 */
.sk::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(100deg,
              transparent 20%,
              var(--sk-hi) 50%,
              transparent 80%);
  transform: translateX(-100%);
  animation: sk-sweep 1.25s ease-in-out infinite;
}
@keyframes sk-sweep { to { transform: translateX(100%); } }

/* 分类瓦片骨架 —— 尺寸对齐 .cat-item（padding 16px 8px / gap 8px / 图标 44px） */
.sk-cat {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 16px 8px;
  border-radius: 12px;
  background: rgba(255, 255, 255, .025);
  border: 1px solid rgba(255, 255, 255, .05);
}
.sk-cat-ico { width: 44px; height: 44px; border-radius: 12px; flex: none; }
.sk-cat-txt { width: 62%; height: 11px; border-radius: 6px; }

/* 文章卡骨架 —— 尺寸对齐 .art-item（封面 4:3 contain / .art-info 10px 10px 12px） */
.sk-art {
  border-radius: 12px;
  overflow: hidden;
  background: rgba(255, 255, 255, .025);
  border: 1px solid rgba(255, 255, 255, .05);
}
.sk-art-cover { width: 100%; aspect-ratio: 4 / 3; border-radius: 0; }
.sk-art-info { padding: 10px 10px 12px; }
.sk-line { height: 11px; border-radius: 6px; }
.sk-line + .sk-line { margin-top: 7px; }
.sk-w60 { width: 60%; }
.sk-w40 { width: 40%; }

/* 详情页骨架 —— 正文是一整块文字，用长条模拟段落 */
.sk-detail { padding: 4px 0; }
.sk-detail .sk-block { height: 132px; border-radius: 10px; margin-bottom: 16px; }
.sk-detail .sk-line { height: 13px; margin-bottom: 11px; }

/* 骨架容器：**不要套外层 div**。直接把 `.sk-cat` 塞进 `#cat-grid`、
   把 `.sk-art` 塞进 `#list-grid` —— 那两个容器本身就是 grid，
   再嵌一层 grid 只会让骨架变成"栅格里的一格"，宽度全错。
   每个骨架子项由 JS 打 `aria-hidden="true"`（对读屏器无意义）。 */

/* ------------------------------------------------------------
 * §5 页面入场过渡（第 11 轮，B6）
 * ------------------------------------------------------------
 * 改之前：切页是 `display:none` → `display:block`，**硬切**，
 * 在全屏内容面前这一下很"闪"。
 *
 * 🔴 只做 opacity，不做 transform —— 见文件头约束。`.page` 内含 sticky 的
 *    `.header`，祖先一旦有 transform 就会新造包含块，动画期间吸顶失效。
 *    想要位移观感靠 §6 的列表错峰淡入补，别在这里加 transform。
 * ---------------------------------------------------------- */
.page.is-enter { animation: page-in .26s ease-out both; }
@keyframes page-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* ------------------------------------------------------------
 * §6 列表错峰淡入（第 11 轮，D1）
 * ------------------------------------------------------------
 * 卡片逐个浮上来，而不是"啪"地一次出现 —— 这一下最能带出"精致感"。
 *
 * 🔴 三个必须注意的：
 *  1. **只在首次渲染时加**。容器由 JS 打上 `.stagger`（`loadArticles(true)`
 *     时 toggle，追加「加载更多」时不打）—— 否则点一次「加载更多」，
 *     已经看过的卡片会全部重新淡入一遍，很晕。
 *  2. 延迟用 `--i` 这个自定义属性传入（和 §2 的 `--p` 同一套做法），
 *     **不在 JS 里写 animation-delay** —— JS 写过的属性 CSS 侧要 !important
 *     才能覆盖，交 CSS 持有才可控。
 *  3. **上限 10 个**（10 × 28ms ≈ 280ms）。不做上限的话，列表页一次渲染
 *     20 条，最后一张要等 560ms 才出现，感觉是卡顿而不是动效。
 *     超出上限的一律按 10 算（`--i` 由 JS 夹好，CSS 这里再兜一层）。
 * ---------------------------------------------------------- */
.stagger > * {
  animation: item-in .34s cubic-bezier(.2, 0, 0, 1) both;
  animation-delay: calc(min(var(--i, 0), 10) * 28ms);
}
@keyframes item-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* 「减少动效」不用在这里单写 —— §3 的全站压制规则已经覆盖了 §5 / §6 / §4：
   `animation-duration: .01ms` + `fill-mode: both` 会让它们瞬间停在终态
   （opacity:1），`.sk::after` 的扫光停在不可见处。别再补第二份，
   两份并存时后面那份反而因为没带 !important 而被 §3 压住，看着像"没生效"。 */

/* ------------------------------------------------------------
 * §7 破图占位（第 11 轮，B4）
 * ------------------------------------------------------------
 * 封面走七牛云，偶发 404 / 链接过期 / 弱网中断时浏览器会留一个
 * 「碎图图标」—— 深色卡片上非常扎眼，比不显示还难看。
 * app.js 的 imgFail() 把坏掉的 <img> 换成这个块。
 *
 * 尺寸对齐被替换的元素：
 *   · 列表封面 `.art-cover` 是 4:3（contain，见 style.css / theme-light §8）；
 *   · 详情封面 `.detail-cover` 手机档是通栏 max-height:300，
 *     PC 档（style-v2 §12b）是 860px 中轴 contain —— 所以这里只给
 *     `--fb-ratio` 兜底比例 + 限高，别硬写死宽高。
 * ---------------------------------------------------------- */
.img-fb {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  aspect-ratio: 4 / 3;
  background: linear-gradient(135deg,
              rgba(99, 102, 241, .13),
              rgba(139, 92, 246, .13));
  color: rgba(255, 255, 255, .3);
  font-size: 26px;
  line-height: 1;
}
.img-fb::before { content: '🖼️'; }

.img-fb-lg {
  aspect-ratio: 16 / 9;
  max-height: 300px;
  border-radius: 0;
  font-size: 44px;
}
@media (min-width: 1024px) {
  /* 与 style-v2 §12b 的 .detail-cover 对齐：860 中轴 + 圆角卡片底 */
  .img-fb-lg {
    max-width: 860px;
    margin: 20px auto 0;
    border: 1px solid var(--line-strong, rgba(255, 255, 255, .12));
    border-radius: 16px;
  }
}

/* ------------------------------------------------------------
 * §8 键盘焦点环（第 11 轮，C5）
 * ------------------------------------------------------------
 * style-v2.css 里已有 `a:focus-visible, button:focus-visible` 的焦点环，
 * 但**子页面不加载 style-v2.css**，而且本站在 app.js / HTML 里大量使用
 * 「div/span + role=button」的伪按钮 —— 这些都不在那条选择器覆盖范围内。
 * 这里补一条作用域更广的，让全站（含子页面）的键盘焦点都看得见。
 *
 * ⚠️ 只写 `:focus-visible` 不写 `:focus` —— 后者会在鼠标点击后也亮环，
 *    观感很差（这正是 :focus-visible 被发明出来要解决的问题）。
 * ---------------------------------------------------------- */
[role="button"]:focus-visible {
  outline: 2px solid rgba(129, 140, 248, .75);
  outline-offset: 2px;
  border-radius: 9px;
}

/* ------------------------------------------------------------
 * §9 搜索空态的「最近搜索 / 热门推荐」（第 11 轮，C3）
 * ------------------------------------------------------------
 * 改之前 `#search-panel` 只有一句话「输入关键词搜索文章或页面」——
 * 首页搜索框下面整整一屏是浪费的。对资源站来说搜索是最主要的导航方式，
 * 空态给出「你搜过什么」+「大家都在搜什么」，能把搜索成本显著压低。
 *
 * 归属说明：这些是**新增 class**，按本文件开头的边界规则放这里，
 * 而不是 style-v2.css —— 后者被标注为「不满意可整文件删掉回滚」，
 * 搜索历史不该跟着那个开关一起消失。
 * ---------------------------------------------------------- */
.sp-sec { padding: 14px 16px 4px; }
.sp-sec + .sp-sec { border-top: 1px solid var(--line-soft, rgba(255, 255, 255, .06)); }

.sp-sec-hd {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 10px;
}
.sp-sec-title {
  font-size: 12px;
  font-weight: 600;
  color: rgba(255, 255, 255, .5);
  letter-spacing: .04em;
}
/* 「清空」做成小一号的弱化按钮 —— 它是破坏性操作，不该抢视觉重量 */
.sp-sec-clear {
  font-size: 11px;
  color: rgba(255, 255, 255, .3);
  padding: 2px 6px;
  border-radius: 6px;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.sp-sec-clear:active { color: rgba(255, 255, 255, .6); }

.sp-chips { display: flex; flex-wrap: wrap; gap: 8px; }

.sp-chip {
  display: inline-block;
  max-width: 100%;
  padding: 6px 12px;
  border-radius: 999px;
  font-size: 12.5px;
  line-height: 1.3;
  color: rgba(255, 255, 255, .78);
  background: rgba(255, 255, 255, .055);
  border: 1px solid rgba(255, 255, 255, .07);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  /* 超长关键词（用户可能搜过一整句）截断，不让一个 chip 撑满整行 */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sp-chip:active {
  background: rgba(99, 102, 241, .22);
  border-color: rgba(99, 102, 241, .45);
  color: #fff;
}

@media (hover: hover) and (pointer: fine) {
  .sp-chip:hover {
    background: rgba(255, 255, 255, .1);
    border-color: rgba(129, 140, 248, .4);
    color: #fff;
  }
  .sp-sec-clear:hover { color: rgba(255, 255, 255, .7); }
}
