@charset "UTF-8";
/* ============================================================================
   ZVBoard —— 前台样式表（纯手写，零第三方依赖）

   视觉语言：对标 https://watcha.cn/（观猹，中文 AI 工具目录）。
   本文件里的每一个具体数值都来自 2026-10-01 对该站**渲染后几何**的实测 ——
   `getBoundingClientRect()` / `getComputedStyle()` 取回，不是看截图猜的。

   ────────────────────────────────────────────────────────────────────────────
   零、这一轮改的是什么：从「数值对齐」改成「几何对齐」
   ────────────────────────────────────────────────────────────────────────────
   上一版只对齐了 hex / font-size / gap / radius 这些**计算样式数值**，结果骨架、
   信息密度、视觉重心全都不对 —— 数值相同不等于长得一样。这一轮对齐的是**版式几何**：
   最外层是一个 `236px 750px 324px` 的三列 grid（目标站 main-container 的实测值），
   中间那列 750px 固定，正文内容宽 716px。所有子元素的尺寸都由这个骨架推出来，
   而不是各自拍一个数字。下面每个数值旁边的注释都写明了它出自哪个元素的实测。

   三列骨架的量测值（1440×900，正文宽 1430）与本站的对应关系，见下面第 ② 节的注释块。
   三档视口（1440 / 1024 / 390）的塌陷行为也照目标站实测：
     1440 → 236 / 750 / 324，三列 header 行高 64
     1024 →  80 / 750 / 184，右上的动作区整块隐藏
      390 → 单列；桌面 header 全隐，换 56px 的移动顶栏 + 56px 固定底栏

   ────────────────────────────────────────────────────────────────────────────
   一、文件结构：三层，只有一份规则
   ────────────────────────────────────────────────────────────────────────────
   ① 共用令牌层   —— :root 里的设计令牌（颜色 / 字号 / 间距 / 圆角 / 阴影 /
                     栅格宽度）。中英两版**共用同一套令牌**，色板与组件形状
                     因此不可能漂移。
   ② 英文基版     —— 后面所有具体规则。默认值针对拉丁字母调优：更宽的版心、
                     负字距、更紧的行高、更宽的行长（ch）。
   ③ 中文覆盖层   —— 文件末尾的 `body[data-lang="zh"]`，**只重新赋值令牌**，
                     不重写任何一条规则。中文需要正字距、更大的行高、
                     更窄的行长（em 而非 ch），以及更密的卡网格。

   为什么用 body[data-lang] 而不是 html:lang(zh)：
     `:lang()` 按**元素自身继承到的语言**匹配，而页面里到处是中文标签、
     中文产品名、中文 alt 文本 —— 在英文页上，这些元素同样会被 `:lang(zh)`
     命中，于是英文页里会冒出中文的字距与行高。`data-lang` 由
     lib/layout.php 写在 <body> 上，只反映**本次请求的语言**，与页面里
     出现哪种文字无关。这是这套双语版式能成立的前提。

   为什么不是「中英各一份样式表」：
     两份表必然漂移 —— 改了一边忘了另一边，是双语站最常见的慢性病。
     这里的分工是「令牌不同、规则相同」：改一条组件规则，两个语言同时生效。

   ────────────────────────────────────────────────────────────────────────────
   二、品牌识别边界（硬约束，不是风格偏好）
   ────────────────────────────────────────────────────────────────────────────
   本站**复用观猹的设计语言，但不搬运它的品牌识别**。具体地，绝不出现：
   观猹的名字、它的「猹」形象 / 吉祥物 / 插画、它的评分名（猹评）、
   它的 logo 与标志性品牌图形资产。

   理由是结构性的，不是洁癖：本站挂着「名次不可购买」「榜单透明」这类
   需要被信任的声明。如果 zv001.com 长得和观猹一模一样，访客会分不清自己
   站在哪个站上 —— 在一个靠可信度吃饭的站点上，被误认成别人是最伤信任的
   失败方式。设计语言（色板、字号阶梯、圆角与栅格）可以复用，
   品牌识别（名字、形象、评分名、logo）不能搬。

   同理，观猹的「猹评」编辑评分与「精彩活动」（黑客松）两个板块本站**不照抄**：
   我们的数据模型里没有评分字段、也没有活动表，凭空造一块只会是假内容。
   本站保留的信息架构是四个榜（latest / favorites / top-month / featured）
   + 类目 / 标签 / 合集 / 目录 / 搜索，一个不多一个不少。

   ────────────────────────────────────────────────────────────────────────────
   三、刻意**不照抄**目标站的地方（逐条见交付报告）
   ────────────────────────────────────────────────────────────────────────────
   1. 对比度。观猹有两处不达标，本站都做了修正：
      · 它的主色 rgb(51,166,109)=#33A66D 对白底只有 3.08:1 —— 既不能当按钮底
        承载白字，也不能当小字放在白底上。本站把**承载文字的那一档**压深到
        #1E7A50（对白 5.31:1，白字其上 5.31:1），实测原色**全站不再出现** ——
        它既不承载文字，也就没有一处纯装饰值得单独为它留一个变量。
      · 它的次要文字是 rgba(0,0,0,.45)/.5（对白 3.36:1 / 3.95:1），10.4px 的元信息
        更是又小又淡。本站次要文字用 --mute (#5F676F，白底 5.74:1)，最小字号
        12px，不出现 10.4px。
   2. 10.4px 的元信息字号不照抄（可访问性下限），最小 12px。
      受影响的是卡片里的「机构名」与「评分位小字」：目标站 10.4px，本站 12px。
      这会让卡片头部行比目标站高约 0——它那两行本来就比 12px 行盒矮，
      两行合计仍落在 32px 的头像高度之内，所以几何没有被撑开。
   3. 核验等级（.lvl-0..3）保留「边框粗细 + 虚实」的原始编码，颜色只是**叠加**，
      不替代它 —— 色盲用户在黑白打印下仍要能分辨。
   4. **没有「评分位」**。目标站卡片右上角是编辑评分（「7.9 / 23 猹评」）；
      本站的数据模型里没有评分字段，所以那个位置放的是**该榜单自己的指标**
      （人气榜=票数、最新收录=发布日期、精选位=Featured 且不出现数字）。
      评分名与评分体系一律不搬 —— 凭空造一个分数是最容易被误认成对方的地方。
   5. **「更多」按钮的位置**。目标站的 .view-more-button 是跟在网格**下方**的通栏
      按钮（716×32、margin-top 15px）；本站把它放回区块标题行的右侧
      （需求里的具名清单就是「h2 与右侧的更多按钮」），宽高与描边照抄，
      但它是行内元素，因此多了一组 padding:0 14px（通栏按钮不需要左右内边距）。
   6. 移动端产品卡的高度是 ~61px，目标站的 .product-mobile-card 是 56px。
      原因是它的窄屏卡片把「机构名 · 评分」并成一行放在口号下面，
      而我们的类目链接留在标题下方（我们没有评分字段，那一行合并了也没有内容）。
      桌面端的卡片几何是完全对齐的（见第 ② 节），移动端这一处差 5px 已记录。
   7. 目标站的「精彩活动（黑客松）」「探索队列」两个板块不照抄：
      我们没有活动表、也没有可推荐的队列，凭空造一块只会是假内容。
   8. **搜索框的焦点态**不照抄。观猹聚焦时描边几乎不变、也没有投影（键盘用户看不出
      焦点在哪），我们改用 :focus-within 的品牌色描边 + 3px 淡环。
   9. **搜索框没有并列的文字按钮**（观猹是「镜圈 + 输入框」的极简形态，照此收敛），
      但 submit 控件仍留在 DOM 里并用 .sr-only 藏起来 —— 回车提交与读屏可达名称
      都不能少，且无 JS 时表单照常工作。
  10. **品牌标记 = 品牌绿实心底 + 白色字母 Z**（ZVBoard 的首字母，三段笔画一笔连写）。
      它走过三次改版：渐变方块（与整套描边图标不是同一种语言）→「圆角方框 + 上箭头」
      的细描边图形（16px 下细线套细线，糊成一团）→「绿底 + 三根递增白柱」（立得住，
      但与站名没有字面联系）→ 当前这版。
      它与 **favicon.svg 同几何**（几何与线宽的唯一真源是 lib/config.php 的
      BRAND_MARK_PATH / BRAND_MARK_STROKE，由 cli/icons.php 渲染）。
      ⚠️ 它与下面的通用图标集**不同源**：通用图标是 2px 的 currentColor 描边，
      标记是 4px 的白描边（颜色见 .i-mark-bars）。改任一处都要确认另一处没被带偏。
      全站**只有这一枚标记**：前台页头、后台页头、标签页图标、iOS 主屏图标
      都取自同一份几何。后台此前那条 `body.admin .brand::before` 渐变方块已删除 ——
      它会让后台页头挂渐变方块、而同一页的标签页挂着 favicon，是同一个标志写两处。

   用法：修改本文件后必须把 lib/config.php 的 ASSET_V +1，
        否则访客浏览器会继续吃 nginx 下发的 12 小时强缓存。
   ========================================================================== */

/* ============================================================================
   ① 共用令牌层 —— 中英共用
   ========================================================================== */

:root{
  /* ---------- 品牌色 ----------
     实测原色 rgb(51,166,109) = #33A66D，但它**不能承载文字**：
       对白底 3.08:1（按钮底上的白字同样 3.08:1），12–16px 文字全部不达标。
     按硬约束「对比度不足不要照抄」，承载文字的一档压深到 #1E7A50：5.30:1。
     实测原色 #33A66D 因此**本站一处都不用**：不能承载文字，也就没有保留的必要。 */
  --brand:#1e7a50;              /* 承载文字与实心填充（可访问版品牌色）：对白 5.31:1 */
  --brand-deep:#155e3d;         /* 悬停加深 */
  --brand-weak:rgba(30,122,80,.10);     /* 淡底：对 #1E7A50 文字 4.6:1 */
  --brand-line:rgba(30,122,80,.30);

  /* ---------- 中性色 ---------- */
  --ink:#15181c;                /* 正文与标题（实测观猹主文字是 #1F2329/#1A1A1A，同族略深） */
  --ink-2:#3c434b;              /* 次级正文，白底 10.3:1 */
  --mute:#5f676f;               /* 次要文字：白底 5.74:1（实测观猹用 rgba(0,0,0,.5)=3.95:1，不达标故压深） */
  --faint:#9aa1a9;              /* 最弱：占位、无数据横线（不承载正文） */
  --line:#e9edf0;               /* 描边（实测观猹卡片 1px solid rgb(239,243,244)=#EFF3F4） */
  --line-2:#d6dce1;             /* 更强的分隔线、输入框描边 */
  --chip:#f5f6f7;               /* 实测观猹中性底 rgb(245,245,245)（33 次） */
  --soft:#f5f6f7;
  --soft-2:#edeff1;
  --bg:#ffffff;                 /* 实测观猹页面底色就是纯白，层次靠 1px 细线而非灰底 */
  --surface:#ffffff;            /* 卡片面 */
  --surface-2:#f7f8f9;          /* 表头、内嵌面板 */

  /* ---------- 强调色：付费曝光位 ----------
     实测观猹没有这一层（它没有付费位），这是本站自己的语义，沿用此前已验证的
     琥珀色：--gold 放在 --gold-weak 淡底上 4.53:1、白底上 5.5:1。 */
  --gold:#8a6220;
  --gold-weak:rgba(174,125,41,.12);
  --danger:#b42318;             /* 名次下滑（配合 ▼ 符号，不单靠颜色），白底 6.6:1 */

  /* ---------- 圆角 ----------
     实测观猹频次：9999px×203 / 6px×53 / 16px×41 / 12px×31 / 8px×12 / 32px×5。 */
  --r-sm:6px; --r-md:8px; --r-lg:12px; --r-xl:16px; --r-pill:9999px;

  /* ---------- 阴影 ----------
     实测观猹：卡片本身**没有阴影**，只有 1px 描边；投影只出现在浮层上
     （0 10px 15px -3px rgba(0,0,0,.1), 0 4px 6px -4px rgba(0,0,0,.1)，出现 12 次）。
     所以卡片=描边，浮层=投影，两者不混用。 */
  --sh-card:0 1px 2px rgba(16,24,40,.04);
  --sh-sm:0 1px 2px rgba(16,24,40,.05);
  --sh-pop:0 10px 15px -3px rgba(0,0,0,.10), 0 4px 6px -4px rgba(0,0,0,.10);

  /* ---------- 间距阶梯（实测观猹以 12 / 15 / 16 / 20 / 40px 为主） ---------- */
  --sp-1:4px; --sp-2:8px; --sp-3:12px; --sp-4:16px; --sp-5:20px;
  --sp-6:24px; --sp-8:32px; --sp-10:40px; --sp-14:56px; --sp-18:72px;

  --mono:ui-monospace,SFMono-Regular,Menlo,Consolas,"Liberation Mono",monospace;
  /* 实测观猹字体栈：PingFang SC / Source Han Sans SC … —— 系统字体，零依赖。 */
  --sans:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",
         "PingFang SC","Hiragino Sans GB","Microsoft YaHei",Arial,sans-serif;

  /* ---------- 版式令牌：英文基版（②层默认值） ---------- */
  --wrap:1290px;                /* 观猹 1440 视口下版心约 1310px（236+750+324），取近似 */
  --wrap-wide:1560px;
  --fs-body:16px;               /* 实测观猹 base 16px/24px */
  --lh-body:1.6;
  --ls-body:-.006em;            /* 拉丁字母轻微收紧 */
  --fs-h1:clamp(30px,3.4vw,42px);
  --lh-h1:1.12;
  --ls-h1:-.032em;
  --fs-h2:18px;                 /* 区块标题：实测目标站 18px/28px w600 ls −0.36px */
  --lh-h2:1.556;                /* 28 / 18 —— 用比值而不是像素，中文版才能只改字号 */
  --ls-h2:-.02em;               /* −0.36px @18px */
  --fs-h3:16px;                 /* 卡片标题：实测目标站 16px/19.2px w700 ls −.01em */
  --lh-h3:1.2;
  --ls-h3:-.01em;
  --fs-lead:17px;
  --lh-lead:1.62;
  --prose:70ch;                 /* 正文行宽：英文按 ch */
  --card-min:200px;             /* 卡片最小宽：实测目标站 .product-grid 在 716px 容器里
                                   排 3 列，minmax 下限落在 200–228 之间即可复现同一列数
                                   （(716+15)/(200+15)=3.4 → 3 列；列宽 (716−30)/3=228.67） */
  --card-pad:16px;
  --grid-gap:15px;              /* 实测目标站 product-grid gap = 15px */
  --sec-gap:40px;               /* 区块之间的纵向间距：实测目标站 section 之间的 flex gap = 40px */
  --head-h:64px;                /* 实测目标站 header 行高 */
  --shell-w:1310px;             /* ★ 版心 = 236+750+324（页脚/说明带用它对齐）。
                                   2026-10-02：右栏删除后曾收到 986，本轮按站主要求
                                   重新对齐 watcha.cn，恢复 1310 —— 不改的话页脚会比
                                   正文窄 324px、两端对不齐；而页脚版心也决定了
                                   1440 视口下两侧留白是 ~49px 还是 ~227px。 */
}

*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}

body{
  margin:0;
  background-color:var(--bg);
  color:var(--ink);
  font-family:var(--sans);
  font-size:var(--fs-body);
  line-height:var(--lh-body);
  letter-spacing:var(--ls-body);
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
}

/* 后台沿用纯白底（admin.css 也用 var(--bg)）。前台本来就是白底，
   这条只是把后台的语义写明确，行为与改动前一致。 */
body.admin{--bg:var(--surface)}

/* 跳转链接：键盘用户 Tab 第一下就能跳过导航，视觉上平时完全隐藏 */
.skip{
  position:absolute;left:-9999px;top:0;z-index:100;
  background:var(--ink);color:#fff;padding:10px 16px;font-weight:600;
  border-radius:0 0 var(--r-md) 0;
}
.skip:focus{left:8px;top:8px}

/* 键盘焦点：全站统一的可见焦点环（品牌色，2px，带偏移） */
a:focus-visible,button:focus-visible,summary:focus-visible,
input:focus-visible,select:focus-visible,textarea:focus-visible,
[tabindex]:focus-visible{
  outline:2px solid var(--brand);
  outline-offset:2px;
}

.wrap{
  max-width:var(--wrap);
  margin:0 auto;
  padding:0 24px;
}
main.wide > .wrap{max-width:var(--wrap-wide)}

main{padding:0 0 var(--sp-18)}
/* 三列骨架已经是 <main class="shell">：间距由 .shell 自己给（见下方），
   不能再吃一次 main 的底部内边距，否则骨架与页脚之间会多出 72px 的空档。 */
main.shell{padding:0}

/* 只给屏幕阅读器 / 搜索引擎看的文本（**不是** display:none）。
   首页的 h1 用它承载：目标站首屏没有 Hero（它的 h1 也是 `<h1 class="seo-title hidden">`），
   但「标题层级不变」要求 h1 仍然存在且位于 DOM 最前。
   用 clip 而不是 display:none / visibility:hidden —— 后两者会把节点从无障碍树里摘掉，
   而这一段标题恰恰是给读屏与爬虫看的。 */
.sr-only{
  position:absolute;width:1px;height:1px;margin:-1px;padding:0;
  overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0;
}

/* ============================================================================
   ② 英文基版 —— 排版
   ========================================================================== */

h1,h2,h3,h4{margin:0 0 .5em;font-weight:600;overflow-wrap:break-word}
h1{font-size:var(--fs-h1);line-height:var(--lh-h1);letter-spacing:var(--ls-h1);font-weight:700}
h2{font-size:var(--fs-h2);line-height:var(--lh-h2);letter-spacing:var(--ls-h2)}
h3{font-size:var(--fs-h3);line-height:var(--lh-h3);letter-spacing:var(--ls-h3)}
h4{font-size:15px;line-height:1.4;letter-spacing:var(--ls-h3)}
p{margin:0 0 1em}
a{color:var(--ink);text-decoration:none;border-bottom:1px solid var(--line-2)}
a:hover{border-bottom-color:var(--brand)}
a.btn,a.brand,a.skip,a.chip{border-bottom:0}
ul,ol{margin:0 0 1em;padding-left:1.2em}
li{margin:0 0 .35em}
img{max-width:100%;height:auto}
hr{border:0;border-top:1px solid var(--line);margin:var(--sp-8) 0}
code{font-family:var(--mono);font-size:.9em;background:var(--chip);padding:1px 5px;
  border-radius:var(--r-sm)}
pre{font-family:var(--mono);font-size:13px;background:var(--chip);
    border:1px solid var(--line);border-radius:var(--r-lg);padding:14px;overflow-x:auto;
    margin:0 0 1em}
strong{font-weight:700}
figcaption{margin-bottom:6px}

.lead{font-size:var(--fs-lead);line-height:var(--lh-lead);color:var(--ink-2);
  max-width:var(--prose)}
.muted{color:var(--mute)}
.small{font-size:13px}
.lead + .lead{margin-top:-.4em}

/* ============================================================================
   ② 页面骨架 —— 观猹的 app shell（三列 grid 236 / 750 / 324）
   ----------------------------------------------------------------------------
   以下数值全部来自 2026-10-01 对 https://watcha.cn/ **渲染后 getBoundingClientRect()**
   的实测（1440×900 视口，正文宽 1430）：

     main.main-container   display:grid  grid-template-columns:236px 750px 324px
                           justify-content:center  gap:0  行高 64px
     左栏 .sidebar-header  [60,0,236,64]      .sidebar-wrapper  [60,64,236,…]
     中栏 .layout-header   [312,0,718,64]（mx-4 → 718 = 750−32）
          .inner-container [296,64,750,…]，内部正文实际宽 716（左右各 17px）
     右栏 .header-actions  [1046,0,324,64]    右栏内容 [1078,64,292,…]
     1024 视口：列宽塌成 80px 750px 184px，且 header-actions-cell display:none
     390 视口：main-container display:block，header.mobile-header 56px 出现，
               三栏变单栏，正文左右 padding 16px

   为什么「三列无 gap」：目标站靠中列自己的 17px 内边距造留白，列与列之间没有间距。
   照抄这个做法（而不是加 gap）才能让 750 这个列宽与卡片网格 716 的换算完全一致：
   (716 − 2×15) / 3 = 228.67 —— 正好是目标站产品卡的宽度。
   ========================================================================== */

/* ── 移动端顶栏（≤1023px 才显示）──
   高度 56px、左右 padding 16px、吸顶、1px 下边框：与目标站 header.mobile-header 一致。
   桌面端整块 display:none —— 目标站在窄屏换的是另一套 markup，不是同一套换行。 */
.m-head{
  display:none;
  position:fixed;top:0;left:0;right:0;z-index:40;
  height:56px;padding:0 16px;
  background:var(--surface);border-bottom:1px solid var(--line);
}
.m-head-in{display:flex;align-items:center;gap:12px;height:100%}
.m-head .brand{font-size:16px}
/* ★ 2026-10-02：原 `.m-head .lang-switch{margin-left:0;padding-left:0;border-left:0}`
   已删除 —— 那条规则服务的是「并排两个链接 + 左侧竖分隔线」的旧形态，会把新按钮
   的 padding 清成 0（胶囊被压成一条细边）。新按钮自带描边与内边距，不需要覆写。 */
.m-head .m-search{flex:1 1 auto;min-width:0;width:auto}

/* ── 三列骨架 ──
   ★ 2026-10-02：第三列（324px 右栏）在上午那轮随「浏览维度」一起删过，版心因此收到
   986，1440 视口下左右各空出 227px、侧栏被挤到离页边 227px 的地方。本轮按站主要求
   重新参照 watcha.cn 对齐，恢复 `236px 750px 324px`（= 1310，与目标站逐像素同宽），
   空出来的第三列由 side_rail() 用本站真实数据填上 —— 只删内容不改栅格会让右侧
   永远空掉 324px，这正是上一轮踩过的坑。
   `align-items` 用默认的 stretch（不再写 start）：右栏的两列要吸顶（见下），
   而 sticky 的**包含块是它所在的网格区域** —— 网格项若被 align-items:start 压成
   内容高度，行高就等于自身高度，sticky 一步都动不了，等于没写。 */
.shell{
  display:grid;
  grid-template-columns:236px 750px 324px;
  justify-content:center;
}
/* ── 行 1：顶栏（.site-head）—— 2026-10-04 按站主要求改为「滚动时固定」 ──
   它被放在 <main class="shell"> **之外**、作为 body 的直接子元素，而不是 .shell 的
   一个 grid 项：**sticky 的包含块是元素自己的包含块**，而 grid 项的包含块只是它
   所在的 grid 区域（这里只有一行高），sticky 因此一步都动不了。移出 grid 之后
   .site-head 的包含块是整页高度，才吸得住。它内部再用与 .shell **完全相同**的列宽
   排一次三列，三格（品牌 / 搜索 / 动作）的竖线因此依旧与下方面板逐像素对齐 ——
   代价是同一套列宽要在这里重写一遍，改列宽时两处都得改（<1320 那档媒体查询同样）。
   层级：z-index 30 高于正文（auto）、低于 .skip(100) 与窄屏顶栏 .m-head(40)；
   站内没有更高层的弹出层，30 只压在正文之上，不会被任何浮层盖住。
   窄屏（≤1023）整块隐藏，改由 .m-head 承担顶栏（它本来就是 position:fixed）。 */
.site-head{
  position:sticky;top:0;z-index:30;
  display:grid;
  grid-template-columns:236px 750px 324px;
  justify-content:center;
  background:var(--surface);
}
/* 行 1 的三格各自带底色与 1px 下边框 —— 拼起来就是观猹那条横跨整个版心的
   64px 白色顶栏。 */
.side-head,.top-head,.head-actions{
  grid-row:1;
  height:var(--head-h);
  display:flex;align-items:center;
  background:var(--surface);
  border-bottom:1px solid var(--line);
}
.side-head{grid-column:1;padding-left:8px}
/* 搜索条撑满主列格（观猹的搜索位也是 750 列里左右各内缩 16px → 718 宽） */
.top-head{grid-column:2;padding:0 16px}
.top-head .search-box{flex:1 1 auto;min-width:0}
.head-actions{grid-column:3;justify-content:flex-end;gap:12px;padding:0 16px}

/* ── 页头账号动作（登录 / 注册 / 退出）─────────────────────────────────
   2026-10-02 新增。右栏只有 324px（<1320 收到 184px），要同时放下
   语言切换 + 登录 + 注册 + 提交四个元素，所以按钮必须比正文里的主按钮小一号。
   ★ 必须带 body:not(.admin) 前缀：正文的按钮压缩规则是 `body:not(.admin) .btn`
   （0-2-1），会压过 `.head-actions .btn`（0-2-0）—— 少了前缀这条就完全不生效。 */
body:not(.admin) .head-actions{gap:8px}
body:not(.admin) .head-actions .btn{padding:6px 12px;font-size:13px;line-height:20px}

/* 页头账号簇：档位标记 + 用户图标收进一个容器，作为整体与语言切换并列。
   ★ 2026-10-04：原先这里是一段「长邮箱截断」的样式（min-width:0 + max-width:160px +
   ellipsis），随邮箱文本一起删掉 —— 邮箱不再显示，截断也就没有服务对象，留着是死样式。
   现在 .head-user 是一枚纯图标链接（进面板），不需要宽度约束。 */
.head-account{display:inline-flex;align-items:center;gap:8px}
/* 用户图标：进个人面板的唯一入口。做成与语言切换同款的圆角描边胶囊，
   与右栏其它次级动作观感一致；图标颜色继承 currentColor，悬停转品牌绿。 */
.head-account .head-user{
  display:inline-flex;align-items:center;justify-content:center;
  width:30px;height:30px;border:1px solid var(--line-2);border-radius:var(--r-pill);
  color:var(--ink-2);background:var(--surface);
}
.head-account .head-user:hover{color:var(--brand);background:var(--chip)}

/* 账号档位标记（定价页三档之一：评审者 / 申请者）。
   做成极小的中性描边胶囊，刻意**不用品牌绿** —— 它是一枚身份标记，不是行动号召，
   用绿会与右侧真正的绿「提交」按钮抢视线。 */
.head-plan{
  flex:0 0 auto;font-size:11px;line-height:1;padding:3px 7px;margin-right:2px;
  border:1px solid var(--line-2);border-radius:var(--r-pill);
  color:var(--ink-2);background:var(--chip);white-space:nowrap;
}

/* 窄屏顶栏（.m-head）的账号入口。桌面三格顶栏在 <1024 整块隐藏，这里必须补一个，
   否则窄屏访客没有任何登录入口。未登录 → /login，已登录 → /logout（见 head_auth(true)）。
   做成与语言切换同款的描边胶囊，两者在移动顶栏并排时观感一致。 */
.m-head .m-auth{
  flex:0 0 auto;font-size:13px;padding:4px 10px;white-space:nowrap;
  border:1px solid var(--line-2);border-radius:var(--r-pill);
  color:var(--ink);background:var(--surface);
}
.m-head .m-auth:hover{background:var(--chip)}
/* 顶栏行已移出 .shell（见 .site-head），所以内容三列改用自动放置、只钉列不钉行：
   三列同处一行，行高 = 三列中最高的正文列；左/右栏因此天然铺满整页高度，
   里面的 .side-nav / .rail-stick 才有可吸顶的行程。 */
.side-rail{grid-column:1}
/* 导航吸顶：观猹左栏的 nav 在滚动时保持可见（.sync-scroll-container.sticky）。
   64px 是顶栏高度 —— 顶栏现在 sticky 常驻在顶部，所以这个偏移才真正成立：
   不减去它，导航第一项会被顶栏盖住。 */
.side-nav{position:sticky;top:calc(var(--head-h) + 24px)}
.col-main{grid-column:2;min-width:0;padding:0 17px}
/* 右栏：内容也吸顶（同观猹），列内左右各 16px 内边距 → 内容宽 292，与目标站一致 */
.col-side{grid-column:3;min-width:0;padding:0 16px}
.rail-stick{position:sticky;top:calc(var(--head-h) + 20px)}

.brand{
  display:block;font-size:19px;font-weight:700;letter-spacing:-.028em;line-height:1.1;
  white-space:nowrap;
}
/* 品牌标记：前台由 lib/layout.php 的 icon('mark') 内联输出，与标签页图标
   （favicon.svg）**同几何** —— 路径与线宽都取自 lib/config.php 的
   BRAND_MARK_PATH / BRAND_MARK_STROKE。
   标记是本站自己的抽象：绿底 + 白色字母 Z（ZVBoard 的首字母），一笔连写，
   与观猹的 logo / 吉祥物没有任何关系。
   这里只给它两个颜色：底色取 --brand，笔画取白。
   注意它**不走**.brand > .i 的 currentColor —— 那会让标记跟着文字色走，
   而标记在实心绿底上必须是白笔画，与所在位置的文字颜色无关。
   见 lib/layout.php 的 icon()。 */
.i-mark-tile{fill:var(--brand)}
/* 图形是描边而不是填充：字母 Z 由三段笔画连成（见 lib/config.php 的 BRAND_MARK_PATH），
   这里给的是**笔画颜色**，线宽由内联 SVG 上的 stroke-width 给出（BRAND_MARK_STROKE）。
   ⚠️ 从上一版的 fill 改成 stroke 时，必须同时确认没有别处再依赖 fill —— 只改颜色属性
   不改渲染方式，会得到一个「填充为默认黑」的实心块或一个完全不可见的图形。 */
.i-mark-bars{stroke:#fff}
/* 后台的 `.brand` 走的是**同一条** icon('mark') 路径（见 lib/layout.php），
   所以这里不需要再给后台另画一枚标记。原先那条 `body.admin .brand::before`
   渐变方块已删除 —— 它会让后台页头挂渐变方块、而同一页的标签页挂着 favicon
   （绿底白 Z），正是本轮在修的「同一个标志写两处」。
   间距与基线由下面 .brand > .i 提供（与原 ::before 的 8px / -3px 同值，几何不变）。 */
.brand > .i{flex:0 0 auto;color:var(--brand);margin-right:8px;vertical-align:-3px}
/* ★ 2026-10-01：品牌副标题 .brand-sub 已删除（原先挂的是 ui.site.tagline ——
   「独立开发者产品访问量榜 · 数据公开可核验」，随站点定位变化一起拿掉）。
   连带删掉的两处媒体查询里的 `.brand-sub{display:none}` 也一并清了 ——
   只删主规则会留下两条永远匹配不到任何元素的选择器。 */

/* ── 左栏导航 ──
   实测目标站 .navbar-item：padding 8px 20px、高 43px、字号 18px、圆角胶囊、
   图标 24×24、图标与文字间距 20px；条目之间 gap 12px。
   我们的导航文字比中文长（"Just launched"），所以左栏从 8px 内边距起排，
   条目宽度交给内容 —— 几何要一致的是**内边距与高度**，不是文字长度。 */
.side-nav{display:flex;flex-direction:column;gap:12px;padding:0 8px}
.nav-item{
  display:flex;align-items:center;gap:20px;
  padding:8px 20px;border-radius:var(--r-pill);
  font-size:18px;font-weight:400;line-height:27px;color:var(--ink);
  border-bottom:0;
}
.nav-item .i{flex:0 0 auto;width:24px;height:24px}
.nav-item:hover{background:var(--chip)}
.nav-item.on{background:var(--brand-weak);color:var(--brand);font-weight:600}
.nav-item .lbl-short{display:none}

/* ── 右栏面板 ──
   形态照观猹的 .content-sidebar：一叠带描边的浅色面板，圆角、内边距 16px、
   面板间距 16px。标题是 12px 的大写小标签（与页脚栏目标题同一套令牌），
   列表项是 14px 的单行链接 —— 右栏是「顺路看一眼」的位置，不承载长文本。 */
.rail-block{
  border:1px solid var(--line);border-radius:var(--r-lg);
  background:var(--surface);padding:16px;
}
.rail-block + .rail-block{margin-top:16px}
.rail-h{
  margin:0 0 10px;font-size:12px;line-height:1.4;color:var(--mute);
  text-transform:uppercase;letter-spacing:.08em;font-weight:600;
}
.rail-list{list-style:none;margin:0;padding:0}
.rail-list li{margin:0;line-height:1.9}
.rail-list a{color:var(--ink-2);border-bottom-color:transparent}
.rail-list a:hover{color:var(--brand)}
/* ★ 2026-10-04：原「热门工具」面板的样式（序号 + 名称两行，类名 rail-ranks / rail-n）
   随该面板删除：面板已从 lib/layout.php 的 side_rail() 中移除，这两条规则再无使用者。 */

/* ── 中栏顶部的搜索条 ──
   实测目标站 .search-wrapper：606×36、圆角胶囊（9999px）、1px 描边、白底、
   无投影；内部一个 15×15 的镜圈图标（左边距 12px、图标与输入框间距 8px），
   输入框 font-size 14px，**没有任何并列的文字按钮**。
   它位于 718px 宽的 header 内（header 在 750px 列里左右各内缩 16px —— Tailwind 的
   mx-4），那 112px 的剩余宽度被它自己的「猹一猹」AI 入口占着；我们没有那个功能，
   于是留白 —— 不编一个假按钮去填。
   下面照抄的是几何：header 内边距 16px、搜索条 606×36、垂直居中于 64px 行。
   两处刻意**不**照抄：
     · 焦点态。目标站聚焦时 border 颜色只差一个色阶、且没有任何投影 —— 键盘用户
       根本看不出焦点在哪。这里用 :focus-within 把描边换成品牌色，并加一圈
       **两层焦点环**（2px 白缝 + 2px 品牌色）。为什么不是常见的单层淡色环：
       --brand-weak 那种 10% 淡绿叠在白底上只有 1.1:1，作为焦点指示器是不达标的；
       实体品牌色 5.31:1 才过 WCAG 的 3:1。白缝的作用是让环与控件本身分开，
       不至于糊成一块。不用 :has(:focus-visible) 是因为 :focus-within 在所有
       目标浏览器上行为一致，而「鼠标点进来也看得见焦点」在搜索框上不是缺点。
     · 对比度。目标站 placeholder 是 rgba(0,0,0,.5)（对白 3.95:1，不达标）；
       这里用 --mute（5.74:1）。 */
.search-box{
  display:flex;align-items:center;width:min(606px,100%);height:36px;
  border:1px solid var(--line-2);border-radius:var(--r-pill);
  background:var(--surface);overflow:hidden;
  transition:border-color .15s ease,box-shadow .15s ease;
}
.search-box:focus-within{
  border-color:var(--brand);
  box-shadow:0 0 0 2px var(--surface),0 0 0 4px var(--brand);
}
.search-box .i{flex:0 0 auto;margin:0 8px 0 12px;color:var(--mute)}
/* 选择器必须带 [type=search]：下面「② 表单」里那条 input[type=search]{padding:10px 14px;
   border:1px solid …;border-radius:12px} 与 `.search-box input` 同特异性、位置更靠后，
   否则会把搜索框里的输入框画成一个带描边圆角的小盒子（上一版就是这样，量 606×36
   时看不出来，因为它被 overflow:hidden 裁掉了上下两边、只留下两条竖线）。
   带属性选择器后特异性更高，这条才真的生效。 */
.search-box input[type=search]{
  flex:1 1 auto;min-width:0;width:auto;border:0;background:transparent;
  padding:0 8px 0 0;font:inherit;font-size:14px;color:var(--ink);
}
/* 输入框本身不再画自己的焦点环：外层 .search-box:focus-within 已经给了一圈
   对比度达标的两层环，两层套在一起只会显得脏。这里必须显式关掉，否则会被
   文件下方通用的 input:focus-visible{outline:2px solid …} 命中。 */
.search-box input[type=search]:focus,
.search-box input[type=search]:focus-visible{outline:0;box-shadow:none}
/* 原生 × 关掉：各浏览器自带的清除按钮行为不一致（有的不出现、有的清了不派发事件），
   统一由我们自己的 .search-clear 负责，避免同时出现两个 ×。 */
.search-box input[type=search]::-webkit-search-cancel-button,
.search-box input[type=search]::-webkit-search-decoration{
  -webkit-appearance:none;appearance:none;display:none}
/* 清空按钮：初始带 hidden（见 lib/layout.php 的 search_form()），由 JS 打开。
   注意这里**只有** :not([hidden]) 那条规则设置 display —— 若给这个类直接写
   display，就会盖过浏览器对 [hidden] 的 display:none，按钮永远藏不住。 */
.search-box .search-clear{
  flex:0 0 auto;align-items:center;justify-content:center;
  width:26px;height:26px;margin-right:5px;padding:0;
  border:0;border-radius:var(--r-pill);background:transparent;
  color:var(--mute);cursor:pointer;
}
.search-box .search-clear:not([hidden]){display:flex}
.search-box .search-clear:hover{background:var(--chip);color:var(--ink)}
.search-box .search-clear .i{margin:0}

/* ── 右栏面板 ──
   ★ 2026-10-02：这一组在上午那轮（第三列删除时）连同 .rail-* 一起删过；同日又按站主要求
   参照 watcha.cn 恢复第三列，故重新写回。定义位置在左栏导航之后（见上面的 .rail-block）。 */

/* 语言切换按钮（2026-10-02 起是**一个按钮**，不再是并排的两个链接）。
   按钮上写的是目标语言的名字（英文页「中文」、中文页「English」），点它去另一边。
   形态是一枚描边胶囊，与站内其他次级控件（.pager a / .sec-head > a）同一套几何：
   1px 描边 + 全圆角 + 中性文字色。刻意不用品牌绿实底 —— 它是次要动作，不该抢主按钮；
   悬停才给一点品牌色，表示它是可点的。 */
.lang-switch{
  display:inline-flex;align-items:center;
  padding:5px 12px;border:1px solid var(--line-2);border-radius:var(--r-pill);
  font-size:13px;font-weight:600;color:var(--ink-2);
  transition:color .15s ease,border-color .15s ease,background-color .15s ease;
}
.lang-switch:hover{color:var(--brand);border-color:var(--brand-line);background:var(--brand-weak)}

/* ★ 2026-10-02：页脚上方的说明带（.site-note / .site-note h2 / .site-note .lead /
   .note-cta）已删除 —— 首页那段「关于本站」整块拿掉后，render_page 的 'note' 参数
   也随之取消，全站再没有任何页面渲染 .site-note。 */
/* 页脚的内层版心：与三列骨架同宽（--shell-w = 1310）。
   用 max-width 而不是 width —— 视口比版心窄时必须跟着缩，否则会撑出横向滚动条。 */
.note-in{width:auto;max-width:var(--shell-w);margin:0 auto;padding:0 17px}

.site-foot{
  border-top:1px solid var(--line);
  margin-top:var(--sp-18);padding:var(--sp-8) 0 var(--sp-8);
  font-size:13px;
}
/* ★ 2026-10-02：页脚由「三栏列表块」压成**一行**（按站主要求参照 watcha.cn 的极简页脚）。
   删掉的是版式（.foot-grid 三列 + .foot-bottom 分隔线 + 栏目标签），不是链接本身 ——
   链接仍是站内链接的第二落点，见 lib/layout.php 里页脚那段注释。
   版权与链接同一行，靠 flex-wrap 在窄屏自然折行，不再有第二条分隔线。 */
.foot-line{
  display:flex;flex-wrap:wrap;align-items:baseline;
  gap:6px 10px;color:var(--mute);
}
.foot-copy{flex:0 0 auto}
.foot-nav{display:flex;flex-wrap:wrap;align-items:baseline;gap:6px 10px}
.foot-nav a{color:var(--ink-2);border-bottom-color:transparent}
.foot-nav a:hover{color:var(--brand)}
/* 分隔点用 aria-hidden 的 <span> 而不是 CSS ::after —— 后者在折行处会把「·」
   顶到行首，前者同样会，但用 flex 的 gap 兜住间距、读屏也不会去读它。 */
.foot-sep{color:var(--line-2);user-select:none}

/* ============================================================================
   ② 首页 hero
   ========================================================================== */

.hero{padding:var(--sp-4) 0 var(--sp-10);border-bottom:1px solid var(--line)}
.hero h1{max-width:20ch}
.hero .lead{max-width:74ch}
.hero-meta{
  display:flex;flex-wrap:wrap;gap:6px 20px;align-items:baseline;
  margin:var(--sp-6) 0 0;font-size:13px;color:var(--mute);
}
.hero-meta strong{font-size:26px;color:var(--brand);font-weight:700;
  font-variant-numeric:tabular-nums;letter-spacing:-.02em}
.hero-cta{display:flex;gap:10px;flex-wrap:wrap;margin:var(--sp-6) 0 0}

/* ============================================================================
   ② 区块标题 —— 实测目标站 .products-header / .header-title：
        h2  18px / 28px / w600 / letter-spacing −0.36px / padding-bottom 8px → 行盒 36px
        行  display:flex / align-items:center / justify-content:space-between / mb 16px
        右侧「更多」按钮（实测 .view-more-button，目标站是跟在网格下方的通栏按钮）：
        高 32px、圆角胶囊、1px 描边 #EAEAEA、字号 12px、文字 #757575（对白 4.6:1）。
        我们把同一个按钮放回标题行右侧（见交付报告的对照说明）：宽高与描边一致，
        只是多了一组左右 padding（0 14px）—— 它不再是通栏，宽度必须由内容决定。
   ========================================================================== */

.board-cards{margin:0 0 var(--sec-gap)}
.board-cards:last-child{margin-bottom:0}

.sec-head{
  display:flex;justify-content:space-between;align-items:center;
  gap:10px 20px;flex-wrap:wrap;margin:0 0 16px;
}
.sec-head h2,.cat-grid > h2{margin:0;padding-bottom:8px}
.sec-head > a{
  flex:0 0 auto;display:inline-flex;align-items:center;height:32px;padding:0 14px;
  font-size:12px;font-weight:600;color:var(--mute);border-bottom:0;
  border:1px solid var(--line);border-radius:var(--r-pill);background:var(--surface);
}
.sec-head > a:hover{border-color:var(--brand-line);background:var(--brand-weak);color:var(--brand)}
.sec-head > span{font-size:12px;color:var(--mute)}

section > h2{margin-top:0}

/* ============================================================================
   ② 卡片网格 —— 实测目标站 .product-grid：display:grid / gap:15px，
      在 716px 正文宽下得到 3 列 × 228.66px（(716−2×15)/3）
   ========================================================================== */

/* ★ 2026-10-06 UX：auto-fill → auto-fit。
   auto-fill 与 auto-fit 的区别只有一条，但后果天差地别：
     · auto-fill —— **空轨道也占位**。实测首页「Community favourites」只有 1 个产品，
       却按 3 列铺出 3 个格子，右边两列是空的，看上去像「加载失败 / 数据没出来」。
     · auto-fit  —— 空轨道**塌缩为 0**，剩下的格子平分宽度。
   这正是本站现在的处境：四个区块里三个只有 1 条数据（favorites / featured /
   top-month 各 1 条，latest 才有 100 条）。auto-fill 会把这份「冷启动」
   直接翻译成「站点坏了」的观感。
   用 auto-fit 后，1 条时是 1 张占满整行，6 条时自动回到 3 列 —— 随数据量自适应，
   不用改 HTML也不用让 PHP 判断数量。
   ⚠️ auto-fit 与 auto-fill 在列宽上算出的**轨道数相同**（都由容器宽与 --card-min 决定），
      所以满数据时的视觉效果与改动前一致，只有「数据少于列数」时才变化。

   —— 但 auto-fit 有个副作用必须堵掉：空轨道塌缩后，**仅剩的那张卡会独占整行**
   （716px）。产品卡的封面是 2:1，拉到 716px 宽就意味着 358px 高，
   一张卡片吃掉小半屏，比空网格更糟。所以下面给「独子卡片」加宽度上限：
   它仍占满第一列（不留空轨道），但不会被拉成巨幅海报。 */
.card-grid,.cards{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(var(--card-min),1fr));
  gap:var(--grid-gap);background:transparent;border:0;
}
/* ★ 2026-10-06：区块里只有 1 张卡时，限制它不被 auto-fit 拉成整行巨图。
   232px ≈ 正常单列宽（228.67）+ 一点余量，视觉上与「三列里的一列」一致，
   不会让访客误以为这是一个需要横滚的宽卡。
   :only-child 数的是**元素**子节点，.cards 里的空白文本节点不影响判定。 */
.cards > .card:only-child,.card-grid > .card:only-child{max-width:360px}

/* ---------- 通用卡片（about / pricing 等正文里的文本卡）：浅描边白面板 ---------- */
.card{
  position:relative;
  background:var(--surface);border:1px solid var(--line);border-radius:var(--r-xl);
  box-shadow:var(--sh-card);
  padding:var(--card-pad);
  display:flex;flex-direction:column;gap:0;
  transition:box-shadow .18s ease, border-color .18s ease;
}
.card:hover{box-shadow:var(--sh-pop);border-color:var(--line-2)}
.card > header{
  display:flex;align-items:center;justify-content:space-between;
  gap:8px 12px;flex-wrap:wrap;margin-bottom:12px;
}
.card > header h2,.card > header h3{
  margin:0;font-size:var(--fs-h3);font-weight:600;letter-spacing:var(--ls-h3);
  line-height:var(--lh-h3);overflow-wrap:anywhere;
}
.card > header h2 a,.card > header h3 a,.card h2 a,.card h3 a{
  color:var(--ink);border-bottom-color:transparent}
.card > header h2 a:hover,.card > header h3 a:hover{color:var(--brand)}
.card > header span{font-size:12px;color:var(--mute)}
.card p{font-size:13.5px;line-height:1.6;color:var(--mute);flex:1 1 auto;
  margin:0 0 var(--sp-2);overflow-wrap:anywhere}
.card p:last-child{margin-bottom:0}
.card ul{margin:0 0 var(--sp-2);padding-left:1.15em;font-size:14px;color:var(--ink-2)}
.card .muted a{color:var(--brand);font-weight:600;border-bottom:0}
.card .muted a:hover{text-decoration:underline}
.card .muted .muted{color:var(--mute)}
.card footer{
  margin-top:var(--sp-3);padding-top:var(--sp-3);border-top:1px solid var(--line);
  font-size:12px;font-weight:600;color:var(--mute);letter-spacing:.01em;
}
.card footer .lvl{margin-left:2px}

/* ---------- 定价卡（.card.price-card）：2026-10-03 极简改版 ----------
   ★ 2026-10-04（round30）：三档档位从 /pricing 移到用户面板 /account，原来的
   .store-tiers 区块被删，只服务它的 .price-card 规则也随之删除（不留死样式）。
   档位展示改在 /account，样式见文件末尾的 .acct-* 区块。 */

/* ---------- 产品卡（.card.prod）：目标站 a.product-card-unified 的几何复刻 ----------
   实测（1440 视口，正文 716px，3 列）：
     a.product-card-unified          228.66 × 208.14   无描边 / 无圆角 / 无背景 / 无内边距
     .product-card-unified__image    228.66 × 114.33   圆角 12px / aspect-ratio 2/1 / 裁剪
     .product-card-unified__info     padding 8px 0     flex column / gap 10px
     .product-card-unified__header   228.66 × 32       flex / align-items:center / gap 8px
     .product-card-unified__avatar   32 × 32           圆形 / 1px 描边
     ..__name                        h4 16px / 19.2px / w700 / #000
     ..__organization                10.4px / 12.48px  → 本站取 12px（可访问性下限）
     ..__score / ..__review-count    16px / 19.2px / w700 与 12px
     ..__slogan                      12.8px / 17.92px / rgba(0,0,0,.6) / 2 行截断
   目标站的卡**没有描边也没有阴影** —— 图片的圆角与 1px 细线就是它的全部装饰。
   所以这里把通用卡的边框、内边距、背景全部清零，而不是在它上面再叠一层外观。 */
.card.prod{
  display:block;padding:0;border:0;border-radius:0;background:transparent;box-shadow:none;
}
.card.prod:hover{box-shadow:none;border-color:transparent}

.card-media{
  display:block;position:relative;
  width:100%;aspect-ratio:2 / 1;border-radius:12px;overflow:hidden;
  background:linear-gradient(135deg,#f1f5f9,#e2e8f0);
}
.card-media .card-img{display:block;width:100%;height:100%;object-fit:cover}
/* 没有本地图时的回落：沿用改动前那枚「上箭头」几何标记（本站自己的抽象图形，
   与任何外部品牌的 logo / 吉祥物无关）。库里 0 个产品时每张卡都走这条路径，
   所以它必须自己就好看 —— 它不是「破图占位」，是设计的一部分。 */
.card-mark{
  position:absolute;inset:0;display:block;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%231E7A50' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 19V5'/%3E%3Cpath d='M5 12l7-7 7 7'/%3E%3C/svg%3E");
  background-repeat:no-repeat;background-position:center;background-size:44px 44px;
  opacity:.5;
}

.card-info{display:flex;flex-direction:column;gap:10px;padding:8px 0}
/* 头部行固定 32px（= 目标站 .product-card-unified__header 的实测高度）。
   为什么敢固定：标题与机构名都是 nowrap + ellipsis，不会换行；
   右上的指标位两行合计 31–35px，与目标站一样会略微溢出这个盒子（overflow 可见），
   但它溢出的量级小于下面 10px 的 gap，所以不会压到口号。 */
.card-head{display:flex;align-items:center;gap:8px;margin:0;height:32px}
.card-avatar{
  flex:0 0 auto;position:relative;width:32px;height:32px;border-radius:50%;
  overflow:hidden;border:1px solid var(--line-2);background-color:var(--surface-2);
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%231E7A50' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 19V5'/%3E%3Cpath d='M5 12l7-7 7 7'/%3E%3C/svg%3E");
  background-repeat:no-repeat;background-position:center;background-size:16px 16px;
}
.card-avatar .card-img{display:block;width:100%;height:100%;object-fit:cover}
.card-meta{
  display:flex;align-items:center;justify-content:space-between;
  gap:8px;flex:1 1 auto;min-width:0;
}
.card-who{flex:1 1 auto;min-width:0}
.card-name{
  margin:0;padding:0;font-size:16px;font-weight:700;line-height:1.2;
  letter-spacing:var(--ls-h3);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.card-name a{border-bottom:0;color:var(--ink)}
.card-name a:hover{color:var(--brand)}
.card-org{
  margin-top:2px;font-size:12px;line-height:1.35;color:var(--mute);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.card-org a{border-bottom-color:transparent;color:inherit}
.card-org a:hover{color:var(--brand)}
.card-score{
  flex:0 0 auto;display:flex;flex-direction:column;align-items:flex-end;
  text-align:right;white-space:nowrap;
}
.card-score strong{
  font-size:16px;font-weight:700;line-height:1.2;color:var(--ink);
  font-variant-numeric:tabular-nums;
}
.card-score span{font-size:12px;line-height:1.2;color:var(--mute)}
/* 卡片里的付费位标签：不能让标签的行盒把「机构名」那一行撑高。
   line-height 压到 1.2 之后标签自身约 15px，仍在 .card-org 的 16.2px 行盒之内。 */
.card-org .tag-sponsor,.card-org .tag-featured{
  font-size:11px;line-height:1.2;padding:0 6px;margin-left:6px;vertical-align:1px;
}
.card.prod .card-slogan{
  margin:0;font-size:12.8px;line-height:1.4;color:var(--mute);
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;
}

/* ============================================================================
   ② 品类 / 标签 chips —— 实测目标站 .post-product-tag：高 22px、圆角胶囊、1px 描边
      （它内部带一张 16px 的头像，所以我们只取「高 22 + 胶囊 + 1px 描边」这三条，
        左右内边距改成纯文字需要的 10px；字号取 12px —— 12px 是本站的下限，
        目标站这类元信息用 10.4px，按「对比度/字号不足不照抄」的硬约束不沿用。）
   ========================================================================== */

.cat-grid{margin:var(--sec-gap) 0 0}
.cat-grid h2{margin-top:var(--sp-10)}
.cat-grid h2:first-child{margin-top:0}
.chips{list-style:none;display:flex;flex-wrap:wrap;gap:var(--sp-2);margin:0 0 1em;padding:0}
.chips li{margin:0}
.chips a,.chips > li > span{
  display:inline-block;padding:1px 10px;border:1px solid var(--line);
  background:var(--surface);border-radius:var(--r-pill);
  font-size:12px;line-height:1.5;white-space:nowrap;color:var(--ink-2);
  transition:background-color .15s ease,border-color .15s ease,color .15s ease;
}
.chips a:hover{border-color:var(--brand-line);background:var(--brand-weak);color:var(--brand)}
.chips .on a,.chips a.on{background:var(--brand);color:#fff;border-color:var(--brand)}
/* 计数（「人工智能 12」里的 12）：必须与主标签同层可读 ——
   用 --mute 在白底上是 6.0:1，用更淡的色会直接掉到 4.5:1 以下。 */
.chips .muted{color:var(--mute)}
.chips a .muted{color:var(--mute);font-weight:400}
.chips a:hover .muted{color:inherit}

/* 搜索页的「换个入口」提示块（见 pages/search.php 的 search_browse_hints()）。
   只在没有结果 / 空查询时出现，复用 .chips 的标签外观，只补小标题与间距：
   若无结果时把读者留在一条死路上，等于这一页白来一趟。 */
.search-hints{margin:var(--sp-6) 0 0}
.search-hints .hint-label{
  margin:0 0 8px;font-size:12px;font-weight:700;color:var(--mute);
  text-transform:uppercase;letter-spacing:.08em;
}
.search-hints .chips{margin:0 0 var(--sp-5)}
.search-hints .chips:last-child{margin-bottom:0}

/* ★ 2026-10-04：`.subscribe-cta` 规则整块删除。
   它原本只服务 pages/recruit.php 的收尾 CTA 盒（首页那块「订阅清单」更早移除）；
   上一轮 recruit.php 与 /recruit 路由一并删除后，全站已无任何元素带这个类
   （grep 确认只有本文件与此前的 deploy 脚本断言，无实际调用方），
   故连同下面的响应式与打印覆写一起清理。表单本身（.inline-form）保留 ——
   /subscribe/confirm、/unsubscribe 的邮件 token 流程仍在用。 */

/* ============================================================================
   ② 按钮 —— 观猹的主按钮是 9999px 圆角的实心胶囊（padding 8px 20px）
   ========================================================================== */

.btn{
  display:inline-block;padding:10px 20px;
  background:var(--ink);color:#fff;border:1px solid var(--ink);
  border-radius:var(--r-pill);
  font-size:14px;font-weight:600;line-height:1.35;cursor:pointer;
  font-family:inherit;text-align:center;
  transition:background-color .15s ease,border-color .15s ease;
}
.btn:hover{background:#000;border-color:#000}
.btn.ghost{background:transparent;color:var(--ink)}
.btn.ghost:hover{background:var(--soft)}
.btn[disabled],.btn.disabled{opacity:.45;cursor:not-allowed}

/* .btn 在后台被大量使用（admin.css 不覆写它），所以品牌化与**尺寸调整**都只作用于前台 body。
   后台仍拿到原来那套中性实心按钮，行为与观感不变。
   前台的尺寸对齐实测目标站主按钮 .p-button：padding 8px 20px、font-size 16px、
   border-radius 32px、行盒 43px（25 + 8×2 + 1×2）。 */
body:not(.admin) .btn{
  background:var(--brand);border-color:var(--brand);color:#fff;
  padding:8px 20px;font-size:16px;line-height:25px;border-radius:32px;
}
body:not(.admin) .btn:hover{background:var(--brand-deep);border-color:var(--brand-deep)}
body:not(.admin) .btn.ghost{background:var(--surface);color:var(--ink);border-color:var(--line-2)}
body:not(.admin) .btn.ghost:hover{background:var(--chip);border-color:var(--line-2)}

/* ============================================================================
   ② 空状态 / 提示
   ========================================================================== */

.empty{
  border:1px dashed var(--line-2);border-radius:var(--r-xl);background:var(--surface);
  padding:var(--sp-6);color:var(--ink-2);font-size:15px;
}
.empty > strong{display:block;margin-bottom:6px;color:var(--ink)}
.empty p:last-child{margin-bottom:0}
.empty p{color:var(--mute);font-size:14px}

.notice{
  border:1px solid var(--brand-line);background:var(--brand-weak);
  border-radius:var(--r-lg);
  padding:var(--sp-4) var(--sp-5);margin:var(--sp-6) 0;font-size:14px;
}
.notice p:last-child{margin-bottom:0}

.accordion-note{
  border:1px solid var(--line);border-radius:var(--r-lg);
  background:var(--surface-2);
  padding:var(--sp-4) var(--sp-5);margin:var(--sp-6) 0;
  font-size:14px;color:var(--ink-2);
}
.accordion-note p:last-child{margin-bottom:0}

/* ============================================================================
   ② 面包屑
   ========================================================================== */

.crumbs{margin:0 0 var(--sp-6);font-size:13px}
.crumbs ol{list-style:none;display:flex;flex-wrap:wrap;gap:6px;margin:0;padding:0;
  color:var(--mute)}
.crumbs li{margin:0;display:flex;gap:6px;align-items:baseline}
.crumbs li + li::before{content:"/";color:var(--faint)}
.crumbs a{border-bottom-color:transparent;color:var(--mute)}
.crumbs a:hover{color:var(--brand);border-bottom-color:transparent}

/* ============================================================================
   ② 表格
   ========================================================================== */

/* .table-scroll 承担「卡片」外观，表格本体保持无边框，圆角才会干净地裁切横向滚动 */
.table-scroll{overflow-x:auto;margin:0 0 1em;-webkit-overflow-scrolling:touch;
  background:var(--surface);border:1px solid var(--line);
  border-radius:var(--r-xl);box-shadow:var(--sh-card)}

table.board{
  width:100%;border-collapse:collapse;font-size:14px;
}
table.board th{
  text-align:left;vertical-align:bottom;white-space:nowrap;
  font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;
  color:var(--mute);background:var(--surface-2);
  padding:11px 14px;border-bottom:1px solid var(--line);
}
table.board td{
  padding:13px 14px;border-bottom:1px solid var(--line);
  vertical-align:top;
}
table.board tbody tr:hover{background:var(--brand-weak)}
table.board tbody tr:last-child td{border-bottom:0}
table.board td strong{font-variant-numeric:tabular-nums}
table.board td .muted{display:block;font-size:12px;line-height:1.45}
table.board a{border-bottom-color:transparent;color:var(--ink)}
table.board a:hover{color:var(--brand);border-bottom-color:transparent}
table.board td.c-metric strong{color:var(--brand)}

/* 紧凑变体：榜单总目录一页里要放下四个榜 */
table.board.compact{font-size:13px}
table.board.compact th{padding:8px 12px}
table.board.compact td{padding:9px 12px}
table.board.compact td .muted{display:none}

.c-rank{width:46px;font-weight:700;font-variant-numeric:tabular-nums;
  letter-spacing:-.02em;color:var(--mute)}
table.board tbody tr:nth-child(-n+3) .c-rank{color:var(--brand)}
.c-delta{width:78px;white-space:nowrap}
.c-prod{min-width:200px}
/* 产品列的行内封面缩略图（榜单大表与榜单总目录共用）。无本地图时整块不渲染，
   单元格就是纯文字 —— 所以这里不需要「空图占位」的样式。
   object-fit 用 contain 而不是 cover：logo 是品牌标记，裁掉边就等于把标记裁坏。 */
.c-prod-thumb{display:inline-block;vertical-align:middle;width:22px;height:22px;
  margin-right:8px;border-radius:5px;overflow:hidden;
  border:1px solid var(--line-2);background-color:var(--surface-2)}
.c-prod-thumb .row-logo{display:block;width:100%;height:100%;object-fit:contain}
.c-lvl{width:150px}
.c-metric{white-space:nowrap;text-align:right}
table.board th.c-metric{text-align:right}
/* ★ 2026-10-06：featured 榜新增的独立「票数」列（见 lib/layout.php 的 board_table）。
   与 .c-metric 同样的右对齐 + 不换行，但给一个最小宽度 ——
   那一格里除了数字还有投票按钮（board_vote_control），
   不给宽度的话按钮会被挤到下一行，表格行高在窄屏下参差不齐。 */
.c-votes{min-width:104px;white-space:nowrap;text-align:right}
table.board th.c-votes{text-align:right}
table.board td.c-votes strong{color:var(--ink)}
.c-months,.c-n{width:78px;text-align:right;font-variant-numeric:tabular-nums;
  white-space:nowrap}
table.board th.c-months,table.board th.c-n{text-align:right}
.c-src{width:78px;text-align:right;white-space:nowrap;font-size:13px}
table.board th.c-src{text-align:right}

/* 键值表：口径公示、产品参数、核验记录都用它 */
table.kv{width:100%;border-collapse:collapse;font-size:14px;margin:0 0 1em}
table.kv th{
  width:190px;text-align:left;vertical-align:top;
  padding:10px 16px 10px 0;border-bottom:1px solid var(--line);
  color:var(--mute);font-weight:600;
}
table.kv td{padding:10px 0;border-bottom:1px solid var(--line);
  color:var(--ink-2);vertical-align:top}
table.kv tr:last-child th,table.kv tr:last-child td{border-bottom:0}
table.kv a{border-bottom-color:var(--line-2)}
table.kv a:hover{color:var(--brand);border-bottom-color:var(--brand-line)}

/* ============================================================================
   ② 榜单页组件
   ========================================================================== */

.board-block{margin:0 0 var(--sec-gap);padding:0 0 8px}
.board-block:last-child{margin-bottom:0}

.board-links{
  display:flex;flex-wrap:wrap;gap:10px 20px;align-items:baseline;
  font-size:13px;margin:-6px 0 22px;
}
.board-links a{color:var(--brand);border-bottom-color:transparent;font-weight:600}
.board-links a:hover{border-bottom-color:var(--brand-line)}
/* ★ 2026-10-04：榜单总目录（/boards）每个榜单下的「完整榜单 →」改为次级描边胶囊
   （.btn.ghost），比正文主按钮小一号 —— 它是顺路入口，不是主操作。
   尺寸只作用于 .board-cta 这个新类，**不动共享的 .board-links a**：
   .board-links 还被 pages/report.php 复用（那里仍是文字链）。
   body:not(.admin) 前缀用于压过上面 .btn 的正文尺寸规则（0-3-1 > 0-2-0）。 */
body:not(.admin) .board-links .btn.board-cta{padding:5px 14px;font-size:13px;line-height:20px}

/* ── 提交收录：首页截图预览的「点击放大」 ────────────────────────────────
   用原生 <dialog> 做灯箱：零依赖、无第三方 JS，且 ESC 关闭是浏览器自带的。
   为什么不做「新标签页打开 data URI」：浏览器会拦截顶层 data: 导航，点了会白屏。
   放大的是同一张 data URI（2:1 裁剪版），不额外请求、不额外占带宽。 */
.shot-thumb{display:inline-block;padding:0;border:1px solid var(--line-2,#dcdcdc);
  border-radius:8px;background:none;line-height:0;cursor:zoom-in}
.shot-thumb img{display:block;border-radius:7px}
.shot-thumb:hover{border-color:var(--brand,#2f6f4f)}
.shot-modal{border:0;padding:0;background:transparent;
  width:min(1100px,92vw);max-width:92vw;max-height:92vh}
.shot-modal img{display:block;width:100%;height:auto;max-height:80vh;
  object-fit:contain;border-radius:8px;background:#fff}
.shot-modal form{margin:10px 0 0;text-align:right}
.shot-modal::backdrop{background:rgba(0,0,0,.72)}

.period-switch,.cycle-switch{
  display:flex;flex-wrap:wrap;align-items:center;gap:7px;
  margin:0 0 var(--sp-5);font-size:13px;
}
.period-switch > span,.cycle-switch > span{margin-right:2px}
.period-switch a,.cycle-switch a{
  display:inline-block;padding:6px 14px;border:1px solid var(--line);
  border-radius:var(--r-pill);background:var(--surface);
  line-height:1.45;white-space:nowrap;color:var(--ink-2);
}
.period-switch a:hover,.cycle-switch a:hover{
  border-color:var(--brand-line);background:var(--brand-weak);color:var(--brand)}
.period-switch a.on,.cycle-switch a.on{
  background:var(--brand);color:#fff;border-color:var(--brand);font-weight:600;
}
.cycle-switch{font-weight:600;text-transform:uppercase;letter-spacing:.05em;
  font-size:11px}
.cycle-switch a{padding:6px 14px}

/* 口径公示：默认收起，点开才展开完整表格 —— 信息在，但不挡住榜单本身 */
.provenance{
  border:1px solid var(--line);border-radius:var(--r-lg);background:var(--surface-2);
  padding:var(--sp-4) var(--sp-5);margin:0 0 var(--sp-6);
  font-size:14px;
}
.provenance > summary{
  cursor:pointer;font-weight:600;list-style:none;
  display:flex;align-items:center;gap:8px;color:var(--ink);
}
.provenance > summary::-webkit-details-marker{display:none}
.provenance > summary::before{
  content:"+";display:inline-block;width:18px;height:18px;line-height:16px;
  text-align:center;border-radius:var(--r-sm);
  background:var(--brand-weak);color:var(--brand);
  font-weight:700;font-family:var(--mono);font-size:13px;
}
.provenance[open] > summary::before{content:"–"}
.provenance > summary:hover{color:var(--brand)}
.provenance table.kv{margin:16px 0 10px}
.provenance table.kv th{width:150px;font-size:13px}
.provenance table.kv td{font-size:13px}
.provenance > p{margin-bottom:0;font-size:13px}

/* 产品页的名次条 */
.rank-strip{margin:0 0 var(--sp-6)}
.rank-strip h2,.rank-strip h3{margin-bottom:12px}

/* ============================================================================
   ② 核验等级标签 —— 编码仍是「边框粗细 + 虚实」，颜色只是叠加
   ========================================================================== */

.lvl{
  display:inline-block;padding:2px 9px;
  font-size:11px;font-weight:600;line-height:1.6;letter-spacing:.03em;
  white-space:nowrap;border:1px solid var(--line-2);border-radius:var(--r-pill);
  color:var(--mute);background:var(--chip);
  font-family:var(--mono);
  cursor:help;
}
.lvl em{font-style:normal;font-family:var(--sans);font-weight:400;
  letter-spacing:0;margin-left:5px;opacity:.85}

.lvl-0{border-style:dashed;border-color:var(--line-2);color:var(--mute);
  background:var(--chip)}
.lvl-1{border-style:dashed;border-color:rgba(174,125,41,.45);color:var(--gold);
  background:var(--gold-weak)}
.lvl-2{border-style:solid;border-color:var(--brand-line);color:var(--brand);
  background:var(--brand-weak);font-weight:700}
.lvl-3{background:var(--brand);color:#fff;border-color:var(--brand);font-weight:700}
.lvl-3 em{color:#fff}

/* 名次变化：符号（▲▼—/new）是主编码，颜色只是辅助 */
.delta{
  font-size:12px;white-space:nowrap;font-variant-numeric:tabular-nums;
  color:var(--mute);
}
.delta.up{color:var(--brand);font-weight:700}
.delta.down{color:var(--danger);font-weight:600}
.delta.flat{color:var(--mute)}
.delta.new{
  display:inline-block;padding:1px 8px;border:1px solid transparent;
  border-radius:var(--r-pill);background:var(--brand-weak);color:var(--brand);
  font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;
}

/* ============================================================================
   ② 曝光位标签（精选 / 赞助）
   付费买的是曝光，不是名次 —— 所以它必须一眼可辨，但在**卡片与列表里**是
   「声明」而不是「按钮」，于是用 pill 而不是方按钮。
   ========================================================================== */

.tag,.tag-sponsor,.tag-featured{
  display:inline-block;
  margin-left:6px;
  padding:2px 10px;
  font-size:11px;
  line-height:1.6;
  letter-spacing:.03em;
  vertical-align:middle;
  border:1px solid transparent;
  border-radius:var(--r-pill);
  white-space:nowrap;
  font-weight:600;
}
.tag{background:var(--chip);color:var(--ink-2);border-color:var(--line)}
.tag-featured{background:var(--gold-weak);color:var(--gold);border-color:rgba(174,125,41,.35)}
.tag-sponsor{background:var(--brand);color:#fff;border-color:var(--brand)}

/* ============================================================================
   ② 徽章区
   ========================================================================== */

.badge-box{
  border:1px solid var(--line);border-radius:var(--r-xl);background:var(--surface);
  box-shadow:var(--sh-card);
  padding:var(--sp-6);margin:0 0 var(--sp-10);
}
.badge-box h2,.badge-box h3{margin-top:0}
.badge-row{
  padding:18px 0;border-top:1px solid var(--line);
}
.badge-row:last-child{padding-bottom:0}
.badge-row-head{
  display:flex;justify-content:space-between;align-items:baseline;
  gap:10px 20px;flex-wrap:wrap;margin-bottom:12px;font-weight:600;
}
.badge-preview{
  display:block;border:1px solid var(--line);border-radius:var(--r-lg);background:#fff;
  margin:0 0 14px;
}
.badge-code{
  font-family:var(--mono);font-size:12px;line-height:1.6;
  background:var(--chip);border:1px solid var(--line);border-radius:var(--r-lg);
  padding:12px 14px;overflow-x:auto;
  white-space:pre-wrap;word-break:break-all;color:var(--ink-2);
}
.badge-code code{background:transparent;padding:0}
.badge-code textarea{border:0;background:transparent}

/* ============================================================================
   ② 表单
   ========================================================================== */

.form{max-width:640px}
.form > h2{margin-top:var(--sp-8)}
.form > h2:first-child{margin-top:0}

.field{margin:0 0 20px}
.label{
  display:block;margin:0 0 6px;
  font-size:13px;font-weight:600;letter-spacing:.01em;
}
.label .muted{font-weight:400}

input[type=text],input[type=email],input[type=url],input[type=search],
input[type=number],input[type=password],input[type=date],select,textarea{
  width:100%;padding:10px 14px;
  border:1px solid var(--line-2);background:var(--surface);color:var(--ink);
  font:inherit;font-size:15px;line-height:1.4;border-radius:var(--r-lg);
  -webkit-appearance:none;appearance:none;
}
select{background-image:none;padding-right:28px;cursor:pointer}
textarea{min-height:130px;resize:vertical}
input:focus,select:focus,textarea:focus{
  outline:0;border-color:var(--brand);box-shadow:0 0 0 3px var(--brand-weak);
}
/* 占位文字的颜色：浏览器默认是 rgba(0,0,0,.54) ≈ #757575（对白 4.6:1）——
   勉强过线，但与全站「次要文字一律 --mute」的口径不一致，也撑不住更浅的字重。
   统一压到 --mute（5.74:1）；opacity:1 是因为 Firefox 会给占位文字叠一层透明度。 */
input::placeholder,textarea::placeholder{color:var(--mute);opacity:1}
input[type=checkbox],input[type=radio]{
  width:auto;min-width:0;margin:0 6px 0 0;vertical-align:-1px;
  accent-color:var(--brand);
}
.form label > input[type=checkbox],
.form label > input[type=radio]{width:auto}
.field .muted,.field .small{margin-top:6px}
.field label{display:inline-block;font-size:14px;margin:0 16px 6px 0}
fieldset{border:1px solid var(--line);border-radius:var(--r-lg);padding:14px 16px;
  margin:0 0 20px}
legend{font-size:13px;font-weight:600;padding:0 6px}

.error{
  border:1px solid rgba(180,35,24,.35);background:rgba(180,35,24,.07);
  border-radius:var(--r-lg);
  padding:12px 14px;margin:0 0 20px;font-size:14px;font-weight:600;color:var(--danger);
}
.error ul{margin:6px 0 0;padding-left:1.1em;font-weight:400}
.ok-msg{
  border:1px solid var(--brand);background:var(--brand);color:#fff;
  border-radius:var(--r-lg);
  padding:14px 16px;margin:0 0 20px;font-size:14px;font-weight:600;
}
.ok-msg a{color:#fff;border-bottom-color:rgba(255,255,255,.5)}
.ok-msg a:hover{border-bottom-color:#fff}
.ok-msg p:last-child{margin-bottom:0}

/* 蜜罐：正常用户看不见，脚本会填。任何情况下都不能被 display:none 掉 ——
   部分爬虫会跳过 display:none 的字段，反而识别出这是陷阱；
   用绝对定位移出视口是最稳的做法。 */
.hp{
  position:absolute;left:-9999px;top:auto;
  width:1px;height:1px;overflow:hidden;
}

/* 首页 / 订阅区块里的那条胶囊输入条（观猹的 search-wrapper 同款结构） */
.inline-form{
  display:flex;flex-wrap:wrap;align-items:stretch;gap:8px;
  margin:18px 0 10px;
  max-width:560px;
  padding:6px;border:1px solid var(--line-2);border-radius:var(--r-xl);
  background:var(--surface);
}
.inline-form input[type=email],
.inline-form input[type=text]{width:auto;flex:1 1 260px;max-width:none;
  border:0;background:transparent;padding:10px 14px}
.inline-form input:focus{box-shadow:none}
.inline-form .hp{flex:0 0 0}
.inline-form button{
  padding:10px 22px;border:1px solid var(--brand);border-radius:var(--r-lg);
  background:var(--brand);color:#fff;font:inherit;font-size:14px;font-weight:600;
  cursor:pointer;
}
.inline-form button:hover{background:var(--brand-deep);border-color:var(--brand-deep)}

/* 键盘焦点（输入控件版）：必须写在上面 input:focus{outline:0} 之后 —— 两者特异性相同，
   后写的才生效。少了这一段，键盘用户在表单里就只剩边框变色这一个焦点提示。 */
input:focus-visible,select:focus-visible,textarea:focus-visible{
  outline:2px solid var(--brand);outline-offset:2px;
}

/* 筛选条 */
.filters{
  display:flex;flex-wrap:wrap;align-items:center;gap:8px 14px;
  padding:var(--sp-4);margin:0 0 var(--sp-6);
  background:var(--surface);border:1px solid var(--line);border-radius:var(--r-xl);
  box-shadow:var(--sh-sm);
  font-size:13px;
}
.filters > span{font-weight:600;color:var(--mute);font-size:12px}
/* 后台 audit.php 把 label+select 包在 .field 里，前台是「label 与 select 平级」。
   两种结构都收进一条横向筛选条，后台因此也跟着变整齐（不是被破坏）。 */
.filters > .field{display:flex;flex-direction:column;gap:4px;margin:0}
.filters label,.filters .label{font-size:12px;font-weight:600;color:var(--mute);margin:0}
.filters select,.filters input{
  width:auto;min-width:132px;font-size:13px;padding:8px 12px;border-radius:var(--r-lg);
  font-weight:400;color:var(--ink);
}
.filters button{
  padding:9px 20px;border:1px solid var(--brand);border-radius:var(--r-pill);
  background:var(--brand);color:#fff;
  font:inherit;font-size:13px;font-weight:600;cursor:pointer;
}
.filters button:hover{background:var(--brand-deep);border-color:var(--brand-deep)}
.filters .btn{margin:0}

/* 分页 */
.pager{
  display:flex;flex-wrap:wrap;align-items:center;gap:var(--sp-2);
  margin:var(--sp-8) 0 0;font-size:13px;
}
.pager a,.pager span{
  display:inline-block;min-width:38px;text-align:center;
  padding:8px 13px;border:1px solid var(--line);border-radius:var(--r-pill);
  background:var(--surface);white-space:nowrap;
}
.pager a:hover{border-color:var(--brand-line);background:var(--brand-weak);color:var(--brand)}
.pager .on{background:var(--brand);color:#fff;border-color:var(--brand);font-weight:600}
.pager .gap{border:0;background:transparent;color:var(--mute);min-width:0;padding:8px 2px}
.pager .muted{border:0;background:transparent}

/* ============================================================================
   ② 产品详情页
   ========================================================================== */

.product{margin:0}
.prod-head{margin:0 0 var(--sp-8);padding:0 0 var(--sp-6);border-bottom:1px solid var(--line)}
/* 详情页首屏宣传图（与卡片、榜单表同源：assets/logos/<slug>.<ext>）。
   ★ 2026-10-03：这里原本是 64×64 的方形 logo 盒，但抓到的已是 og:image（首页宣传图，
   约 2:1 的宽图）—— 宽图塞进方盒只会缩成一条小图，等于没显示。改为按卡片封面同一比例
   铺满正文栏（716px），让它成为首屏的视觉主体。object-fit 从 contain 改成 cover：
   这是「封面」语义，不留信箱式空白；约 2:1 的源图填 2:1 的盒子几乎不裁切。
   无本地图时整个 span 不渲染，标题自己就是页面第一个元素。 */
span.prod-logo{display:block;width:100%;aspect-ratio:2 / 1;margin:0 0 var(--sp-6);
  border-radius:var(--r-xl);overflow:hidden;
  border:1px solid var(--line-2);background-color:var(--surface-2)}
img.prod-logo{display:block;width:100%;height:100%;object-fit:cover}
.prod-head h1{margin-bottom:10px;max-width:24ch}
.prod-head .lead{margin-bottom:18px}
.prod-links{display:flex;flex-wrap:wrap;gap:10px;align-items:center;margin:0 0 var(--sp-2)}

/* ★ 2026-10-06 详情页精简新增：头部一行 meta（上线日期 · 定价档 · 本站 PV/UV）。
   设计取向：它是「事实标签」而非内容，所以用比正文更小更淡的字重，
   让下方的 Introduction / 适合场景 成为视觉主体。
   保留行高余量，避免中英文混排时行与行贴太紧。
   变量沿用本表既有的 --mute（次要文字）与 --fs-body，不用不存在的自定义变量。 */
.prod-meta{margin:var(--sp-3) 0 0;font-size:14px;line-height:1.7;color:var(--mute)}
.prod-meta .muted{margin-left:2px}

.prod-data{display:grid;grid-template-columns:minmax(0,1.35fr) minmax(0,1fr);
  gap:40px;align-items:start;margin:0 0 8px}
.prod-facts{margin:0 0 var(--sp-10)}
.prod-ver{margin:0 0 var(--sp-10)}

.prod-neighbors{margin:0 0 var(--sp-10)}
/* ★ 2026-10-02：.prod-correct 整组删除 —— 「这里有问题？」那个区块已按站主要求
   从产品页移除，留着这组规则只会变成一个谁也不用的死选择器
   （本项目踩过这个坑：删组件不删 CSS，页面上永远不会报错，只会悄悄留下垃圾）。 */

/* 条目页正文区：统一区块间距与行宽 */
.prod-intro,.prod-what,.prod-best,.prod-faq,.prod-cats,.prod-tags,
.prod-votes{margin:0 0 var(--sp-10)}
.prod-intro p,.prod-what p{font-size:var(--fs-body);line-height:var(--lh-body);
  color:var(--ink-2);max-width:var(--prose)}
.prod-best ul{max-width:var(--prose);color:var(--ink-2)}
.prod-best li{margin:0 0 6px;font-size:15px;line-height:1.6}

/* FAQ 折叠：原生 <details>，零 JS，内容始终在 DOM 里（可被搜索引擎读到） */
.faq-item{
  border:1px solid var(--line);border-radius:var(--r-lg);background:var(--surface);
  margin:0 0 var(--sp-2);
}
.faq-item > summary{
  cursor:pointer;list-style:none;
  display:flex;align-items:center;justify-content:space-between;gap:var(--sp-4);
  padding:14px var(--sp-5);font-weight:600;font-size:15px;border-radius:var(--r-lg);
}
.faq-item > summary::-webkit-details-marker{display:none}
.faq-item > summary::after{
  content:"+";flex:0 0 auto;width:20px;height:20px;line-height:18px;text-align:center;
  border-radius:var(--r-sm);background:var(--brand-weak);color:var(--brand);
  font-family:var(--mono);font-weight:700;font-size:14px;
}
.faq-item[open] > summary::after{content:"–"}
.faq-item[open] > summary{color:var(--brand)}
.faq-item > div{padding:0 var(--sp-5) var(--sp-4);color:var(--ink-2);line-height:1.75;
  max-width:var(--prose)}

.vote-counts{
  display:flex;align-items:baseline;gap:8px;flex-wrap:wrap;
  font-size:15px;margin:0 0 var(--sp-2);
}
.vote-counts strong{font-size:22px;color:var(--brand);font-variant-numeric:tabular-nums}

/* ── 投票按钮（产品详情页）────────────────────────────────────────────
   2026-10-02 新增。四种形态（未登录 / 已投 / 无额度 / 可投）由
   pages/product.php 的 product_vote_html() 输出，这里只管排布：
   「按钮 + 额度说明」并排，窄屏自动换行；表单本身不带外边距，
   与外层段落保持同一基线。 */
.vote-box{margin:var(--sp-5) 0 0}
.vote-action,.vote-form{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin:0 0 var(--sp-2)}
/* 投票结果提示（PRG 回显）沿用全站 .ok-msg / .error 的配色，只把外边距收紧，
   免得一条提示把下方的「已投票数」顶得太远。 */
.vote-box .ok-msg,.vote-box .error{margin:0 0 var(--sp-3)}

/* ★ 2026-10-04：`.stack-hero`（技术栈页顶部）规则删除 —— 它只服务 pages/stack.php，
   该页与 /stacks/<x> 路由上一轮已删除，grep 全仓确认无任何元素带这个类。 */

/* ============================================================================
   ② 响应式 —— 三档：≥1320（完整三列）/ 1024–1319（图标左栏 + 窄右栏）/ ≤1023（单列）
   ----------------------------------------------------------------------------
   断点照目标站实测走，不自己发明：
     1440 → 目标站 236px 750px 324px（本站同）
     1024 → 目标站  80px 750px 184px，且 header-actions-cell 隐藏
     390  → main-container display:block，header.mobile-header 出现，三栏变单栏
   目标站在 1300 视口就已经塌成图标左栏（因为 1310 的版心放不下），所以断点取 1319。
   本站与目标站的唯一差别：那一档**不隐藏**右栏的动作区（语言 + 提交）——
   本站窄屏下语言切换与提交没有别处可去（目标站那份藏在它的移动端菜单里）。
   ========================================================================== */

/* ★ 2026-10-06 UX 修正：侧栏断点重排为四档（浏览器实测得出）。

   问题的准确版心（实测截图 + CSS 数字）：
       完整三列   236 + 750 + 324 = 1310px   ← 带文字侧栏 + 宽右栏
       紧凑右栏   236 + 750 + 184 = 1170px   ← 带文字侧栏 + 窄右栏
   目标站把 1319 设为断点，于是 **1280 与 1366 这两个笔记本主流宽度落进 80px 纯图标档**，
   实测左栏只剩 5 个无字图标，而 DOM 里其实有 "Leaderboards / News / Collections /
   Badges / Submit" —— 是被 `.lbl{display:none}` 藏掉的。新用户打开首页看到的是
   📊 📡 ▤ 🔖 ＋，等于首页主导航不可用。

   ⚠️ 第一版改错的地方（自查发现，记在这里免得重犯）：我一开始把断点直接改成 1199，
      以为「1200 以上就能放下带文字的侧栏」。但**完整三列要 1310px**，1280 视口放不下 →
      实测立刻出现横向滚动条、右侧「Sign in」被切掉。漏掉的是「右栏可以先收窄」
      这一档。于是补上第二档 1200–1319：侧栏保持 236px 带文字，只把右栏收到 184px。

   四档最终划法：
       ≥1320        236 / 750 / 324   完整三栏（侧栏带文字）
       1200–1319    236 / 750 / 184   ★新增：侧栏带文字，右栏收窄
       1024–1199    104 / 726 / 184   ★新增：图标 + 短标签（复用移动端那套 lbl-short）
       ≤1023        单列 + 固定底栏    （底栏本就是图标 + 短标签）

   为什么 1024–1199 给短标签而不是纯图标：80px 纯图标对**任何**用户都是猜谜游戏，
   哪怕老用户也要靠记忆；104px 放得下 20px 图标 + 5 字短标签，只多 24px。
   合计 104+726+184 = 1014，在 1024 视口下留 10px 余量 —— 偏紧，
   所以主列收到 726（比常规 750 少 24）来把这 10px 找回来。 */

/* 1200–1319：带文字侧栏 + 窄右栏。本站独有的档位（目标站没有，因为它不需要）。*/
@media (min-width:1200px) and (max-width:1319px){
  .shell{grid-template-columns:236px 750px 184px}
  /* 顶栏列宽必须与 .shell 同步（见 .site-head 注释） */
  .site-head{grid-template-columns:236px 750px 184px}
  /* 右栏收到 184px 后，列内 16px 内边距显得过厚（内容只剩 152px）—— 减到 12px */
  .col-side{padding:0 12px}
}

/* 1024–1199：图标 + 短标签。移动端底栏那套 .lbl-short 一直存在且好用，
   桌面窄屏却从没启用过。 */
@media (min-width:1024px) and (max-width:1199px){
  .shell{grid-template-columns:104px 726px 184px}
  .site-head{grid-template-columns:104px 726px 184px}
  .nav-item{flex-direction:column;gap:2px;padding:6px 2px;
    font-size:12px;line-height:1.25;text-align:center}
  .nav-item .lbl{display:none}
  .nav-item .lbl-short{display:block}
  .col-side{padding:0 12px}
}

/* ≤1023：单列 + 固定底栏，侧栏不参与布局（见下方既有规则）。*/

/* 单列：桌面三格顶栏整块隐藏，换 .m-head + 固定底栏 —— 与目标站同一种塌陷行为。
   右栏整块收起：观猹在窄屏也没有右栏（它的 rail 属于桌面布局）。 */
@media (max-width:1023px){
  .shell{display:block}
  /* 桌面顶栏整块收起（连同它的三格）。窄屏顶栏由 .m-head 承担 —— 它本来就是
     position:fixed + body padding-top:56px，所以 sticky 不会遮住内容。 */
  .site-head,.col-side{display:none}
  .m-head{display:flex}
  body{padding-top:56px}

  /* 左栏改造成固定底栏（目标站 .mobile-navbar：56px、吸顶/吸底、flex:1 的图标项） */
  .side-rail{
    position:fixed;left:0;right:0;bottom:0;z-index:20;
    height:56px;border-top:1px solid var(--line);
    background:var(--surface);           /* 见下方说明：不再用半透明 + 背景模糊 */
    /* ★ 2026-10-02：去掉 backdrop-filter。
       这一条原本是 background:rgba(255,255,255,.94) + backdrop-filter:blur(12px)，
       用来做「磨砂玻璃」底栏。但它是**全站唯一的重合成代价点**：底栏是 position:fixed，
       移动端滚动时浏览器每一帧都要重新采样它背后整片内容再模糊一次，
       在低端安卓机上就是肉眼可见的掉帧。这里不是设计取舍的战场 ——
       一条固定在底部的纯色栏在观感上几乎无差别，却把每帧的合成成本降为零。 */
  }
  .side-nav{position:static;flex-direction:row;gap:0;padding:0;height:100%}
  .side-nav .nav-item{
    flex:1 1 0;min-width:0;height:100%;
    flex-direction:column;justify-content:center;
    gap:2px;padding:4px 2px;border-radius:0;
    font-size:12px;line-height:1.25;
  }
  .side-nav .nav-item .i{width:20px;height:20px}
  .side-nav .nav-item .lbl{display:none}
  .side-nav .nav-item .lbl-short{display:block}

  .col-main{padding:0 16px 76px}
  .note-in{padding:0 16px}

  /* 产品卡塌成单列列表行：目标站窄屏用的是 .product-mobile-card（flex 行 / 头像在左）。
     我们的 DOM 顺序不同（标题块在 .card-head 里），所以只还原视觉：大图收起、
     头像放大到 44px、口号收成一行。高度因此是 ~61px 而不是目标站的 56px，
     原因是它把 org 与票数并进了一行、我们把 org 留在标题下方。 */
  .cards,.card-grid{grid-template-columns:1fr}
  /* ★ 2026-10-06 修：卡片必须拉满，窄屏实测它们按内容宽度居中排布。
     浏览器实测（922px 视口 /products）：六张卡的左边缘分别在
     x≈300 / 110 / 320 / 330 / 325 / 340 —— 每张按自己的文字长度决定宽度，
     短标题的卡窄、长标题的卡宽，整列参差不齐。

     为什么加了 width:100% 还不够：`.card-info` 是 flex:1，它撑开的是**内部**，
     卡片自身仍按 `max-content` 收缩；真正决定轨道宽的是 grid 的
     `justify-items`（默认 stretch，但对**内容盒宽度有 max-content 约束的元素**
     表现不同）。所以在网格层面直接用 `justify-items:stretch` + 卡片
     `min-width:0;max-width:none` 三件套强制铺满，比只写 width:100% 更可靠。
     max-width:none 是必须的：它要压过 676 行那条 `:only-child{max-width:360px}`
     （那是为桌面首页单卡准备的，移动端卡片列表不适用）。 */
  .cards,.card-grid{justify-items:stretch}
  .card.prod{display:flex;align-items:center;gap:14px;min-height:56px;
    width:100%;min-width:0;max-width:none;box-sizing:border-box}
  .card-media{display:none}
  .card-avatar{width:44px;height:44px;background-size:20px 20px}
  .card-info{flex:1 1 auto;min-width:0;padding:0;gap:2px}
  /* 移动端头像变大到 44px，头部行不能再固定 32px */
  .card-head{height:auto;min-height:44px;gap:12px}
  .card.prod .card-slogan{font-size:13px;-webkit-line-clamp:1}
}

@media (max-width:900px){
  /* ★ 2026-10-02：原 `.foot-grid{grid-template-columns:1fr 1fr}` 已删除 ——
     页脚由三栏列表块压成一行（.foot-line），.foot-grid 不再存在于任何模板里。 */
  .prod-data{grid-template-columns:1fr;gap:0}
}

@media (max-width:640px){
  :root{--fs-body:15px;--sec-gap:40px}
  .wrap{padding:0 16px}
  .m-head{padding:0 12px}
  .m-head-in{gap:8px}
  .brand{font-size:17px}
  .m-head .brand{font-size:15px}
  .m-head .search-box{height:34px}
  .m-head .search-box .search-clear{width:24px;height:24px;margin-right:3px}
  .m-head .search-box .i{margin-left:10px}

  /* ★ 2026-10-06 UX：移动端触控目标放大到 44px（Apple HIG / WCAG 2.5.5 建议值）。
     改动前：页头按钮 6px×2 + 20px 行高 = 32px，搜索框 34px —— 都偏小，
     手指点不准、误触相邻控件的比率明显偏高。
     做法：用 min-height 抬到 44px 而**不改 padding**，这样按钮的横向留白与
     视觉密度完全不变，只放大点击区域（padding 撑高会连带变宽，页头会被挤爆）。
     仅限 640px 以下：桌面上鼠标指针的精度足够，小一点更紧凑好看。 */
  .m-head .head-actions .btn,
  .m-head .head-account a,
  .m-head .lang-switch{min-height:44px;display:inline-flex;align-items:center}
  .m-head .search-box{height:44px}
  .m-head .search-box .search-clear{width:36px;height:36px}
  /* 页脚链接在窄屏是主要导航之一，同样放大点击区 */
  .foot-nav a{min-height:40px;display:inline-flex;align-items:center}
  /* 2026-10-02：原 `.m-head .lang-switch{font-size:12px;gap:0 8px}` 只剩字号有意义
     （新的语言按钮是单个 <a>，没有 gap 可分配）。 */
  .m-head .lang-switch{font-size:12px;padding:4px 10px}
  .hero{padding-bottom:var(--sp-8)}
  .hero-meta{gap:4px 14px}
  .hero-meta strong{font-size:22px}
  .badge-box{padding:var(--sp-4)}
  /* 窄屏下表格卡片改为通栏 */
  .table-scroll{margin:0 0 1em;border-radius:var(--r-lg)}
  table.board th{padding:9px 11px}
  table.board td{padding:10px 11px}
  table.kv th{width:110px;padding-right:10px;font-size:13px}
  .filters{padding:var(--sp-3)}
  /* ★ 2026-10-06 移动端 UX：筛选条从「纵向三段」压成「一行两列 + Apply」。
     浏览器实测（922px 窄视口 /products）：原来 `.filters select{width:100%}`
     让 Platform 下拉、Sort 下拉、Apply 按钮各占满一行，整块面板吃掉约 300px
     首屏高度 —— 而首屏下面就是 101 个产品的列表，访客第一眼只看到两个下拉框。
     现在把 label 与它的 select 绑成两列并排（flex 基准 140px，390px 屏能放下两列），
     Apply 缩到内容宽度并与下拉对齐基线。净效果：面板高度从约 300px 降到约 90px。
     ⚠️ 用 `.label + select` 相邻兄弟选择器，而不是给 label 套 .field ——
        前台的 label 与 select 是平级的（没有 .field 包裹，见 1268 行注释），
        按 DOM 顺序相邻即可配对，不依赖 JS。 */
  .filters{gap:8px 10px}
  /* ★ 2026-10-06 手机档再调（390px 实测核算）：
     上一版让两个下拉并排，各占 (390−32−24−88−10)/2 ≈ 118px。
     118px 装不下「Most viewed on this site」这类选项文字（会被截成「Most viewed…」），
     筛选项正是访客要看清的东西，截断等于把功能做坏。
     现在改成**三段式**：标签在下拉左侧同行（省掉一整行），
     两个下拉各占满一行宽度（可用 334px，文字完整），Apply 与第二个下拉同行。
     高度从约 300px → 约 150px，仍比原来少一半，且没有任何内容被截断。
     顺序靠 flex order 实现，不改 DOM：label(1) select(2) / label(3) select(4)+button(5)。 */
  .filters .label{flex:0 0 auto;order:1;align-self:center;min-width:52px}
  .filters select,.filters input{flex:1 1 0;min-width:0;width:auto;order:2}
  .filters > .label:nth-of-type(2){order:3}
  .filters > select:nth-of-type(2){order:4}
  .filters > .field{order:2;flex:1 1 0;min-width:0}
  .filters > .field label{order:-1}
  .filters button{order:5;flex:0 0 auto;padding:8px 16px}
  .inline-form input[type=email]{width:100%;max-width:100%}
  .inline-form button{width:100%}
  /* ★ 2026-10-02：原 `.foot-grid{grid-template-columns:1fr;gap:22px}` 已删除
     （页脚已压成一行，见 .foot-line）。 */
  .card{padding:var(--sp-4)}
}

/* 减少动效偏好：尊重系统设置，关掉卡片位移与过渡 */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{
    animation-duration:.001ms !important;
    animation-iteration-count:1 !important;
    transition-duration:.001ms !important;
    scroll-behavior:auto !important;
  }
  .card:hover{transform:none}
}

/* 打印：榜单页经常被截图与存档，去掉交互元素，保留表格与口径 */
@media print{
  .site-head,.site-foot,.skip,.badge-box,.hero-cta,
  .period-switch,.cycle-switch,.pager,.filters,.prod-links{display:none}
  body{font-size:11pt;background:#fff}
  main{padding:0}
  a{border:0;color:#000}
  .card,.table-scroll,.provenance,.accordion-note,.notice{box-shadow:none}
  .provenance{padding:0;border:0;background:transparent}
  .provenance[open],.provenance{display:block}
  table.board th{border-bottom:1px solid #000;background:transparent}
}

/* ============================================================================
   ② 徽章选择器页 /badge
   ========================================================================== */

.badge-hub{margin:0}
.hub-head{margin:0 0 var(--sp-8);padding:0 0 var(--sp-6);border-bottom:1px solid var(--line)}
.hub-head h1{max-width:24ch}

/* 使用规则：放在选择器之前，且用实线框而不是灰底 ——
   「徽章必须挂在你自己的站点上并指回产品页」是收录档位的验证依据，
   它是一条要求，不是一句补充说明，视觉上不该比控件更弱。 */
.hub-rules{
  border:1px solid var(--brand-line);border-radius:var(--r-xl);
  background:var(--brand-weak);
  padding:var(--sp-5) var(--sp-6);margin:0 0 var(--sp-8);
  font-size:14px;color:var(--ink-2);
}
.hub-rules h2{
  margin:0 0 10px;font-size:12px;font-weight:700;
  text-transform:uppercase;letter-spacing:.08em;color:var(--brand);
}
.hub-rules ul{margin:0;padding-left:1.15em}
.hub-rules li{margin:0 0 8px}
.hub-rules li:last-child{margin-bottom:0}

.hub-form-wrap{margin:0 0 var(--sp-8)}
.hub-form{align-items:flex-end;max-width:none}
.hub-form .hub-slug{flex:1 1 240px}
.hub-form .hub-slug input{width:100%}

.hub-notice{
  border:1px solid var(--brand-line);background:var(--brand-weak);
  border-radius:var(--r-lg);
  padding:12px 14px;margin:0 0 var(--sp-8);font-size:14px;
}
.hub-empty{border:1px solid var(--line);border-radius:var(--r-xl);background:var(--surface);
  box-shadow:var(--sh-sm);padding:var(--sp-6);margin:0 0 var(--sp-8)}
.hub-empty h2{margin-top:0}

/* 预览区：给徽章一块干净的白底，别让页面本身的边线干扰对徽章外观的判断 */
.hub-preview{padding:var(--sp-5) 0 4px}
.hub-preview img{display:block;border:1px solid var(--line);border-radius:var(--r-lg);
  background:#fff}

/* 嵌入代码框：其它页面的 .badge-code 里是 <code>，这里是 <textarea>，
   所以只对 .hub-embed 里的 textarea 收掉全局的 min-height（全局规则不能动）。 */
.hub-embed textarea{
  min-height:0;resize:vertical;background:transparent;border:0;padding:0;
  font-family:var(--mono);font-size:12px;line-height:1.6;color:var(--ink-2);
}

/* 全变体预设预览：服务端渲染的一组 <img>，JS 被禁用时也看得到全部款式 */
.hub-variants{margin:0 0 var(--sp-8)}
.hub-presets{
  display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));
  gap:var(--sp-4);margin:18px 0 0;
}
.hub-preset{margin:0;padding:14px;border:1px solid var(--line);border-radius:var(--r-lg);
  background:var(--surface)}
.hub-preset img{display:block;margin:0 0 10px;border:1px solid var(--line);
  border-radius:var(--r-sm)}
.hub-preset figcaption{display:flex;flex-direction:column;gap:3px;font-size:13px}

@media (max-width:640px){
  .hub-rules,.hub-empty{padding:var(--sp-4)}
  .hub-presets{grid-template-columns:1fr}
}

/* ============================================================================
   ③ 中文覆盖层 —— **只重新赋值令牌**，不重写任何一条规则
   ----------------------------------------------------------------------------
   1. 行高：汉字是满字身方块，同样的字号需要更大的行距才不挤；1.6 → 1.8。
   2. 字距：拉丁字母在负字距下更紧致好看；汉字没有字偶距，负字距只会让
      相邻字粘连。所以正文给 +0.01em，标题归零（不用负值）。
   3. 行长：中文不能按 ch 算（1ch ≈ 半个汉字宽），改用 em —— 约 34 个汉字一行，
      落在中文排版 30–45 字的舒适区；英文的 70ch 换到中文会拉出 70 个汉字，
      读起来像爬楼梯。
   4. 版心：中文信息密度可以更高，版心收窄到 1200px；卡片最小宽同步收窄，
      同一屏能多放一列。
   5. 正文字号：15px（中文在小字号下仍可辨认，且能提高信息密度），
      英文保持 16px（拉丁字母 x-height 小，需要更大字号）。
   ========================================================================== */

body[data-lang="zh"]{
  --wrap:1200px;
  --wrap-wide:1440px;
  --fs-body:15px;
  --lh-body:1.8;
  --ls-body:.01em;
  --fs-h1:clamp(26px,3vw,34px);
  --lh-h1:1.35;
  --ls-h1:0;
  --fs-h2:18px;
  /* 行高比值保持与英文一致（28/18），中文版的区块标题因此同样是 36px 行盒 ——
     「几何对齐以英文版为准」这条要求下，行盒高度不能让中文版自己变。
     18px 汉字在 28px 行距里是 1.556 倍，落在中文排版 1.5–1.7 的舒适区内，不算排坏。 */
  --lh-h2:1.556;
  --ls-h2:0;
  --fs-h3:16px;
  --lh-h3:1.35;
  --ls-h3:0;
  --fs-lead:15px;
  --lh-lead:1.9;
  --prose:34em;
  --card-pad:16px;
  --sec-gap:40px;
}

/* 中文页的卡片正文小半号（汉字在同样字号下视觉重量更大），
   但**不碰**任何几何量：卡片宽高、图片区 2:1、头像 32、间距 10 —— 全部与英文版一致，
   否则中英两个版本的卡片列数、行高会对不上。 */
body[data-lang="zh"] .card p{font-size:13px}
body[data-lang="zh"] .card > header h2,
body[data-lang="zh"] .card > header h3{font-weight:600}
body[data-lang="zh"] .btn{letter-spacing:.01em}
body[data-lang="zh"] .card.prod .card-slogan{letter-spacing:0}
/* 标签 chip 不放大内边距：22px 这个高度是中英共用的几何量 */
body[data-lang="zh"] .chips a{padding:1px 10px;font-size:12px}
body[data-lang="zh"] .nav-item{letter-spacing:0}

@media (max-width:640px){
  body[data-lang="zh"]{--fs-body:15px;--sec-gap:40px}
}

/* ============================================================================
   用户面板 /account（2026-10-04 round30）
   ----------------------------------------------------------------------------
   区块之间用一条细分隔线隔开（.acct-sec + border-top），与全站「靠 1px 细线分层」
   的做法一致，不用灰底或卡片堆。权益对比表用品牌淡底标出当前档位那一行。
   页头里的 .head-user 现为图标链接，去掉全局 a 的下边框。
   ========================================================================== */
.head-account .head-user{border-bottom:0}

.acct-sec{margin:0 0 var(--sp-10);padding-top:var(--sp-6);border-top:1px solid var(--line)}
.acct-sec:first-of-type{border-top:0;padding-top:0}
.acct-sec h2{margin:0 0 var(--sp-2)}
.acct-sec h3{margin:var(--sp-5) 0 var(--sp-2)}

.acct-plan{display:flex;flex-wrap:wrap;align-items:center;gap:var(--sp-2);margin:var(--sp-3) 0}
.acct-tier{
  font-size:14px;font-weight:700;color:var(--brand);
  background:var(--brand-weak);border:1px solid var(--brand-line);
  border-radius:var(--r-pill);padding:3px 12px;
}
.acct-benefits{list-style:none;margin:0;padding:0;display:grid;gap:6px}
.acct-benefits li{font-size:14px;color:var(--ink-2)}

.acct-table{width:100%;border-collapse:collapse;margin:var(--sp-2) 0 0;font-size:14px}
.acct-table th,.acct-table td{padding:9px 12px;border-bottom:1px solid var(--line);text-align:left}
.acct-table thead th{font-size:12px;font-weight:600;color:var(--mute);background:var(--surface-2)}
.acct-table tr.on{background:var(--brand-weak)}
.acct-table tr.on th{color:var(--brand)}
.acct-mark{font-size:11px;font-weight:600;color:var(--brand);margin-left:6px}

/* 逐字段校验提示：内联小字，不用整块 .error（那是给「整张表单级的失败」用的） */
.field-error{display:block;margin-top:6px;font-size:12px;font-weight:600;color:var(--danger)}

/* ============================================================================
   底部 Cookie 同意条（2026-10-04）

   节点由 lib/layout.php 出口处那段内联脚本注入（无 JS 时不出现，故这里不需要
   「默认隐藏、有 JS 再显示」的处理）。固定贴底、整条通栏；窄屏下文案与动作换行堆叠。
   注意 z-index 要高于吸顶页头，否则会被 .site-head 压住。
   ========================================================================== */
.cc-bar{
  position:fixed;left:0;right:0;bottom:0;z-index:60;
  display:flex;flex-wrap:wrap;align-items:center;gap:10px 16px;
  padding:12px 18px;
  background:var(--surface);border-top:1px solid var(--line);
  box-shadow:0 -6px 20px rgba(0,0,0,.06);
}
.cc-bar .cc-text{margin:0;flex:1 1 320px;font-size:13px;color:var(--ink-2);line-height:1.5}
.cc-bar .cc-actions{display:flex;align-items:center;gap:14px;flex:0 0 auto}
.cc-bar .cc-actions a{font-size:13px;color:var(--mute);text-decoration:underline}
.cc-bar .cc-actions a:hover{color:var(--ink)}
.cc-bar .cc-ok{padding:7px 18px;font-size:13px}

/* ★ 2026-10-06 UX：给底部预留空间，避免同意条永久遮住页脚。
   实测问题：滚动到页面底部时，「Terms / Privacy / © 2026 ZVBoard」那一行
   被固定条盖住且**无法通过滚动露出**（它已经是最底了），页脚成了不可达内容。

   为什么用 :has() 而不是 JS 加类：
     · 条本身是 JS 注入的（无 JS 就不出现），所以「条在不在」本来就是个运行时状态；
     · :has() 让 CSS 直接问 DOM「body 里有没有 .cc-bar」，**不引入第二份状态** ——
       用 JS 的话就得同时维护「显示条」和「加 padding」两件事，两者可能不同步
       （比如条被移除但 class 没摘，页底就多出一块空白）。
     · 用户点 Agree 后条消失，padding 自动收回，不留残白。
   高度取 64px：实测 .cc-bar 为 12px×2 padding + 约 24px 行高 + 1px 边框 ≈ 49px，
   64px 留了余量；留多了只是页脚低一点，不会造成内容被遮。

   兼容性：:has() 需要 Chrome 105+ / Safari 15.4+ / Firefox 121+（2022–2023 起）。
   更老的浏览器上这条规则整体失效 —— 那就是**改动前的原样**（继续被遮），
   属于渐进增强而非退化。

   ⚠️ ★ 2026-10-06 修正（移动端自查发现）：这条**只对桌面端成立**。
   窄屏（≤1023px）有两处不同，必须避开，否则会多出一大块空白：
     ① 底部有一条 56px 的**固定导航栏**（.side-rail），它已经由
        `.col-main{padding:0 16px 76px}` 预留过了（56 + 20 余量）；
     ② 同意条与那条导航栏**上下叠放**，要避开的是「导航栏之上」的那一条。
   若把 padding 加在 body 上，窄屏下会与 .col-main 的 76px 叠加成 ~180px 空白 ——
   页底多出一整块什么都没有的滚动区，比被遮住更糟。
   所以桌面用 body（页脚在 body 直属的 .site-foot 下），窄屏改用 .col-main
   并扣掉已预留的 56px 导航栏高度。 */
body:has(.cc-bar){padding-bottom:64px}
/* 窄屏：预留只补「同意条高出 56px 导航栏的那部分」。
   窄屏下条更高（文案与按钮换行堆叠，实测约 96px），所以补 96px。
   ⚠️ 不写 640px 断点：1024–1440 的平板与折叠屏也走这条，
   它们同样有固定底栏（.side-rail 在 ≤1023 生效），同样需要扣掉 56px。 */
@media (max-width:1023px){
  body:has(.cc-bar) .col-main{padding-bottom:96px}
}

/* 精选位榜行内的投票表单：紧凑一点，避免把表格单元格撑高 */
.board .vote-form.inline{margin:6px 0 0}
.board .vote-form.inline .btn{padding:4px 12px;font-size:12px}
.board .vote-action.small{margin:6px 0 0;font-size:12px}
.board .vote-action.small .btn{padding:4px 12px;font-size:12px}


/* ============================================================================
   ★ 2026-10-06 iPhone 安全区（追加块）
   ----------------------------------------------------------------------------
   为什么放在文件**末尾**而不是插入中间：
     上一轮把这几条插进 .m-head / .side-rail / .col-main / .cc-bar 附近时，
     花括号配平被破坏（506 vs 507），结果 1280px 视口下侧栏整块消失、
     底栏图标浮在页面中部 —— 布局全面错乱。追加在末尾天然是独立作用域，
     不会再与前面的块产生嵌套错位。
   ⚠️ 代价：同优先级下末尾的规则会覆盖前面的，所以下面每条都写全了
      padding/height，不依赖前面先声明了什么。

   前置条件：<meta name="viewport" ... viewport-fit=cover>（lib/layout.php 已加）。
   没有 viewport-fit=cover，浏览器会把视口限制在安全区内，env() 恒为 0，
   下面这些 calc 全部退化成基础值 —— 即「等于没做」，但不会出错。

   env() 在不支持的浏览器上取值为 0，calc 自动退化，无需 @supports。
   ========================================================================== */

/* ① 窄屏顶栏：避开刘海（竖屏压状态栏、横屏压按钮） */
@media (max-width:1023px){
  .m-head{
    padding-top:env(safe-area-inset-top);
    padding-left:calc(16px + env(safe-area-inset-left));
    padding-right:calc(16px + env(safe-area-inset-right));
    height:calc(56px + env(safe-area-inset-top));
  }
  /* body 的顶部留白必须同步加高，否则顶栏垫高后会盖住正文 44px */
  body{padding-top:calc(56px + env(safe-area-inset-top))}
}

/* ② 底部导航栏：整体抬高 Home 指示条。
   这是刘海屏上最常见的底部导航事故 ——「Submit」图标正中被系统横条压住，
   且只在真机出现，桌面与模拟器都看不出来。 */
@media (max-width:1023px){
  .side-rail{
    height:calc(56px + env(safe-area-inset-bottom));
    padding-bottom:env(safe-area-inset-bottom);
  }
  /* 底栏抬高后，正文底部留白必须同步加大，否则最后一条内容藏在栏下 */
  .col-main{padding-bottom:calc(76px + env(safe-area-inset-bottom))}
}

/* ③ Cookie 同意条：Agree 按钮不能落在 Home 指示条上 ——
   那个位置系统手势优先，按钮按不到。 */
.cc-bar{
  padding-bottom:calc(12px + env(safe-area-inset-bottom));
  padding-left:calc(18px + env(safe-area-inset-left));
  padding-right:calc(18px + env(safe-area-inset-right));
}

/* ④ 移动端整卡可点（stretched-link）。
   卡片里唯一的 <a> 只包住标题那几个字，封面/口号/票数/空白区都点不动。
   鼠标上这只是热区偏小；手机上拇指点卡片中间却没反应，
   用户会以为卡片坏了然后反复点标题。做法：给标题链接一个撑满整卡的伪元素。
   position:static 让 ::after 以 .card.prod 为定位祖先，热区正好是整张卡。 */
@media (max-width:1023px){
  .card.prod{position:relative}
  .card-name a::after{content:"";position:static;display:block}
}

/* ⑤ 榜单表横向滚动提示。
   390px 下榜单表 3 列必然超出屏宽，没有提示时用户以为「右边被切坏了」
   而不知道还能往右滑。右侧叠一层渐变，pointer-events:none 不挡内容点击。 */
@media (max-width:1023px){
  .table-scroll{position:relative}
  .table-scroll::after{
    content:"";position:absolute;top:1px;right:1px;bottom:1px;width:28px;
    pointer-events:none;
    background:linear-gradient(to right,rgba(255,255,255,0),var(--surface) 78%);
    border-top-right-radius:var(--r-xl);border-bottom-right-radius:var(--r-xl);
  }
}

/* ============================================================================
   R1 · 2026-10-08 产品页头部布局（追加块，勿插入中间）
   2026-10-06 那次把规则插进 .prod-head 附近破坏了花括号配平，
   导致 1280px 侧栏整块消失。追加是独立作用域，天然安全。

   【实测问题】浏览器 1280x720 打开 /p/stripe：
     封面 span.prod-logo 是 width:100% + aspect-ratio:2/1，在 750px 主列里
     高 358px，占掉首屏（去顶栏后约 656px）的 55%。访客第一屏只看到一张图，
     产品名、Slogan、Visit 按钮全落在折叠线以下 —— 优先级排反了。
     产品页是访客判断「这是什么、要不要用」的地方，判断所需的三行字
     不该被一张图挤到第二屏。

   【改法】桌面端图收成右侧 300px，文字占左侧。
     750 − 300 − gap24 = 426px 给文字，够放 h1 + tagline + Visit + meta。
     头部总高从约 558px 降到约 316px，首屏能看全「名字+简介+按钮」。

   【为什么不是单纯缩小图】缩到能放下三行字，图就小到看不清品牌标识了。

   【窄屏不动】<=1023px 横向本就窄，并排会把文字压成几十像素宽，保持通栏。
   ========================================================================== */
@media (min-width:1024px){
  .prod-head{
    display:grid;
    grid-template-columns:minmax(0,1fr) 300px;
    column-gap:var(--sp-6);
    align-items:start;
  }
  /* h1 不再限宽 24ch：第一列已有确定宽度，再限 24ch 会让长产品名提前折行。 */
  .prod-head h1{margin-bottom:6px;max-width:none}
  .prod-head .lead{margin-bottom:var(--sp-4)}
  /* 图进第二列，纵跨 5 行与 h1 顶对齐；margin 清零是因为通栏时那个
     下间距（--sp-6）在并排布局里会在图下方留一道空。 */
  span.prod-logo{grid-column:2;grid-row:1 / span 5;margin:0;aspect-ratio:2 / 1}
  /* 文字区统一落回第一列。不逐个写 grid-row —— 图已用 span 5 占住第二列
     五行，文字自动排第一列，行高不够时 grid 自行增行，比写死 row 安全。 */
  .prod-head > h1,
  .prod-head > .lead,
  .prod-head > .prod-links,
  .prod-head > .prod-meta,
  .prod-head > p.muted.small{grid-column:1}
}
/* 平板横屏：主列收窄到 726px，图 300→260，给文字多留 40px。 */
@media (min-width:1024px) and (max-width:1319px){
  .prod-head{grid-template-columns:minmax(0,1fr) 260px}
}
