/* ══════════════════════════════════════════════════════════════════════════════════════════════════
   shapes.css — 形态层（#1318，设计文档 D1 / D2 / D4）
   ══════════════════════════════════════════════════════════════════════════════════════════════════

   🔴 **这份文件是拼出来的，别手改**（#1387，设计文档 D20）。手改会被下一次
      `node scripts/block-build/build-blocks.js` 覆盖，而 `scripts/block-build/generated-fresh.test.js`
      会在那之前就把它点名。要改一个形态的几何，改 `blocks/<块>/<形态>/shape.css`；要加一个形态，
      新建一个子文件夹放 `shape.css` + `shape.md`，然后跑一次生成器。
      这段开头住在 `scripts/block-build/shapes-head.css`。

   一个块**怎么排**住在这里，一份，平台的。主题表（`public/themes/<id>.css`）只剩皮：颜色、字体、
   圆角、阴影、内边距、间距刻度。契约 v3（`docs/reference/theme-css-contract.md`）把几何那一族从
   §2 的主题可写属性表里拿掉了，所以一套主题从此**写不出排版错**。

   ── 怎么被选中 ────────────────────────────────────────────────────────────────────────────────
   每条规则按 `[data-block="<块类型>"][data-shape="<画法名>"]` 点名。两个属性都由模板写在块的根
   元素上（`src/lib/sections/blockAttrs.ts`）：`data-block` 是块的类型，`data-shape` 是这个站在这个
   块上选的画法。取值三级（spec D18，`scripts/sync-config.js`）：
       页面 JSON 里这个块自己的 `shape`  →  主题的选择单（`scripts/theme-pool.json` 的 `shapes`）
                                         →  块的默认形态（`blocks/<块>/` 里 `order` 最小的那个子文件夹）

   ── 一个块有几个画法 ──────────────────────────────────────────────────────────────────────────
   🔴 **= 今天真的量到几种不同的排版**，不为「将来可能有」预留名字（PM 2026-09-15 裁定 ③）。
   这份表是从 `ember-12` / `azure-29` 两套表搬迁前那一版里逐条剪出来的：**搬迁那一刻**是 31 个块里
   19 个两套画得不一样（各两个名字）、12 个逐字相同（各一个名字），一共 50 个 (block, shape) 对。
   🔴 **那三个数是 2026-09 搬迁时的历史读数，早就不是今天的了 —— 别从这里抄。** 自己取（#1387 之后
   形态清单就是子文件夹清单，所以直接数目录）：
       find templates/nextjs/blocks -mindepth 1 -maxdepth 1 -type d | wc -l    # 有几个块
       find templates/nextjs/blocks -mindepth 2 -maxdepth 2 -type d | wc -l    # 有几个 (块, 形态) 对
   🔴 这两条用 `find` 而不是 `ls -d` 加通配，是因为这一段住在一个 CSS 块注释里：通配那种写法里
      「星号紧跟斜杠」的组合**会把这条注释当场关掉**，后面几十行就变成了选择器。第一版就是那么写的，
      `scripts/block-migration/css-equiv.js` 当场读出「多了一条 (媒体查询, 选择器)」，
      而生成器现在自己也拦这一格（见 build-blocks.js 的 assertNoLostRules）。
   📌 别在共享树 `/root/work/ai1st` 上取这个数：它常年落后 main 几百个 commit，读到的是旧的那一版
      区块库。要 main 的读数就在自己那棵票的工作区里 `git fetch` 之后取。
   要加新的一种，先量出「它跟已有的哪一个真的不同」—— 判据是 D5 的**几何读数**（真浏览器量边界框），
   不是「排版意图的元组跟别人不同」：元组相同**不阻断**，但必须量得出盒子差别（PM 2026-09-16 裁定；
   今天 main 上就有多组元组逐字相同的形态）。

   ── 第 51 个：`hero · text-center`（#1333）──────────────────────────────────────────────────
   它是 hero 的新默认排版。为什么非有一个不可：#1333 把 `form-side` 整段搬去了 `hero-with-form`，
   而 hero 剩下的 `media-cover` 要求有图，当不了默认（`block-manifest.js` 的 `checkManifestShape`：
   「默认排版的 needs 必须为空」）。

   按上面那条规矩量过了，对照的是 hero 名下另一个 `media-cover`。单变量：**同一个站、同一套主题
   （ember-12）、同一张图**，只把页面 JSON 里那个块的 `shape` 换掉，各构建一次、各在 1280px 真浏览器里
   量边界框：

       量的东西            text-center            media-cover
       图的 y              561–1089（在文之下）    186–1418（跟文叠在同一行）
       正文的 y            194–494                636–936
       图与文的关系        不重叠                 完全重叠（两者都 grid-row 1）
       靠什么排位          order 1/2/3            grid-row 1/1/2（order 全是 0）
       块自己的 min-height 416px（26rem）         576px（36rem）
       块量出来的高        1075px                 1444px
       justify-items       center                 normal

   ⟹ 两者在「图放哪」这根轴上取值相反（below vs cover），不是同一个排版换了个名字。
   📌 (block, shape) 对因此是 **51** 个，不是 50：hero 名下 `form-side` → `text-center` 是换名不加数，
   真正新增的是这一个；`hero-with-form/form-side` 是把 hero 名下那个整段改挂，也不加数。

   ── 有一个画法不是从那两套表里剪出来的 ────────────────────────────────────────────────────
   `row`（横排条）是 #1320 手写的。搬迁前两套表给 services-nav / trusted-brands
   写的都是一列的网格，而那正是它要治的缺陷：桌面上六个协会名、八个保险公司名一个一行竖着排，
   名字还被折成两行。设计文档 D1 只允许这一族块用它（「同级项横排、放不下换行」）—— 原文点名三个，其中一个（logo 墙那个）已在 #1375 按 D19 删掉，今天剩两个。它的几何
   读数在 `scripts/theme-css-invariants.mjs` 的检查 ⑧ —— D5 那条「没有读数的形态不许进」。
   🔴 所以它自己不加数：这三个块各自把 `stack` 换成了 `row`，不是各多一个名字。（今天的总数是 51，
   多出来的那一个是上面 #1333 那段量过的 `hero/text-center`。）
   留着旧名字就是文件头上面那条自己禁止的「为将来可能有预留名字」。

   ── 这一层可以引用什么 ────────────────────────────────────────────────────────────────────────
   刻度变量（`--section-block-gap` / `--radius-block` 这类）可以引；几何常量写死。间距和圆角本身
   仍然是主题的，所以这里不写 `gap` / `padding` / `margin` —— 它们留在主题表里。

   ── 交替节奏为什么并进画法名 ──────────────────────────────────────────────────────────────────
   `content-split` 的名字是 `media-right-alternate` / `narrow-stack-uniform`：节奏不是独立的一维，
   `order` 的值本来就是按画法算出来的（`sheet-recipes.js:1049/:1058`），所以不加第二个 `data-rhythm`
   钩子（PM 2026-09-15 裁定 ②）。兄弟链上**两端都带 `[data-shape]`**：这条规则说的是「一个
   media-right-alternate 的图文段跟在另一个后面时翻面」，今天同一个站里同类型的块共用一个画法，
   所以与搬迁前逐像素相同。

   🔴 **这份文件不是生成物。** `public/themes/*.css` 由 `scripts/theme-pipeline/` 现算、
   `sheet-fresh.js` 逐字节复算；这一份是手维护的平台文件，改它就是改全部站的排版。
   ══════════════════════════════════════════════════════════════════════════════════════════════════ */

/* ══ blog ══════════════════════════════════════════════════════════════════════ */
/* 条目网格（itemsColumns：手机 ≤1 · iPad ≤2 · ≥992 按旋钮）—— 由 scripts/block-build/items-grid.js 生成，别在 block.css 里写 */
[data-block="blog"][data-items-layout="grid"] .bl-grid {
  --items-cols: 1;
  display: grid;
  grid-template-columns: repeat(var(--items-cols), minmax(0, 1fr));
}
@media (min-width: 768px) {
  [data-block="blog"][data-items-layout="grid"][data-items-columns="2"] .bl-grid {
    --items-cols: 2;
  }
  [data-block="blog"][data-items-layout="grid"][data-items-columns="3"] .bl-grid {
    --items-cols: 2;
  }
}
@media (min-width: 992px) {
  [data-block="blog"][data-items-layout="grid"][data-items-columns="3"] .bl-grid {
    --items-cols: 3;
  }
}

/* 块头排版（introAlign / introPosition）—— 由 scripts/block-build/intro-layout.js 生成，别在 block.css 里写 */
[data-block="blog"][data-intro-align="center"] .bl-intro-text {
  text-align: center;
}
[data-block="blog"][data-intro-align="right"] .bl-intro-text {
  text-align: end;
}
@media (min-width: 992px) {
  [data-block="blog"][data-intro-align="center"] .bl-intro-text {
    width: 100%;
    max-width: 80%;
    margin-inline: auto;
  }
}
[data-block="blog"][data-intro-position="bottom"] .bl-frame {
  flex-direction: column-reverse;
}
@media (max-width: 991.98px) {
  [data-block="blog"][data-intro-position="right"] .bl-frame {
    flex-direction: column-reverse;
  }
}
@media (min-width: 992px) {
  [data-block="blog"][data-intro-position="left"] .bl-introcol,
  [data-block="blog"][data-intro-position="right"] .bl-introcol {
    flex: 0 0 auto;
    width: 33.3333%;
    position: sticky;
    top: 2rem;
    align-self: flex-start;
  }
  [data-block="blog"][data-intro-position="left"] .bl-itemscol,
  [data-block="blog"][data-intro-position="right"] .bl-itemscol {
    flex: 0 0 auto;
    width: 66.6667%;
  }
  [data-block="blog"][data-intro-position="left"] .bl-intro-text,
  [data-block="blog"][data-intro-position="right"] .bl-intro-text {
    max-width: none;
    margin: 0;
  }
  [data-block="blog"][data-intro-position="right"] .bl-frame {
    flex-direction: row-reverse;
  }
}

/* ── blog · 跨形态的规则（#1497）──────────────────────────────────────────────────────────────
   blog 的排版由**实际生效的旋钮**决定，写在根元素上：
     intro  data-intro-position（left / right / top / bottom）· data-intro-align（left / center / right）
     posts  data-items-layout（grid / list / featured）· data-items-columns（2 / 3，只管 grid）
     post   data-item-style（plain / card）· data-item-image（top / left / background）
            🔴 封面放哪按**每篇文章自己的** `data-image` 命中（Section.tsx 算：featured 的第一篇恒为 top，其余 = itemImage）。
               按根元素的 data-item-image 写的话，background 那几条（带 !important）会把 featured 的大篇也铺满 ——
               正文与 AC4 要的是「大篇封面固定在上，小篇跟 itemImage 走」（#1497 第一版就这么错过一次，e2e 量出来的）。
     底色   data-tone（light / dark / brand，scripts/lib/contrast.js §toneForBg）
   五个形态目录都是预设，自己不带几何（理由在各自的 shape.css 里）。规则逐条照定稿图册
   （docs/reference/webpixels/gallery/build.py 的 `.bl` 那几行），`body.opt-<旋钮>-<值>` 换成根元素的 `data-*`。

   🔴 Webpixels 的工具类全带 !important（mb-* / text-muted …）。这里要压过它们的规则**也带 !important**，
      而且选择器从 [data-block="blog"] 起步再加属性 / 类 —— 特异度一定高过工具类的一个类。
   🔴 部件的有无靠渲染（Section.tsx 有数据才画），不靠 CSS 藏。唯一「藏」的是 featured 里小篇的摘要（图册如此：
      右边一列的小篇只留标题）。
   🔴 纵向写 gy-10、不写 g-10（g-10 的横向负边距在手机上溢出 4px，#1475 量出来的；图册 g-10 同款）。 */

/* ═══ intro（块头）═══════════════════════════════════════════════════════════════════════════════ */

[data-block="blog"] .bl-intro-text > :last-child {
  margin-bottom: 0 !important;
}
[data-block="blog"] .bl-cta-row {
  margin-top: 1.5rem;
}

/* 块头排版（introAlign / introPosition：对齐、≥992 center 最宽 80%、左右两列、上下反向）由 manifest 的 introLayout 生成
   （scripts/block-build/intro-layout.js，#1535），这里不写。
   文章的位置不跟 introAlign 走（Chris 2026-09-30：list 时文章跟着居中是 bug）。 */

/* ═══ 每篇文章 ═══════════════════════════════════════════════════════════════════════════════════ */

[data-block="blog"] .bl-img {
  display: block;
  overflow: hidden;
  border-radius: 1rem;
  background: var(--scheme-surface-sunken);
}
[data-block="blog"] .bl-img img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* 没有封面：一块主色 10% 底 + 分类名（不是空白、不是坏图）。高度由下面各档的 aspect-ratio 给。 */
[data-block="blog"] .bl-ph {
  display: flex;
  align-items: center;
  justify-content: center;
  background: color-mix(in srgb, var(--x-primary, #3b82f6) 10%, var(--x-body-bg));
  color: var(--scheme-primary-ink, var(--x-primary, #3b82f6));
  text-decoration: none;
}
[data-block="blog"] .bl-author img {
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 50%;
  object-fit: cover;
}
[data-block="blog"] .bl-title a:hover {
  color: var(--scheme-primary-ink, var(--x-primary)) !important;
}
[data-block="blog"] .bl-text {
  min-width: 0;
}

/* itemImage top：封面在上 16:10。 */
[data-block="blog"] .bl-post[data-image="top"] .bl-img {
  aspect-ratio: 16 / 10;
  margin-bottom: 1.25rem;
}
/* itemImage left：≥768 两栏（图 40%）；小屏图在上 16:10。 */
@media (min-width: 768px) {
  [data-block="blog"] .bl-post[data-image="left"] .bl-inner {
    display: flex;
    gap: 1.5rem;
    align-items: flex-start;
  }
  [data-block="blog"] .bl-post[data-image="left"] .bl-img {
    flex: 0 0 40%;
    aspect-ratio: 4 / 3;
  }
  [data-block="blog"] .bl-post[data-image="left"] .bl-text {
    flex: 1 1 0;
  }
}
@media (max-width: 767.98px) {
  [data-block="blog"] .bl-post[data-image="left"] .bl-img {
    aspect-ratio: 16 / 10;
    margin-bottom: 1.25rem;
  }
}

/* itemStyle card：白底 1px 描边 rounded-4；封面在上时封面贴卡顶。 */
[data-block="blog"][data-item-style="card"] .bl-inner {
  background: var(--x-body-bg);
  border: 1px solid var(--x-border-color, #e5e7eb);
  border-radius: 1rem;
  overflow: hidden;
}
[data-block="blog"][data-item-style="card"] .bl-post[data-image="top"] .bl-img {
  border-radius: 0;
  margin-bottom: 0;
}
[data-block="blog"][data-item-style="card"] .bl-text {
  padding: 1.5rem;
}
[data-block="blog"][data-item-style="card"] .bl-post[data-image="left"] .bl-inner {
  padding: 1rem;
}
[data-block="blog"][data-item-style="card"] .bl-post[data-image="left"] .bl-text {
  padding: 0.5rem 0;
}

/* itemImage background：封面铺满整张卡，底部深色渐变，文字压在图上、白字。卡最小高 26rem（list 18rem）。 */
[data-block="blog"] .bl-post[data-image="background"] .bl-inner {
  position: relative;
  overflow: hidden;
  border-radius: 1rem;
  min-height: 26rem;
  display: flex !important;
  flex-direction: column;
  justify-content: flex-end;
  padding: 0 !important;
  border: 0 !important;
}
[data-block="blog"] .bl-post[data-image="background"] .bl-img {
  position: absolute !important;
  inset: 0;
  border-radius: 0;
  margin: 0 !important;
  aspect-ratio: auto !important;
}
[data-block="blog"] .bl-post[data-image="background"] .bl-img:not(.bl-ph)::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(transparent 35%, rgba(2, 6, 23, 0.85));
}
/* 没封面时铺满的是占位色块，字要照样读得清：占位改成主色实底、分类名淡到背景里。 */
[data-block="blog"] .bl-post[data-image="background"] .bl-ph {
  background: var(--x-primary, #3b82f6);
  align-items: flex-start;
  padding: 1.75rem;
}
[data-block="blog"] .bl-post[data-image="background"] .bl-ph-label {
  color: rgba(255, 255, 255, 0.35);
}
[data-block="blog"] .bl-post[data-image="background"] .bl-text {
  position: relative;
  z-index: 1;
  padding: 1.75rem !important;
  color: #fff;
}
[data-block="blog"] .bl-post[data-image="background"] .bl-text .text-muted,
[data-block="blog"] .bl-post[data-image="background"] .bl-meta {
  color: rgba(255, 255, 255, 0.92) !important;
}
[data-block="blog"] .bl-post[data-image="background"] .bl-cat,
[data-block="blog"] .bl-post[data-image="background"] .bl-title,
[data-block="blog"] .bl-post[data-image="background"] .bl-title a {
  color: #fff !important;
}
[data-block="blog"][data-items-layout="list"] .bl-post[data-image="background"] .bl-inner {
  min-height: 18rem;
}
[data-block="blog"][data-items-layout="featured"] .bl-post[data-image="background"]:not(:first-child) .bl-inner {
  min-height: 12rem;
}

/* ═══ 排法（itemsLayout）══════════════════════════════════════════════════════════════════════════ */

/* grid：列数与断点（手机 1 列、iPad 2 列、≥992 按列数）归 manifest 的 itemsGrid，由 scripts/block-build/items-grid.js 生成（#1537）。
   这里只管行距 2rem、列距 1.5rem。 */
[data-block="blog"][data-items-layout="grid"] .bl-grid {
  gap: 2rem 1.5rem;
}

/* list：一篇一行、占满整列（Chris 2026-09-30：不限宽），位置不跟 introAlign 走。行距 2rem。 */
[data-block="blog"][data-items-layout="list"] .bl-grid {
  display: flex;
  flex-direction: column;
  gap: 2rem;
}
@media (min-width: 768px) {
  [data-block="blog"][data-items-layout="list"] .bl-post[data-image="left"] .bl-img {
    flex: 0 0 min(40%, 20rem);
  }
}

/* featured：第一篇大（左 7/12，封面固定在上），其余在右边一列；小篇跟 itemImage 走（Chris 2026-09-30），不带摘要。 */
[data-block="blog"][data-items-layout="featured"] .bl-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 1.5rem;
}
@media (min-width: 992px) {
  [data-block="blog"][data-items-layout="featured"] .bl-grid {
    grid-template-columns: minmax(0, 7fr) minmax(0, 5fr);
    grid-auto-rows: auto;
    gap: 1.5rem 2.5rem;
  }
  [data-block="blog"][data-items-layout="featured"] .bl-post:first-child {
    grid-row: span 3;
  }
  [data-block="blog"][data-items-layout="featured"][data-item-image="top"] .bl-post:first-child {
    grid-row: span 2;
  }
  [data-block="blog"][data-items-layout="featured"] .bl-post:first-child .bl-inner {
    display: block;
  }
  [data-block="blog"][data-items-layout="featured"] .bl-post:first-child .bl-img {
    aspect-ratio: 16 / 10;
    margin-bottom: 1.25rem;
    flex: none;
  }
  [data-block="blog"][data-items-layout="featured"] .bl-post:first-child .bl-title {
    font-size: 1.5rem;
  }
  [data-block="blog"][data-items-layout="featured"] .bl-post[data-image="left"]:not(:first-child) .bl-inner {
    display: flex;
    gap: 1rem;
    align-items: flex-start;
  }
  [data-block="blog"][data-items-layout="featured"] .bl-post[data-image="left"]:not(:first-child) .bl-img {
    flex: 0 0 38%;
    aspect-ratio: 4 / 3;
    margin: 0;
  }
  [data-block="blog"][data-items-layout="featured"] .bl-post[data-image="top"]:not(:first-child) .bl-img {
    aspect-ratio: 16 / 9;
    margin-bottom: 0.75rem;
  }
  [data-block="blog"][data-items-layout="featured"] .bl-post:not(:first-child) .bl-ex {
    display: none !important;
  }
}

/* ═══ 字色按背景亮度（data-tone，scripts/lib/contrast.js §toneForBg）══════════════════════════════
   深底 / 品牌底：标题反白，分类 / 日期 / 摘要白 .92，card 半透明底 + 半透明白描边，描边按钮反白。
   🔴 各块共有的那几条（根上字色 · 标题 · eyebrow · 主按钮 / 描边 / link 按钮反白）住 `scripts/lib/site-css.js`
      §DEEP_COMMON，全站一份（#1533）；这里只留本块特有的，别再抄回来（`scripts/block-deep-common.test.js`）。 */

[data-block="blog"][data-tone="dark"] h3,
[data-block="blog"][data-tone="brand"] h3,
[data-block="blog"][data-tone="dark"] .bl-cat,
[data-block="blog"][data-tone="brand"] .bl-cat {
  color: #fff !important;
}
[data-block="blog"][data-tone="dark"] .bl-body,
[data-block="blog"][data-tone="brand"] .bl-body {
  color: rgba(255, 255, 255, 0.92) !important;
}
[data-block="blog"][data-tone="dark"][data-item-style="card"] .bl-post:not([data-image="background"]) .bl-inner,
[data-block="blog"][data-tone="brand"][data-item-style="card"] .bl-post:not([data-image="background"]) .bl-inner {
  background: rgba(255, 255, 255, 0.06);
  border-color: rgba(255, 255, 255, 0.15);
}

/* ── blog · cards（预设 Cards）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：blog 是一份 markup + 六个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-items-*` / `data-item-*` / `data-tone`，规则全在 `blocks/blog/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="blog"][data-shape="cards"] {
  --bl-preset: cards;
}

/* ── blog · list（预设 List）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：blog 是一份 markup + 六个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-items-*` / `data-item-*` / `data-tone`，规则全在 `blocks/blog/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="blog"][data-shape="list"] {
  --bl-preset: list;
}

/* ── blog · featured（预设 Featured）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：blog 是一份 markup + 六个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-items-*` / `data-item-*` / `data-tone`，规则全在 `blocks/blog/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="blog"][data-shape="featured"] {
  --bl-preset: featured;
}

/* ── blog · side-intro（预设 Side intro）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：blog 是一份 markup + 六个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-items-*` / `data-item-*` / `data-tone`，规则全在 `blocks/blog/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="blog"][data-shape="side-intro"] {
  --bl-preset: side-intro;
}

/* ── blog · cover（预设 Cover）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：blog 是一份 markup + 六个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-items-*` / `data-item-*` / `data-tone`，规则全在 `blocks/blog/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="blog"][data-shape="cover"] {
  --bl-preset: cover;
}

/* ══ contact ══════════════════════════════════════════════════════════════════════ */
/* 块头排版（introAlign）—— 由 scripts/block-build/intro-layout.js 生成，别在 block.css 里写 */
[data-block="contact"][data-intro-align="center"] .ct-intro-text {
  text-align: center;
}
[data-block="contact"][data-intro-align="right"] .ct-intro-text {
  text-align: end;
}
[data-block="contact"][data-side-position="bottom"][data-intro-align="center"] .ct-sidecol .ct-form {
  margin-inline-start: auto;
  margin-inline-end: auto;
}
[data-block="contact"][data-side-position="bottom"][data-intro-align="right"] .ct-sidecol .ct-form {
  margin-inline-start: auto;
}
@media (min-width: 992px) {
  [data-block="contact"][data-intro-align="center"] .ct-intro-text {
    width: 100%;
    max-width: 80%;
    margin-inline: auto;
  }
}

/* ── contact · 跨形态的规则（#1489）──────────────────────────────────────────────────────────────
   contact 的排版由**实际生效的旋钮**决定，写在根元素上：
     块头    data-intro-position（top / beside）· data-intro-align（left / center / right）
     侧列    data-side-position（left / right / bottom）· data-form（none / teaser / full）· data-form-style（plain / card）
     items   data-items-layout（list / grid）· data-item-style（plain / card）· data-item-align（left / center）· data-item-icon（left / top）
     地图    data-map（none / beside / bottom）
     两列    data-split（form = items 5/12 + 侧列 7/12 · even = 各半（侧列只有地图）· one = 只剩一列，占满）—— Section.tsx 算
     底色    data-tone（light / dark / brand，scripts/lib/contrast.js §toneForBg）
   五个形态目录都是预设，自己不带几何（理由在各自的 shape.css 里）。规则照定稿图册 contact 段
   （docs/reference/webpixels/gallery/build.py 的 contact CSS，Chris 2026-09-29）。

   🔴 Webpixels 的工具类全带 !important（mb-* / text-muted / d-flex …）。这里要压过它们的规则**也带 !important**，
      而且选择器从 [data-block="contact"] 起步再加属性 / 类 —— 特异度一定高过工具类的一个类。
   🔴 部件的有无靠渲染（Section.tsx 有数据才画），不靠 CSS 藏：块头只画一份；items 空、侧列空、没坐标的地图都没有节点。
   🔴 间距照定稿：段 py-16 py-lg-24；两列 gy-10 gx-lg-16（纵向写 gy-10、不写 g-10：g-10 的横向负边距在手机上溢出，#1475 量出来的）。 */

/* ═══ 块头 ════════════════════════════════════════════════════════════════════════════════════════ */

[data-block="contact"] .ct-intro-text > :last-child {
  margin-bottom: 0 !important;
}
/* 块头在顶上、下面既没有 items 也没有侧列 / 地图 ⟹ 它是最后一个，mb-10 那 2.5rem 是多出来的（#1507）。 */
[data-block="contact"] .ct-top:last-child {
  margin-bottom: 0 !important;
}
[data-block="contact"] .ct-body {
  overflow-wrap: anywhere;
}
/* beside：放进 items 那一列的最上面，跟下面的 items 隔 2.5rem。 */
[data-block="contact"] .ct-colintro:not(:last-child) {
  margin-bottom: 2.5rem;
}
/* 块头排版（introAlign：对齐、≥992 center 最宽 80%）由 manifest 的 introLayout 生成（scripts/block-build/intro-layout.js，#1535），这里不写。
   bottom 时表单跟着 introAlign 摆（introLayout.follow）。 */

/* ═══ 两列（§split）════════════════════════════════════════════════════════════════════════════════
   sidePosition left / right（≥992）= 两列：items 5/12、侧列 7/12；侧列只有地图时各半；只剩一列时占满。
   小屏：left 侧列在上，right 在下。bottom = 上下叠（items 在上、侧列在下），表单限 40rem、位置跟 introAlign。 */
@media (min-width: 992px) {
  [data-block="contact"][data-split="form"]:not([data-side-position="bottom"]) .ct-textcol {
    flex: 0 0 auto;
    width: 41.6667%;
  }
  [data-block="contact"][data-split="form"]:not([data-side-position="bottom"]) .ct-sidecol {
    flex: 0 0 auto;
    width: 58.3333%;
  }
  [data-block="contact"][data-split="even"]:not([data-side-position="bottom"]) .ct-textcol,
  [data-block="contact"][data-split="even"]:not([data-side-position="bottom"]) .ct-sidecol {
    flex: 0 0 auto;
    width: 50%;
  }
  /* 块头在 items 列里、侧列是地图：那一列上下居中（图册同一条）。 */
  [data-block="contact"][data-intro-position="beside"][data-split="even"] .ct-textcol {
    align-self: center;
  }
}
[data-block="contact"][data-side-position="left"] .ct-frame {
  flex-direction: row-reverse;
}
@media (max-width: 991.98px) {
  [data-block="contact"][data-side-position="left"] .ct-frame {
    flex-direction: column-reverse;
  }
}
[data-block="contact"][data-side-position="bottom"] .ct-sidecol .ct-form {
  max-width: 40rem;
}

/* ═══ contact items ════════════════════════════════════════════════════════════════════════════════
   itemsLayout list = 一行一条；grid = 网格（items 列窄时两列，占满整宽时四列）。
   🔴 item 的对齐只听 itemAlign，跟 introAlign 无关（图册 4c34a06a 那次改动的原话）。 */
[data-block="contact"] .ct-ch-icon {
  width: 2.5rem;
  height: 2.5rem;
  font-size: 1.125rem;
}
[data-block="contact"] .ct-value {
  overflow-wrap: anywhere;
  text-decoration: none;
}
[data-block="contact"] .ct-value[href]:hover {
  text-decoration: underline;
}
/* 一条 item 和它图标右边那一栏都能收窄（#1507）：`min-w-0` 本意是 `min-width: 0` —— 这套 Bootstrap 栈里没有这条
   工具类（Tailwind 的同名类在这里不生效），没有它 flex 子项不肯比内容窄，长邮箱 / 长地址就撑出卡片、不换行。 */
[data-block="contact"] .ct-ch,
[data-block="contact"] .min-w-0 {
  min-width: 0;
}
[data-block="contact"] .ct-ch-hint {
  overflow-wrap: anywhere;
}
[data-block="contact"][data-items-layout="list"] .ct-info {
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
}
/* list + center：整组居中、每条仍左对齐（图标排成一条竖线），同 pricing 的清单。 */
[data-block="contact"][data-items-layout="list"][data-item-align="center"] .ct-info {
  width: fit-content;
  max-width: 100%;
  margin-inline-start: auto;
  margin-inline-end: auto;
}
[data-block="contact"][data-items-layout="grid"] .ct-info {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1rem;
}
@media (min-width: 576px) {
  [data-block="contact"][data-items-layout="grid"] .ct-info {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
@media (min-width: 992px) {
  [data-block="contact"][data-items-layout="grid"][data-side-position="bottom"] .ct-info,
  [data-block="contact"][data-items-layout="grid"][data-split="one"] .ct-info {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}
[data-block="contact"][data-item-icon="top"] .ct-ch-inner {
  flex-direction: column !important;
}
[data-block="contact"][data-item-style="card"] .ct-ch-inner {
  background: var(--x-body-bg);
  border: 1px solid var(--x-border-color, #e5e7eb);
  border-radius: 1rem;
  padding: 1.5rem;
}
[data-block="contact"][data-items-layout="grid"][data-item-align="center"] .ct-ch-inner {
  text-align: center;
  align-items: center !important;
}

/* ═══ 表单 ═════════════════════════════════════════════════════════════════════════════════════════ */

[data-block="contact"][data-form-style="card"] .ct-form {
  background: var(--x-body-bg);
  border: 1px solid var(--x-border-color, #e5e7eb);
  border-radius: 1rem;
  padding: 2rem;
}

/* ═══ 地图（点之前是地址卡，点了是 OSM 嵌入；ContactMap.tsx）═══════════════════════════════════════
   占位卡和 iframe 住在同一个盒子里，盒子高度在这里定 ⟹ 点开前后不跳动。
   侧列：独占侧列时至少 24rem、撑满那一列；在表单下面时 20rem、离表单 1.5rem。最底下那一条 20rem。 */
[data-block="contact"] .ct-map {
  height: 20rem;
  background: var(--scheme-surface-map);
  color: var(--scheme-ink);
}
[data-block="contact"] .ct-map-side {
  min-height: 24rem;
  height: 100%;
}
[data-block="contact"] .ct-form + .ct-map-side {
  height: 20rem;
  min-height: 0;
  margin-top: 1.5rem;
}
[data-block="contact"] .ct-map-bottom {
  height: 20rem;
}
/* 占位卡和 iframe 撑满那个盒子（#1513）：它们的 h-100 只在盒子有确定高度时才算得出来，
   而独占侧列的那一格只有 min-height 是写死的 ⟹ 把下限也传给它们，点开前后不会矮一截。 */
[data-block="contact"] .ct-map-card,
[data-block="contact"] .ct-map-frame {
  min-height: inherit;
}
[data-block="contact"] .ct-map-address {
  max-width: 24rem;
}
[data-block="contact"] .ct-map-open {
  white-space: nowrap;
}
[data-block="contact"] .ct-pin svg {
  width: 2rem;
  height: 2rem;
}
[data-block="contact"] .ct-map-credit {
  background: rgba(255, 255, 255, 0.85);
  color: #334155;
}
[data-block="contact"] .ct-map-credit a {
  color: inherit;
}

/* ═══ 深底（bg 深色 / brand / 深渐变）═══════════════════════════════════════════════════════════════
   标题 / item 标题 / 值反白，item 卡半透明底 + 半透明白描边，图标底半透明白；提示白 .92 用全站那一条
   （scripts/lib/site-css.js §ON_DEEP_MUTED，认根上的 data-tone），这里不再抄一份（#1477 的规矩）；
   表单卡保持白底、卡里字回到深色（同 pricing 的 outline 卡）；地图那一格保持浅底深字。
   🔴 各块共有的那几条（根上字色 · 标题 · eyebrow · 主按钮 / 描边 / link 按钮反白）住 `scripts/lib/site-css.js`
      §DEEP_COMMON，全站一份（#1533）；这里只留本块特有的，别再抄回来（`scripts/block-deep-common.test.js`）。 */

[data-block="contact"]:not([data-tone="light"]) .ct-ch-title,
[data-block="contact"]:not([data-tone="light"]) .ct-value {
  color: #fff !important;
}
[data-block="contact"]:not([data-tone="light"])[data-item-style="card"] .ct-ch-inner {
  background: rgba(255, 255, 255, 0.06);
  border-color: rgba(255, 255, 255, 0.15);
}
[data-block="contact"]:not([data-tone="light"]) .ct-ch-icon {
  background: rgba(255, 255, 255, 0.14) !important;
  color: #fff !important;
}
[data-block="contact"]:not([data-tone="light"])[data-form-style="card"] .ct-form {
  color: #1e293b;
}
[data-block="contact"]:not([data-tone="light"])[data-form-style="card"] .ct-form .text-muted {
  color: #64748b !important;
}

/* ── contact · form-beside（预设 Form beside）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：contact 是一份 markup + 十个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-side-*` / `data-form*` / `data-item*` / `data-map` / `data-tone`，规则全在 `blocks/contact/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="contact"][data-shape="form-beside"] {
  --ct-preset: form-beside;
}

/* ── contact · details-beside（预设 Details beside）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：contact 是一份 markup + 十个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-side-*` / `data-form*` / `data-item*` / `data-map` / `data-tone`，规则全在 `blocks/contact/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="contact"][data-shape="details-beside"] {
  --ct-preset: details-beside;
}

/* ── contact · stacked（预设 Stacked）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：contact 是一份 markup + 十个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-side-*` / `data-form*` / `data-item*` / `data-map` / `data-tone`，规则全在 `blocks/contact/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="contact"][data-shape="stacked"] {
  --ct-preset: stacked;
}

/* ── contact · map-beside（预设 Map beside）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：contact 是一份 markup + 十个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-side-*` / `data-form*` / `data-item*` / `data-map` / `data-tone`，规则全在 `blocks/contact/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="contact"][data-shape="map-beside"] {
  --ct-preset: map-beside;
}

/* ── contact · map（预设 Map）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：contact 是一份 markup + 十个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-side-*` / `data-form*` / `data-item*` / `data-map` / `data-tone`，规则全在 `blocks/contact/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="contact"][data-shape="map"] {
  --ct-preset: map;
}

/* ══ content ══════════════════════════════════════════════════════════════════════ */
/* 图的位置（image）—— 由 scripts/block-build/media-layout.js 生成，别在 block.css 里写 */
[data-block="content"][data-image="left"] .co-outer,
[data-block="content"][data-image="right"] .co-outer,
[data-block="content"][data-image="top"] .co-outer,
[data-block="content"][data-image="bottom"] .co-outer {
  display: flex;
  flex-direction: column;
  gap: 2.5rem;
}
[data-block="content"][data-image="left"] .co-outer,
[data-block="content"][data-image="top"] .co-outer {
  flex-direction: column-reverse;
}
[data-block="content"][data-image="left"] .co-frame,
[data-block="content"][data-image="right"] .co-frame,
[data-block="content"][data-image="top"] .co-frame,
[data-block="content"][data-image="bottom"] .co-frame,
[data-block="content"][data-image="left"] .co-img,
[data-block="content"][data-image="right"] .co-img,
[data-block="content"][data-image="top"] .co-img,
[data-block="content"][data-image="bottom"] .co-img {
  min-width: 0;
}
@media (min-width: 992px) {
  [data-block="content"][data-image="left"] .co-outer,
  [data-block="content"][data-image="right"] .co-outer {
    flex-direction: row;
    align-items: center;
    gap: 4rem;
  }
  [data-block="content"][data-image="left"] .co-outer {
    flex-direction: row-reverse;
  }
  [data-block="content"][data-image="left"] .co-img,
  [data-block="content"][data-image="right"] .co-img {
    flex: 0 0 calc((100% - 4rem) * 0.5);
  }
  [data-block="content"][data-image="left"] .co-frame,
  [data-block="content"][data-image="right"] .co-frame {
    flex: 1 1 0;
  }
}

/* ── content · 跨形态的规则（#1498）──────────────────────────────────────────────────────────
   content 的排版由**实际生效的旋钮**决定，写在根元素上：
     data-headline-position（top / left）· data-text-align（left / center）· data-text-style（article / statement）·
     data-image（none / left / right / top / bottom）· data-frame（none / card）
     底色   data-tone（light / dark / brand，scripts/lib/contrast.js §toneForBg）
   四个形态目录都是预设，自己不带几何（理由在各自的 shape.css 里）。规则逐条照定稿图册
   （docs/reference/webpixels/gallery/build.py 的 `.co` 那几行），`body.opt-<旋钮>-<值>` 换成根元素的 `data-*`。

   🔴 Webpixels 的工具类全带 !important（text-muted …）。这里要压过它们的规则**也带 !important**，
      而且选择器从 [data-block="content"] 起步再加属性 / 类 —— 特异度一定高过工具类的一个类。
   🔴 部件的有无靠渲染（Section.tsx 有数据才画），不靠 CSS 藏。
   🔴 正文占满整列、不限宽（Chris 2026-09-30）：这里没有一条 max-width。 */

/* ═══ 块头 · 正文 · 按钮 ═══════════════════════════════════════════════════════════════════════ */

[data-block="content"] .co-head {
  margin-bottom: 2rem;
}
[data-block="content"] .co-eyebrow + .co-title {
  margin-top: 1rem;
}
[data-block="content"] .co-main {
  min-width: 0;
}
[data-block="content"] .co-body {
  color: var(--x-body-color, #334155);
  font-size: 1.0625rem;
  line-height: 1.7;
}
[data-block="content"] .co-body p {
  margin-bottom: 1.25rem;
}
/* 列表符号要写明：当时（#1426 之前）站上还带 Tailwind，它的 preflight 把 `menu, ol, ul` 一律 `list-style: none`（图册用的
   Webpixels 没有这一层，所以图册里看得见圆点、站上看不见 —— 真站截图量出来的）。Tailwind 退场后那个成因不在了。 */
[data-block="content"] .co-body ul,
[data-block="content"] .co-body ol {
  padding-inline-start: 1.25rem;
  margin-bottom: 1.25rem;
}
[data-block="content"] .co-body ul {
  list-style: disc;
}
[data-block="content"] .co-body ol {
  list-style: decimal;
}
[data-block="content"] .co-body li {
  margin-bottom: 0.5rem;
}
[data-block="content"] .co-body > :last-child {
  margin-bottom: 0;
}
[data-block="content"] .co-body strong {
  color: var(--x-heading-color, var(--scheme-ink-strong));
}
[data-block="content"] .co-ctas {
  margin-top: 2rem;
}

/* textAlign center：标题 / 正文 / 按钮居中；列表整块居中、每行仍左对齐（居中的列表行首参差，难读）。 */
[data-block="content"][data-text-align="center"] .co-inner {
  text-align: center;
}
[data-block="content"][data-text-align="center"] .co-ctas {
  justify-content: center;
}
[data-block="content"][data-text-align="center"] .co-body ul,
[data-block="content"][data-text-align="center"] .co-body ol {
  display: inline-block;
  text-align: start;
}

/* headlinePosition left：≥992 块头单独一列（1/3）、正文一列（2/3）；小屏块头在上。
   只在真有块头时分栏 —— 没有块头（Statement 常见）时正文不该缩进 1/3 那一列。 */
@media (min-width: 992px) {
  [data-block="content"][data-headline-position="left"] .co-inner:has(> .co-head) {
    display: grid;
    grid-template-columns: 1fr 2fr;
    gap: 4rem;
  }
  [data-block="content"][data-headline-position="left"] .co-head {
    margin-bottom: 0;
  }
}

/* textStyle statement：一段大字陈述 —— 正文放大成主角，标题保持原样（不缩成小字标签，小字提示只用 eyebrow）。 */
[data-block="content"][data-text-style="statement"] .co-body {
  font-size: clamp(1.375rem, 2.4vw, 1.875rem);
  line-height: 1.45;
  font-weight: 500;
  letter-spacing: -0.01em;
  color: var(--x-heading-color, var(--scheme-ink-strong));
}
[data-block="content"][data-text-style="statement"] .co-body strong {
  font-weight: 700;
}
[data-block="content"][data-text-style="statement"] .co-body li {
  margin-bottom: 0.25rem;
}
[data-block="content"][data-text-style="statement"] .co-head {
  margin-bottom: 1.5rem;
}

/* ═══ 图（image）══════════════════════════════════════════════════════════════════════════════════ */

/* 图在哪一边、两栏多宽、上下叠隔多少：manifest 的 `mediaLayout` 生成（scripts/block-build/media-layout.js，#1536），
   这里只剩图自己的比例 —— 并排 4:3，top / bottom 一整条 21:9。 */
[data-block="content"] .co-outer {
  display: flex;
  flex-direction: column;
}
[data-block="content"] .co-frame {
  min-width: 0;
}
[data-block="content"] .co-img img {
  display: block;
  aspect-ratio: 4 / 3;
}
[data-block="content"][data-image="top"] .co-img img,
[data-block="content"][data-image="bottom"] .co-img img {
  aspect-ratio: 21 / 9;
}

/* ═══ 卡（frame）══════════════════════════════════════════════════════════════════════════════════
   card = 整段文字放进一张白卡（有图时卡只包文字，图在卡外 —— 图是 .co-frame 的兄弟，不在它里面）。 */
[data-block="content"][data-frame="card"] .co-frame {
  background: var(--x-body-bg);
  border: 1px solid var(--x-border-color, #e5e7eb);
  border-radius: 1.5rem;
  padding: 2rem;
}
@media (min-width: 992px) {
  [data-block="content"][data-frame="card"] .co-frame {
    padding: 3.5rem 4rem;
  }
}

/* ═══ 字色按背景亮度（data-tone，scripts/lib/contrast.js §toneForBg）══════════════════════════════
   深底 / 品牌底：标题 / 正文 / 加粗反白，小字白 .92，描边按钮反白，卡半透明底 + 半透明白描边。
   🔴 各块共有的那几条（根上字色 · 标题 · eyebrow · 主按钮 / 描边 / link 按钮反白）住 `scripts/lib/site-css.js`
      §DEEP_COMMON，全站一份（#1533）；这里只留本块特有的，别再抄回来（`scripts/block-deep-common.test.js`）。 */

[data-block="content"][data-tone="dark"] .co-body,
[data-block="content"][data-tone="brand"] .co-body,
[data-block="content"][data-tone="dark"] .co-body strong,
[data-block="content"][data-tone="brand"] .co-body strong,
[data-block="content"][data-tone="dark"] .co-body a,
[data-block="content"][data-tone="brand"] .co-body a {
  color: #fff !important;
}
[data-block="content"][data-tone="dark"][data-frame="card"] .co-frame,
[data-block="content"][data-tone="brand"][data-frame="card"] .co-frame {
  background: rgba(255, 255, 255, 0.06);
  border-color: rgba(255, 255, 255, 0.15);
}

/* ── 按钮的底色和字色：接平台按这个站最终配色算好的那几个变量（`scripts/lib/button-ink.js`，同 cta）。
   拿不到这些变量的页面落回 Webpixels 自己的值。 */
[data-block="content"] .btn-primary {
  --x-btn-bg: var(--btn-primary-bg, var(--x-primary));
  --x-btn-border-color: var(--btn-primary-bg, var(--x-primary));
  --x-btn-color: var(--btn-primary-ink, #fff);
  --x-btn-hover-bg: var(--btn-primary-hover, var(--btn-primary-bg, var(--x-primary)));
  --x-btn-hover-border-color: var(--btn-primary-hover, var(--btn-primary-bg, var(--x-primary)));
  --x-btn-hover-color: var(--btn-primary-ink, #fff);
  --x-btn-active-bg: var(--btn-primary-hover, var(--btn-primary-bg, var(--x-primary)));
  --x-btn-active-color: var(--btn-primary-ink, #fff);
}
[data-block="content"] .btn-outline-primary {
  --x-btn-color: var(--btn-outline-ink, var(--x-primary));
  --x-btn-border-color: var(--btn-outline-ink, var(--x-primary));
}

/* ── content · article（预设 Article）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：content 是一份 markup + 五个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-headline-position` / `data-text-align` / `data-text-style` / `data-image` / `data-frame` / `data-tone`，
   规则全在 `blocks/content/block.css`）。按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，
   页面就会排成一个跟旋钮说的不一样的样子。所以这份文件只有这段说明。 */
[data-block="content"][data-shape="article"] {
  --co-preset: article;
}

/* ── content · title-left（预设 Title left）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：content 是一份 markup + 五个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-headline-position` / `data-text-align` / `data-text-style` / `data-image` / `data-frame` / `data-tone`，
   规则全在 `blocks/content/block.css`）。按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，
   页面就会排成一个跟旋钮说的不一样的样子。所以这份文件只有这段说明。 */
[data-block="content"][data-shape="title-left"] {
  --co-preset: title-left;
}

/* ── content · image-right（预设 Image right）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：content 是一份 markup + 五个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-headline-position` / `data-text-align` / `data-text-style` / `data-image` / `data-frame` / `data-tone`，
   规则全在 `blocks/content/block.css`）。按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，
   页面就会排成一个跟旋钮说的不一样的样子。所以这份文件只有这段说明。 */
[data-block="content"][data-shape="image-right"] {
  --co-preset: image-right;
}

/* ── content · statement（预设 Statement）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：content 是一份 markup + 五个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-headline-position` / `data-text-align` / `data-text-style` / `data-image` / `data-frame` / `data-tone`，
   规则全在 `blocks/content/block.css`）。按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，
   页面就会排成一个跟旋钮说的不一样的样子。所以这份文件只有这段说明。 */
[data-block="content"][data-shape="statement"] {
  --co-preset: statement;
}

/* ══ cta ══════════════════════════════════════════════════════════════════════ */
/* 图的位置（image）—— 由 scripts/block-build/media-layout.js 生成，别在 block.css 里写 */
[data-block="cta"][data-image="left"] .cta-row,
[data-block="cta"][data-image="right"] .cta-row {
  display: flex;
  flex-direction: column;
  gap: 2.5rem;
}
[data-block="cta"][data-image="left"] .cta-row {
  flex-direction: column-reverse;
}
[data-block="cta"][data-image="left"] .cta-main,
[data-block="cta"][data-image="right"] .cta-main,
[data-block="cta"][data-image="left"] .cta-img,
[data-block="cta"][data-image="right"] .cta-img {
  min-width: 0;
}
@media (min-width: 992px) {
  [data-block="cta"][data-image="left"] .cta-row,
  [data-block="cta"][data-image="right"] .cta-row {
    flex-direction: row;
    align-items: center;
    gap: 4rem;
  }
  [data-block="cta"][data-image="left"] .cta-row {
    flex-direction: row-reverse;
  }
  [data-block="cta"][data-image="left"] .cta-img,
  [data-block="cta"][data-image="right"] .cta-img {
    flex: 0 0 calc((100% - 4rem) * 0.5);
  }
  [data-block="cta"][data-image="left"] .cta-main,
  [data-block="cta"][data-image="right"] .cta-main {
    flex: 1 1 0;
  }
}

/* ── cta · 跨形态的规则（#1479）────────────────────────────────────────────────────────────
   cta 的排版由**实际生效的旋钮**决定，写在根元素上：data-layout（centered / inline）· data-frame
   （none / boxed）· data-text-align（center / left / right）· data-image（none / left / right / background）·
   data-form（none / teaser / full）· data-tone（light / dark / brand）。
   六个形态目录都是预设，自己不带几何（理由在各自的 shape.css 里）。

   🔴 Webpixels 的工具类全带 !important（text-muted / bg-primary-subtle / mb-* …）。这里要压过它们的规则
      **也带 !important**，而且选择器从 [data-block="cta"] 起步再加属性 / 类 —— 特异度一定高过工具类的一个类。
   🔴 藏东西这里一条都没有：部件有数据才渲染（Section.tsx），不靠 CSS 藏。
   🔴 盒子的圆角 / 内边距是 Section.tsx 上的工具类（rounded-4 p-12 py-lg-16 px-lg-20 = 1.5rem · 3rem · lg 4rem 5rem，
      同图册 boxed），这里只给它一个默认底色（没写 bg 时）。 */

/* ── 盒子：没写 bg 时的默认底色（写了 bg 由 Section.tsx 的内联样式涂，压过这一条）。 */
[data-block="cta"][data-frame="boxed"] .cta-frame {
  background: var(--scheme-surface-muted);
}

/* ── 图 + 主体：图在哪一边、两栏多宽、上下叠隔多少由 manifest 的 `mediaLayout` 生成（scripts/block-build/media-layout.js，#1536）。
   没有图（none / background）时这一行只有主体一个孩子。 */
[data-block="cta"] .cta-row {
  display: flex;
  flex-direction: column;
}
[data-block="cta"] .cta-img img {
  aspect-ratio: 4 / 3;
}

/* ── 文字块与动作区：centered = 上下（gap 2rem）；inline = ≥992 左右一行（gap 4rem），<992 上下叠。 */
[data-block="cta"] .cta-inline {
  display: flex;
  flex-direction: column;
  gap: 2rem;
}
@media (min-width: 992px) {
  [data-block="cta"][data-layout="inline"] .cta-inline {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    gap: 4rem;
  }
  [data-block="cta"][data-layout="inline"] .cta-text {
    flex: 1 1 0;
    min-width: 0;
  }
  [data-block="cta"][data-layout="inline"] .cta-action {
    flex: 0 0 auto;
  }
  /* 表单在右侧：给它一个上限宽度（组件自己是 w-100 / 一行 flex，auto 宽会被压成最窄）。 */
  [data-block="cta"][data-layout="inline"][data-form="teaser"] .cta-action,
  [data-block="cta"][data-layout="inline"][data-form="full"] .cta-action {
    flex: 0 1 30rem;
    min-width: 0;
  }
}

/* 文字块最后一个元素不带下边距（间距交给上面的 gap）。正文不单独限宽（#1486）。 */
[data-block="cta"] .cta-text > :last-child {
  margin-bottom: 0 !important;
}
/* 正文不单独限宽，跟标题同宽（#1486）。规则留着是因为块前缀 class 都要有一条带声明的规则
   （scripts/block-class-rules.test.js，#1519）。 */
[data-block="cta"] .cta-body {
  max-width: none;
}

/* 表单：共用组件自带 `hro-form mt-6`，这里去掉上边距（间距交给 gap）并限宽 34rem。 */
[data-block="cta"] .cta-action .hro-form {
  margin-top: 0 !important;
  max-width: 34rem;
}

/* ── textAlign：只管文字块。centered 排布下动作区跟着对齐、主体按对齐走位置；inline 下不限宽。
   主体宽度（T6.1 / #1486，Chris 2026-09-29，照 team）：left / right = 100%；center 在 ≥992 最宽 80%、居中，<992 = 100%。 */
[data-block="cta"][data-text-align="center"] .cta-text {
  text-align: center;
}
[data-block="cta"][data-text-align="right"] .cta-text {
  text-align: end;
}
/* width: 100% 不能省：块头住在 flex 列里，两边 margin: auto 会让它缩成内容宽，话短时就到不了 80%（#1486 实测 0.72）。 */
@media (min-width: 992px) {
  [data-block="cta"][data-layout="centered"][data-text-align="center"] .cta-main {
    width: 100%;
    max-width: 80%;
    margin-inline: auto;
  }
}
[data-block="cta"][data-layout="centered"][data-text-align="center"] .cta-ctas,
[data-block="cta"][data-layout="centered"][data-text-align="center"] .hro-form .d-flex {
  justify-content: center;
}
[data-block="cta"][data-layout="centered"][data-text-align="center"] .hro-form {
  margin-inline-start: auto;
  margin-inline-end: auto;
}
[data-block="cta"][data-layout="centered"][data-text-align="right"] .cta-ctas,
[data-block="cta"][data-layout="centered"][data-text-align="right"] .hro-form .d-flex {
  justify-content: flex-end;
}
[data-block="cta"][data-layout="centered"][data-text-align="right"] .hro-form {
  margin-inline-start: auto;
}
/* 图在旁边时主体占满图旁那一列（80% 那条取消、不再居中）。 */
@media (min-width: 992px) {
  [data-block="cta"][data-layout="centered"][data-image="left"] .cta-main,
  [data-block="cta"][data-layout="centered"][data-image="right"] .cta-main {
    max-width: none;
    margin-inline-start: 0;
    margin-inline-end: 0;
  }
}

/* ── 照片铺底：遮罩在 [data-part="bg"] 上（Section.tsx 的内联渐变，55%）。 */
[data-block="cta"] .cta-cover {
  z-index: 0;
}

/* ── 字色按背景亮度（data-tone，scripts/lib/contrast.js §toneForBg）───────────────────────────────
   dark = 深底或照片铺底：标题 / 正文 / eyebrow 反白，outline 按钮翻成白描边。
   brand = 主题主色底：同上，另外主按钮翻成白底主色字（dark 也一样，同 hero）。
   🔴 各块共有的那几条（根上字色 · 标题 · eyebrow · 主按钮 / 描边 / link 按钮反白）住 `scripts/lib/site-css.js`
      §DEEP_COMMON，全站一份（#1533）；这里只留本块特有的，别再抄回来（`scripts/block-deep-common.test.js`）。 */
[data-block="cta"][data-tone="dark"] .text-danger,
[data-block="cta"][data-tone="brand"] .text-danger {
  color: #fecaca !important;
}

/* ── 按钮的底色和字色：接平台按这个站最终配色算好的那几个变量（`scripts/lib/button-ink.js`，同 hero）。
   拿不到这些变量的页面落回 Webpixels 自己的值。 */
[data-block="cta"] .btn-primary {
  --x-btn-bg: var(--btn-primary-bg, var(--x-primary));
  --x-btn-border-color: var(--btn-primary-bg, var(--x-primary));
  --x-btn-color: var(--btn-primary-ink, #fff);
  --x-btn-hover-bg: var(--btn-primary-hover, var(--btn-primary-bg, var(--x-primary)));
  --x-btn-hover-border-color: var(--btn-primary-hover, var(--btn-primary-bg, var(--x-primary)));
  --x-btn-hover-color: var(--btn-primary-ink, #fff);
  --x-btn-active-bg: var(--btn-primary-hover, var(--btn-primary-bg, var(--x-primary)));
  --x-btn-active-color: var(--btn-primary-ink, #fff);
}
[data-block="cta"] .btn-outline-primary {
  --x-btn-color: var(--btn-outline-ink, var(--x-primary));
  --x-btn-border-color: var(--btn-outline-ink, var(--x-primary));
}
[data-block="cta"][data-tone="dark"] .form-control,
[data-block="cta"][data-tone="brand"] .form-control,
[data-block="cta"][data-tone="dark"] .form-select,
[data-block="cta"][data-tone="brand"] .form-select {
  color: #0f172a !important;
  background-color: #fff !important;
  border-color: #fff !important;
}

/* ── 浅底盒子里的表单：输入框本身是浅灰底，跟 boxed 的默认盒底 #f1f5f9 同色会看不出框 —— 盒子里给它白底。
   深底 / 主色底那一档上面已经是白底。 */
[data-block="cta"][data-frame="boxed"][data-tone="light"] .form-control,
[data-block="cta"][data-frame="boxed"][data-tone="light"] .form-select {
  background-color: var(--x-body-bg) !important;
}

/* ── cta · centered（预设 Centered）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：cta 是一份 markup + 五个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-layout` / `data-frame` / `data-text-align` / `data-image` / `data-form` / `data-tone`，
   规则全在 `blocks/cta/block.css`）。按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，
   页面就会排成一个跟旋钮说的不一样的样子。所以这份文件只有这段说明。 */
[data-block="cta"][data-shape="centered"] {
  --cta-preset: centered;
}

/* ── cta · boxed（预设 Boxed）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：cta 是一份 markup + 五个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-layout` / `data-frame` / `data-text-align` / `data-image` / `data-form` / `data-tone`，
   规则全在 `blocks/cta/block.css`）。按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，
   页面就会排成一个跟旋钮说的不一样的样子。所以这份文件只有这段说明。 */
[data-block="cta"][data-shape="boxed"] {
  --cta-preset: boxed;
}

/* ── cta · inline（预设 Inline）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：cta 是一份 markup + 五个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-layout` / `data-frame` / `data-text-align` / `data-image` / `data-form` / `data-tone`，
   规则全在 `blocks/cta/block.css`）。按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，
   页面就会排成一个跟旋钮说的不一样的样子。所以这份文件只有这段说明。 */
[data-block="cta"][data-shape="inline"] {
  --cta-preset: inline;
}

/* ── cta · photo（预设 Photo）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：cta 是一份 markup + 五个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-layout` / `data-frame` / `data-text-align` / `data-image` / `data-form` / `data-tone`，
   规则全在 `blocks/cta/block.css`）。按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，
   页面就会排成一个跟旋钮说的不一样的样子。所以这份文件只有这段说明。 */
[data-block="cta"][data-shape="photo"] {
  --cta-preset: photo;
}

/* ── cta · lead-form（预设 Lead form）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：cta 是一份 markup + 五个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-layout` / `data-frame` / `data-text-align` / `data-image` / `data-form` / `data-tone`，
   规则全在 `blocks/cta/block.css`）。按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，
   页面就会排成一个跟旋钮说的不一样的样子。所以这份文件只有这段说明。 */
[data-block="cta"][data-shape="lead-form"] {
  --cta-preset: lead-form;
}

/* ── cta · cover（预设 Cover）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：cta 是一份 markup + 五个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-layout` / `data-frame` / `data-text-align` / `data-image` / `data-form` / `data-tone`，
   规则全在 `blocks/cta/block.css`）。按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，
   页面就会排成一个跟旋钮说的不一样的样子。所以这份文件只有这段说明。 */
[data-block="cta"][data-shape="cover"] {
  --cta-preset: cover;
}

/* ══ faq ══════════════════════════════════════════════════════════════════════ */
/* 条目网格（itemsColumns：手机 ≤1 · iPad ≤2 · ≥992 按旋钮）—— 由 scripts/block-build/items-grid.js 生成，别在 block.css 里写 */
[data-block="faq"] .fq-grid {
  --items-cols: 1;
  display: flex;
  flex-wrap: wrap;
}
[data-block="faq"] .fq-item {
  flex: 0 0 auto;
  width: calc((100% - (var(--items-cols) - 1) * var(--fq-gap)) / var(--items-cols));
}
@media (min-width: 768px) {
  [data-block="faq"][data-items-columns="2"] .fq-grid {
    --items-cols: 2;
  }
  [data-block="faq"][data-items-columns="3"] .fq-grid {
    --items-cols: 2;
  }
}
@media (min-width: 992px) {
  [data-block="faq"][data-items-columns="3"] .fq-grid {
    --items-cols: 3;
  }
}

/* 块头排版（introAlign / introPosition）—— 由 scripts/block-build/intro-layout.js 生成，别在 block.css 里写 */
[data-block="faq"][data-intro-align="center"] .fq-intro-text {
  text-align: center;
}
[data-block="faq"][data-intro-align="right"] .fq-intro-text {
  text-align: end;
}
[data-block="faq"][data-intro-align="center"] .fq-help {
  margin-inline-start: auto;
  margin-inline-end: auto;
}
[data-block="faq"][data-intro-align="right"] .fq-help {
  margin-inline-start: auto;
}
@media (min-width: 992px) {
  [data-block="faq"][data-intro-align="center"] .fq-intro-text {
    width: 100%;
    max-width: 80%;
    margin-inline: auto;
  }
}
[data-block="faq"][data-intro-position="bottom"] .fq-frame {
  flex-direction: column-reverse;
}
@media (max-width: 991.98px) {
  [data-block="faq"][data-intro-position="right"] .fq-frame {
    flex-direction: column-reverse;
  }
}
@media (min-width: 992px) {
  [data-block="faq"][data-intro-position="left"] .fq-introcol,
  [data-block="faq"][data-intro-position="right"] .fq-introcol {
    flex: 0 0 auto;
    width: 41.6667%;
    position: sticky;
    top: 2rem;
    align-self: flex-start;
  }
  [data-block="faq"][data-intro-position="left"] .fq-itemscol,
  [data-block="faq"][data-intro-position="right"] .fq-itemscol {
    flex: 0 0 auto;
    width: 58.3333%;
  }
  [data-block="faq"][data-intro-position="left"] .fq-intro-text,
  [data-block="faq"][data-intro-position="right"] .fq-intro-text {
    max-width: none;
    margin: 0;
  }
  [data-block="faq"][data-intro-position="right"] .fq-frame {
    flex-direction: row-reverse;
  }
}

/* ── faq · 跨形态的规则（#1484）──────────────────────────────────────────────────────────────
   faq 的排版由**实际生效的旋钮**决定，写在根元素上：
     intro  data-intro-position（left / right / top / bottom）· data-intro-align（left / center / right）
     items  data-items-mode（accordion / open）· data-items-columns（1 / 2 / 3）
     item   data-item-style（divided / card / plain）· data-item-toggle（chevron / plus）
     底色   data-tone（light / dark / brand，scripts/lib/contrast.js §toneForBg）
   四个形态目录都是预设，自己不带几何（理由在各自的 shape.css 里）。规则照定稿图册 faq 段
   （docs/reference/webpixels/gallery/build.py 的 faq CSS，Chris 2026-09-29）。

   🔴 Webpixels 的工具类全带 !important（mb-* / text-muted / d-flex …）。这里要压过它们的规则**也带 !important**，
      而且选择器从 [data-block="faq"] 起步再加属性 / 类 —— 特异度一定高过工具类的一个类。
   🔴 部件的有无靠渲染（Section.tsx 有数据才画），不靠 CSS 藏：accordion / open 两种画法 DOM 里只有一种，
      help 卡、眉标没数据就没有节点。
   🔴 问答的列数由 itemsGrid 生成（#1537，flex + calc，列距读 --fq-gap），不用负边距（milestones #1482 在 390 上量出过负边距溢出 4px）。
   间距照定稿：段 py-16 py-lg-24；块头与问答 gy-10 gx-lg-16（纵向写 gy-10、不写 g-10：g-10 的横向负边距在手机上溢出，
   #1475 量出来的）；问句上下 1.25rem；open 模式行距 2.5rem（card 时 1.5rem）。 */

/* ═══ intro（块头 + help 卡）═════════════════════════════════════════════════════════════════════ */

[data-block="faq"] .fq-intro-text > :last-child {
  margin-bottom: 0 !important;
}
/* 正文不单独限宽，跟标题同宽（#1486）。规则留着是因为块前缀 class 都要有一条带声明的规则
   （scripts/block-class-rules.test.js，#1519）。 */
[data-block="faq"] .fq-body {
  max-width: none;
}

/* help 卡：挨着块头，在块头那一列最下面；最宽 28rem；跟着 introAlign 走位置（下面 introAlign 那一段）。 */
[data-block="faq"] .fq-help {
  background: var(--scheme-surface-muted);
  max-width: 28rem;
  margin-top: 2rem;
}
[data-block="faq"] .fq-help:first-child {
  margin-top: 0;
}
[data-block="faq"] .fq-help-cta {
  margin-top: 1rem;
}

/* 块头排版（introAlign / introPosition：对齐、≥992 center 最宽 80%、左右两列、上下反向）由 manifest 的 introLayout 生成
   （scripts/block-build/intro-layout.js，#1535），这里不写。
   help 卡跟着 introAlign 走位置（introLayout.follow）；块头在侧列时 help 卡贴列的左边（同图册）—— 下面这条是 faq 自己的。 */
@media (min-width: 992px) {
  [data-block="faq"][data-intro-position="left"] .fq-help,
  [data-block="faq"][data-intro-position="right"] .fq-help {
    margin-inline-start: 0;
  }
}

/* ═══ items ════════════════════════════════════════════════════════════════════════════════════════ */

/* itemsColumns 的列数与断点（手机一列；≥768 时 2 / 3 都是两列；≥992 按列数）归 manifest 的 itemsGrid，
   由 scripts/block-build/items-grid.js 生成（#1537）。这里只管列距 / 行距。 */
[data-block="faq"] .fq-grid {
  --fq-gap: 1.5rem;
  column-gap: var(--fq-gap);
  row-gap: 0;
}
[data-block="faq"] .fq-item {
  min-width: 0;
}

/* 问答永远占满整列、不跟 introAlign —— 块头文字和 help 卡才跟（Chris 2026-10-01，#1515）。
   原来这里有三条规则：1 列且块头在上 / 下时限 48rem、再按 introAlign 居中或贴右。那是 2026-09-29 的老写法，
   跟后来定的两条规矩冲突（条目从不跟 introAlign、内容占满整列，content / blog 已经这样）。 */

/* itemsMode：accordion = 原生 details（点问句开合）；open = 问句 <h3> + 答案 <p> 直接写出来。行距见 itemStyle。 */
[data-block="faq"] summary {
  list-style: none;
  cursor: pointer;
  padding: 1.25rem 0;
  font-size: 1.0625rem;
}
[data-block="faq"] summary::-webkit-details-marker {
  display: none;
}
[data-block="faq"] .fq-a {
  padding: 0 0 1.25rem;
  font-size: 0.9375rem;
  line-height: 1.65;
}
[data-block="faq"] .fq-open .fq-a {
  padding: 0;
}
[data-block="faq"] .fq-open .fq-q {
  font-size: 1.0625rem;
}
[data-block="faq"][data-items-mode="open"] .fq-grid {
  row-gap: 2.5rem;
}

/* itemToggle（只管 accordion）：chevron 展开时转 180°；plus 展开时换成 minus（Section.tsx 画 plus + dash 两个，这里换着显示）。 */
[data-block="faq"] .fq-toggle {
  font-size: 1.125rem;
}
[data-block="faq"] .fq-toggle svg {
  transition: transform 0.2s;
}
[data-block="faq"] details[open] .fq-chev {
  transform: rotate(180deg);
}
[data-block="faq"] .fq-minus,
[data-block="faq"] details[open] .fq-plus {
  display: none;
}
[data-block="faq"] details[open] .fq-minus {
  display: inline;
}

/* ═══ itemStyle ════════════════════════════════════════════════════════════════════════════════════
   divided = 条与条之间 1px 横线（1 列时第一条上面也有一根）；card = 每条白底 1px 描边、1rem 圆角、条间 .75rem；
   plain = 无线无框。 */
[data-block="faq"][data-item-style="divided"] .fq-inner {
  border-bottom: 1px solid var(--x-border-color, #e5e7eb);
}
[data-block="faq"][data-item-style="divided"][data-items-columns="1"] .fq-item:first-child .fq-inner {
  border-top: 1px solid var(--x-border-color, #e5e7eb);
}
[data-block="faq"][data-item-style="divided"][data-items-mode="open"] .fq-open {
  border-top: 1px solid var(--x-border-color, #e5e7eb);
  padding-top: 1.25rem;
}
[data-block="faq"][data-item-style="card"][data-items-mode="accordion"] .fq-grid {
  row-gap: 0.75rem;
}
[data-block="faq"][data-item-style="card"] .fq-inner,
[data-block="faq"][data-item-style="card"] .fq-open {
  background: var(--x-body-bg);
  border: 1px solid var(--x-border-color, #e5e7eb);
  border-radius: 1rem;
  padding: 0 1.5rem;
}
[data-block="faq"][data-item-style="card"] .fq-open {
  padding: 1.5rem;
}
[data-block="faq"][data-item-style="card"][data-items-mode="open"] .fq-grid {
  row-gap: 1.5rem;
}
[data-block="faq"][data-item-style="plain"][data-items-mode="accordion"] .fq-grid {
  row-gap: 0.25rem;
}

/* ═══ 字色按背景亮度（data-tone，scripts/lib/contrast.js §toneForBg）══════════════════════════════
   dark = 深底 / 深色渐变；brand = 主题主色底。两者一样：标题 / 问句反白、答案白 .92（不是灰 —— 那条规则全站只有一份，
   住 scripts/lib/site-css.js §ON_DEEP_MUTED，#1477；这里不再抄）、横线和卡描边换半透明白、card 底 .06、help 卡底 .08、
   主按钮白底（dark 深字、brand 主色字）。
   🔴 各块共有的那几条（根上字色 · 标题 · eyebrow · 主按钮 / 描边 / link 按钮反白）住 `scripts/lib/site-css.js`
      §DEEP_COMMON，全站一份（#1533）；这里只留本块特有的，别再抄回来（`scripts/block-deep-common.test.js`）。 */

[data-block="faq"][data-tone="dark"] .fq-q,
[data-block="faq"][data-tone="brand"] .fq-q,
[data-block="faq"][data-tone="dark"] .fq-help-title,
[data-block="faq"][data-tone="brand"] .fq-help-title {
  color: #fff !important;
}
[data-block="faq"][data-tone="dark"] .fq-inner,
[data-block="faq"][data-tone="brand"] .fq-inner,
[data-block="faq"][data-tone="dark"] .fq-open,
[data-block="faq"][data-tone="brand"] .fq-open {
  border-color: rgba(255, 255, 255, 0.2) !important;
}
[data-block="faq"][data-tone="dark"][data-item-style="card"] .fq-inner,
[data-block="faq"][data-tone="brand"][data-item-style="card"] .fq-inner,
[data-block="faq"][data-tone="dark"][data-item-style="card"] .fq-open,
[data-block="faq"][data-tone="brand"][data-item-style="card"] .fq-open {
  background: rgba(255, 255, 255, 0.06);
}
[data-block="faq"][data-tone="dark"] .fq-help,
[data-block="faq"][data-tone="brand"] .fq-help {
  background: rgba(255, 255, 255, 0.08);
}

/* ── 按钮的底色和字色：接平台按这个站最终配色算好的那几个变量（`scripts/lib/button-ink.js`，同 features / milestones）。
   轮廓按钮 / 纯文字链接用 `--x-primary-text-emphasis`，理由见 features/block.css 同一段（浅主题色压白底读不出来）。 */
[data-block="faq"] .btn-primary {
  --x-btn-bg: var(--btn-primary-bg, var(--x-primary));
  --x-btn-border-color: var(--btn-primary-bg, var(--x-primary));
  --x-btn-color: var(--btn-primary-ink, #fff);
  --x-btn-hover-bg: var(--btn-primary-hover, var(--btn-primary-bg, var(--x-primary)));
  --x-btn-hover-border-color: var(--btn-primary-hover, var(--btn-primary-bg, var(--x-primary)));
  --x-btn-hover-color: var(--btn-primary-ink, #fff);
  --x-btn-active-bg: var(--btn-primary-hover, var(--btn-primary-bg, var(--x-primary)));
  --x-btn-active-color: var(--btn-primary-ink, #fff);
}
[data-block="faq"] .btn-outline-primary {
  --x-btn-color: var(--x-primary-text-emphasis, var(--x-primary));
  --x-btn-border-color: var(--x-primary-text-emphasis, var(--x-primary));
}
[data-block="faq"] .btn-link {
  --x-btn-color: var(--x-primary-text-emphasis, var(--x-primary));
  --x-btn-hover-color: var(--x-primary-text-emphasis, var(--x-primary));
  --x-btn-active-color: var(--x-primary-text-emphasis, var(--x-primary));
}

/* ── faq · accordion（预设 Accordion）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：faq 是一份 markup + 六个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-items-*` / `data-item-*` / `data-tone`，规则全在 `blocks/faq/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="faq"][data-shape="accordion"] {
  --fq-preset: accordion;
}

/* ── faq · side-intro（预设 Side intro）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：faq 是一份 markup + 六个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-items-*` / `data-item-*` / `data-tone`，规则全在 `blocks/faq/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="faq"][data-shape="side-intro"] {
  --fq-preset: side-intro;
}

/* ── faq · cards（预设 Cards）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：faq 是一份 markup + 六个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-items-*` / `data-item-*` / `data-tone`，规则全在 `blocks/faq/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="faq"][data-shape="cards"] {
  --fq-preset: cards;
}

/* ── faq · open-grid（预设 Open grid）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：faq 是一份 markup + 六个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-items-*` / `data-item-*` / `data-tone`，规则全在 `blocks/faq/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="faq"][data-shape="open-grid"] {
  --fq-preset: open-grid;
}

/* ══ features ══════════════════════════════════════════════════════════════════════ */
/* 条目网格（itemsColumns：手机 ≤1 · iPad ≤2 · ≥992 按旋钮）—— 由 scripts/block-build/items-grid.js 生成，别在 block.css 里写 */
[data-block="features"] .fx-grid {
  --items-cols: 1;
}
[data-block="features"] .fx-item {
  flex: 0 0 auto;
  width: 100%;
}
@media (min-width: 768px) {
  [data-block="features"][data-items-columns="2"] .fx-grid {
    --items-cols: 2;
  }
  [data-block="features"][data-items-columns="2"] .fx-item {
    width: 50%;
  }
  [data-block="features"][data-items-columns="3"] .fx-grid {
    --items-cols: 2;
  }
  [data-block="features"][data-items-columns="3"] .fx-item {
    width: 50%;
  }
  [data-block="features"][data-items-columns="4"] .fx-grid {
    --items-cols: 2;
  }
  [data-block="features"][data-items-columns="4"] .fx-item {
    width: 50%;
  }
}
@media (min-width: 992px) {
  [data-block="features"][data-items-columns="3"] .fx-grid {
    --items-cols: 3;
  }
  [data-block="features"][data-items-columns="3"] .fx-item {
    width: 33.3333%;
  }
  [data-block="features"][data-items-columns="4"] .fx-grid {
    --items-cols: 4;
  }
  [data-block="features"][data-items-columns="4"] .fx-item {
    width: 25%;
  }
}

/* 图的位置（introImage / itemsImage）—— 由 scripts/block-build/media-layout.js 生成，别在 block.css 里写 */
[data-block="features"][data-intro-image="left"] .fx-intro,
[data-block="features"][data-intro-image="right"] .fx-intro,
[data-block="features"][data-intro-image="top"] .fx-intro,
[data-block="features"][data-intro-image="bottom"] .fx-intro {
  display: flex;
  flex-direction: column;
  gap: 2.5rem;
}
[data-block="features"][data-intro-image="left"] .fx-intro,
[data-block="features"][data-intro-image="top"] .fx-intro {
  flex-direction: column-reverse;
}
[data-block="features"][data-intro-image="left"] .fx-intro-text,
[data-block="features"][data-intro-image="right"] .fx-intro-text,
[data-block="features"][data-intro-image="top"] .fx-intro-text,
[data-block="features"][data-intro-image="bottom"] .fx-intro-text,
[data-block="features"][data-intro-image="left"] .fx-intro-img,
[data-block="features"][data-intro-image="right"] .fx-intro-img,
[data-block="features"][data-intro-image="top"] .fx-intro-img,
[data-block="features"][data-intro-image="bottom"] .fx-intro-img {
  min-width: 0;
}
@media (min-width: 992px) {
  [data-block="features"][data-intro-image="left"] .fx-intro,
  [data-block="features"][data-intro-image="right"] .fx-intro {
    flex-direction: row;
    align-items: center;
    gap: 4rem;
  }
  [data-block="features"][data-intro-image="left"] .fx-intro {
    flex-direction: row-reverse;
  }
  [data-block="features"][data-intro-image="left"] .fx-intro-img,
  [data-block="features"][data-intro-image="right"] .fx-intro-img {
    flex: 0 0 calc((100% - 4rem) * 0.5);
  }
  [data-block="features"][data-intro-image="left"] .fx-intro-text,
  [data-block="features"][data-intro-image="right"] .fx-intro-text {
    flex: 1 1 0;
  }
}
[data-block="features"][data-items-image="left"] .fx-itemsrow,
[data-block="features"][data-items-image="right"] .fx-itemsrow {
  display: flex;
  flex-direction: column;
  gap: 2.5rem;
}
[data-block="features"][data-items-image="left"] .fx-itemsrow {
  flex-direction: column-reverse;
}
[data-block="features"][data-items-image="left"] .fx-itemsgrid,
[data-block="features"][data-items-image="right"] .fx-itemsgrid,
[data-block="features"][data-items-image="left"] .fx-itemsimg,
[data-block="features"][data-items-image="right"] .fx-itemsimg {
  min-width: 0;
}
@media (min-width: 992px) {
  [data-block="features"][data-items-image="left"] .fx-itemsrow,
  [data-block="features"][data-items-image="right"] .fx-itemsrow {
    flex-direction: row;
    align-items: center;
    gap: 4rem;
  }
  [data-block="features"][data-items-image="left"] .fx-itemsrow {
    flex-direction: row-reverse;
  }
  [data-block="features"][data-items-image="left"] .fx-itemsimg,
  [data-block="features"][data-items-image="right"] .fx-itemsimg {
    flex: 0 0 calc((100% - 4rem) * 0.5);
  }
  [data-block="features"][data-items-image="left"] .fx-itemsgrid,
  [data-block="features"][data-items-image="right"] .fx-itemsgrid {
    flex: 1 1 0;
  }
}

/* 块头排版（introAlign / introPosition）—— 由 scripts/block-build/intro-layout.js 生成，别在 block.css 里写 */
[data-block="features"][data-intro-align="center"] .fx-intro-text {
  text-align: center;
}
[data-block="features"][data-intro-align="right"] .fx-intro-text {
  text-align: end;
}
[data-block="features"][data-intro-align="center"] .fx-ctas {
  justify-content: center;
}
[data-block="features"][data-intro-align="right"] .fx-ctas {
  justify-content: flex-end;
}
@media (min-width: 992px) {
  [data-block="features"][data-intro-align="center"] .fx-intro-text {
    width: 100%;
    max-width: 80%;
    margin-inline: auto;
  }
}
[data-block="features"][data-intro-position="bottom"] .fx-frame {
  flex-direction: column-reverse;
}
@media (max-width: 991.98px) {
  [data-block="features"][data-intro-position="right"] .fx-frame {
    flex-direction: column-reverse;
  }
}
@media (min-width: 992px) {
  [data-block="features"][data-intro-position="left"] .fx-introcol,
  [data-block="features"][data-intro-position="right"] .fx-introcol {
    flex: 0 0 auto;
    width: 33.3333%;
    position: sticky;
    top: 2rem;
    align-self: flex-start;
  }
  [data-block="features"][data-intro-position="left"] .fx-itemscol,
  [data-block="features"][data-intro-position="right"] .fx-itemscol {
    flex: 0 0 auto;
    width: 66.6667%;
  }
  [data-block="features"][data-intro-position="left"] .fx-intro-text,
  [data-block="features"][data-intro-position="right"] .fx-intro-text {
    max-width: none;
    margin: 0;
  }
  [data-block="features"][data-intro-position="right"] .fx-frame {
    flex-direction: row-reverse;
  }
}

/* ── features · 跨形态的规则（#1475）──────────────────────────────────────────────────────────
   features 的排版由**实际生效的旋钮**决定，写在根元素上：
     intro  data-intro-position（left / right / top / bottom）· data-intro-align（left / center / right）·
            data-intro-image（none / left / right / top / bottom）
     items  data-items-layout（grid / list）· data-items-columns（2 / 3 / 4，只管 grid）· data-items-image（none / left / right）
     item   data-item-style（plain / card）· data-item-align · data-item-icon（top / left / right / none）·
            data-item-image（none / left / right / top / bottom / background）· data-item-connector（none / line）
     底色   data-tone（light / dark / brand，scripts/lib/contrast.js §toneForBg）
   七个形态目录都是预设，自己不带几何（理由在各自的 shape.css 里）。旋钮彼此独立：这里没有一条规则
   按「另一个旋钮是什么」去改这个旋钮的效果（定稿 2026-09-29 删掉了图册里 Side intro 强制两列那一条）。

   🔴 Webpixels 的工具类全带 !important（mb-* / text-muted / bg-primary-subtle …）。这里要压过它们的规则
      **也带 !important**，而且选择器从 [data-block="features"] 起步再加属性 / 类 —— 特异度一定高过工具类的一个类。
   🔴 部件的有无靠渲染（Section.tsx 有数据才画），不靠 CSS 藏。这里唯一「藏」的是连线在一行最后一项上的那一截
      （它按构造没有下一项可连）。
   🔴 条目里图与内容上下叠时的 2.5rem 用 margin 给，不用 g-*：Bootstrap 的 g-* 只对 .row 起作用，
      .fx-inner 是普通 flex，写 g-* 等于没写（图册里那几处上下叠时就是贴在一起的）。块头图 / items 旁那张图
      的间距不归这里，由 manifest 的 `mediaLayout` 生成（#1536）。
   间距照 Webpixels 尺度：段 py-16 py-lg-24、块头与 items 之间 2.5rem（gy-10）/ ≥992 横向 4rem（gx-lg-16）、items g-6、
   card 内 2rem（= p-8）。🔴 纵向写 gy-10、不写 g-10：g-10 连横向也设成 2.5rem，.row 的负边距 −1.25rem 比容器内边距 1rem 大，
   手机上每边溢出 4px（390 宽读到 scrollWidth 394，#1475 e2e 阳性对照那一格量出来的；图册 g-10 同款）。 */

/* ═══ intro（块头）═══════════════════════════════════════════════════════════════════════════════ */

[data-block="features"] .fx-intro {
  display: flex;
  flex-direction: column;
}
[data-block="features"] .fx-intro-text > :last-child {
  margin-bottom: 0 !important;
}
/* 正文不单独限宽，跟标题同宽（#1486）。规则留着是因为块前缀 class 都要有一条带声明的规则
   （scripts/block-class-rules.test.js，#1519）。 */
[data-block="features"] .fx-body {
  max-width: none;
}
[data-block="features"] .fx-ctas {
  margin-top: 2rem;
}

/* 块头排版（introAlign / introPosition：对齐、≥992 center 最宽 80%、左右两列、上下反向）由 manifest 的 introLayout 生成
   （scripts/block-build/intro-layout.js，#1535），这里不写。
   按钮行跟着 introAlign 走（introLayout.follow）。 */

/* introImage / itemsImage：图在哪一边、两栏多宽、上下叠隔多少由 manifest 的 `mediaLayout` 生成
   （scripts/block-build/media-layout.js，#1536）。这里只剩图自己的样子：4:3；块头图 top / bottom 时限 64rem 居中（同图册）；
   并排时块头的字不吃 introAlign 的 80% 限宽（那一栏本来就只有一半宽）。 */
[data-block="features"] .fx-intro-img img,
[data-block="features"] .fx-itemsimg img {
  aspect-ratio: 4 / 3;
}
[data-block="features"][data-intro-image="top"] .fx-intro-img,
[data-block="features"][data-intro-image="bottom"] .fx-intro-img {
  width: 100%;
  max-width: 64rem;
  margin-inline-start: auto;
  margin-inline-end: auto;
}
@media (min-width: 992px) {
  [data-block="features"][data-intro-image="left"] .fx-intro-text,
  [data-block="features"][data-intro-image="right"] .fx-intro-text {
    max-width: none;
  }
}

/* introPosition left / right（≥992）：块头那一列窄，里面的图一律叠在文字上面（同图册）。两列本身见上面那句。 */
@media (min-width: 992px) {
  [data-block="features"][data-intro-position="left"] .fx-intro,
  [data-block="features"][data-intro-position="right"] .fx-intro {
    flex-direction: column-reverse !important;
    align-items: stretch;
    gap: 2.5rem;
  }
  [data-block="features"][data-intro-position="left"] .fx-intro-img,
  [data-block="features"][data-intro-position="right"] .fx-intro-img {
    flex: none;
    margin-inline: 0;
  }
}

/* ═══ items（整组）═══════════════════════════════════════════════════════════════════════════════ */

/* grid：列数与断点（手机 1 列、iPad 2 列、≥992 按 itemsColumns）归 manifest 的 itemsGrid，由 scripts/block-build/items-grid.js
   生成（#1537；画法照旧：.fx-grid 是 Bootstrap 的 `.row g-6`，条目按百分比宽，列距由 row 自己的内边距给）。
   列数在 --items-cols 里，连线按它算长度（见下）。 */
[data-block="features"] .fx-grid {
  container-type: inline-size;
}

/* list：一项一行整宽（itemsColumns 不管它）；项目没有图时限 52rem、有图时占全宽；
   位置只听 itemAlign（跟 introAlign 无关）。 */
[data-block="features"][data-items-layout="list"] .fx-grid {
  --x-gutter-y: 2.5rem;
}
[data-block="features"][data-items-layout="list"] .fx-grid > .fx-item {
  width: 100% !important;
}
[data-block="features"][data-items-layout="list"][data-item-image="none"] .fx-grid > .fx-item {
  max-width: 52rem;
}
[data-block="features"][data-items-layout="list"][data-item-align="center"] .fx-grid {
  justify-content: center;
}
[data-block="features"][data-items-layout="list"][data-item-align="right"] .fx-grid {
  justify-content: flex-end;
}

/* ═══ item（每一项）═════════════════════════════════════════════════════════════════════════════ */

[data-block="features"] .fx-inner {
  display: flex;
  flex-direction: column;
}
[data-block="features"] .fx-content {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
}

/* itemStyle=card：白底 + 1px 描边 + 1rem 圆角，内容 2rem 内边距。只有这一项有图时才裁圆角
   （裁了的话，连线那一截会被卡片边缘截掉）。 */
[data-block="features"] .fx-grid {
  --fx-pad: 0px;
}
[data-block="features"][data-item-style="card"] .fx-grid {
  --fx-pad: calc(2rem + 1px);
}
[data-block="features"][data-item-style="card"] .fx-inner {
  background: var(--x-body-bg);
  border: 1px solid var(--x-border-color, #e5e7eb);
  border-radius: 1rem;
}
[data-block="features"][data-item-style="card"]:not([data-item-image="none"]) .fx-inner {
  overflow: hidden;
}
[data-block="features"][data-item-style="card"] .fx-content {
  padding: 2rem;
}

/* itemAlign：只管 items 里的文字与标记。 */
[data-block="features"][data-item-align="center"] .fx-content {
  text-align: center;
  align-items: center;
}
[data-block="features"][data-item-align="right"] .fx-content {
  text-align: end;
  align-items: flex-end;
}

/* 条目的小清单（items[].bullets，#1527）：每行一个勾号 + 文字，同 pricing 的功能清单。字色跟正文同一个类（text-muted）⟹
   深底 / 背景图上的反白直接沿用那两条规则；勾号自己的主色在那两种底上也翻白（下面 data-tone 段 + background 段）。
   center / right 时 .fx-content 的 align-items 把整个清单推过去，行内仍然是勾号在前。 */
[data-block="features"] .fx-bullets > li + li {
  margin-top: 0.5rem;
}
[data-block="features"] .fx-check {
  margin-top: 0.2rem;
  color: var(--x-primary-text-emphasis, var(--x-primary));
}
[data-block="features"][data-item-image="background"] .fx-check,
[data-block="features"][data-tone="dark"] .fx-check,
[data-block="features"][data-tone="brand"] .fx-check {
  color: #fff;
}

/* 标记（number / icon）：3rem 的方块；两个都有时并排。 */
[data-block="features"] .fx-mark {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex: 0 0 auto;
  /* 只占两个徽标那么宽（.fx-content 是 stretch 的列，不写的话它被拉满一整行，横向连线就从行尾才开始）；
     位置跟着 itemAlign 走（center / right 时 .fx-content 的 align-items 把它推过去）。 */
  width: max-content;
  --fx-mark-w: 3rem;
}
[data-block="features"] .fx-mark[data-mark="2"] {
  --fx-mark-w: 6.75rem;
}
[data-block="features"] .fx-icon,
[data-block="features"] .fx-number {
  width: 3rem;
  height: 3rem;
  flex: 0 0 auto;
}
[data-block="features"] .fx-icon {
  font-size: 1.5rem;
}
[data-block="features"] .fx-number {
  font-size: 1.125rem;
}

/* itemIcon：left / right 时标记在文字旁（<768 回到上面）；top / none 时在上。 */
@media (min-width: 768px) {
  [data-block="features"][data-item-icon="left"] .fx-content,
  [data-block="features"][data-item-icon="right"] .fx-content {
    flex-direction: row;
    align-items: flex-start;
  }
  [data-block="features"][data-item-icon="right"] .fx-content {
    flex-direction: row-reverse;
  }
  [data-block="features"][data-item-icon="right"] .fx-text {
    text-align: end;
  }
  [data-block="features"][data-item-icon="left"] .fx-text,
  [data-block="features"][data-item-icon="right"] .fx-text {
    flex: 1 1 0;
    min-width: 0;
  }
}
[data-block="features"][data-items-layout="list"][data-item-icon="left"] .fx-icon,
[data-block="features"][data-items-layout="list"][data-item-icon="right"] .fx-icon,
[data-block="features"][data-items-layout="list"][data-item-icon="left"] .fx-number,
[data-block="features"][data-items-layout="list"][data-item-icon="right"] .fx-number {
  width: 2.5rem;
  height: 2.5rem;
}
[data-block="features"][data-items-layout="list"][data-item-icon="left"] .fx-mark,
[data-block="features"][data-items-layout="list"][data-item-icon="right"] .fx-mark {
  --fx-mark-w: 2.5rem;
}

/* itemImage（每项的图）：top 图在上、bottom 图在下（plain 时图自己圆角并跟文字隔 1.25rem；card 时贴卡片边）。 */
[data-block="features"] .fx-img {
  aspect-ratio: 4 / 3;
  overflow: hidden;
}
[data-block="features"][data-item-image="bottom"] .fx-inner {
  flex-direction: column-reverse;
}
[data-block="features"][data-item-style="plain"][data-item-image="top"] .fx-img {
  border-radius: 1rem;
  margin-bottom: 1.25rem;
}
[data-block="features"][data-item-style="plain"][data-item-image="bottom"] .fx-img {
  border-radius: 1rem;
  margin-top: 1.25rem;
}
[data-block="features"][data-items-layout="list"][data-item-image="top"] .fx-img,
[data-block="features"][data-items-layout="list"][data-item-image="bottom"] .fx-img {
  aspect-ratio: auto;
  max-height: 22rem;
}

/* left / right：<992 图在上、跟文字隔 2.5rem（plain 用图的下边距；card 用内容的上内边距 —— 图贴卡片顶边）；
   ≥992 并排、图 40%。 */
[data-block="features"][data-item-style="plain"][data-item-image="left"] .fx-img,
[data-block="features"][data-item-style="plain"][data-item-image="right"] .fx-img {
  border-radius: 1rem;
  margin-bottom: 2.5rem;
}
[data-block="features"][data-item-style="card"][data-item-image="left"] .fx-content,
[data-block="features"][data-item-style="card"][data-item-image="right"] .fx-content {
  padding-top: 2.5rem;
}
@media (min-width: 992px) {
  [data-block="features"][data-item-image="left"] .fx-inner,
  [data-block="features"][data-item-image="right"] .fx-inner {
    flex-direction: row;
  }
  [data-block="features"][data-item-image="right"] .fx-inner {
    flex-direction: row-reverse;
  }
  [data-block="features"][data-item-image="left"] .fx-img,
  [data-block="features"][data-item-image="right"] .fx-img {
    flex: 0 0 40%;
    aspect-ratio: auto;
    min-height: 12rem;
  }
  [data-block="features"][data-item-image="left"] .fx-content,
  [data-block="features"][data-item-image="right"] .fx-content {
    flex: 1 1 0;
    min-width: 0;
  }
  [data-block="features"][data-item-style="plain"][data-item-image="left"] .fx-img {
    margin-block: 0;
    margin-inline: 0 1.25rem;
  }
  [data-block="features"][data-item-style="plain"][data-item-image="right"] .fx-img {
    margin-block: 0;
    margin-inline: 1.25rem 0;
  }
  [data-block="features"][data-item-style="card"][data-item-image="left"] .fx-content,
  [data-block="features"][data-item-style="card"][data-item-image="right"] .fx-content {
    padding-top: 2rem;
  }
}

/* background：图铺底 + 底部深色渐变，文字靠下、反白（= Bootstrap custom cards；icon 由 Section.tsx 不渲染）。 */
[data-block="features"][data-item-image="background"] .fx-inner {
  position: relative;
  min-height: 18rem;
  border-radius: 1rem;
  overflow: hidden;
  color: #fff;
}
[data-block="features"][data-item-image="background"] .fx-img {
  position: absolute;
  inset: 0;
  aspect-ratio: auto;
}
[data-block="features"][data-item-image="background"] .fx-img::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgba(2, 6, 23, 0.85), rgba(2, 6, 23, 0.15));
}
[data-block="features"][data-item-image="background"] .fx-content {
  position: relative;
  z-index: 1;
  padding: 2rem;
  justify-content: flex-end;
  flex: 1 1 auto;
}
[data-block="features"][data-item-image="background"] .fx-content .fx-item-title,
[data-block="features"][data-item-image="background"] .fx-content .text-muted,
[data-block="features"][data-item-image="background"] .fx-content .btn-link {
  color: #fff !important;
}
[data-block="features"][data-item-image="background"] .fx-number {
  background: rgba(255, 255, 255, 0.14) !important;
  color: #fff !important;
}

/* ═══ 连线（itemConnector=line，Section.tsx 只在有编号时画）══════════════════════════════════════
   grid：从这一项的标记右边横着连到下一项的标记。下一项的标记恰好在「一项的宽度」之外，而一项的宽度 =
         容器宽 / 列数（.fx-grid 是尺寸容器，100cqw 就是它的宽），所以长度 = 100cqw / 列数 − 标记宽 − 两段留白。
         一行最后一项没有「右边那一项」⟹ 那一截不画；手机一列时横线没有意义，也不画。
   list：从标记下面竖着连到下一项的标记（跨过行间距）。 */
[data-block="features"] .fx-connector {
  position: absolute;
  display: block;
  background: var(--x-border-color, #e5e7eb);
}
[data-block="features"][data-items-layout="grid"] .fx-mark {
  position: relative;
}
[data-block="features"][data-items-layout="grid"] .fx-connector {
  top: calc(50% - 1px);
  height: 2px;
  inset-inline-start: calc(100% + 0.75rem);
  width: calc(100cqw / var(--items-cols) - var(--fx-mark-w) - 1.5rem);
}
[data-block="features"][data-items-layout="grid"] .fx-connector {
  display: none;
}
@media (min-width: 768px) {
  [data-block="features"][data-items-layout="grid"] .fx-grid .fx-connector {
    display: block;
  }
}
@media (min-width: 768px) and (max-width: 991.98px) {
  [data-block="features"][data-items-layout="grid"] .fx-grid > .fx-item:nth-child(2n) .fx-connector {
    display: none;
  }
}
@media (min-width: 992px) {
  [data-block="features"][data-items-layout="grid"][data-items-columns="2"] .fx-grid > .fx-item:nth-child(2n) .fx-connector,
  [data-block="features"][data-items-layout="grid"][data-items-columns="3"] .fx-grid > .fx-item:nth-child(3n) .fx-connector,
  [data-block="features"][data-items-layout="grid"][data-items-columns="4"] .fx-grid > .fx-item:nth-child(4n) .fx-connector {
    display: none;
  }
}
[data-block="features"][data-items-layout="list"] .fx-item {
  position: relative;
}
/* list 的竖线挂在序号（标记里的第一个徽标，2.5rem）正下方，所以只在标记排在文字旁边时画（≥768 且 itemIcon
   left / right）：标记在文字上方时（top / none，以及 <768 时 left / right 退回上方），任何连到下一项序号的竖线
   都必然从文字中间穿过 ⟹ 不画（同 grid 手机一列不画横线，#1493）。right 时标记在行尾，序号是它的第一个徽标，
   所以从右边量：两个徽标并排时标记宽 2.5rem × 2 + 0.75rem 间距。 */
[data-block="features"][data-items-layout="list"] .fx-connector {
  display: none;
  width: 2px;
  top: calc(var(--fx-pad) + 2.5rem + 0.5rem);
  bottom: calc(0.5rem - var(--x-gutter-y) - var(--fx-pad));
}
@media (min-width: 768px) {
  [data-block="features"][data-items-layout="list"][data-item-icon="left"] .fx-connector,
  [data-block="features"][data-items-layout="list"][data-item-icon="right"] .fx-connector {
    display: block;
  }
  [data-block="features"][data-items-layout="list"][data-item-icon="left"] .fx-connector {
    inset-inline-start: calc(var(--x-gutter-x) * 0.5 + var(--fx-pad) + 1.25rem - 1px);
  }
  [data-block="features"][data-items-layout="list"][data-item-icon="right"] .fx-connector {
    inset-inline-end: calc(var(--x-gutter-x) * 0.5 + var(--fx-pad) + 2.5rem - 1.25rem - 1px);
  }
  [data-block="features"][data-items-layout="list"][data-item-icon="right"] .fx-mark[data-mark="2"] .fx-connector {
    inset-inline-end: calc(var(--x-gutter-x) * 0.5 + var(--fx-pad) + 5.75rem - 1.25rem - 1px);
  }
}

/* ═══ 字色按背景亮度（data-tone，scripts/lib/contrast.js §toneForBg）══════════════════════════════
   dark = 深底 / 深色渐变；brand = 主题主色底。两者一样：标题与项目标题反白、正文白 .92（不是灰 —— 那条规则全站
   只有一份，住 scripts/lib/site-css.js §ON_DEEP_MUTED，#1477；这里不再抄）、
   card 描边换半透明白、标记底换半透明白；outline 按钮翻成白描边、主按钮白底主色字（同 cta）。
   🔴 各块共有的那几条（根上字色 · 标题 · eyebrow · 主按钮 / 描边 / link 按钮反白）住 `scripts/lib/site-css.js`
      §DEEP_COMMON，全站一份（#1533）；这里只留本块特有的，别再抄回来（`scripts/block-deep-common.test.js`）。 */

[data-block="features"][data-tone="dark"] .fx-item-title,
[data-block="features"][data-tone="brand"] .fx-item-title {
  color: #fff !important;
}
[data-block="features"][data-tone="dark"][data-item-style="card"] .fx-inner,
[data-block="features"][data-tone="brand"][data-item-style="card"] .fx-inner {
  background: rgba(255, 255, 255, 0.06);
  border-color: rgba(255, 255, 255, 0.15);
}
[data-block="features"][data-tone="dark"] .fx-icon,
[data-block="features"][data-tone="brand"] .fx-icon,
[data-block="features"][data-tone="dark"] .fx-number,
[data-block="features"][data-tone="brand"] .fx-number {
  background: rgba(255, 255, 255, 0.14) !important;
  color: #fff !important;
}
[data-block="features"][data-tone="dark"] .fx-connector,
[data-block="features"][data-tone="brand"] .fx-connector {
  background: rgba(255, 255, 255, 0.3);
}

/* ── 按钮的底色和字色：接平台按这个站最终配色算好的那几个变量（`scripts/lib/button-ink.js`，同 cta）。 */
[data-block="features"] .btn-primary {
  --x-btn-bg: var(--btn-primary-bg, var(--x-primary));
  --x-btn-border-color: var(--btn-primary-bg, var(--x-primary));
  --x-btn-color: var(--btn-primary-ink, #fff);
  --x-btn-hover-bg: var(--btn-primary-hover, var(--btn-primary-bg, var(--x-primary)));
  --x-btn-hover-border-color: var(--btn-primary-hover, var(--btn-primary-bg, var(--x-primary)));
  --x-btn-hover-color: var(--btn-primary-ink, #fff);
  --x-btn-active-bg: var(--btn-primary-hover, var(--btn-primary-bg, var(--x-primary)));
  --x-btn-active-color: var(--btn-primary-ink, #fff);
}
/* 轮廓按钮与纯文字链接的字色：用 `--x-primary-text-emphasis`（Bootstrap 给「主题色的字压浅底」留的那一档，
   主题色往黑里压 60%），不用 `--btn-outline-ink`。
   🔴 `--btn-outline-ink` 那一档是按 services-list 那块底挑的（`scripts/lib/button-ink.js` ③a），不是按本块的底：
   ember-12 上它挑出一个浅金色，压本块的白底是 2.01:1（#1475 PM 验收，`theme-css-invariants-all-sheets.sh`
   --shard 2/2，块头第二颗 outline 按钮）。本块是 essential，那把尺要量它；cta / hero 同一条写法没被量到，
   是因为它们是 optional。`.btn-link` 的 `--x-link-color` 就是主题色本身，浅主题色同样读不出来，一起换。
   hover 同色，不往浅里走。深底 / 品牌底 / 封面图那几条是 `!important` 白字，照旧盖过这里。 */
[data-block="features"] .btn-outline-primary {
  --x-btn-color: var(--x-primary-text-emphasis, var(--x-primary));
  --x-btn-border-color: var(--x-primary-text-emphasis, var(--x-primary));
}
[data-block="features"] .btn-link {
  --x-btn-color: var(--x-primary-text-emphasis, var(--x-primary));
  --x-btn-hover-color: var(--x-primary-text-emphasis, var(--x-primary));
  --x-btn-active-color: var(--x-primary-text-emphasis, var(--x-primary));
}

/* ── features · grid（预设 Grid）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：features 是一份 markup + 十一个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-items-*` / `data-item-*` / `data-tone`，规则全在 `blocks/features/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="features"][data-shape="grid"] {
  --fx-preset: grid;
}

/* ── features · cards（预设 Cards）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：features 是一份 markup + 十一个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-items-*` / `data-item-*` / `data-tone`，规则全在 `blocks/features/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="features"][data-shape="cards"] {
  --fx-preset: cards;
}

/* ── features · side-intro（预设 Side intro）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：features 是一份 markup + 十一个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-items-*` / `data-item-*` / `data-tone`，规则全在 `blocks/features/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="features"][data-shape="side-intro"] {
  --fx-preset: side-intro;
}

/* ── features · intro-photo（预设 Intro photo）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：features 是一份 markup + 十一个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-items-*` / `data-item-*` / `data-tone`，规则全在 `blocks/features/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="features"][data-shape="intro-photo"] {
  --fx-preset: intro-photo;
}

/* ── features · photo-list（预设 Photo list）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：features 是一份 markup + 十一个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-items-*` / `data-item-*` / `data-tone`，规则全在 `blocks/features/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="features"][data-shape="photo-list"] {
  --fx-preset: photo-list;
}

/* ── features · photo-cards（预设 Photo cards）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：features 是一份 markup + 十一个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-items-*` / `data-item-*` / `data-tone`，规则全在 `blocks/features/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="features"][data-shape="photo-cards"] {
  --fx-preset: photo-cards;
}

/* ── features · cover-cards（预设 Cover cards）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：features 是一份 markup + 十一个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-items-*` / `data-item-*` / `data-tone`，规则全在 `blocks/features/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="features"][data-shape="cover-cards"] {
  --fx-preset: cover-cards;
}

/* ── features · steps（预设 Steps）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：features 是一份 markup + 十一个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-items-*` / `data-item-*` / `data-tone`，规则全在 `blocks/features/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="features"][data-shape="steps"] {
  --fx-preset: steps;
}

/* ══ gallery ══════════════════════════════════════════════════════════════════════ */
/* 条目网格（itemsColumns：手机 ≤2 · iPad ≤2 · ≥992 按旋钮）—— 由 scripts/block-build/items-grid.js 生成，别在 block.css 里写 */
[data-block="gallery"][data-items-layout="grid"]:not([data-item-shape="original"]) .gl-grid {
  --items-cols: 2;
  display: grid;
  grid-template-columns: repeat(var(--items-cols), minmax(0, 1fr));
}
@media (min-width: 992px) {
  [data-block="gallery"][data-items-layout="grid"]:not([data-item-shape="original"])[data-items-columns="3"] .gl-grid {
    --items-cols: 3;
  }
  [data-block="gallery"][data-items-layout="grid"]:not([data-item-shape="original"])[data-items-columns="4"] .gl-grid {
    --items-cols: 4;
  }
}
[data-block="gallery"][data-items-layout="mosaic"] .gl-grid {
  --items-cols: 2;
  display: grid;
  grid-template-columns: repeat(var(--items-cols), minmax(0, 1fr));
}
@media (min-width: 992px) {
  [data-block="gallery"][data-items-layout="mosaic"][data-items-columns="3"] .gl-grid {
    --items-cols: 3;
  }
  [data-block="gallery"][data-items-layout="mosaic"][data-items-columns="4"] .gl-grid {
    --items-cols: 4;
  }
}
[data-block="gallery"][data-items-layout="grid"][data-item-shape="original"] .gl-grid {
  --items-cols: 2;
  column-count: var(--items-cols);
}
@media (min-width: 992px) {
  [data-block="gallery"][data-items-layout="grid"][data-item-shape="original"][data-items-columns="3"] .gl-grid {
    --items-cols: 3;
  }
  [data-block="gallery"][data-items-layout="grid"][data-item-shape="original"][data-items-columns="4"] .gl-grid {
    --items-cols: 4;
  }
}

/* 块头排版（introAlign / introPosition）—— 由 scripts/block-build/intro-layout.js 生成，别在 block.css 里写 */
[data-block="gallery"][data-intro-align="center"] .gl-intro-text {
  text-align: center;
}
[data-block="gallery"][data-intro-align="right"] .gl-intro-text {
  text-align: end;
}
@media (min-width: 992px) {
  [data-block="gallery"][data-intro-align="center"] .gl-intro-text {
    width: 100%;
    max-width: 80%;
    margin-inline: auto;
  }
}
[data-block="gallery"][data-intro-position="bottom"] .gl-frame {
  flex-direction: column-reverse;
}
@media (max-width: 991.98px) {
  [data-block="gallery"][data-intro-position="right"] .gl-frame {
    flex-direction: column-reverse;
  }
}
@media (min-width: 992px) {
  [data-block="gallery"][data-intro-position="left"] .gl-introcol,
  [data-block="gallery"][data-intro-position="right"] .gl-introcol {
    flex: 0 0 auto;
    width: 33.3333%;
    position: sticky;
    top: 2rem;
    align-self: flex-start;
  }
  [data-block="gallery"][data-intro-position="left"] .gl-itemscol,
  [data-block="gallery"][data-intro-position="right"] .gl-itemscol {
    flex: 0 0 auto;
    width: 66.6667%;
  }
  [data-block="gallery"][data-intro-position="left"] .gl-intro-text,
  [data-block="gallery"][data-intro-position="right"] .gl-intro-text {
    max-width: none;
    margin: 0;
  }
  [data-block="gallery"][data-intro-position="right"] .gl-frame {
    flex-direction: row-reverse;
  }
}

/* ── gallery · 跨形态的规则（#1495）──────────────────────────────────────────────────────────
   gallery 的排版由**实际生效的旋钮**决定，写在根元素上：
     intro   data-intro-position（left / right / top / bottom）· data-intro-align（left / center / right）
     items   data-items-layout（grid / mosaic）· data-items-columns（2 / 3 / 4）·
             data-item-shape（original / square / landscape / portrait）· data-item-caption（below / overlay）
     底色    data-tone（light / dark / brand，scripts/lib/contrast.js §toneForBg）
   四个形态目录都是预设，自己不带几何（理由在各自的 shape.css 里）。数值照图册 `docs/reference/webpixels/gallery/build.py`
   的 gallery 段（Chris 2026-09-30 定稿）。

   🔴 Webpixels 的工具类全带 !important（mb-* / text-muted / bg-primary-subtle …）。这里要压过它们的规则**也带 !important**，
      而且选择器从 [data-block="gallery"] 起步再加属性 / 类 —— 特异度一定高过工具类的一个类。
   🔴 部件的有无靠渲染（Section.tsx 有数据才画），不靠 CSS 藏：没有 title / caption 的那一张就没有 .gl-cap。
   间距：段 py-16 py-lg-24；块头与照片之间 2.5rem（gy-10）/ ≥992 横向 4rem（gx-lg-16）；照片之间 1rem（≥992 1.5rem）；
   圆角 1rem；图注在下时离图 .75rem。纵向写 gy-10、不写 g-10（g-10 的横向负边距在手机上溢出，#1475 量出来的）。 */

/* ═══ intro（块头）═══════════════════════════════════════════════════════════════════════════════ */

[data-block="gallery"] .gl-intro-text > :last-child {
  margin-bottom: 0 !important;
}

/* 块头排版（introAlign / introPosition：对齐、≥992 center 最宽 80%、左右两列、上下反向）由 manifest 的 introLayout 生成
   （scripts/block-build/intro-layout.js，#1535），这里不写。 */

/* ═══ items（照片）═══════════════════════════════════════════════════════════════════════════════ */

[data-block="gallery"] .gl-item {
  margin: 0;
  min-width: 0;
}
[data-block="gallery"] .gl-img {
  overflow: hidden;
  border-radius: 1rem;
  background: var(--scheme-surface-sunken);
  cursor: zoom-in;
}
[data-block="gallery"] .gl-img img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* 照片形状（裁图）：square 1:1 · landscape 4:3 · portrait 3:4。original 不裁（下面瀑布流那一段）。 */
[data-block="gallery"][data-item-shape="square"] .gl-img {
  aspect-ratio: 1 / 1;
}
[data-block="gallery"][data-item-shape="landscape"] .gl-img {
  aspect-ratio: 4 / 3;
}
[data-block="gallery"][data-item-shape="portrait"] .gl-img {
  aspect-ratio: 3 / 4;
}

/* itemsColumns 的列数与断点（手机、iPad 一律 2 列，Chris 2026-09-30；≥992 按数）归 manifest 的 itemsGrid，
   由 scripts/block-build/items-grid.js 生成（#1537）—— grid / mosaic 是 CSS grid，grid + original 是 CSS 多列（同一条规矩）。
   这里只管间距。 */
/* grid（裁图）：每张同样大。 */
[data-block="gallery"][data-items-layout="grid"]:not([data-item-shape="original"]) .gl-grid,
[data-block="gallery"][data-items-layout="mosaic"] .gl-grid {
  gap: 1rem;
}
@media (min-width: 992px) {
  [data-block="gallery"][data-items-layout="grid"]:not([data-item-shape="original"]) .gl-grid,
  [data-block="gallery"][data-items-layout="mosaic"] .gl-grid {
    gap: 1.5rem;
  }
}
/* grid + original = 瀑布流：按原图比例，CSS 多列错落排。 */
[data-block="gallery"][data-items-layout="grid"][data-item-shape="original"] .gl-grid {
  column-gap: 1rem;
}
[data-block="gallery"][data-items-layout="grid"][data-item-shape="original"] .gl-item {
  break-inside: avoid;
  margin-bottom: 1rem;
}
[data-block="gallery"][data-items-layout="grid"][data-item-shape="original"] .gl-img img {
  height: auto;
}
@media (min-width: 992px) {
  [data-block="gallery"][data-items-layout="grid"][data-item-shape="original"] .gl-grid {
    column-gap: 1.5rem;
  }
  [data-block="gallery"][data-items-layout="grid"][data-item-shape="original"] .gl-item {
    margin-bottom: 1.5rem;
  }
}

/* mosaic：第一张占 2×2，其余一格（grid-auto-flow: dense）。mosaic 必须裁图 ⟹ original 在这里按 landscape。
   第一张的图拉满它那两行（它自己的比例由格子定，不再按 aspect-ratio）。 */
[data-block="gallery"][data-items-layout="mosaic"] .gl-grid {
  grid-auto-flow: dense;
}
[data-block="gallery"][data-items-layout="mosaic"][data-item-shape="original"] .gl-img {
  aspect-ratio: 4 / 3;
}
[data-block="gallery"][data-items-layout="mosaic"] .gl-item:first-child {
  grid-column: span 2;
  grid-row: span 2;
  display: flex;
  flex-direction: column;
}
[data-block="gallery"][data-items-layout="mosaic"] .gl-item:first-child .gl-img {
  aspect-ratio: auto;
  flex: 1 1 auto;
  min-height: 0;
}

/* ═══ 图注：below = 图下面（离图 .75rem）；overlay = 压在图底部（深色渐变 + 白字，小字白 .92）════════════ */
[data-block="gallery"] .gl-cap {
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
}
[data-block="gallery"] .gl-cap-s {
  color: var(--scheme-ink-muted);
}
[data-block="gallery"][data-item-caption="below"] .gl-cap {
  margin-top: 0.75rem;
}
[data-block="gallery"][data-item-caption="overlay"] .gl-item {
  position: relative;
}
[data-block="gallery"][data-item-caption="overlay"] .gl-cap {
  position: absolute;
  inset: auto 0 0 0;
  padding: 2.5rem 1rem 1rem;
  border-radius: 0 0 1rem 1rem;
  background: linear-gradient(transparent, rgba(2, 6, 23, 0.75));
  color: #fff;
  pointer-events: none;
}
[data-block="gallery"][data-item-caption="overlay"] .gl-cap-s {
  color: rgba(255, 255, 255, 0.92);
}
/* ═══ 大图（Bootstrap Modal + Carousel）══════════════════════════════════════════════════════════
   暗底 rgba(2,6,23,.94)；右上关闭；图 max-height min(80vh, 40rem)、object-fit contain；手机上左右箭头藏起来（靠滑）。 */
/* `slide` 是 Bootstrap 的 JS 开关（`bootstrap/js/dist/carousel.js` 的 `_isAnimated()` 读它：有它才做滑动动画，图册定稿就是滑动），
   Bootstrap 自己的 CSS 里没有任何 `.slide` 规则 —— theme-css 的「每个 class 都要有规则」那条（#992）会把它点名。
   这条规则有实际作用：横滑交给 Carousel 的手势处理、竖向交给浏览器 —— 跟 Bootstrap 自己给 `.carousel.pointer-event` 写的是同一个声明，
   那条要等 JS 给元素加上 `pointer-event` 类才生效，这条从第一帧起就在。 */
[data-block="gallery"] .gl-modal .carousel.slide {
  touch-action: pan-y;
}
[data-block="gallery"] .gl-modal .modal-content {
  background: rgba(2, 6, 23, 0.94);
  border: 0;
  border-radius: 0;
  justify-content: center;
}
[data-block="gallery"] .gl-modal .gl-lb-close {
  position: absolute;
  top: 1.25rem;
  inset-inline-end: 1.25rem;
  z-index: 3;
  opacity: 0.9;
}
[data-block="gallery"] .gl-modal .carousel {
  padding: 0 4.5rem 3rem;
}
[data-block="gallery"] .gl-modal .carousel-item img {
  max-width: 100%;
  max-height: min(80vh, 40rem);
  border-radius: 0.5rem;
  object-fit: contain;
}
[data-block="gallery"] .gl-modal .carousel-control-prev,
[data-block="gallery"] .gl-modal .carousel-control-next {
  width: 4.5rem;
}
/* 🔴 轮播只为【接键盘】而可聚焦（`Section.tsx` 的 `tabIndex={-1}` + `Lightbox.tsx` 在 shown.bs.modal 里
   focus 它）。它在 `.modal-fullscreen` 里铺满视口 ⟹ 浏览器的默认 focus ring 会沿【整个视口】画一圈
   （Chris 2026-10-01 截图：按方向键后出现横贯全宽的蓝线）。去掉它不丢无障碍：
   · 轮播是 `tabindex="-1"`，Tab 到不了它，只能被程序聚焦 —— 环在这儿不回答「我在哪」；
   · 真正的控件（`.carousel-control-prev/-next`、`.carousel-indicators [data-bs-slide-to]`）各自保留
     Bootstrap 的默认 focus 样式（本文件对它们只改宽度和位置，没动 outline）。
   📌 跟 `testimonials` 的处置【故意不同】：那个轮播是页内内容块，它那条
   `.tn-carousel:focus-visible { outline: 2px solid var(--x-primary) }` 看着像有意设计；这个铺满视口。 */
[data-block="gallery"] .gl-modal .carousel:focus,
[data-block="gallery"] .gl-modal .carousel:focus-visible {
  outline: none;
}
[data-block="gallery"] .gl-modal .carousel-indicators {
  bottom: 0;
  margin-bottom: 0;
}
[data-block="gallery"] .gl-lb-cap {
  color: #fff;
  text-align: center;
  font-size: 0.9375rem;
  margin-top: 1rem;
}
[data-block="gallery"] .gl-lb-cap span {
  display: block;
  color: rgba(255, 255, 255, 0.75);
  font-size: 0.8125rem;
}
@media (max-width: 767.98px) {
  [data-block="gallery"] .gl-modal .carousel {
    padding: 0 0.75rem 3rem;
  }
  [data-block="gallery"] .gl-modal .carousel-control-prev,
  [data-block="gallery"] .gl-modal .carousel-control-next {
    display: none !important;
  }
}

/* ═══ 字色按背景亮度（data-tone，scripts/lib/contrast.js §toneForBg）══════════════════════════════
   dark = 深底 / 深色渐变；brand = 主题主色底。两者一样：标题 / 块头正文反白，图注在下时的小字白 .92
   （块头正文 .text-muted 的白 .92 由全站那条给：scripts/lib/site-css.js §ON_DEEP_MUTED）。
   🔴 各块共有的那几条（根上字色 · 标题 · eyebrow · 主按钮 / 描边 / link 按钮反白）住 `scripts/lib/site-css.js`
      §DEEP_COMMON，全站一份（#1533）；这里只留本块特有的，别再抄回来（`scripts/block-deep-common.test.js`）。 */

[data-block="gallery"][data-tone="dark"] .gl-cap-s,
[data-block="gallery"][data-tone="brand"] .gl-cap-s {
  color: rgba(255, 255, 255, 0.92);
}

/* ── gallery · grid（预设 Grid）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：gallery 是一份 markup + 六个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-items-*` / `data-item-*` / `data-tone`，规则全在 `blocks/gallery/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="gallery"][data-shape="grid"] {
  --gl-preset: grid;
}

/* ── gallery · masonry（预设 Masonry）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：gallery 是一份 markup + 六个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-items-*` / `data-item-*` / `data-tone`，规则全在 `blocks/gallery/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="gallery"][data-shape="masonry"] {
  --gl-preset: masonry;
}

/* ── gallery · mosaic（预设 Mosaic）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：gallery 是一份 markup + 六个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-items-*` / `data-item-*` / `data-tone`，规则全在 `blocks/gallery/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="gallery"][data-shape="mosaic"] {
  --gl-preset: mosaic;
}

/* ── gallery · side-intro（预设 Side intro）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：gallery 是一份 markup + 六个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-items-*` / `data-item-*` / `data-tone`，规则全在 `blocks/gallery/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="gallery"][data-shape="side-intro"] {
  --gl-preset: side-intro;
}

/* ══ hero ══════════════════════════════════════════════════════════════════════ */
/* 图的位置（image）—— 由 scripts/block-build/media-layout.js 生成，别在 block.css 里写 */
[data-block="hero"][data-image="left"] .hro-row,
[data-block="hero"][data-image="right"] .hro-row,
[data-block="hero"][data-image="top"] .hro-row,
[data-block="hero"][data-image="bottom"] .hro-row {
  display: flex;
  flex-direction: column;
  gap: 2.5rem;
}
[data-block="hero"][data-image="left"] .hro-row,
[data-block="hero"][data-image="top"] .hro-row {
  flex-direction: column-reverse;
}
[data-block="hero"][data-image="left"] .hro-textcol,
[data-block="hero"][data-image="right"] .hro-textcol,
[data-block="hero"][data-image="top"] .hro-textcol,
[data-block="hero"][data-image="bottom"] .hro-textcol,
[data-block="hero"][data-image="left"] .hro-side,
[data-block="hero"][data-image="right"] .hro-side,
[data-block="hero"][data-image="top"] .hro-side,
[data-block="hero"][data-image="bottom"] .hro-side {
  min-width: 0;
}
@media (min-width: 992px) {
  [data-block="hero"][data-image="left"] .hro-row,
  [data-block="hero"][data-image="right"] .hro-row {
    flex-direction: row;
    align-items: center;
    gap: 4rem;
  }
  [data-block="hero"][data-image="left"] .hro-row {
    flex-direction: row-reverse;
  }
  [data-block="hero"][data-image="left"] .hro-side,
  [data-block="hero"][data-image="right"] .hro-side {
    flex: 0 0 calc((100% - 4rem) * 0.5);
  }
  [data-block="hero"][data-image="left"] .hro-textcol,
  [data-block="hero"][data-image="right"] .hro-textcol {
    flex: 1 1 0;
  }
}

/* ── hero · 跨形态的规则（#1463）────────────────────────────────────────────────────────────
   hero 的排版由**实际生效的旋钮**决定，写在根元素上：data-text-align（left / center / right）· data-image
   （none / left / right / top / bottom / background）· data-form · data-tone（light / dark / brand）。
   🔴 #1470 —— 两个旋钮各管各的：几何（图多大、文字块多宽）跟 data-image，文字怎么对齐跟 data-text-align。
      #1463 那几条挂在 data-align="center" 上的规则，是因为那时「居中」和「图在上下」是同一个旋钮；
      现在按它们真正管的那一维重挂，不是把属性名替换一遍。
   五个形态目录都是预设，自己不带几何（理由在各自的 shape.css 里）。

   🔴 Webpixels 的工具类全带 !important（text-muted / bg-primary-subtle / text-primary …）。这里要压过
      它们的规则**也带 !important**，而且选择器从 [data-block="hero"] 起步再加一个属性 —— 两个属性
      选择器 + 一个类，特异度一定高过工具类的一个类。
   🔴 藏东西这里一条都没有：部件有数据才渲染（Section.tsx），不靠 CSS 藏。 */

/* 🔴 行的间距在 Section.tsx 里是 `gx-8 gy-10 gx-lg-16`，**不是**定稿抄来的 `g-10 gx-lg-16`，别「照定稿修回去」：
      `.row` 左右负外边距 = 横向间距的一半，`g-10` 是 20px，而 `.container` 在手机上的内距只有 16px ⟹ 390 宽下
      整页横向滚动 4px（#1463 实测 scrollWidth 394 / 390）。也别写成 `g-10 gx-8`：Bootstrap 按尺寸逐档生成
      g / gx / gy，`.g-10` 排在 `.gx-8` 后面，会把它压回去。竖向与 ≥992 的横向照定稿不变。 */

/* 图：并排（left / right）时 4:3；上下叠（top / bottom）时一整条 21:9、最高 460 —— 跟文字怎么对齐无关。 */
[data-block="hero"] .hro-img {
  aspect-ratio: 4 / 3;
}
[data-block="hero"][data-image="top"] .hro-img,
[data-block="hero"][data-image="bottom"] .hro-img {
  aspect-ratio: 21 / 9;
  max-height: 460px;
}
/* 🔴 1100 − 4rem，不是 1100：#1536 之前图列是 Bootstrap 的 col-12，≥992 时两边各 2rem 内距，1100 量在带内距的盒子上
      ⟹ 图本身最宽 1036。现在图列没有内距（两栏 / 上下叠由 mediaLayout 生成），照抄 1100 图会宽出 64px。
   🔴 width: 100% 不能省（#1536 r2，QA2 实测）：col-12 自带它；现在图列是纵向 flex 列里的子项，两边 margin: auto 会让它
      不再横向拉伸、缩成图片原始宽度 —— 原图比 1036 窄时（用户上传的图常常是），图就不再拉满（640 宽的图 1440 下只剩 640）。 */
[data-block="hero"][data-image="top"] .hro-side,
[data-block="hero"][data-image="bottom"] .hro-side {
  width: 100%;
  max-width: calc(1100px - 4rem);
  margin-inline-start: auto;
  margin-inline-end: auto;
}

/* 大字号标题的光学对齐：文字靠左时让大写字母左缘跟 eyebrow / 副标题对齐。 */
[data-block="hero"][data-text-align="left"] .hro-title {
  margin-inline-start: -0.04em;
}

/* 文字块宽度（T6.1 / #1486，Chris 2026-09-29，照 team）：副标题不单独限宽，跟标题同宽。
   上下叠（top / bottom）时文字列占满一整行，里面这一块：left / right = 100%；center 在 ≥992 最宽 80%、居中，<992 = 100%。
   并排（left / right / 铺底）和没图时文字列本来就是 col-lg-6 / col-lg-8（Section.tsx §textCol），不再另收。 */
/* 正文不单独限宽，跟标题同宽（#1486）。规则留着是因为块前缀 class 都要有一条带声明的规则
   （scripts/block-class-rules.test.js，#1519）。 */
[data-block="hero"] .hro-sub {
  max-width: none;
}
/* width: 100% 不能省：块头住在 flex 列里，两边 margin: auto 会让它缩成内容宽，话短时就到不了 80%（#1486 实测 0.72）。 */
@media (min-width: 992px) {
  [data-block="hero"][data-text-align="center"][data-image="top"] .hro-textcol,
  [data-block="hero"][data-text-align="center"][data-image="bottom"] .hro-textcol {
    width: 100%;
    max-width: 80%;
    margin-inline: auto;
  }
  /* 🔴 有图时是 80% − 0.8rem（#1536）：那时文字列原来是 Bootstrap 的 col-12，两边各 2rem 内距、行两边各 −2rem 外距，
        80% 量在带内距的盒子上 ⟹ 字宽 = 80% ×（内容宽 + 4rem）− 4rem = 80% − 0.8rem。现在有图时文字列没有内距
        （上下叠由 mediaLayout 生成，行是 .hro-row），照抄 80% 字会宽出 12.8px、折行变了（1440 下 hero centered 高 3px）。
        没图时（top / bottom 但没给图）文字列仍是 col-12，上面那条 80% 照旧对。 */
  [data-block="hero"][data-text-align="center"][data-image="top"] .hro-row > .hro-textcol,
  [data-block="hero"][data-text-align="center"][data-image="bottom"] .hro-row > .hro-textcol {
    max-width: calc(80% - 0.8rem);
  }
}
/* 限了宽的表单跟着文字对齐走：center 居中、right 贴右。 */
[data-block="hero"][data-text-align="center"] .hro-form {
  margin-inline-start: auto;
  margin-inline-end: auto;
}
[data-block="hero"][data-text-align="right"] .hro-form {
  margin-inline-start: auto;
}

/* 表单跟标题同宽，不超过 34rem。 */
[data-block="hero"] .hro-form {
  max-width: 34rem;
}

/* 头像组叠在一起；logo 一排等高。 */
[data-block="hero"] .hro-avatar + .hro-avatar {
  margin-inline-start: -10px;
}
[data-block="hero"] .hro-logo {
  height: 26px;
  width: auto;
  opacity: 0.75;
}

/* 图片带：竖构图 3:4。列数 = 张数（col-md 均分）；手机上两张一排。 */
[data-block="hero"] .hro-band-img {
  aspect-ratio: 3 / 4;
}

/* 图铺底：遮罩在 [data-part="bg"] 上（Section.tsx 的内联渐变），块本身撑高、内容竖直居中。 */
[data-block="hero"][data-image="background"] {
  min-height: 640px;
  display: flex;
  align-items: center;
}
[data-block="hero"][data-image="background"] > .container {
  width: 100%;
}

/* ── 字色按背景亮度（data-tone，scripts/lib/contrast.js §toneForBg）───────────────────────────────
   dark = 深底或图铺底：标题 / 副标题 / eyebrow / 统计 / logo 反白，outline 按钮翻成白描边。
   brand = 主题主色底：同上，另外主按钮翻成白底主色字。
   🔴 各块共有的那几条（根上字色 · 标题 · eyebrow · 主按钮 / 描边 / link 按钮反白）住 `scripts/lib/site-css.js`
      §DEEP_COMMON，全站一份（#1533）；这里只留本块特有的，别再抄回来（`scripts/block-deep-common.test.js`）。 */

/* 深底 / brand 上的 `.text-muted`：白 .92，全站一条（`scripts/lib/site-css.js` §ON_DEEP_MUTED，#1477）。 */
[data-block="hero"][data-tone="dark"] .border-top,
[data-block="hero"][data-tone="brand"] .border-top {
  border-color: rgba(255, 255, 255, 0.3) !important;
}
[data-block="hero"][data-tone="dark"] .hro-logo,
[data-block="hero"][data-tone="brand"] .hro-logo {
  filter: brightness(0) invert(1);
  opacity: 0.9;
}
/* 图铺底时 pill 用白底深字（图册 hero 段的样子：遮罩上半透明的白太淡）。
   🔴 `[data-tone]` 不是多余的：它把特异度抬到 0-4-0，压过 site.css §DEEP_COMMON 那条深底 pill（0-3-0）。
      不能靠「shapes.css 后加载」赢 —— 单格页（admin 预览）把 site.css 的 <link> 放在 body 里、排在 shapes.css
      之后，同特异度时那边是 site.css 赢（#1533 截图比对实测：hero cover 三种底的 pill 都变成了半透明白）。 */
[data-block="hero"][data-image="background"][data-tone] [data-eyebrow="pill"] {
  background: #fff !important;
  color: #0f172a !important;
}
/* ── 按钮的底色和字色：接平台按这个站最终配色算好的那几个变量（`scripts/lib/button-ink.js`）──────────
   Webpixels 的 `.btn-primary` 用 `$primary` 当底、按**纯色**对比度挑白 / 黑字；中间调的主色上两种字都贴着 4.5
   （实测 ember-12 的 #907230：白字纯色 4.53、黑字 4.60，抗锯齿后 4.42 / 4.49，theme-css-invariants 判红）。
   `button-ink.js` 解的正是这件事：底色沿梯子挑一档过线的、字色按抗锯齿后的对比度挑，写成 `--btn-primary-*`
   （旧块的 `.btn-primary` 读的也是它们）。拿不到这些变量的页面落回 Webpixels 自己的值。
   🔴 这是平台算的可读性变量，不是主题表的皮 —— 跟 manifest 的 `skin: "site-css"` 不冲突。 */
[data-block="hero"] .btn-primary {
  --x-btn-bg: var(--btn-primary-bg, var(--x-primary));
  --x-btn-border-color: var(--btn-primary-bg, var(--x-primary));
  --x-btn-color: var(--btn-primary-ink, #fff);
  --x-btn-hover-bg: var(--btn-primary-hover, var(--btn-primary-bg, var(--x-primary)));
  --x-btn-hover-border-color: var(--btn-primary-hover, var(--btn-primary-bg, var(--x-primary)));
  --x-btn-hover-color: var(--btn-primary-ink, #fff);
  --x-btn-active-bg: var(--btn-primary-hover, var(--btn-primary-bg, var(--x-primary)));
  --x-btn-active-color: var(--btn-primary-ink, #fff);
}
[data-block="hero"] .btn-outline-primary {
  --x-btn-color: var(--btn-outline-ink, var(--x-primary));
  --x-btn-border-color: var(--btn-outline-ink, var(--x-primary));
}

[data-block="hero"][data-tone="dark"] .btn-primary:active, [data-block="hero"][data-tone="brand"] .btn-primary:active {
  background: #e2e8f0 !important;
  border-color: #e2e8f0 !important;
  color: #0f172a !important;
}
[data-block="hero"][data-tone="dark"] .form-control,
[data-block="hero"][data-tone="brand"] .form-control,
[data-block="hero"][data-tone="dark"] .form-select,
[data-block="hero"][data-tone="brand"] .form-select {
  color: #0f172a !important;
  background-color: #fff !important;
  border-color: #fff !important;
}

/* ── hero · split（预设 Split）────────────────────────────────────────────────────────────────
   这个形态没有自己的几何：hero 是一份 markup + 三个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-align` / `data-image` / `data-form` / `data-reverse` / `data-tone`，规则全在
   `blocks/hero/block.css`）。按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，
   页面就会排成一个跟旋钮说的不一样的样子。所以这份文件只有这段说明。 */
[data-block="hero"][data-shape="split"] {
  --hro-preset: split;
}

/* ── hero · centered（预设 Centered）────────────────────────────────────────────────────────────────
   这个形态没有自己的几何：hero 是一份 markup + 三个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-align` / `data-image` / `data-form` / `data-reverse` / `data-tone`，规则全在
   `blocks/hero/block.css`）。按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，
   页面就会排成一个跟旋钮说的不一样的样子。所以这份文件只有这段说明。 */
[data-block="hero"][data-shape="centered"] {
  --hro-preset: centered;
}

/* ── hero · cover（预设 Cover）────────────────────────────────────────────────────────────────
   这个形态没有自己的几何：hero 是一份 markup + 三个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-align` / `data-image` / `data-form` / `data-reverse` / `data-tone`，规则全在
   `blocks/hero/block.css`）。按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，
   页面就会排成一个跟旋钮说的不一样的样子。所以这份文件只有这段说明。 */
[data-block="hero"][data-shape="cover"] {
  --hro-preset: cover;
}

/* ── hero · lead-form（预设 Lead form）────────────────────────────────────────────────────────────────
   这个形态没有自己的几何：hero 是一份 markup + 三个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-align` / `data-image` / `data-form` / `data-reverse` / `data-tone`，规则全在
   `blocks/hero/block.css`）。按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，
   页面就会排成一个跟旋钮说的不一样的样子。所以这份文件只有这段说明。 */
[data-block="hero"][data-shape="lead-form"] {
  --hro-preset: lead-form;
}

/* ── hero · text-only（预设 Text only）────────────────────────────────────────────────────────────────
   这个形态没有自己的几何：hero 是一份 markup + 三个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-align` / `data-image` / `data-form` / `data-reverse` / `data-tone`，规则全在
   `blocks/hero/block.css`）。按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，
   页面就会排成一个跟旋钮说的不一样的样子。所以这份文件只有这段说明。 */
[data-block="hero"][data-shape="text-only"] {
  --hro-preset: text-only;
}

/* ══ logos ══════════════════════════════════════════════════════════════════════ */
/* 条目网格（itemsColumns：手机 ≤2 · iPad ≤3 · ≥992 按旋钮）—— 由 scripts/block-build/items-grid.js 生成，别在 block.css 里写 */
[data-block="logos"][data-items-layout="grid"] .lo-grid {
  --items-cols: 2;
  display: grid;
  grid-template-columns: repeat(var(--items-cols), minmax(0, 1fr));
}
@media (min-width: 768px) {
  [data-block="logos"][data-items-layout="grid"][data-items-columns="3"] .lo-grid {
    --items-cols: 3;
  }
  [data-block="logos"][data-items-layout="grid"][data-items-columns="4"] .lo-grid {
    --items-cols: 3;
  }
  [data-block="logos"][data-items-layout="grid"][data-items-columns="6"] .lo-grid {
    --items-cols: 3;
  }
}
@media (min-width: 992px) {
  [data-block="logos"][data-items-layout="grid"][data-items-columns="4"] .lo-grid {
    --items-cols: 4;
  }
  [data-block="logos"][data-items-layout="grid"][data-items-columns="6"] .lo-grid {
    --items-cols: 6;
  }
}

/* 块头排版（introAlign / introPosition）—— 由 scripts/block-build/intro-layout.js 生成，别在 block.css 里写 */
[data-block="logos"][data-intro-align="center"] .lo-intro-text {
  text-align: center;
}
[data-block="logos"][data-intro-align="right"] .lo-intro-text {
  text-align: end;
}
@media (min-width: 992px) {
  [data-block="logos"][data-intro-align="center"] .lo-intro-text {
    width: 100%;
    max-width: 80%;
    margin-inline: auto;
  }
}
[data-block="logos"][data-intro-position="bottom"] .lo-frame {
  flex-direction: column-reverse;
}
@media (max-width: 991.98px) {
  [data-block="logos"][data-intro-position="right"] .lo-frame {
    flex-direction: column-reverse;
  }
}
@media (min-width: 992px) {
  [data-block="logos"][data-intro-position="left"] .lo-introcol,
  [data-block="logos"][data-intro-position="right"] .lo-introcol {
    flex: 0 0 auto;
    width: 33.3333%;
  }
  [data-block="logos"][data-intro-position="left"] .lo-itemscol,
  [data-block="logos"][data-intro-position="right"] .lo-itemscol {
    flex: 0 0 auto;
    width: 66.6667%;
  }
  [data-block="logos"][data-intro-position="left"] .lo-intro-text,
  [data-block="logos"][data-intro-position="right"] .lo-intro-text {
    max-width: none;
    margin: 0;
  }
  [data-block="logos"][data-intro-position="right"] .lo-frame {
    flex-direction: row-reverse;
  }
}

/* ── logos · 跨形态的规则（#1496）──────────────────────────────────────────────────────────────
   logos 的排版由**实际生效的旋钮**决定，写在根元素上：
     intro  data-intro-position（left / right / top / bottom）· data-intro-align（left / center / right）· data-intro-size（sm / lg）
     items  data-items-layout（row / grid）· data-items-columns（3 / 4 / 6，只管 grid）
     item   data-item-style（plain / card）· data-logo-color（mono / original）
     底色   data-tone（light / dark / brand，scripts/lib/contrast.js §toneForBg）
   四个形态目录都是预设，自己不带几何（理由在各自的 shape.css 里）。规则照定稿图册 logos 段
   （docs/reference/webpixels/gallery/build.py 的 logos CSS，Chris 2026-09-30）。

   🔴 Webpixels 的工具类全带 !important（mb-* / display-5 / fs-5 / text-muted …）。这里要压过它们的规则**也带 !important**，
      而且选择器从 [data-block="logos"] 起步再加属性 / 类 —— 特异度一定高过工具类的一个类。
   🔴 部件的有无靠渲染（Section.tsx 有数据才画），不靠 CSS 藏：眉标、标题、正文、链接没数据就没有节点。
   间距照定稿：段 py-12 py-lg-16；块头与 logo gy-8 gx-lg-16（纵向写 gy-8、不写 g-8：g-* 的横向负边距在手机上溢出，
   #1475 量出来的）。 */

/* ═══ intro ════════════════════════════════════════════════════════════════════════════════════════ */

[data-block="logos"] .lo-intro-text > :last-child {
  margin-bottom: 0 !important;
}
[data-block="logos"] .lo-cta {
  margin-top: 1rem;
}

/* introSize：lg = 正常大标题（display-5）；sm = 标题缩成一行小字（1.125rem / 600），正文也缩小（.9375rem）——
   正文照样显示（Chris 2026-09-30：sm 不藏内容，只缩字号）。 */
[data-block="logos"][data-intro-size="sm"] .lo-title {
  font-size: 1.125rem !important;
  font-weight: 600 !important;
  letter-spacing: 0 !important;
  line-height: 1.4 !important;
  margin-bottom: 0 !important;
}
[data-block="logos"][data-intro-size="sm"] .lo-body {
  font-size: 0.9375rem !important;
  margin-top: 0.25rem;
}
[data-block="logos"][data-intro-size="sm"] .lo-eyebrow-wrap {
  margin-bottom: 0.5rem !important;
}

/* 块头排版（introAlign / introPosition：对齐、≥992 center 最宽 80%、左右两列、上下反向）由 manifest 的 introLayout 生成
   （scripts/block-build/intro-layout.js，#1535），这里不写。
   logos 的块头列不 sticky：两列垂直居中（.lo-frame 带 align-items-center）。 */

/* ═══ items ════════════════════════════════════════════════════════════════════════════════════════
   每格：plain 高 4.5rem、card 高 6rem；logo 图最高 2rem、宽按比例、不超出格子。 */
[data-block="logos"] .lo-inner {
  height: 4.5rem;
  padding: 0 1rem;
}
[data-block="logos"] .lo-logo {
  display: block;
  max-height: 2rem;
  max-width: 100%;
  width: auto;
  height: auto;
}

/* itemsLayout=row：每个 logo 按自己的宽度排、居中、放不下就换行；上下左右同一个间距 1rem，左右内边距 .75rem
   （Chris 2026-09-30：行距与列距一样、比 grid 紧凑）。块头在侧时靠左排。 */
[data-block="logos"][data-items-layout="row"] .lo-grid {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 1rem;
}
[data-block="logos"][data-items-layout="row"] .lo-inner {
  padding: 0 0.75rem;
}
@media (min-width: 992px) {
  [data-block="logos"][data-items-layout="row"][data-intro-position="left"] .lo-grid,
  [data-block="logos"][data-items-layout="row"][data-intro-position="right"] .lo-grid {
    justify-content: flex-start;
  }
}

/* itemsLayout=grid：按列数等宽格子 —— 列数与断点（手机 2 列、iPad（≥768）3 列、≥992 按 itemsColumns）归 manifest 的 itemsGrid，
   由 scripts/block-build/items-grid.js 生成（#1537）。这里只管间距。 */
[data-block="logos"][data-items-layout="grid"] .lo-grid {
  gap: 1rem;
}

/* itemStyle=card：每个 logo 一张白卡（1px 描边、1rem 圆角、高 6rem）。 */
[data-block="logos"][data-item-style="card"] .lo-inner {
  background: var(--x-body-bg);
  border: 1px solid var(--x-border-color, #e5e7eb);
  border-radius: 1rem;
  height: 6rem;
}

/* logoColor：mono = 统一灰度（grayscale + 60% 不透明）；original = 原色。深底 + mono = 反白（下面 data-tone 那一段）。 */
[data-block="logos"][data-logo-color="mono"] .lo-logo {
  filter: grayscale(1);
  opacity: 0.6;
}

/* ═══ 字色按背景亮度（data-tone，scripts/lib/contrast.js §toneForBg）══════════════════════════════
   dark = 深底 / 深色渐变；brand = 主题主色底。两者一样：标题 / 正文反白（正文白 .92 那条规则全站只有一份，
   住 scripts/lib/site-css.js §ON_DEEP_MUTED，#1477；这里不再抄）、card 半透明底 + 半透明白描边、mono logo 反白。
   🔴 各块共有的那几条（根上字色 · 标题 · eyebrow · 主按钮 / 描边 / link 按钮反白）住 `scripts/lib/site-css.js`
      §DEEP_COMMON，全站一份（#1533）；这里只留本块特有的，别再抄回来（`scripts/block-deep-common.test.js`）。 */

[data-block="logos"][data-tone="dark"][data-item-style="card"] .lo-inner,
[data-block="logos"][data-tone="brand"][data-item-style="card"] .lo-inner {
  background: rgba(255, 255, 255, 0.06);
  border-color: rgba(255, 255, 255, 0.15);
}
[data-block="logos"][data-tone="dark"][data-logo-color="mono"] .lo-logo,
[data-block="logos"][data-tone="brand"][data-logo-color="mono"] .lo-logo {
  filter: brightness(0) invert(1);
  opacity: 0.75;
}

/* ── 按钮的底色和字色：接平台按这个站最终配色算好的那几个变量（`scripts/lib/button-ink.js`，同 features / milestones）。
   轮廓按钮 / 纯文字链接用 `--x-primary-text-emphasis`，理由见 features/block.css 同一段（浅主题色压白底读不出来）。 */
[data-block="logos"] .btn-primary {
  --x-btn-bg: var(--btn-primary-bg, var(--x-primary));
  --x-btn-border-color: var(--btn-primary-bg, var(--x-primary));
  --x-btn-color: var(--btn-primary-ink, #fff);
  --x-btn-hover-bg: var(--btn-primary-hover, var(--btn-primary-bg, var(--x-primary)));
  --x-btn-hover-border-color: var(--btn-primary-hover, var(--btn-primary-bg, var(--x-primary)));
  --x-btn-hover-color: var(--btn-primary-ink, #fff);
  --x-btn-active-bg: var(--btn-primary-hover, var(--btn-primary-bg, var(--x-primary)));
  --x-btn-active-color: var(--btn-primary-ink, #fff);
}
[data-block="logos"] .btn-outline-primary {
  --x-btn-color: var(--x-primary-text-emphasis, var(--x-primary));
  --x-btn-border-color: var(--x-primary-text-emphasis, var(--x-primary));
}
[data-block="logos"] .btn-link {
  --x-btn-color: var(--x-primary-text-emphasis, var(--x-primary));
  --x-btn-hover-color: var(--x-primary-text-emphasis, var(--x-primary));
  --x-btn-active-color: var(--x-primary-text-emphasis, var(--x-primary));
}

/* ── logos · strip（预设 Strip）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：logos 是一份 markup + 七个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-items-*` / `data-item-style` / `data-logo-color` / `data-tone`，规则全在 `blocks/logos/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="logos"][data-shape="strip"] {
  --lo-preset: strip;
}

/* ── logos · grid（预设 Grid）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：logos 是一份 markup + 七个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-items-*` / `data-item-style` / `data-logo-color` / `data-tone`，规则全在 `blocks/logos/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="logos"][data-shape="grid"] {
  --lo-preset: grid;
}

/* ── logos · cards（预设 Cards）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：logos 是一份 markup + 七个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-items-*` / `data-item-style` / `data-logo-color` / `data-tone`，规则全在 `blocks/logos/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="logos"][data-shape="cards"] {
  --lo-preset: cards;
}

/* ── logos · side-intro（预设 Side intro）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：logos 是一份 markup + 七个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-items-*` / `data-item-style` / `data-logo-color` / `data-tone`，规则全在 `blocks/logos/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="logos"][data-shape="side-intro"] {
  --lo-preset: side-intro;
}

/* ══ milestones ══════════════════════════════════════════════════════════════════════ */
/* 条目网格（statsColumns：手机 ≤2 · iPad ≤2 · ≥992 按旋钮）—— 由 scripts/block-build/items-grid.js 生成，别在 block.css 里写 */
[data-block="milestones"] .mi-grid {
  --items-cols: 2;
  display: flex;
  flex-wrap: wrap;
}
[data-block="milestones"] .mi-stat {
  flex: 0 0 auto;
  width: calc((100% - (var(--items-cols) - 1) * var(--mi-gap)) / var(--items-cols));
}
[data-block="milestones"][data-stats-columns="1"] .mi-grid {
  --items-cols: 1;
}
@media (min-width: 992px) {
  [data-block="milestones"][data-stats-columns="auto"] .mi-stat {
    flex: 1 1 0;
    width: auto;
  }
  [data-block="milestones"][data-stats-columns="3"] .mi-grid {
    --items-cols: 3;
  }
  [data-block="milestones"][data-stats-columns="4"] .mi-grid {
    --items-cols: 4;
  }
}

/* 图的位置（blockImage / introImage）—— 由 scripts/block-build/media-layout.js 生成，别在 block.css 里写 */
[data-block="milestones"][data-block-image="left"] .mi-outer,
[data-block="milestones"][data-block-image="right"] .mi-outer {
  display: flex;
  flex-direction: column;
  gap: 2.5rem;
}
[data-block="milestones"][data-block-image="left"] .mi-outer {
  flex-direction: column-reverse;
}
[data-block="milestones"][data-block-image="left"] .mi-main,
[data-block="milestones"][data-block-image="right"] .mi-main,
[data-block="milestones"][data-block-image="left"] .mi-bimg,
[data-block="milestones"][data-block-image="right"] .mi-bimg {
  min-width: 0;
}
@media (min-width: 992px) {
  [data-block="milestones"][data-block-image="left"] .mi-outer,
  [data-block="milestones"][data-block-image="right"] .mi-outer {
    flex-direction: row;
    align-items: center;
    gap: 4rem;
  }
  [data-block="milestones"][data-block-image="left"] .mi-outer {
    flex-direction: row-reverse;
  }
  [data-block="milestones"][data-block-image="left"] .mi-bimg,
  [data-block="milestones"][data-block-image="right"] .mi-bimg {
    flex: 0 0 calc((100% - 4rem) * 0.5);
  }
  [data-block="milestones"][data-block-image="left"] .mi-main,
  [data-block="milestones"][data-block-image="right"] .mi-main {
    flex: 1 1 0;
  }
}
[data-block="milestones"][data-intro-image="left"] .mi-intro,
[data-block="milestones"][data-intro-image="right"] .mi-intro {
  display: flex;
  flex-direction: column;
  gap: 2.5rem;
}
[data-block="milestones"][data-intro-image="left"] .mi-intro {
  flex-direction: column-reverse;
}
[data-block="milestones"][data-intro-image="left"] .mi-intro-text,
[data-block="milestones"][data-intro-image="right"] .mi-intro-text,
[data-block="milestones"][data-intro-image="left"] .mi-intro-img,
[data-block="milestones"][data-intro-image="right"] .mi-intro-img {
  min-width: 0;
}
@media (min-width: 992px) {
  [data-block="milestones"][data-intro-image="left"] .mi-intro,
  [data-block="milestones"][data-intro-image="right"] .mi-intro {
    flex-direction: row;
    align-items: center;
    gap: 4rem;
  }
  [data-block="milestones"][data-intro-image="left"] .mi-intro {
    flex-direction: row-reverse;
  }
  [data-block="milestones"][data-intro-image="left"] .mi-intro-img,
  [data-block="milestones"][data-intro-image="right"] .mi-intro-img {
    flex: 0 0 calc((100% - 4rem) * 0.5);
  }
  [data-block="milestones"][data-intro-image="left"] .mi-intro-text,
  [data-block="milestones"][data-intro-image="right"] .mi-intro-text {
    flex: 1 1 0;
  }
}

/* 块头排版（introAlign / introPosition）—— 由 scripts/block-build/intro-layout.js 生成，别在 block.css 里写 */
[data-block="milestones"][data-intro-align="center"] .mi-intro-text {
  text-align: center;
}
[data-block="milestones"][data-intro-align="right"] .mi-intro-text {
  text-align: end;
}
[data-block="milestones"][data-intro-align="center"] .mi-ctas {
  justify-content: center;
}
[data-block="milestones"][data-intro-align="right"] .mi-ctas {
  justify-content: flex-end;
}
@media (min-width: 992px) {
  [data-block="milestones"][data-intro-align="center"] .mi-intro-text {
    width: 100%;
    max-width: 80%;
    margin-inline: auto;
  }
}
[data-block="milestones"][data-intro-position="bottom"] .mi-frame {
  flex-direction: column-reverse;
}
@media (max-width: 991.98px) {
  [data-block="milestones"][data-intro-position="right"] .mi-frame {
    flex-direction: column-reverse;
  }
}
@media (min-width: 992px) {
  [data-block="milestones"][data-intro-position="left"] .mi-introcol,
  [data-block="milestones"][data-intro-position="right"] .mi-introcol {
    flex: 0 0 auto;
    width: 41.6667%;
    position: sticky;
    top: 2rem;
    align-self: flex-start;
  }
  [data-block="milestones"][data-intro-position="left"] .mi-statscol,
  [data-block="milestones"][data-intro-position="right"] .mi-statscol {
    flex: 0 0 auto;
    width: 58.3333%;
  }
  [data-block="milestones"][data-intro-position="left"] .mi-intro-text,
  [data-block="milestones"][data-intro-position="right"] .mi-intro-text {
    max-width: none;
    margin: 0;
  }
  [data-block="milestones"][data-intro-position="right"] .mi-frame {
    flex-direction: row-reverse;
  }
}

/* ── milestones · 跨形态的规则（#1482）──────────────────────────────────────────────────────────
   milestones 的排版由**实际生效的旋钮**决定，写在根元素上：
     块     data-block-image（none / left / right / background）
     intro  data-intro-position（left / right / top / bottom）· data-intro-align（left / center / right）·
            data-intro-image（none / left / right）
     stats  data-stats-columns（auto / 1 / 2 / 3 / 4）· data-stat-size（md / xl）· data-stat-style（plain / card / divided）·
            data-stat-align（left / center）
     底色   data-tone（light / dark / brand，scripts/lib/contrast.js §toneForBg；blockImage=background 且有图时 Section.tsx 给 dark）
   五个形态目录都是预设，自己不带几何（理由在各自的 shape.css 里）。旋钮彼此独立：这里没有一条规则
   按「另一个旋钮是什么」去改这个旋钮的效果（定稿 2026-09-29 删掉了图册里那两条强制改列数）。

   🔴 Webpixels 的工具类全带 !important（mb-* / text-muted / display-4 / bg-primary-subtle …）。这里要压过它们的规则
      **也带 !important**，而且选择器从 [data-block="milestones"] 起步再加属性 / 类 —— 特异度一定高过工具类的一个类。
   🔴 部件的有无靠渲染（Section.tsx 有数据才画），不靠 CSS 藏。
   🔴 上下叠时的 2.5rem 用 margin 给：Bootstrap 的 g-* 只对 .row 起作用，.mi-outer / .mi-intro 写 g-* 等于没写。
   间距照 Webpixels 尺度：段 py-16 py-lg-24、块头与 stats 之间 2.5rem（gy-10）/ ≥992 横向 4rem（gx-lg-16）、
   stats 行距 2.5rem（card 1.5rem）、card 内 2rem（= p-8）。纵向写 gy-10、不写 g-10（g-10 的横向负边距在手机上溢出 4px，
   #1475 量出来的）。 */

/* ═══ 块级那张图（blockImage）═════════════════════════════════════════════════════════════════════ */

/* left / right：图在哪一边、两栏多宽、上下叠隔多少由 manifest 的 `mediaLayout` 生成（scripts/block-build/media-layout.js，#1536）。
   这里只剩图自己的比例。 */
[data-block="milestones"] .mi-outer {
  display: flex;
  flex-direction: column;
}
[data-block="milestones"] .mi-main {
  min-width: 0;
}
[data-block="milestones"] .mi-bimg img {
  aspect-ratio: 4 / 5;
}

/* background：真照片铺整段 + 60% 深色遮罩；字色由 data-tone="dark" 那一组给（Section.tsx 在有图时给 dark），
   描线 / 卡片换成铺图专用的那一档（比纯深底略亮一点，照片上才看得出）。 */
[data-block="milestones"] .mi-cover {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
}
[data-block="milestones"] .mi-cover::after {
  content: "";
  position: absolute;
  inset: 0;
  background: rgba(2, 6, 23, 0.6);
}
[data-block="milestones"] .mi-container {
  position: relative;
  z-index: 1;
}

/* ═══ intro（块头）═══════════════════════════════════════════════════════════════════════════════ */

[data-block="milestones"] .mi-intro {
  display: flex;
  flex-direction: column;
}
[data-block="milestones"] .mi-intro-text > :last-child {
  margin-bottom: 0 !important;
}
/* 正文不单独限宽，跟标题同宽（#1486）。规则留着是因为块前缀 class 都要有一条带声明的规则
   （scripts/block-class-rules.test.js，#1519）。 */
[data-block="milestones"] .mi-body {
  max-width: none;
}
[data-block="milestones"] .mi-ctas {
  margin-top: 2rem;
}

/* 块头排版（introAlign / introPosition：对齐、≥992 center 最宽 80%、左右两列、上下反向）由 manifest 的 introLayout 生成
   （scripts/block-build/intro-layout.js，#1535），这里不写。
   按钮行跟着 introAlign 走（introLayout.follow）。 */

/* introImage：图在哪一边、两栏多宽、上下叠隔多少由 manifest 的 `mediaLayout` 生成（#1536）。
   并排时块头的字不吃 introAlign 的 80% 限宽（那一栏本来就只有一半宽）。 */
[data-block="milestones"] .mi-intro-img img {
  aspect-ratio: 4 / 3;
}
@media (min-width: 992px) {
  [data-block="milestones"][data-intro-image="left"] .mi-intro-text,
  [data-block="milestones"][data-intro-image="right"] .mi-intro-text {
    max-width: none;
    margin: 0;
  }
}

/* introPosition left / right（≥992）：块头那一列窄，里面的图一律叠在文字上面（同 features）。两列本身见上面那句。 */
@media (min-width: 992px) {
  [data-block="milestones"][data-intro-position="left"] .mi-intro,
  [data-block="milestones"][data-intro-position="right"] .mi-intro {
    flex-direction: column-reverse !important;
    align-items: stretch;
    gap: 2.5rem;
  }
  [data-block="milestones"][data-intro-position="left"] .mi-intro-img,
  [data-block="milestones"][data-intro-position="right"] .mi-intro-img {
    flex: none;
  }
}

/* ═══ stats ═══════════════════════════════════════════════════════════════════════════════════════ */

/* statsColumns 的列数与断点（手机 / iPad 一律两列，`1` 例外一列；≥992 auto = 一行均分，2 / 3 / 4 按列数折行）归 manifest 的
   itemsGrid，由 scripts/block-build/items-grid.js 生成（#1537，画法照旧 flex + calc）。不用负边距 —— card 那档原来 `margin: 0 -.75rem`，
   390 上溢出 4px（#1482 e2e AC2 那一格量出来的）。这里只管列距 / 行距。 */
[data-block="milestones"] .mi-grid {
  --mi-gap: 0px;
  row-gap: 2.5rem;
  column-gap: var(--mi-gap);
}
[data-block="milestones"] .mi-stat {
  min-width: 0;
  padding: 0 1rem;
}

/* statSize：md = display-4 的字号（组件上的类：calc(1.475rem + 2.7vw)，≥1200 为 3.5rem）；xl = 特大等宽数字，给 Big number。
   两档都再按**这一格的内容宽度**封顶（`.mi-inner` 是尺寸容器，21cqi ≈ 7 个粗体字符放得下）：卡片两列排在 390 上时
   内容区只有 ~100px，`14,000+` 照 display-4 画是 ~140px，页面被撑出 4px 横向滚动（本票 e2e AC2 量出来的）。
   超长的值再允许断开，不把页面撑宽。 */
[data-block="milestones"] .mi-inner {
  container-type: inline-size;
}
[data-block="milestones"] .mi-value {
  font-size: min(calc(1.475rem + 2.7vw), 21cqi);
  overflow-wrap: anywhere;
}
@media (min-width: 1200px) {
  [data-block="milestones"] .mi-value {
    font-size: min(3.5rem, 21cqi);
  }
}
[data-block="milestones"][data-stat-size="xl"] .mi-value {
  font-size: min(clamp(3.5rem, 9vw, 7rem), 21cqi) !important;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}

/* statAlign：只管每条 stat 里的内容（图标跟着走，它是 inline-flex）。 */
[data-block="milestones"][data-stat-align="center"] .mi-inner {
  text-align: center;
}

/* 图标：2.75rem 的方块。 */
[data-block="milestones"] .mi-icon {
  width: 2.75rem;
  height: 2.75rem;
  font-size: 1.25rem;
}

/* statStyle：plain 无底无线（stat 本身不带底色，Chris 2026-09-28）；card = 白底 + 1px 描边 + 1rem 圆角 + 2rem 内边距；
   divided = 相邻 stat 之间 1px 竖线，每一行的第一条没有（手机两列 ⟹ 奇数条；≥992 按列数；auto 一行 ⟹ 只有首条）。 */
[data-block="milestones"][data-stat-style="card"] .mi-grid {
  --mi-gap: 1.5rem;
  row-gap: 1.5rem;
}
[data-block="milestones"][data-stat-style="card"] .mi-stat {
  padding: 0;
}
[data-block="milestones"][data-stat-style="card"] .mi-inner {
  background: var(--x-body-bg);
  border: 1px solid var(--x-border-color, #e5e7eb);
  border-radius: 1rem;
  padding: 2rem;
}
[data-block="milestones"][data-stat-style="divided"] .mi-stat {
  border-inline-start: 1px solid var(--x-border-color, #e5e7eb);
}
[data-block="milestones"][data-stat-style="divided"] .mi-stat:first-child,
[data-block="milestones"][data-stat-style="divided"][data-stats-columns="1"] .mi-stat {
  border-inline-start: 0;
}
@media (max-width: 991.98px) {
  [data-block="milestones"][data-stat-style="divided"] .mi-grid > .mi-stat:nth-child(odd) {
    border-inline-start: 0;
  }
}
@media (min-width: 992px) {
  [data-block="milestones"][data-stat-style="divided"][data-stats-columns="2"] .mi-grid > .mi-stat:nth-child(2n+1),
  [data-block="milestones"][data-stat-style="divided"][data-stats-columns="3"] .mi-grid > .mi-stat:nth-child(3n+1),
  [data-block="milestones"][data-stat-style="divided"][data-stats-columns="4"] .mi-grid > .mi-stat:nth-child(4n+1) {
    border-inline-start: 0;
  }
}

/* ═══ 字色按背景亮度（data-tone，scripts/lib/contrast.js §toneForBg）══════════════════════════════
   dark = 深底 / 深色渐变 / 铺图；brand = 主题主色底。两者一样：标题 / 数字 / label 反白、正文白 .92（不是灰）、
   card 换半透明底 + 半透明白描边、竖线与图标底换半透明白；outline 按钮翻成白描边、主按钮白底主色字（同 features）。
   🔴 各块共有的那几条（根上字色 · 标题 · eyebrow · 主按钮 / 描边 / link 按钮反白）住 `scripts/lib/site-css.js`
      §DEEP_COMMON，全站一份（#1533）；这里只留本块特有的，别再抄回来（`scripts/block-deep-common.test.js`）。 */

[data-block="milestones"][data-tone="dark"] .mi-value,
[data-block="milestones"][data-tone="brand"] .mi-value,
[data-block="milestones"][data-tone="dark"] .mi-label,
[data-block="milestones"][data-tone="brand"] .mi-label {
  color: #fff !important;
}
[data-block="milestones"][data-tone="dark"][data-stat-style="card"] .mi-inner,
[data-block="milestones"][data-tone="brand"][data-stat-style="card"] .mi-inner {
  background: rgba(255, 255, 255, 0.06);
  border-color: rgba(255, 255, 255, 0.15);
}
[data-block="milestones"][data-tone="dark"][data-stat-style="divided"] .mi-stat,
[data-block="milestones"][data-tone="brand"][data-stat-style="divided"] .mi-stat {
  border-color: rgba(255, 255, 255, 0.2);
}
[data-block="milestones"][data-tone="dark"] .mi-icon,
[data-block="milestones"][data-tone="brand"] .mi-icon {
  background: rgba(255, 255, 255, 0.14) !important;
  color: #fff !important;
}
/* 铺图那一档：卡片 .08 底 + .18 描边（不能是白卡 —— 白卡上反白的数字看不见，Chris 2026-09-29）；竖线 .25。 */
[data-block="milestones"][data-block-image="background"][data-tone="dark"][data-stat-style="card"] .mi-inner {
  background: rgba(255, 255, 255, 0.08);
  border-color: rgba(255, 255, 255, 0.18);
}
[data-block="milestones"][data-block-image="background"][data-tone="dark"][data-stat-style="divided"] .mi-stat {
  border-color: rgba(255, 255, 255, 0.25);
}

/* ── 按钮的底色和字色：接平台按这个站最终配色算好的那几个变量（`scripts/lib/button-ink.js`，同 features）。
   轮廓按钮 / 纯文字链接用 `--x-primary-text-emphasis`，理由见 features/block.css 同一段（浅主题色压白底读不出来）。 */
[data-block="milestones"] .btn-primary {
  --x-btn-bg: var(--btn-primary-bg, var(--x-primary));
  --x-btn-border-color: var(--btn-primary-bg, var(--x-primary));
  --x-btn-color: var(--btn-primary-ink, #fff);
  --x-btn-hover-bg: var(--btn-primary-hover, var(--btn-primary-bg, var(--x-primary)));
  --x-btn-hover-border-color: var(--btn-primary-hover, var(--btn-primary-bg, var(--x-primary)));
  --x-btn-hover-color: var(--btn-primary-ink, #fff);
  --x-btn-active-bg: var(--btn-primary-hover, var(--btn-primary-bg, var(--x-primary)));
  --x-btn-active-color: var(--btn-primary-ink, #fff);
}
[data-block="milestones"] .btn-outline-primary {
  --x-btn-color: var(--x-primary-text-emphasis, var(--x-primary));
  --x-btn-border-color: var(--x-primary-text-emphasis, var(--x-primary));
}
[data-block="milestones"] .btn-link {
  --x-btn-color: var(--x-primary-text-emphasis, var(--x-primary));
  --x-btn-hover-color: var(--x-primary-text-emphasis, var(--x-primary));
  --x-btn-active-color: var(--x-primary-text-emphasis, var(--x-primary));
}

/* ── milestones · divided-row（预设 Row）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：milestones 是一份 markup + 八个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-block-image` / `data-intro-*` / `data-stats-*` / `data-stat-*` / `data-tone`，规则全在 `blocks/milestones/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="milestones"][data-shape="divided-row"] {
  --ms-preset: divided-row;
}

/* ── milestones · side-intro（预设 Side intro）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：milestones 是一份 markup + 八个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-block-image` / `data-intro-*` / `data-stats-*` / `data-stat-*` / `data-tone`，规则全在 `blocks/milestones/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="milestones"][data-shape="side-intro"] {
  --ms-preset: side-intro;
}

/* ── milestones · photo（预设 Photo）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：milestones 是一份 markup + 八个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-block-image` / `data-intro-*` / `data-stats-*` / `data-stat-*` / `data-tone`，规则全在 `blocks/milestones/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="milestones"][data-shape="photo"] {
  --ms-preset: photo;
}

/* ── milestones · photo-side（预设 Photo side）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：milestones 是一份 markup + 八个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-block-image` / `data-intro-*` / `data-stats-*` / `data-stat-*` / `data-tone`，规则全在 `blocks/milestones/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="milestones"][data-shape="photo-side"] {
  --ms-preset: photo-side;
}

/* ── milestones · big-number（预设 Big number）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：milestones 是一份 markup + 八个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-block-image` / `data-intro-*` / `data-stats-*` / `data-stat-*` / `data-tone`，规则全在 `blocks/milestones/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="milestones"][data-shape="big-number"] {
  --ms-preset: big-number;
}

/* ══ page-header ══════════════════════════════════════════════════════════════════════ */
/* 图的位置（image）—— 由 scripts/block-build/media-layout.js 生成，别在 block.css 里写 */
[data-block="page-header"][data-image="left"] .phn-outer,
[data-block="page-header"][data-image="right"] .phn-outer {
  display: flex;
  flex-direction: column;
  gap: 2.5rem;
}
[data-block="page-header"][data-image="left"] .phn-outer {
  flex-direction: column-reverse;
}
[data-block="page-header"][data-image="left"] .phn-text,
[data-block="page-header"][data-image="right"] .phn-text,
[data-block="page-header"][data-image="left"] .phn-img,
[data-block="page-header"][data-image="right"] .phn-img {
  min-width: 0;
}
@media (min-width: 992px) {
  [data-block="page-header"][data-image="left"] .phn-outer,
  [data-block="page-header"][data-image="right"] .phn-outer {
    flex-direction: row;
    align-items: center;
    gap: 4rem;
  }
  [data-block="page-header"][data-image="left"] .phn-outer {
    flex-direction: row-reverse;
  }
  [data-block="page-header"][data-image="left"] .phn-img,
  [data-block="page-header"][data-image="right"] .phn-img {
    flex: 0 0 calc((100% - 4rem) * 0.5);
  }
  [data-block="page-header"][data-image="left"] .phn-text,
  [data-block="page-header"][data-image="right"] .phn-text {
    flex: 1 1 0;
  }
}

/* ── page-header · 跨形态的规则（#1502）─────────────────────────────────────────────────────
   排版由**实际生效的旋钮**决定，写在根元素上：data-headline-position（top / left）· data-text-align
   （left / center）· data-image（none / left / right / background）· data-tone（light / dark / brand）。
   五个形态目录都是预设，自己不带几何（理由在各自的 shape.css 里）。出处：图册 page-header 段
   （`docs/reference/webpixels/gallery/build.py` 那几行 `.ph`），`body.opt-*` 换成根元素上的 data-*。

   🔴 Webpixels 的工具类全带 !important（text-muted / mb-* …）。这里要压过它们的规则**也带 !important**，
      而且选择器从 [data-block="page-header"] 起步再加属性 / 类 —— 特异度一定高过工具类的一个类。
   🔴 藏东西这里一条都没有：部件有数据才渲染（Section.tsx），不靠 CSS 藏。 */

/* ── 段的上下留白：3.5rem，≥992 5rem；铺图时要有高度：5rem，≥992 8rem。 */
[data-block="page-header"] {
  padding: 3.5rem 0;
}
[data-block="page-header"][data-image="background"] {
  padding: 5rem 0;
}
@media (min-width: 992px) {
  [data-block="page-header"] {
    padding: 5rem 0;
  }
  [data-block="page-header"][data-image="background"] {
    padding: 8rem 0;
  }
}

/* ── 根 / 面包屑 / 副标题列这三个 class 各要一条自己的规则（#1502 r2，theme-css「unstyled class」）。都是收窄用的：
   · .phn —— 标题是 display-4，手机上一个长单词（Waterproofing / 长地名）比屏宽还宽时折行，不撑出横向滚动。
   · .phn-crumbs —— 面包屑每一级是 flex 子项，break-word 不改它的最小宽度 ⟹ 用 anywhere，长页面名在 390 上折行。
   · .phn-main —— headlinePosition=left 时它是 1fr 那一列，grid 子项默认 min-width:auto，长内容会把 1:1 撑歪。 */
[data-block="page-header"].phn {
  overflow-wrap: break-word;
}
[data-block="page-header"] .phn-crumbs {
  overflow-wrap: anywhere;
}
[data-block="page-header"] .phn-main {
  min-width: 0;
}

/* ── 面包屑：Bootstrap 的 .breadcrumb；分隔符 #94a3b8，链接正文色、悬停主色，当前页 #64748b。 */
[data-block="page-header"] .breadcrumb {
  --x-breadcrumb-divider-color: var(--scheme-ink-faint);
  flex-wrap: wrap;
}
[data-block="page-header"] .breadcrumb a {
  color: var(--x-body-color, #334155);
  text-decoration: none;
}
[data-block="page-header"] .breadcrumb a:hover {
  color: var(--scheme-primary-ink, var(--x-primary));
}
[data-block="page-header"] .breadcrumb .active {
  color: var(--scheme-ink-muted);
}

/* ── 标题块离副标题 1.25rem；按钮离副标题 2rem。副标题不限宽（Chris 2026-09-30），占满文字那一列。 */
[data-block="page-header"] .phn-head:not(:last-child) {
  margin-bottom: 1.25rem;
}
[data-block="page-header"] .phn-sub {
  max-width: none;
}
[data-block="page-header"] .phn-sub + .phn-ctas {
  margin-top: 2rem;
}

/* ── textAlign：面包屑 / 标题 / 副标题 / 按钮一起对齐；center 时面包屑和按钮行也居中。 */
[data-block="page-header"][data-text-align="center"] .phn-text {
  text-align: center;
}
[data-block="page-header"][data-text-align="center"] .breadcrumb,
[data-block="page-header"][data-text-align="center"] .phn-ctas {
  justify-content: center !important;
}

/* ── headlinePosition=left：≥992 标题一列、副标题 + 按钮一列（各 1fr，间距 4rem，底边对齐）；面包屑仍在最上面
   一整行（它在 .phn-inner 外面）。小屏回到上下排。 */
@media (min-width: 992px) {
  [data-block="page-header"][data-headline-position="left"] .phn-inner {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 4rem;
    align-items: end;
  }
  [data-block="page-header"][data-headline-position="left"] .phn-head {
    margin-bottom: 0;
  }
}

/* ── image=left / right：图 4:3。图在哪一边、两栏多宽、上下叠隔多少由 manifest 的 `mediaLayout` 生成
   （scripts/block-build/media-layout.js，#1536）。 */
[data-block="page-header"] .phn-img img {
  aspect-ratio: 4 / 3;
}

/* ── image=background：整条铺图（object-fit: cover）+ 深色遮罩；字色按深底（Section.tsx 给 data-tone=dark）。 */
[data-block="page-header"] .phn-bgimg {
  position: absolute;
  inset: 0;
}
[data-block="page-header"] .phn-bgimg img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
[data-block="page-header"] .phn-bgimg::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(15, 23, 42, 0.55), rgba(15, 23, 42, 0.75));
}

/* ── 深底（bg 深色 / brand / 铺图）：标题反白（副标题白 .92 是全站一条 `.text-muted` 规则，
   `scripts/lib/site-css.js` §ON_DEEP_MUTED，#1477），面包屑白 .85、分隔符白 .6，描边按钮反白，
   dash / plain 两种 eyebrow 白 .85。
   🔴 各块共有的那几条（根上字色 · 标题 · eyebrow · 主按钮 / 描边 / link 按钮反白）住 `scripts/lib/site-css.js`
      §DEEP_COMMON，全站一份（#1533）；这里只留本块特有的，别再抄回来（`scripts/block-deep-common.test.js`）。 */

[data-block="page-header"][data-tone="dark"] .breadcrumb,
[data-block="page-header"][data-tone="brand"] .breadcrumb {
  --x-breadcrumb-divider-color: rgba(255, 255, 255, 0.6);
}
[data-block="page-header"][data-tone="dark"] .breadcrumb a,
[data-block="page-header"][data-tone="brand"] .breadcrumb a,
[data-block="page-header"][data-tone="dark"] .breadcrumb .active,
[data-block="page-header"][data-tone="brand"] .breadcrumb .active {
  color: rgba(255, 255, 255, 0.85);
}

/* ── 按钮的底色和字色：接平台按这个站最终配色算好的那几个变量（`scripts/lib/button-ink.js`，同 cta）。 */
[data-block="page-header"] .btn-primary {
  --x-btn-bg: var(--btn-primary-bg, var(--x-primary));
  --x-btn-border-color: var(--btn-primary-bg, var(--x-primary));
  --x-btn-color: var(--btn-primary-ink, #fff);
  --x-btn-hover-bg: var(--btn-primary-hover, var(--btn-primary-bg, var(--x-primary)));
  --x-btn-hover-border-color: var(--btn-primary-hover, var(--btn-primary-bg, var(--x-primary)));
  --x-btn-hover-color: var(--btn-primary-ink, #fff);
  --x-btn-active-bg: var(--btn-primary-hover, var(--btn-primary-bg, var(--x-primary)));
  --x-btn-active-color: var(--btn-primary-ink, #fff);
}
[data-block="page-header"] .btn-outline-primary {
  --x-btn-color: var(--btn-outline-ink, var(--x-primary));
  --x-btn-border-color: var(--btn-outline-ink, var(--x-primary));
}

/* ── page-header · simple（预设 Simple）───────────────────────────────────────────────────────
   这个形态没有自己的几何：page-header 是一份 markup + 三个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-headline-position` / `data-text-align` / `data-image` / `data-tone`，规则全在
   `blocks/page-header/block.css`）。按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，
   页面就会排成一个跟旋钮说的不一样的样子。所以这份文件只有这段说明。 */
[data-block="page-header"][data-shape="simple"] {
  --phn-preset: simple;
}

/* ── page-header · centered（预设 Centered）───────────────────────────────────────────────────────
   这个形态没有自己的几何：page-header 是一份 markup + 三个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-headline-position` / `data-text-align` / `data-image` / `data-tone`，规则全在
   `blocks/page-header/block.css`）。按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，
   页面就会排成一个跟旋钮说的不一样的样子。所以这份文件只有这段说明。 */
[data-block="page-header"][data-shape="centered"] {
  --phn-preset: centered;
}

/* ── page-header · split（预设 Split）───────────────────────────────────────────────────────
   这个形态没有自己的几何：page-header 是一份 markup + 三个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-headline-position` / `data-text-align` / `data-image` / `data-tone`，规则全在
   `blocks/page-header/block.css`）。按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，
   页面就会排成一个跟旋钮说的不一样的样子。所以这份文件只有这段说明。 */
[data-block="page-header"][data-shape="split"] {
  --phn-preset: split;
}

/* ── page-header · photo（预设 Photo）───────────────────────────────────────────────────────
   这个形态没有自己的几何：page-header 是一份 markup + 三个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-headline-position` / `data-text-align` / `data-image` / `data-tone`，规则全在
   `blocks/page-header/block.css`）。按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，
   页面就会排成一个跟旋钮说的不一样的样子。所以这份文件只有这段说明。 */
[data-block="page-header"][data-shape="photo"] {
  --phn-preset: photo;
}

/* ── page-header · cover（预设 Cover）───────────────────────────────────────────────────────
   这个形态没有自己的几何：page-header 是一份 markup + 三个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-headline-position` / `data-text-align` / `data-image` / `data-tone`，规则全在
   `blocks/page-header/block.css`）。按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，
   页面就会排成一个跟旋钮说的不一样的样子。所以这份文件只有这段说明。 */
[data-block="page-header"][data-shape="cover"] {
  --phn-preset: cover;
}

/* ══ pricing ══════════════════════════════════════════════════════════════════════ */
/* 条目网格（plansColumns：手机 ≤1 · iPad ≤2 · ≥992 按旋钮）—— 由 scripts/block-build/items-grid.js 生成，别在 block.css 里写 */
[data-block="pricing"] .pr-grid {
  --items-cols: 1;
  display: flex;
  flex-wrap: wrap;
}
[data-block="pricing"] .pr-plan {
  flex: 0 0 auto;
  width: calc((100% - (var(--items-cols) - 1) * var(--pr-gap)) / var(--items-cols));
}
@media (min-width: 768px) {
  [data-block="pricing"][data-plans-columns="auto"] .pr-grid {
    --items-cols: 2;
  }
  [data-block="pricing"][data-plans-columns="2"] .pr-grid {
    --items-cols: 2;
  }
  [data-block="pricing"][data-plans-columns="3"] .pr-grid {
    --items-cols: 2;
  }
  [data-block="pricing"][data-plans-columns="4"] .pr-grid {
    --items-cols: 2;
  }
}
@media (min-width: 992px) {
  [data-block="pricing"][data-plans-columns="auto"] .pr-plan {
    flex: 1 1 0;
    width: auto;
  }
  [data-block="pricing"][data-plans-columns="3"] .pr-grid {
    --items-cols: 3;
  }
  [data-block="pricing"][data-plans-columns="4"] .pr-grid {
    --items-cols: 4;
  }
}
@media (max-width: 991.98px) {
  [data-block="pricing"][data-plan-style="divided"] .pr-grid {
    --items-cols: 1;
  }
}

/* 块头排版（introAlign / introPosition）—— 由 scripts/block-build/intro-layout.js 生成，别在 block.css 里写 */
[data-block="pricing"][data-intro-align="center"] .pr-intro-text {
  text-align: center;
}
[data-block="pricing"][data-intro-align="right"] .pr-intro-text {
  text-align: end;
}
[data-block="pricing"][data-intro-align="center"] .pr-proof {
  justify-content: center;
}
[data-block="pricing"][data-intro-align="center"] .pr-logo-row {
  justify-content: center;
}
[data-block="pricing"][data-intro-align="center"][data-intro-position="top"] .pr-billing-wrap,
[data-block="pricing"][data-intro-align="center"][data-intro-position="bottom"] .pr-billing-wrap {
  text-align: center;
}
[data-block="pricing"][data-intro-align="right"] .pr-proof {
  justify-content: flex-end;
}
[data-block="pricing"][data-intro-align="right"] .pr-logo-row {
  justify-content: flex-end;
}
[data-block="pricing"][data-intro-align="right"][data-intro-position="top"] .pr-billing-wrap,
[data-block="pricing"][data-intro-align="right"][data-intro-position="bottom"] .pr-billing-wrap {
  text-align: end;
}
@media (min-width: 992px) {
  [data-block="pricing"][data-intro-align="center"] .pr-intro-text {
    width: 100%;
    max-width: 80%;
    margin-inline: auto;
  }
}
[data-block="pricing"][data-intro-position="bottom"] .pr-frame {
  flex-direction: column-reverse;
}
@media (max-width: 991.98px) {
  [data-block="pricing"][data-intro-position="right"] .pr-frame {
    flex-direction: column-reverse;
  }
}
@media (min-width: 992px) {
  [data-block="pricing"][data-intro-position="left"] .pr-introcol,
  [data-block="pricing"][data-intro-position="right"] .pr-introcol {
    flex: 0 0 auto;
    width: 41.6667%;
    position: sticky;
    top: 2rem;
    align-self: flex-start;
  }
  [data-block="pricing"][data-intro-position="left"] .pr-planscol,
  [data-block="pricing"][data-intro-position="right"] .pr-planscol {
    flex: 0 0 auto;
    width: 58.3333%;
  }
  [data-block="pricing"][data-intro-position="left"] .pr-intro-text,
  [data-block="pricing"][data-intro-position="right"] .pr-intro-text {
    max-width: none;
    margin: 0;
  }
  [data-block="pricing"][data-intro-position="right"] .pr-frame {
    flex-direction: row-reverse;
  }
}

/* ── pricing · 跨形态的规则（#1483）──────────────────────────────────────────────────────────
   pricing 的排版由**实际生效的旋钮**决定，写在根元素上：
     intro  data-intro-position（left / right / top / bottom）· data-intro-align（left / center / right）
     plans  data-plans-columns（auto / 1 / 2 / 3 / 4）· data-plan-features（inside / below）·
            data-plan-style（card / divided / plain）· data-plan-align（left / center / right）· data-plan-cta（top / bottom）·
            data-featured（outline / background）
     底色   data-tone（light / dark / brand，scripts/lib/contrast.js §toneForBg 按 bg 算）
     高亮色 data-fc-kind（solid / gradient）· data-fc-tone（light / dark / brand）+ 根上的 --pr-fc / --pr-fc-line /
            --pr-fc-on / --pr-fc-ink / --pr-fc-muted（Section.tsx §featuredVars 按 featuredColor 算；空 = brand）
   六个形态目录都是预设，自己不带几何（理由在各自的 shape.css 里）。
   「盒子」是谁：planFeatures=inside 时是 .pr-inner（清单在盒子里，.pr-card 不成盒）；below 时是 .pr-card（清单在盒子下面）。

   🔴 Webpixels 的工具类全带 !important（mb-* / text-muted / d-flex / bg-white / justify-content-between …）。这里要压过它们的规则
      **也带 !important**，而且选择器从 [data-block="pricing"] 起步再加属性 / 类 —— 特异度一定高过工具类的一个类。
   🔴 部件的有无靠渲染（Section.tsx 有数据才画），不靠 CSS 藏 ⟹ 这里没有一条「显示某部件」的 display 规则会被
      另一条盖掉（图册那次：居中 highlights 的 display:flex!important 盖掉了隐藏规则，没勾也露出来）。
   🔴 列数由 itemsGrid 生成（#1537，flex + calc，列距读 --pr-gap），不用负边距 —— 负边距在 390 上溢出（#1482 量出来的同一个坑）。
   间距照 Webpixels 尺度：段 py-16 py-lg-24、块头与套餐 2.5rem（gy-10）/ ≥992 横向 4rem（gx-lg-16）、套餐之间 1.5rem、
   卡内 2rem（= p-8）、billing 与套餐之间 2rem。 */

/* ═══ intro（块头）═══════════════════════════════════════════════════════════════════════════════ */

[data-block="pricing"] .pr-intro {
  display: flex;
  flex-direction: column;
}
[data-block="pricing"] .pr-intro-text > :last-child {
  margin-bottom: 0 !important;
}
/* 正文不单独限宽，跟标题同宽（#1486）。规则留着是因为块前缀 class 都要有一条带声明的规则
   （scripts/block-class-rules.test.js，#1519）。 */
[data-block="pricing"] .pr-body {
  max-width: none;
}

/* 部件：highlights（图标 + 标题 + 一句）· proof（头像 + 评分）· logos。各自跟上一样隔 2rem。 */
[data-block="pricing"] .pr-highlights,
[data-block="pricing"] .pr-proof,
[data-block="pricing"] .pr-logos {
  margin-top: 2rem;
}
[data-block="pricing"] .pr-hl {
  margin-bottom: 1.25rem;
}
[data-block="pricing"] .pr-hl:last-child {
  margin-bottom: 0;
}
[data-block="pricing"] .pr-hl-icon {
  width: 3rem;
  height: 3rem;
  font-size: 1.25rem;
}
[data-block="pricing"] .pr-avatar + .pr-avatar {
  margin-inline-start: -10px;
}
[data-block="pricing"] .pr-logo {
  height: 24px;
  width: auto;
  opacity: 0.75;
}

/* 块头排版（introAlign / introPosition：对齐、≥992 center 最宽 80%、左右两列、上下反向）由 manifest 的 introLayout 生成
   （scripts/block-build/intro-layout.js，#1535），这里不写。
   proof / logos 跟着居中 / 靠右、billing 在块头上下时跟着对齐（introLayout.follow）。
   下面是 pricing 自己的：highlights 在 center 时排成一排（≥768 四列、手机两列，图标在上、字居中），right 时镜像（图标在右、字靠右）。 */
[data-block="pricing"][data-intro-align="center"] .pr-highlights {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 1.5rem;
}
[data-block="pricing"][data-intro-align="center"] .pr-hl {
  flex: 0 0 auto;
  width: calc((100% - 1.5rem) / 2);
  min-width: 0;
  flex-direction: column;
  align-items: center !important;
  text-align: center;
  gap: 0.75rem !important;
  margin-bottom: 0;
}
@media (min-width: 768px) {
  [data-block="pricing"][data-intro-align="center"] .pr-hl {
    width: calc((100% - 4.5rem) / 4);
  }
}
[data-block="pricing"][data-intro-align="right"] .pr-hl {
  flex-direction: row-reverse;
  text-align: end;
}

/* ═══ billing（月付 / 年付，住在套餐正上方）═══════════════════════════════════════════════════════
   块头在上 / 下时跟 introAlign 对齐，块头在侧时靠左。 */
[data-block="pricing"] .pr-billing-wrap {
  margin-bottom: 2rem;
}
[data-block="pricing"] .pr-billing {
  flex-wrap: wrap;
  max-width: 100%;
}

/* ═══ plans ═══════════════════════════════════════════════════════════════════════════════════════ */

/* plansColumns 的列数与断点（手机 1 列、iPad 2 列（`1` 仍 1 列）；≥992 auto = 一行均分，2 / 3 / 4 按列数折行，1 = 单套餐宽卡；
   divided 在 <992 一律 1 列）归 manifest 的 itemsGrid，由 scripts/block-build/items-grid.js 生成（#1537，画法照旧 flex + calc，不用负边距）。
   这里只管列距 / 行距。 */
[data-block="pricing"] .pr-grid {
  --pr-gap: 1.5rem;
  row-gap: 1.5rem;
  column-gap: var(--pr-gap);
}
[data-block="pricing"] .pr-plan {
  min-width: 0;
}

/* plansColumns=1：单套餐宽卡（最大 56rem、居中），≥768 清单分两列；按钮在价格下面时不通栏、跟 planAlign 走。 */
[data-block="pricing"][data-plans-columns="1"] .pr-plan {
  width: 100%;
  max-width: 56rem;
  margin-inline-start: auto;
  margin-inline-end: auto;
}
@media (min-width: 768px) {
  [data-block="pricing"][data-plans-columns="1"] .pr-features {
    columns: 2;
    column-gap: 3rem;
  }
}
[data-block="pricing"][data-plans-columns="1"] .pr-features li {
  break-inside: avoid;
}
[data-block="pricing"][data-plans-columns="1"][data-plan-cta="top"] .pr-cta {
  width: auto !important;
  align-self: flex-start;
}
[data-block="pricing"][data-plans-columns="1"][data-plan-cta="top"][data-plan-align="center"] .pr-cta {
  align-self: center;
}
[data-block="pricing"][data-plans-columns="1"][data-plan-cta="top"][data-plan-align="right"] .pr-cta {
  align-self: flex-end;
}

/* 套餐里的几行 */
[data-block="pricing"] .pr-amount,
[data-block="pricing"] .pr-period,
[data-block="pricing"] .pr-hl-text,
[data-block="pricing"] .pr-billing-note {
  overflow-wrap: anywhere;
}
[data-block="pricing"] .pr-features {
  margin-top: 1.25rem;
  text-align: start;
}
[data-block="pricing"] .pr-check {
  margin-top: 0.15rem;
  color: var(--x-primary-text-emphasis, var(--x-primary));
}
[data-block="pricing"] .pr-badge {
  background: var(--x-primary-bg-subtle, #e0e7ff);
  color: var(--x-primary-text-emphasis, var(--x-primary));
}

/* planFeatures：inside = 清单在盒子里（.pr-card 不成盒）；below = 清单在盒子下面。 */
[data-block="pricing"][data-plan-features="inside"] .pr-card {
  display: contents;
}
[data-block="pricing"][data-plan-features="below"] .pr-card {
  display: flex;
  flex-direction: column;
}
[data-block="pricing"][data-plan-features="below"] .pr-features {
  padding: 0 0.5rem;
}

/* planCta：top = 价格下面；bottom = 最底下（card 时通栏贴底）。 */
[data-block="pricing"][data-plan-cta="bottom"] .pr-cta {
  order: 9;
  margin-top: 1.5rem;
}
[data-block="pricing"][data-plan-cta="bottom"][data-plan-style="card"] .pr-cta {
  margin: auto -2rem -2rem;
  width: auto !important;
  border-radius: 0 0 1rem 1rem;
  padding: 1rem;
}
/* 通栏贴底的按钮跟卡片共边（#1587，Chris 2026-10-05：按钮边框色要跟套餐卡一样）：非主推卡的 outline 按钮只画顶上一条分隔线、
   颜色用卡片的描边色；左右和底边由卡片自己的描边画，不然卡片 1px 灰边里侧再贴一圈 1px 主色边，两道边两个色。
   主推卡（data-featured="background"）的按钮走下面 :3xx 自己的规则，不在此列。图册 build.py 同一条（选择器换名后逐字相同）。 */
[data-block="pricing"][data-plan-cta="bottom"][data-plan-style="card"] .pr-plan:not(.pr-featured) .pr-cta.btn-outline-primary {
  border-color: var(--x-border-color, #e5e7eb);
  border-width: 1px 0 0;
}
[data-block="pricing"][data-plan-cta="bottom"][data-plan-style="card"] .pr-plan:not(.pr-featured) .pr-cta.btn-outline-primary:hover {
  border-color: var(--x-primary);
}
[data-block="pricing"][data-plan-cta="bottom"][data-plan-style="card"][data-plan-features="inside"] .pr-features {
  margin-bottom: 1.5rem !important;
}
[data-block="pricing"][data-plan-cta="bottom"][data-plan-style="card"][data-plan-features="below"] .pr-desc {
  margin-bottom: 1.5rem !important;
}

/* planStyle：card = 白底 1px 描边 1rem 圆角 2rem 内边距；divided = 无框、相邻套餐之间 1px 竖线（小屏换成横线）；
   plain = 无框无线。divided 的竖线要贴着套餐之间，所以列间距归零、内边距挪进 .pr-inner。 */
[data-block="pricing"][data-plan-style="card"][data-plan-features="inside"] .pr-inner,
[data-block="pricing"][data-plan-style="card"][data-plan-features="below"] .pr-card {
  background: var(--x-body-bg);
  border: 1px solid var(--x-border-color, #e5e7eb);
  border-radius: 1rem;
  padding: 2rem;
}
[data-block="pricing"][data-plan-style="divided"] .pr-inner {
  padding: 0 1.25rem;
}
[data-block="pricing"][data-plan-style="plain"] .pr-inner {
  padding: 0 0.5rem;
}
@media (max-width: 991.98px) {
  [data-block="pricing"][data-plan-style="divided"] .pr-plan {
    border-top: 1px solid var(--x-border-color, #e5e7eb);
    padding-top: 1.5rem;
  }
  [data-block="pricing"][data-plan-style="divided"] .pr-plan:first-child {
    border-top: 0;
    padding-top: 0;
  }
}
@media (min-width: 992px) {
  [data-block="pricing"][data-plan-style="divided"] .pr-grid {
    --pr-gap: 0px;
  }
  [data-block="pricing"][data-plan-style="divided"] .pr-plan {
    border-inline-start: 1px solid var(--x-border-color, #e5e7eb);
  }
  [data-block="pricing"][data-plan-style="divided"] .pr-plan:first-child,
  [data-block="pricing"][data-plan-style="divided"][data-plans-columns="1"] .pr-plan,
  [data-block="pricing"][data-plan-style="divided"][data-plans-columns="2"] .pr-grid > .pr-plan:nth-child(2n+1),
  [data-block="pricing"][data-plan-style="divided"][data-plans-columns="3"] .pr-grid > .pr-plan:nth-child(3n+1),
  [data-block="pricing"][data-plan-style="divided"][data-plans-columns="4"] .pr-grid > .pr-plan:nth-child(4n+1) {
    border-inline-start: 0;
  }
}

/* planAlign：名字 / 价格 / 说明 / 按钮跟着走（right 时 badge 换到左边）；清单整块跟着走、每行仍左对齐（勾号排成一条竖线）。 */
[data-block="pricing"][data-plan-align="center"] .pr-inner {
  text-align: center;
}
[data-block="pricing"][data-plan-align="center"] .pr-head {
  justify-content: center !important;
}
[data-block="pricing"][data-plan-align="center"] .pr-price {
  justify-content: center;
}
[data-block="pricing"][data-plan-align="center"] .pr-features {
  width: fit-content;
  max-width: 100%;
  margin-inline-start: auto;
  margin-inline-end: auto;
}
[data-block="pricing"][data-plan-align="right"] .pr-inner {
  text-align: end;
}
[data-block="pricing"][data-plan-align="right"] .pr-head {
  flex-direction: row-reverse;
}
[data-block="pricing"][data-plan-align="right"] .pr-price {
  justify-content: flex-end;
}
[data-block="pricing"][data-plan-align="right"] .pr-features {
  width: fit-content;
  max-width: 100%;
  margin-inline-start: auto;
}

/* ═══ featured：高亮那个套餐 = 样式（outline | background）× 颜色（featuredColor）═════════════════════
   两档都用同一个色。非 card 时高亮的那个自己补一个盒子（圆角 + 2rem 内边距，不许边框贴字）。 */

/* outline：2px 边框 = 这个色（纯色 / brand 直接是 border-color；渐变用 padding-box / border-box 两层背景画，内层不透明白），
   卡保持白底深字 —— 整段 bg 深的时候也一样（图册截图里出过「白卡白字」，这一组规则守的就是它）。 */
[data-block="pricing"][data-featured="outline"][data-plan-features="inside"] .pr-featured .pr-inner,
[data-block="pricing"][data-featured="outline"][data-plan-features="below"] .pr-featured .pr-card {
  border: 2px solid var(--pr-fc-line) !important;
  background: var(--x-body-bg) !important;
  border-radius: 1rem;
  padding: 2rem;
  color: var(--scheme-ink);
}
[data-block="pricing"][data-fc-kind="gradient"][data-featured="outline"][data-plan-features="inside"] .pr-featured .pr-inner,
[data-block="pricing"][data-fc-kind="gradient"][data-featured="outline"][data-plan-features="below"] .pr-featured .pr-card {
  border-color: transparent !important;
  background: linear-gradient(var(--x-body-bg), var(--x-body-bg)) padding-box, var(--pr-fc) border-box !important;
}
[data-block="pricing"][data-featured="outline"] .pr-featured .pr-badge,
[data-block="pricing"][data-featured="outline"] .pr-featured .btn-primary {
  background: var(--pr-fc) !important;
  border-color: transparent !important;
  color: var(--pr-fc-on) !important;
}
[data-block="pricing"][data-featured="outline"] .pr-featured .pr-check {
  color: var(--pr-fc-ink);
}

/* background：卡底 = 这个色，卡里的字 / 勾号按亮度反白（--pr-fc-on；反白时小字白 .92 = --pr-fc-muted），
   主按钮和 badge 翻成反白色底 + 这个色的字。 */
[data-block="pricing"][data-featured="background"][data-plan-features="inside"] .pr-featured .pr-inner,
[data-block="pricing"][data-featured="background"][data-plan-features="below"] .pr-featured .pr-card {
  background: var(--pr-fc) !important;
  border: 1px solid transparent !important;
  border-radius: 1rem;
  padding: 2rem;
  color: var(--pr-fc-on);
}
[data-block="pricing"][data-featured="background"] .pr-featured .pr-name,
[data-block="pricing"][data-featured="background"] .pr-featured .pr-amount,
[data-block="pricing"][data-featured="background"] .pr-featured .pr-check,
[data-block="pricing"][data-featured="background"][data-plan-features="inside"] .pr-featured .pr-features {
  color: var(--pr-fc-on) !important;
}
[data-block="pricing"][data-featured="background"] .pr-featured .pr-card .text-muted,
[data-block="pricing"][data-featured="background"][data-plan-features="inside"] .pr-featured .text-muted {
  color: var(--pr-fc-muted) !important;
}
[data-block="pricing"][data-featured="background"] .pr-featured .pr-badge,
[data-block="pricing"][data-featured="background"] .pr-featured .btn-primary {
  background: var(--pr-fc-on) !important;
  border-color: var(--pr-fc-on) !important;
  color: var(--pr-fc-ink) !important;
}
[data-block="pricing"][data-featured="background"] .pr-featured .pr-card .btn-outline-primary,
[data-block="pricing"][data-featured="background"][data-plan-features="inside"] .pr-featured .btn-outline-primary {
  color: var(--pr-fc-on) !important;
  border-color: var(--pr-fc-on) !important;
}

/* ═══ 字色按背景亮度（data-tone，scripts/lib/contrast.js §toneForBg）══════════════════════════════
   dark = 深底 / 深色渐变；brand = 主题主色底。两者一样：标题 / 名字 / 价格反白（小字白 .92 是全站那一条，
   site-css.js §ON_DEEP_MUTED）；card 换半透明底 + 半透明白描边、竖线换半透明白；outline 按钮翻成白描边、
   主按钮白底主色字（同 milestones）。
   🔴 各块共有的那几条（根上字色 · 标题 · eyebrow · 主按钮 / 描边 / link 按钮反白）住 `scripts/lib/site-css.js`
      §DEEP_COMMON，全站一份（#1533）；这里只留本块特有的，别再抄回来（`scripts/block-deep-common.test.js`）。 */

[data-block="pricing"][data-tone="dark"] .pr-hl-title,
[data-block="pricing"][data-tone="brand"] .pr-hl-title,
[data-block="pricing"][data-tone="dark"] .pr-name,
[data-block="pricing"][data-tone="brand"] .pr-name,
[data-block="pricing"][data-tone="dark"] .pr-amount,
[data-block="pricing"][data-tone="brand"] .pr-amount,
[data-block="pricing"][data-tone="dark"] .pr-check,
[data-block="pricing"][data-tone="brand"] .pr-check {
  color: #fff !important;
}
[data-block="pricing"][data-tone="dark"][data-plan-style="card"] .pr-inner,
[data-block="pricing"][data-tone="brand"][data-plan-style="card"] .pr-inner,
[data-block="pricing"][data-tone="dark"][data-plan-style="card"] .pr-card,
[data-block="pricing"][data-tone="brand"][data-plan-style="card"] .pr-card {
  background: rgba(255, 255, 255, 0.06);
  border-color: rgba(255, 255, 255, 0.15);
}
[data-block="pricing"][data-tone="dark"][data-plan-style="card"][data-plan-features="inside"] .pr-card,
[data-block="pricing"][data-tone="brand"][data-plan-style="card"][data-plan-features="inside"] .pr-card {
  background: none;
}
[data-block="pricing"][data-tone="dark"] .pr-plan,
[data-block="pricing"][data-tone="brand"] .pr-plan {
  border-color: rgba(255, 255, 255, 0.2) !important;
}
[data-block="pricing"][data-tone="dark"] .pr-hl-icon,
[data-block="pricing"][data-tone="brand"] .pr-hl-icon {
  background: rgba(255, 255, 255, 0.1) !important;
  border-color: rgba(255, 255, 255, 0.2) !important;
  color: #fff;
}
[data-block="pricing"][data-tone="dark"] .pr-logo,
[data-block="pricing"][data-tone="brand"] .pr-logo {
  filter: brightness(0) invert(1);
  opacity: 0.9;
}
[data-block="pricing"][data-tone="dark"] .pr-badge,
[data-block="pricing"][data-tone="brand"] .pr-badge {
  background: rgba(255, 255, 255, 0.14) !important;
  color: #fff !important;
}
[data-block="pricing"][data-tone="dark"] .pr-billing,
[data-block="pricing"][data-tone="brand"] .pr-billing {
  border-color: rgba(255, 255, 255, 0.3) !important;
}
[data-block="pricing"][data-tone="dark"] .pr-billing .btn-link,
[data-block="pricing"][data-tone="brand"] .pr-billing .btn-link {
  color: #fff !important;
}

/* 深底 + outline：高亮那张是白卡 ⟹ 卡里的字回到深色，不跟整段反白（上面几条是整段的，这几条特异度更高、专管那张卡）。 */
[data-block="pricing"][data-featured="outline"][data-tone] .pr-featured .pr-name,
[data-block="pricing"][data-featured="outline"][data-tone] .pr-featured .pr-amount {
  color: var(--scheme-ink-strong) !important;
}
[data-block="pricing"][data-featured="outline"][data-tone] .pr-featured .text-muted {
  color: var(--scheme-ink-muted) !important;
}
[data-block="pricing"][data-featured="outline"][data-tone] .pr-featured .pr-features {
  color: var(--scheme-ink);
}
[data-block="pricing"][data-featured="outline"][data-tone] .pr-featured .pr-check {
  color: var(--pr-fc-ink) !important;
}
[data-block="pricing"][data-featured="outline"][data-tone] .pr-featured .btn-outline-primary {
  color: var(--x-primary-text-emphasis, var(--x-primary)) !important;
  border-color: var(--x-primary-text-emphasis, var(--x-primary)) !important;
}
/* 深底 + background：高亮卡的字跟着 featuredColor 走（上面 featured 那一组），整段的反白规则不许盖过来。 */
[data-block="pricing"][data-featured="background"][data-tone] .pr-featured .pr-name,
[data-block="pricing"][data-featured="background"][data-tone] .pr-featured .pr-amount,
[data-block="pricing"][data-featured="background"][data-tone] .pr-featured .pr-check {
  color: var(--pr-fc-on) !important;
}
[data-block="pricing"][data-featured="background"][data-tone] .pr-featured .text-muted {
  color: var(--pr-fc-muted) !important;
}
[data-block="pricing"][data-featured="outline"][data-tone] .pr-featured .pr-badge,
[data-block="pricing"][data-featured="outline"][data-tone] .pr-featured .btn-primary {
  background: var(--pr-fc) !important;
  border-color: transparent !important;
  color: var(--pr-fc-on) !important;
}
[data-block="pricing"][data-featured="background"][data-tone] .pr-featured .pr-badge,
[data-block="pricing"][data-featured="background"][data-tone] .pr-featured .btn-primary {
  background: var(--pr-fc-on) !important;
  border-color: var(--pr-fc-on) !important;
  color: var(--pr-fc-ink) !important;
}

/* ── 按钮的底色和字色：接平台按这个站最终配色算好的那几个变量（`scripts/lib/button-ink.js`，同 features / milestones）。 */
[data-block="pricing"] .btn-primary {
  --x-btn-bg: var(--btn-primary-bg, var(--x-primary));
  --x-btn-border-color: var(--btn-primary-bg, var(--x-primary));
  --x-btn-color: var(--btn-primary-ink, #fff);
  --x-btn-hover-bg: var(--btn-primary-hover, var(--btn-primary-bg, var(--x-primary)));
  --x-btn-hover-border-color: var(--btn-primary-hover, var(--btn-primary-bg, var(--x-primary)));
  --x-btn-hover-color: var(--btn-primary-ink, #fff);
  --x-btn-active-bg: var(--btn-primary-hover, var(--btn-primary-bg, var(--x-primary)));
  --x-btn-active-color: var(--btn-primary-ink, #fff);
}
[data-block="pricing"] .btn-outline-primary {
  --x-btn-color: var(--x-primary-text-emphasis, var(--x-primary));
  --x-btn-border-color: var(--x-primary-text-emphasis, var(--x-primary));
}
[data-block="pricing"] .btn-link {
  --x-btn-color: var(--x-primary-text-emphasis, var(--x-primary));
  --x-btn-hover-color: var(--x-primary-text-emphasis, var(--x-primary));
  --x-btn-active-color: var(--x-primary-text-emphasis, var(--x-primary));
}

/* ── pricing · plan-cards（预设 Plan cards）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：pricing 是一份 markup + 八个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-plan*` / `data-featured` / `data-tone` / `data-fc-*`，规则全在 `blocks/pricing/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="pricing"][data-shape="plan-cards"] {
  --pr-preset: plan-cards;
}

/* ── pricing · side-intro（预设 Side intro）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：pricing 是一份 markup + 八个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-plan*` / `data-featured` / `data-tone` / `data-fc-*`，规则全在 `blocks/pricing/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="pricing"][data-shape="side-intro"] {
  --pr-preset: side-intro;
}

/* ── pricing · open-columns（预设 Open columns）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：pricing 是一份 markup + 八个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-plan*` / `data-featured` / `data-tone` / `data-fc-*`，规则全在 `blocks/pricing/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="pricing"][data-shape="open-columns"] {
  --pr-preset: open-columns;
}

/* ── pricing · features-below（预设 Features below）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：pricing 是一份 markup + 八个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-plan*` / `data-featured` / `data-tone` / `data-fc-*`，规则全在 `blocks/pricing/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="pricing"][data-shape="features-below"] {
  --pr-preset: features-below;
}

/* ── pricing · one-plan（预设 One plan）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：pricing 是一份 markup + 八个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-plan*` / `data-featured` / `data-tone` / `data-fc-*`，规则全在 `blocks/pricing/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="pricing"][data-shape="one-plan"] {
  --pr-preset: one-plan;
}

/* ── pricing · rainbow（预设 Rainbow）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：pricing 是一份 markup + 八个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-plan*` / `data-featured` / `data-tone` / `data-fc-*`，规则全在 `blocks/pricing/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="pricing"][data-shape="rainbow"] {
  --pr-preset: rainbow;
}

/* ══ reviews ══════════════════════════════════════════════════════════════════════ */
/* 块头排版（introAlign / introPosition）—— 由 scripts/block-build/intro-layout.js 生成，别在 block.css 里写 */
[data-block="reviews"][data-intro-align="center"] .rv-intro-text {
  text-align: center;
}
[data-block="reviews"][data-intro-align="right"] .rv-intro-text {
  text-align: end;
}
[data-block="reviews"][data-intro-align="center"] .rv-total {
  justify-content: center;
}
[data-block="reviews"][data-intro-align="right"] .rv-total {
  justify-content: flex-end;
}
@media (min-width: 992px) {
  [data-block="reviews"][data-intro-align="center"] .rv-intro-text {
    width: 100%;
    max-width: 80%;
    margin-inline: auto;
  }
}
[data-block="reviews"][data-intro-position="bottom"] .rv-frame {
  flex-direction: column-reverse;
}
@media (max-width: 991.98px) {
  [data-block="reviews"][data-intro-position="right"] .rv-frame {
    flex-direction: column-reverse;
  }
}
@media (min-width: 992px) {
  [data-block="reviews"][data-intro-position="left"] .rv-introcol,
  [data-block="reviews"][data-intro-position="right"] .rv-introcol {
    flex: 0 0 auto;
    width: 33.3333%;
    position: sticky;
    top: 2rem;
    align-self: flex-start;
  }
  [data-block="reviews"][data-intro-position="left"] .rv-itemscol,
  [data-block="reviews"][data-intro-position="right"] .rv-itemscol {
    flex: 0 0 auto;
    width: 66.6667%;
  }
  [data-block="reviews"][data-intro-position="left"] .rv-intro-text,
  [data-block="reviews"][data-intro-position="right"] .rv-intro-text {
    max-width: none;
    margin: 0;
  }
  [data-block="reviews"][data-intro-position="right"] .rv-frame {
    flex-direction: row-reverse;
  }
}

/* ── reviews · 跨形态的规则（#1504）──────────────────────────────────────────────────────────────
   reviews 的排版由**实际生效的旋钮**决定，写在根元素上：
     intro  data-intro-position（left / right / top / bottom）· data-intro-align（left / center / right）· data-total（none / show）
     items  data-items-layout（grid / row）
     item   data-item-style（plain / card）· data-item-align（left / center / right —— 不跟 introAlign）
     底色   data-tone（light / dark / brand，scripts/lib/contrast.js §toneForBg）
   grid 的列数按平台个数：Section.tsx 在 .rv-items 上写 --rv-n。三个形态目录都是预设，自己不带几何（理由在各自的 shape.css 里）。
   规则照定稿图册 reviews 段（docs/reference/webpixels/gallery/build.py 的 reviews CSS，Chris 2026-09-30）。

   🔴 Webpixels 的工具类全带 !important（mb-* / display-5 / fs-5 / text-muted / text-sm …）。这里要压过它们的规则**也带 !important**，
      而且选择器从 [data-block="reviews"] 起步再加属性 / 类 —— 特异度一定高过工具类的一个类。
   🔴 部件的有无靠渲染（Section.tsx 有数据才画），不靠 CSS 藏：眉标、标题、正文、总分没数据 / total=none 就没有节点。
   间距照定稿：段 py-16 py-lg-24；块头与平台 gy-10 gx-lg-16（纵向写 gy-10、不写 g-10：g-* 的横向负边距在手机上溢出，
   #1475 量出来的）；grid 格间 1.5rem（<576 .75rem）；row 小条间 .75rem 1rem。 */

/* ═══ intro ════════════════════════════════════════════════════════════════════════════════════════ */

[data-block="reviews"] .rv-intro-text > :last-child {
  margin-bottom: 0 !important;
}
/* 🔴 markup 里每个 rv-* 类都要有一条规则：theme-css 守卫（#992「unstyled class」）点名没有规则的类（#1496 踩过）。 */
[data-block="reviews"] .rv-eyebrow-wrap {
  line-height: 1;
}
[data-block="reviews"] .rv-body {
  text-wrap: pretty;
}

/* total=show：块头正文下面一行总分 —— 大数字（3.5rem 粗体）+ 五星（20px）+「405 reviews on 4 platforms」，跟着 introAlign 对齐。 */
[data-block="reviews"] .rv-total {
  margin-top: 1.5rem;
}
[data-block="reviews"] .rv-total-score {
  font-size: 3.5rem;
  letter-spacing: -0.02em;
}
[data-block="reviews"] .rv-total .rv-stars svg {
  width: 20px;
  height: 20px;
}

/* 块头排版（introAlign / introPosition：对齐、≥992 center 最宽 80%、左右两列、上下反向）由 manifest 的 introLayout 生成
   （scripts/block-build/intro-layout.js，#1535），这里不写。
   总分条跟着 introAlign 走（introLayout.follow）。 */

/* ═══ 每个平台：logo · 大分数 · 星 + 条数 ═══════════════════════════════════════════════════════════
   logo 三档（Section.tsx §logo）：上传的图 → 内置品牌图标（品牌色，--rv-brand）→ 粗体平台名。 */
[data-block="reviews"] .rv-item {
  transition: box-shadow 0.15s;
}
[data-block="reviews"] .rv-logo {
  display: inline-flex;
  align-items: center;
  line-height: 1;
}
[data-block="reviews"] .rv-icon {
  color: var(--rv-brand);
}
[data-block="reviews"] .rv-logo-img img {
  display: block;
  height: 1em;
  width: auto;
  max-width: 8rem;
}
[data-block="reviews"] .rv-meta {
  gap: 0.25rem;
}
[data-block="reviews"] .rv-score {
  letter-spacing: -0.02em;
}
[data-block="reviews"] .rv-stars {
  line-height: 1;
}

/* itemsLayout=grid：等宽格子，格里竖排。列数按平台个数 n（--rv-n）：<576 一行 1 个；576–991 一行 min(n, 2) 个；
   ≥992 块头在上 / 下时一行 min(n, 4) 个、块头在左 / 右时仍 min(n, 2) 个 ⟹ 4 个平台不会出现 3 + 1 的孤儿格。 */
[data-block="reviews"][data-items-layout="grid"] .rv-items {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 1.5rem;
}
@media (min-width: 576px) {
  [data-block="reviews"][data-items-layout="grid"] .rv-items {
    grid-template-columns: repeat(min(var(--rv-n), 2), minmax(0, 1fr));
  }
}
@media (min-width: 992px) {
  [data-block="reviews"][data-items-layout="grid"][data-intro-position="top"] .rv-items,
  [data-block="reviews"][data-items-layout="grid"][data-intro-position="bottom"] .rv-items {
    grid-template-columns: repeat(min(var(--rv-n), 4), minmax(0, 1fr));
  }
}
[data-block="reviews"][data-items-layout="grid"] .rv-item {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}
[data-block="reviews"][data-items-layout="grid"] .rv-logo {
  font-size: 2.25rem;
}
[data-block="reviews"][data-items-layout="grid"] .rv-name {
  font-size: 1.25rem;
  min-height: 2.25rem;
}
[data-block="reviews"][data-items-layout="grid"] .rv-score {
  font-size: 2.25rem;
}
[data-block="reviews"][data-items-layout="grid"][data-item-align="center"] .rv-item {
  align-items: center;
  text-align: center;
}
[data-block="reviews"][data-items-layout="grid"][data-item-align="center"] .rv-item .rv-meta {
  align-items: center;
}
[data-block="reviews"][data-items-layout="grid"][data-item-align="right"] .rv-item {
  align-items: flex-end;
  text-align: end;
}
[data-block="reviews"][data-items-layout="grid"][data-item-align="right"] .rv-item .rv-meta {
  align-items: flex-end;
}

/* <576：grid 的格变成横排矮条（logo · 分数 · 星 + 条数，左对齐），一行一个。 */
@media (max-width: 575.98px) {
  [data-block="reviews"][data-items-layout="grid"] .rv-items {
    gap: 0.75rem;
  }
  [data-block="reviews"][data-items-layout="grid"] .rv-item,
  [data-block="reviews"][data-items-layout="grid"][data-item-align] .rv-item {
    flex-direction: row;
    align-items: center;
    text-align: start;
    gap: 1rem;
  }
  [data-block="reviews"][data-items-layout="grid"] .rv-logo {
    font-size: 1.75rem;
  }
  [data-block="reviews"][data-items-layout="grid"] .rv-name {
    font-size: 1rem;
    min-height: 0;
  }
  [data-block="reviews"][data-items-layout="grid"] .rv-score {
    font-size: 1.75rem;
  }
  [data-block="reviews"][data-items-layout="grid"][data-item-align] .rv-item .rv-meta {
    align-items: flex-start;
  }
}

/* itemsLayout=row：一行紧凑的小条，按自己的宽度排、放不下折行；整行的对齐跟 itemAlign。 */
[data-block="reviews"][data-items-layout="row"] .rv-items {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem 1rem;
}
[data-block="reviews"][data-items-layout="row"][data-item-align="center"] .rv-items {
  justify-content: center;
}
[data-block="reviews"][data-items-layout="row"][data-item-align="right"] .rv-items {
  justify-content: flex-end;
}
[data-block="reviews"][data-items-layout="row"] .rv-item {
  display: inline-flex;
  align-items: center;
  gap: 0.75rem;
}
[data-block="reviews"][data-items-layout="row"] .rv-logo {
  font-size: 1.5rem;
}
[data-block="reviews"][data-items-layout="row"] .rv-name {
  font-size: 0.9375rem;
}
[data-block="reviews"][data-items-layout="row"] .rv-score {
  font-size: 1.5rem;
}
[data-block="reviews"][data-items-layout="row"] .rv-item .rv-count {
  font-size: 0.75rem !important;
}

/* itemStyle=card：浅灰底、1rem 圆角（定稿图册的值），悬停描一圈 2px 主色；grid 内边距 2rem 1.5rem（<576 1rem 1.25rem）、
   row .75rem 1.25rem。plain = 无底，row 时小条间距加大到 1rem 2.5rem。 */
[data-block="reviews"][data-item-style="card"] .rv-item {
  background: var(--scheme-surface-muted);
  border-radius: 1rem;
}
[data-block="reviews"][data-item-style="card"] .rv-item:hover {
  box-shadow: 0 0 0 2px var(--x-primary);
}
[data-block="reviews"][data-item-style="card"][data-items-layout="grid"] .rv-item {
  padding: 2rem 1.5rem;
}
@media (max-width: 575.98px) {
  [data-block="reviews"][data-item-style="card"][data-items-layout="grid"] .rv-item {
    padding: 1rem 1.25rem;
  }
}
[data-block="reviews"][data-item-style="card"][data-items-layout="row"] .rv-item {
  padding: 0.75rem 1.25rem;
}
[data-block="reviews"][data-item-style="plain"][data-items-layout="row"] .rv-items {
  gap: 1rem 2.5rem;
}
[data-block="reviews"][data-item-style="plain"] a.rv-item:hover .rv-count {
  text-decoration: underline;
}

/* ═══ 字色按背景亮度（data-tone，scripts/lib/contrast.js §toneForBg）══════════════════════════════
   dark = 深底 / 深色渐变；brand = 主题主色底。两者一样：标题 / 分数 / 总分反白，正文 / 条数白 .92（那条规则全站只有一份，
   住 scripts/lib/site-css.js §ON_DEEP_MUTED，#1477；这里不再抄），card 底 rgba(255,255,255,.08)，品牌图标白。
   🔴 各块共有的那几条（根上字色 · 标题 · eyebrow · 主按钮 / 描边 / link 按钮反白）住 `scripts/lib/site-css.js`
      §DEEP_COMMON，全站一份（#1533）；这里只留本块特有的，别再抄回来（`scripts/block-deep-common.test.js`）。 */

[data-block="reviews"][data-tone="dark"] .rv-score,
[data-block="reviews"][data-tone="brand"] .rv-score,
[data-block="reviews"][data-tone="dark"] .rv-total-score,
[data-block="reviews"][data-tone="brand"] .rv-total-score,
[data-block="reviews"][data-tone="dark"] .rv-name,
[data-block="reviews"][data-tone="brand"] .rv-name,
[data-block="reviews"][data-tone="dark"] .rv-icon,
[data-block="reviews"][data-tone="brand"] .rv-icon {
  color: #fff !important;
}
[data-block="reviews"][data-tone="dark"][data-item-style="card"] .rv-item,
[data-block="reviews"][data-tone="brand"][data-item-style="card"] .rv-item {
  background: rgba(255, 255, 255, 0.08);
}
[data-block="reviews"][data-tone="dark"][data-item-style="card"] .rv-item:hover,
[data-block="reviews"][data-tone="brand"][data-item-style="card"] .rv-item:hover {
  box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.7);
}

/* ── reviews · cards（预设 Cards）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：reviews 是一份 markup + 六个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-total` / `data-items-layout` / `data-item-*` / `data-tone`，规则全在 `blocks/reviews/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="reviews"][data-shape="cards"] {
  --rv-preset: cards;
}

/* ── reviews · side-score（预设 Side score）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：reviews 是一份 markup + 六个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-total` / `data-items-layout` / `data-item-*` / `data-tone`，规则全在 `blocks/reviews/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="reviews"][data-shape="side-score"] {
  --rv-preset: side-score;
}

/* ── reviews · strip（预设 Strip）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：reviews 是一份 markup + 六个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-total` / `data-items-layout` / `data-item-*` / `data-tone`，规则全在 `blocks/reviews/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="reviews"][data-shape="strip"] {
  --rv-preset: strip;
}

/* ══ team ══════════════════════════════════════════════════════════════════════ */
/* 条目网格（membersColumns：手机 ≤1 · iPad ≤2 · ≥992 按旋钮）—— 由 scripts/block-build/items-grid.js 生成，别在 block.css 里写 */
[data-block="team"] .tm-grid {
  --items-cols: 1;
  display: flex;
  flex-wrap: wrap;
}
[data-block="team"] .tm-member {
  flex: 0 0 auto;
  width: calc((100% - (var(--items-cols) - 1) * var(--tm-gap)) / var(--items-cols));
}
@media (min-width: 768px) {
  [data-block="team"][data-members-columns="2"] .tm-grid {
    --items-cols: 2;
  }
  [data-block="team"][data-members-columns="3"] .tm-grid {
    --items-cols: 2;
  }
  [data-block="team"][data-members-columns="4"] .tm-grid {
    --items-cols: 2;
  }
}
@media (min-width: 992px) {
  [data-block="team"][data-members-columns="3"] .tm-grid {
    --items-cols: 3;
  }
  [data-block="team"][data-members-columns="4"] .tm-grid {
    --items-cols: 4;
  }
}

/* 块头排版（introAlign / introPosition）—— 由 scripts/block-build/intro-layout.js 生成，别在 block.css 里写 */
[data-block="team"][data-intro-align="center"] .tm-intro-text {
  text-align: center;
}
[data-block="team"][data-intro-align="right"] .tm-intro-text {
  text-align: end;
}
@media (min-width: 992px) {
  [data-block="team"][data-intro-align="center"] .tm-intro-text {
    width: 100%;
    max-width: 80%;
    margin-inline: auto;
  }
}
[data-block="team"][data-intro-position="bottom"] .tm-frame {
  flex-direction: column-reverse;
}
@media (max-width: 991.98px) {
  [data-block="team"][data-intro-position="right"] .tm-frame {
    flex-direction: column-reverse;
  }
}
@media (min-width: 992px) {
  [data-block="team"][data-intro-position="left"] .tm-introcol,
  [data-block="team"][data-intro-position="right"] .tm-introcol {
    flex: 0 0 auto;
    width: 33.3333%;
    position: sticky;
    top: 2rem;
    align-self: flex-start;
  }
  [data-block="team"][data-intro-position="left"] .tm-memberscol,
  [data-block="team"][data-intro-position="right"] .tm-memberscol {
    flex: 0 0 auto;
    width: 66.6667%;
  }
  [data-block="team"][data-intro-position="left"] .tm-intro-text,
  [data-block="team"][data-intro-position="right"] .tm-intro-text {
    max-width: none;
    margin: 0;
  }
  [data-block="team"][data-intro-position="right"] .tm-frame {
    flex-direction: row-reverse;
  }
}

/* ── team · 跨形态的规则（#1487）──────────────────────────────────────────────────────────
   team 的排版由**实际生效的旋钮**决定，写在根元素上：
     intro    data-intro-position（left / right / top / bottom）· data-intro-align（left / center / right）
     members  data-members-columns（2 / 3 / 4）· data-member-photo（top / left）· data-photo-shape（square / circle）·
              data-member-style（plain / card）· data-member-align（left / center）
     底色     data-tone（light / dark / brand，scripts/lib/contrast.js §toneForBg）
   五个形态目录都是预设，自己不带几何（理由在各自的 shape.css 里）。

   🔴 Webpixels 的工具类全带 !important（mb-* / text-muted / bg-primary-subtle …）。这里要压过它们的规则**也带 !important**，
      而且选择器从 [data-block="team"] 起步再加属性 / 类 —— 特异度一定高过工具类的一个类。
   🔴 部件的有无靠渲染（Section.tsx 有数据才画），不靠 CSS 藏。
   🔴 `memberAlign` 只在 `memberPhoto=top` 时起作用：它的每一条规则都带 [data-member-photo="top"]（左图右字本来就靠左）。
   间距照图册（Chris 2026-09-29）：段 py-16 py-lg-24；块头与成员之间 2.5rem（gy-10）/ ≥992 横向 4rem（gx-lg-16）；
   成员左右间距 1.5rem、行距 2.5rem（card 1.5rem）；照片与名字 / 文字 1.25rem。纵向写 gy-10、不写 g-10
   （g-10 的横向负边距在手机上溢出，#1475 量出来的）。 */

/* ═══ intro（块头）═══════════════════════════════════════════════════════════════════════════════ */

[data-block="team"] .tm-intro-text > :last-child {
  margin-bottom: 0 !important;
}

/* 块头排版（introAlign / introPosition：对齐、≥992 center 最宽 80%、左右两列、上下反向）由 manifest 的 introLayout 生成
   （scripts/block-build/intro-layout.js，#1535），这里不写。 */

/* ═══ members ═════════════════════════════════════════════════════════════════════════════════════ */

/* membersColumns 的列数与断点（手机一列，Chris 2026-09-29；iPad 两列；≥992 按列数）归 manifest 的 itemsGrid，
   由 scripts/block-build/items-grid.js 生成（#1537，画法照旧 flex + calc，不用负边距）。这里只管列距 / 行距。 */
[data-block="team"] .tm-grid {
  --tm-gap: 1.5rem;
  row-gap: 2.5rem;
  column-gap: var(--tm-gap);
}
[data-block="team"] .tm-member {
  min-width: 0;
}

[data-block="team"] .tm-text {
  min-width: 0;
}
[data-block="team"] .tm-role {
  margin-top: 0.125rem;
}
[data-block="team"] .tm-bio,
[data-block="team"] .tm-links {
  margin-top: 0.75rem;
}
[data-block="team"] .tm-link {
  display: inline-flex;
  font-size: 1.125rem;
  text-decoration: none;
}

/* 照片：top + square = 占满格宽的 1:1 方图、圆角 1rem；top + circle = 6rem 圆头像；left = 5rem 方图（圆角 .75rem）或圆。 */
[data-block="team"] .tm-photo img {
  display: block;
  object-fit: cover;
}
[data-block="team"][data-member-photo="top"] .tm-photo {
  margin-bottom: 1.25rem;
}
[data-block="team"][data-member-photo="top"][data-photo-shape="square"] .tm-photo img {
  width: 100%;
  aspect-ratio: 1 / 1;
  border-radius: 1rem;
}
[data-block="team"][data-member-photo="top"][data-photo-shape="circle"] .tm-photo img {
  width: 6rem;
  height: 6rem;
  border-radius: 50%;
}
[data-block="team"][data-member-photo="left"] .tm-inner {
  display: flex;
  gap: 1.25rem;
  align-items: flex-start;
}
[data-block="team"][data-member-photo="left"] .tm-photo {
  flex: 0 0 auto;
}
[data-block="team"][data-member-photo="left"] .tm-photo img {
  width: 5rem;
  height: 5rem;
  border-radius: 0.75rem;
}
[data-block="team"][data-member-photo="left"][data-photo-shape="circle"] .tm-photo img {
  border-radius: 50%;
}

/* memberAlign：名字 / 职位 / 简介 / 链接 / 照片的对齐，只在照片在上时起作用；招聘卡跟着走。 */
[data-block="team"][data-member-align="center"][data-member-photo="top"] .tm-inner {
  text-align: center;
}
[data-block="team"][data-member-align="center"][data-member-photo="top"] .tm-photo img {
  margin-inline-start: auto;
  margin-inline-end: auto;
}
[data-block="team"][data-member-align="center"][data-member-photo="top"] .tm-links {
  justify-content: center;
}

/* memberStyle：plain 无底无线、行距 2.5rem；card = 白底 1px 描边 1rem 圆角（rounded-4）2rem 内边距（p-8）、行距 1.5rem。 */
[data-block="team"][data-member-style="card"] .tm-grid {
  row-gap: 1.5rem;
}
[data-block="team"][data-member-style="card"] .tm-inner {
  background: var(--x-body-bg);
  border: 1px solid var(--x-border-color, #e5e7eb);
  border-radius: 1rem;
  padding: 2rem;
}

/* ═══ 招聘卡（join）：排在成员网格的最后一格，跟成员同宽 ══════════════════════════════════════════
   虚线框 1.5px dashed + 1rem 圆角 + 2rem 内边距；3rem 的图标圈（给定尺寸，别被 flex 拉宽）；描边按钮。 */
[data-block="team"] .tm-join {
  border: 1.5px dashed var(--scheme-line);
  border-radius: 1rem;
  padding: 2rem;
  min-height: 12rem;
}
[data-block="team"] .tm-join-icon {
  width: 3rem;
  height: 3rem;
  flex: 0 0 auto;
  align-self: flex-start;
  font-size: 1.5rem;
}
[data-block="team"][data-member-align="center"][data-member-photo="top"] .tm-join {
  align-items: center;
  text-align: center;
}
[data-block="team"][data-member-align="center"][data-member-photo="top"] .tm-join-icon,
[data-block="team"][data-member-align="center"][data-member-photo="top"] .tm-join-cta {
  align-self: center !important;
}

/* ═══ 字色按背景亮度（data-tone，scripts/lib/contrast.js §toneForBg）══════════════════════════════
   dark = 深底 / 深色渐变；brand = 主题主色底。两者一样：标题 / 名字反白；职位 / 简介（.text-muted）白 .92 由全站那条给
   （scripts/lib/site-css.js §ON_DEEP_MUTED，本块不抄一份）；card 换 .06 半透明底 + 半透明白描边；招聘卡虚线 .35 白、
   图标圈半透明白、按钮反白。
   🔴 各块共有的那几条（根上字色 · 标题 · eyebrow · 主按钮 / 描边 / link 按钮反白）住 `scripts/lib/site-css.js`
      §DEEP_COMMON，全站一份（#1533）；这里只留本块特有的，别再抄回来（`scripts/block-deep-common.test.js`）。 */

[data-block="team"][data-tone="dark"] .tm-name,
[data-block="team"][data-tone="brand"] .tm-name,
[data-block="team"][data-tone="dark"] .tm-join-title,
[data-block="team"][data-tone="brand"] .tm-join-title {
  color: #fff !important;
}
[data-block="team"][data-tone="dark"][data-member-style="card"] .tm-inner,
[data-block="team"][data-tone="brand"][data-member-style="card"] .tm-inner {
  background: rgba(255, 255, 255, 0.06);
  border-color: rgba(255, 255, 255, 0.15);
}
[data-block="team"][data-tone="dark"] .tm-join,
[data-block="team"][data-tone="brand"] .tm-join {
  border-color: rgba(255, 255, 255, 0.35);
}
[data-block="team"][data-tone="dark"] .tm-join-icon,
[data-block="team"][data-tone="brand"] .tm-join-icon {
  background: rgba(255, 255, 255, 0.14) !important;
  color: #fff !important;
}

/* ── 按钮的底色和字色：接平台按这个站最终配色算好的那几个变量（`scripts/lib/button-ink.js`，同 features / milestones）。 */
[data-block="team"] .btn-primary {
  --x-btn-bg: var(--btn-primary-bg, var(--x-primary));
  --x-btn-border-color: var(--btn-primary-bg, var(--x-primary));
  --x-btn-color: var(--btn-primary-ink, #fff);
  --x-btn-hover-bg: var(--btn-primary-hover, var(--btn-primary-bg, var(--x-primary)));
  --x-btn-hover-border-color: var(--btn-primary-hover, var(--btn-primary-bg, var(--x-primary)));
  --x-btn-hover-color: var(--btn-primary-ink, #fff);
  --x-btn-active-bg: var(--btn-primary-hover, var(--btn-primary-bg, var(--x-primary)));
  --x-btn-active-color: var(--btn-primary-ink, #fff);
}
[data-block="team"] .btn-outline-primary {
  --x-btn-color: var(--x-primary-text-emphasis, var(--x-primary));
  --x-btn-border-color: var(--x-primary-text-emphasis, var(--x-primary));
}
[data-block="team"] .btn-link {
  --x-btn-color: var(--x-primary-text-emphasis, var(--x-primary));
  --x-btn-hover-color: var(--x-primary-text-emphasis, var(--x-primary));
  --x-btn-active-color: var(--x-primary-text-emphasis, var(--x-primary));
}

/* ── team · photo-grid（预设 Photo grid）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：team 是一份 markup + 七个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-members-columns` / `data-member-*` / `data-photo-shape` / `data-tone`，规则全在 `blocks/team/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="team"][data-shape="photo-grid"] {
  --tm-preset: photo-grid;
}

/* ── team · cards（预设 Cards）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：team 是一份 markup + 七个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-members-columns` / `data-member-*` / `data-photo-shape` / `data-tone`，规则全在 `blocks/team/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="team"][data-shape="cards"] {
  --tm-preset: cards;
}

/* ── team · side-intro（预设 Side intro）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：team 是一份 markup + 七个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-members-columns` / `data-member-*` / `data-photo-shape` / `data-tone`，规则全在 `blocks/team/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="team"][data-shape="side-intro"] {
  --tm-preset: side-intro;
}

/* ── team · list（预设 List）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：team 是一份 markup + 七个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-members-columns` / `data-member-*` / `data-photo-shape` / `data-tone`，规则全在 `blocks/team/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="team"][data-shape="list"] {
  --tm-preset: list;
}

/* ── team · hiring（预设 Hiring）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：team 是一份 markup + 七个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-members-columns` / `data-member-*` / `data-photo-shape` / `data-tone`，规则全在 `blocks/team/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="team"][data-shape="hiring"] {
  --tm-preset: hiring;
}

/* ══ testimonials ══════════════════════════════════════════════════════════════════════ */
/* 条目网格（itemsColumns：手机 ≤1 · iPad ≤2 · ≥992 按旋钮）—— 由 scripts/block-build/items-grid.js 生成，别在 block.css 里写 */
[data-block="testimonials"][data-items-layout="grid"] .tn-grid {
  --items-cols: 1;
  display: grid;
  grid-template-columns: repeat(var(--items-cols), minmax(0, 1fr));
}
@media (min-width: 768px) {
  [data-block="testimonials"][data-items-layout="grid"][data-items-columns="2"] .tn-grid {
    --items-cols: 2;
  }
  [data-block="testimonials"][data-items-layout="grid"][data-items-columns="3"] .tn-grid {
    --items-cols: 2;
  }
}
@media (min-width: 992px) {
  [data-block="testimonials"][data-items-layout="grid"][data-items-columns="3"] .tn-grid {
    --items-cols: 3;
  }
}
[data-block="testimonials"] .tn-slide {
  --items-cols: 1;
  display: grid;
  grid-template-columns: repeat(var(--items-cols), minmax(0, 1fr));
}
@media (min-width: 768px) {
  [data-block="testimonials"][data-items-columns="2"] .tn-slide {
    --items-cols: 2;
  }
  [data-block="testimonials"][data-items-columns="3"] .tn-slide {
    --items-cols: 2;
  }
}
@media (min-width: 992px) {
  [data-block="testimonials"][data-items-columns="3"] .tn-slide {
    --items-cols: 3;
  }
}

/* 块头排版（introAlign / introPosition）—— 由 scripts/block-build/intro-layout.js 生成，别在 block.css 里写 */
[data-block="testimonials"][data-intro-align="center"] .tn-intro-text {
  text-align: center;
}
[data-block="testimonials"][data-intro-align="right"] .tn-intro-text {
  text-align: end;
}
[data-block="testimonials"][data-intro-align="center"] .tn-intro-text .tn-summary {
  justify-content: center;
}
[data-block="testimonials"][data-intro-align="right"] .tn-intro-text .tn-summary {
  justify-content: flex-end;
}
@media (min-width: 992px) {
  [data-block="testimonials"][data-intro-align="center"] .tn-intro-text {
    width: 100%;
    max-width: 80%;
    margin-inline: auto;
  }
}
[data-block="testimonials"][data-intro-position="bottom"] .tn-frame {
  flex-direction: column-reverse;
}
[data-block="testimonials"][data-intro-position="bottom"] .tn-introcol {
  margin-top: 2rem;
}
@media (max-width: 991.98px) {
  [data-block="testimonials"][data-intro-position="right"] .tn-frame {
    flex-direction: column-reverse;
  }
  [data-block="testimonials"][data-intro-position="right"] .tn-introcol {
    margin-top: 2rem;
  }
}
@media (min-width: 992px) {
  [data-block="testimonials"][data-intro-position="left"] .tn-introcol,
  [data-block="testimonials"][data-intro-position="right"] .tn-introcol {
    flex: 0 0 auto;
    width: 33.3333%;
    position: sticky;
    top: 2rem;
    align-self: flex-start;
  }
  [data-block="testimonials"][data-intro-position="left"] .tn-itemscol,
  [data-block="testimonials"][data-intro-position="right"] .tn-itemscol {
    flex: 0 0 auto;
    width: 66.6667%;
  }
  [data-block="testimonials"][data-intro-position="left"] .tn-intro-text,
  [data-block="testimonials"][data-intro-position="right"] .tn-intro-text {
    max-width: none;
    margin: 0;
  }
  [data-block="testimonials"][data-intro-position="right"] .tn-frame {
    flex-direction: row-reverse;
  }
}

/* ── testimonials · 跨形态的规则（#1488）─────────────────────────────────────────────────────
   testimonials 的排版由**实际生效的旋钮**决定，写在根元素上：
     intro  data-intro-position（left / right / top / bottom）· data-intro-align（left / center / right）
     summary data-summary-style（inline / cards，#1500）
     items  data-items-layout（grid / carousel）· data-items-columns（1 / 2 / 3）
     item   data-item-style（plain / card）· data-quote-size（md / lg）· data-item-align（left / center）
     底色   data-tone（light / dark / brand，scripts/lib/contrast.js §toneForBg）
   五个形态目录都是预设，自己不带几何。规则逐条照定稿图册（`docs/reference/webpixels/gallery/build.py:233-267`），
   只把 `body.opt-*` 换成根元素上的属性。

   🔴 Webpixels 的工具类全带 !important（gap-* / justify-content-* / text-muted / bg-primary-subtle …）。这里要压过它们的规则
      **也带 !important**，而且选择器从 [data-block="testimonials"] 起步再加属性 / 类 —— 特异度一定高过工具类的一个类。
   🔴 部件的有无靠渲染（Section.tsx 有数据才画；圆点 / 按钮只在 carousel 时画），不靠 CSS 藏。
   🔴 轮播（#1494 起是 Bootstrap Carousel）：每条评价都在服务端 HTML 里；不是当前那张的 slide 由 Bootstrap 自己的
      `.carousel-item` 规则收起（它的 display 切换），这里不另写藏 / 显的规则。
   间距照 Webpixels 尺度：段 py-16 py-lg-24、块头与评价之间 2.5rem（gy-10）/ ≥992 横向 4rem（gx-lg-16）、
   条与条 1.5rem（plain 网格行 3rem 列 2.5rem）、card 内 2rem（= p-8）。 */

/* ═══ intro（块头）═══════════════════════════════════════════════════════════════════════════════
   宽度照 T6.1（#1486）的新规则：left / right 对齐 = 100%；center 在 ≥992 最宽 80%、居中，<992 = 100%。
   块头在侧（≥992 的 left / right）时那一列本身就窄，80% 那条取消（图册 build.py:239）。inline 那一排 summary 跟着对齐走（见下面 summary 一节）。 */
[data-block="testimonials"] .tn-intro-text {
  max-width: none;
}
[data-block="testimonials"] .tn-intro-text > :last-child {
  margin-bottom: 0 !important;
}

/* 块头排版（introAlign / introPosition：对齐、≥992 center 最宽 80%、左右两列、上下反向）由 manifest 的 introLayout 生成
   （scripts/block-build/intro-layout.js，#1535），这里不写。块头叠在评价下面时隔 2rem（图册 build.py:240-241）= introLayout.stackGap；
   inline 那排评分条跟着 introAlign 走 = introLayout.follow。 */

/* ═══ summary（一组平台评分，#1500）══════════════════════════════════════════════════════════════
   summaryStyle 只决定出哪一处（Section.tsx 两处只渲染一处）：
     inline = 块头正文下面一排小条，间距 .75rem、放不下换行，**跟 introAlign 对齐**；
     cards  = 评价那一列最上面一排平台卡（Webpixels reviews-1），离评价 2.5rem，**不跟 introAlign**（条目从不跟它）。
   logo：上传的图 / 内置品牌图标（品牌色，`--tn-brand`）/ 平台名，三档由 Section.tsx 选（review-platforms.js）。
   有 href 的那一项是 <a>：悬停描一圈主色。 */
[data-block="testimonials"] .tn-platform {
  background: var(--scheme-surface-muted);
}
[data-block="testimonials"] a.tn-platform:hover,
[data-block="testimonials"] a.tn-platform:focus-visible {
  box-shadow: 0 0 0 1px var(--x-primary);
}
[data-block="testimonials"] .tn-logo-icon {
  color: var(--tn-brand);
}
/* 图片那一档 logo（#1519：选择器原来是 `.tn-logo img`。`<img>` 只出现在挂 `tn-logo-img` 的那一档里，
   命中的元素一个不变；改写成这一档自己的钩子，它才有一条规则）。 */
[data-block="testimonials"] .tn-logo-img img {
  width: auto;
  max-width: 8rem;
  object-fit: contain;
}
[data-block="testimonials"] .tn-summary {
  gap: 0.75rem;
}
[data-block="testimonials"] .tn-platform-inline .tn-logo-icon svg,
[data-block="testimonials"] .tn-platform-inline .tn-logo img {
  width: 1.75rem;
  height: 1.75rem;
}
[data-block="testimonials"] .tn-platform-inline .tn-logo img {
  width: auto;
}
[data-block="testimonials"] .tn-summary-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
  gap: 1.5rem;
  margin-bottom: 2.5rem;
}
[data-block="testimonials"] .tn-platform-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 0.5rem;
  padding: 2rem 1.5rem;
}
[data-block="testimonials"] .tn-platform-card .tn-logo-icon svg,
[data-block="testimonials"] .tn-platform-card .tn-logo img {
  width: 2.25rem;
  height: 2.25rem;
}
[data-block="testimonials"] .tn-platform-card .tn-logo img {
  width: auto;
}
[data-block="testimonials"] .tn-pc-logo {
  margin-bottom: 0.25rem;
}
/* <576：卡变成一条横排的矮条 —— 左上 logo、左下「4.9 out of 5」，右上星、右下条数。 */
@media (max-width: 575.98px) {
  [data-block="testimonials"] .tn-summary-cards {
    grid-template-columns: 1fr;
    gap: 0.75rem;
  }
  [data-block="testimonials"] .tn-platform-card {
    display: grid;
    grid-template-columns: 1fr auto;
    grid-template-areas: "logo stars" "score count";
    align-items: center;
    text-align: start;
    gap: 0.25rem 1rem;
    padding: 1rem 1.25rem;
  }
  /* logo / 星这两格做成 flex、行高 1：它们是行内元素时行盒按正文行高撑高，一张卡就超过 80px。 */
  [data-block="testimonials"] .tn-pc-logo,
  [data-block="testimonials"] .tn-pc-stars {
    display: flex;
    align-items: center;
    line-height: 1;
  }
  [data-block="testimonials"] .tn-pc-logo { grid-area: logo; margin-bottom: 0; }
  [data-block="testimonials"] .tn-pc-stars { grid-area: stars; justify-self: end; }
  [data-block="testimonials"] .tn-pc-score { grid-area: score; line-height: 1.25; }
  [data-block="testimonials"] .tn-pc-count { grid-area: count; justify-self: end; line-height: 1.25; }
  [data-block="testimonials"] .tn-platform-card .tn-logo-icon svg,
  [data-block="testimonials"] .tn-platform-card .tn-logo img {
    height: 1.25rem;
  }
  [data-block="testimonials"] .tn-platform-card .tn-logo-icon svg {
    width: 1.25rem;
  }
}

/* ═══ items：grid = 全部摊开；carousel = 横向轮播。手机一列 / 一屏一条，iPad 最多两条，≥992 按列数 ═══════ */
/* 列数与断点（手机一列、iPad 最多两条、≥992 按列数）归 manifest 的 itemsGrid，由 scripts/block-build/items-grid.js 生成（#1537）。 */
[data-block="testimonials"][data-items-layout="grid"] .tn-grid {
  gap: 1.5rem;
}
[data-block="testimonials"][data-items-layout="grid"][data-item-style="plain"] .tn-grid {
  row-gap: 3rem;
  column-gap: 2.5rem;
}

/* 一列时也占满整列、不跟 introAlign（Chris 2026-10-01，#1516）：评价那一列永远是 col-12 的整行宽，块头文字才跟 introAlign；
   条目里的文字对齐只由 itemAlign 管。以前那三条「限 48rem + 跟对齐」已删。 */

/* carousel = Bootstrap Carousel（#1494）：一张 slide（.carousel-item）放 itemsColumns 条，服务端分好组；slide 里是 .tn-slide 网格 ——
   <768 竖着叠、768–991 最多两列、≥992 按列数。轨道 / 切换 / 动画是 Bootstrap 自己的 .carousel 规则（site.css）。
   以前那条手写轨道（横向滚动 + 自己算位置）已删。 */
[data-block="testimonials"] .tn-slide {
  gap: 1.5rem;
  padding-bottom: 2px; /* card 的下描边不被 .carousel-inner 的 overflow 裁掉 */
}
[data-block="testimonials"][data-item-style="plain"] .tn-slide {
  column-gap: 2.5rem;
}
/* `.slide` 是 Bootstrap 的 JS 用来判「切换时要不要动画」的开关，它自己在 CSS 里没有规则；轮播外壳本来就是定位上下文。 */
[data-block="testimonials"] .tn-carousel.slide {
  position: relative;
}
/* 每一条（grid 的格子 / slide 里的格子）：网格子项能收窄，长词不把一列撑宽。 */
[data-block="testimonials"] .tn-item {
  min-width: 0;
}
[data-block="testimonials"] .tn-carousel:focus-visible {
  outline: 2px solid var(--x-primary);
  outline-offset: 4px;
}

/* 圆点 = Bootstrap 的 .carousel-indicators（每张 slide 一个），从压在图上的那条拉回下面左边、换成图册的小圆点，
   当前那个拉长成主色短条；前 / 后按钮在右边。一屏一条且 itemAlign=center 时两者居中叠在引言下面。
   🔴 `.carousel-indicators` 自带 position:absolute / margin / 半透明的细条，这里逐条压回去（特异度从 [data-block] 起步，高过它）。 */
[data-block="testimonials"] .tn-dots {
  position: static;
  margin: 0;
  padding: 0;
  justify-content: flex-start;
  flex-wrap: wrap;
  gap: 0.5rem;
  min-width: 0;
}
[data-block="testimonials"] .tn-dots [data-bs-target] {
  box-sizing: border-box;
  flex: 0 0 auto;
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 999px;
  border: 0;
  margin: 0;
  padding: 0;
  background: var(--scheme-line);
  opacity: 1;
  text-indent: 0;
  transition: width 0.2s;
  /* #1426 —— 字色跟随块，不吃浏览器给 <button> 的默认色（原来由 Tailwind preflight 的 `button { color: inherit }` 给；
     圆点没有文字，今天看不出差别）。前 / 后按钮 .tn-arrow 自己已经写了 color: inherit。 */
  color: inherit;
}
[data-block="testimonials"] .tn-dots .active {
  width: 1.5rem;
  background: var(--x-primary);
}
[data-block="testimonials"] .tn-arrows {
  flex-shrink: 0;
}
[data-block="testimonials"] .tn-arrow {
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 50%;
  border: 1px solid var(--x-border-color, #e5e7eb);
  background: var(--x-body-bg);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  color: inherit;
}
[data-block="testimonials"] .tn-arrow:hover {
  border-color: var(--x-primary);
  color: var(--scheme-primary-ink, var(--x-primary));
}
/* 圆点多（最多 6 条）时换行、不把前后按钮挤出去；前后按钮那一组不收缩（#1507）。 */
[data-block="testimonials"] .tn-dots {
  flex-wrap: wrap;
  min-width: 0;
}
[data-block="testimonials"] .tn-arrows {
  flex-shrink: 0;
}
[data-block="testimonials"][data-item-align="center"][data-items-columns="1"] .tn-pager {
  justify-content: center !important;
  flex-direction: column;
}
[data-block="testimonials"][data-item-align="center"][data-items-columns="1"] .tn-dots {
  justify-content: center;
}

/* ═══ 每一条：引言 → 人 → 星级 + 来源（贴底）════════════════════════════════════════════════════ */
[data-block="testimonials"] .tn-inner {
  gap: 1.25rem;
}
[data-block="testimonials"] .tn-quote {
  font-size: 1rem;
  line-height: 1.65;
}
[data-block="testimonials"] .tn-quote::before {
  content: "\201C";
}
[data-block="testimonials"] .tn-quote::after {
  content: "\201D";
}
[data-block="testimonials"] .tn-avatar {
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 50%;
  object-fit: cover;
  flex: 0 0 auto;
}
[data-block="testimonials"] .tn-initials {
  font-size: 0.875rem;
}
[data-block="testimonials"] .tn-meta {
  margin-top: auto;
}
/* 星级那一排不收缩；来源 / 身份 / 正文 / 总评分条数遇到长词（网址、长名字）换行，不撑破卡片（#1507）。
   `min-w-0` 是名字 + 身份那一栏（flex 子项）：本意是 `min-width: 0`，让它在头像旁边能收窄、长字才会换行 ——
   这套 Bootstrap 栈里没有这条工具类，规则写在这里（Tailwind 的同名类在这里不生效）。 */
[data-block="testimonials"] .tn-stars {
  flex-shrink: 0;
}
[data-block="testimonials"] .min-w-0 {
  min-width: 0;
}
[data-block="testimonials"] .tn-body,
[data-block="testimonials"] .tn-role,
[data-block="testimonials"] .tn-source,
[data-block="testimonials"] .tn-platform-count,
[data-block="testimonials"] .tn-logo-name {
  overflow-wrap: anywhere;
}
[data-block="testimonials"] .tn-star svg {
  width: 1rem;
  height: 1rem;
}

/* quoteSize：lg = 大字引言（字重 500），头像放大到 3.5rem。 */
[data-block="testimonials"][data-quote-size="lg"] .tn-quote {
  font-size: clamp(1.25rem, 2.4vw, 1.75rem);
  line-height: 1.45;
  font-weight: 500;
  letter-spacing: -0.01em;
}
[data-block="testimonials"][data-quote-size="lg"] .tn-avatar {
  width: 3.5rem;
  height: 3.5rem;
}

/* itemStyle：card = 白底 + 1px 描边 + 1rem 圆角 + 2rem 内边距；plain 无底无线。 */
[data-block="testimonials"][data-item-style="card"] .tn-inner {
  background: var(--x-body-bg);
  border: 1px solid var(--x-border-color, #e5e7eb);
  border-radius: 1rem;
  padding: 2rem;
}

/* itemAlign：center 时引言 / 人 / 星级都居中，头像在名字上面。 */
[data-block="testimonials"][data-item-align="center"] .tn-inner {
  text-align: center;
  align-items: center;
}
[data-block="testimonials"][data-item-align="center"] .tn-meta {
  justify-content: center !important;
}
[data-block="testimonials"][data-item-align="center"] .tn-author {
  flex-direction: column;
  gap: 0.5rem !important;
}

/* ═══ 字色按背景亮度（data-tone，scripts/lib/contrast.js §toneForBg）══════════════════════════════
   dark = 深底 / 深色渐变；brand = 主题主色底。两者一样：标题 / 名字 / 引言反白；身份 / 来源 / 总评分那行白 .92
   （`.text-muted` → 全站那条 scripts/lib/site-css.js §ON_DEEP_MUTED，这里不抄一份）；card 半透明底 + 半透明白描边；
   summary 每个平台的底 .08、品牌图标反白（#1500）；首字母圆半透明白；圆点半透明白（当前那个白）；前后按钮透明底 + 半透明白描边。
   🔴 各块共有的那几条（根上字色 · 标题 · eyebrow · 主按钮 / 描边 / link 按钮反白）住 `scripts/lib/site-css.js`
      §DEEP_COMMON，全站一份（#1533）；这里只留本块特有的，别再抄回来（`scripts/block-deep-common.test.js`）。 */

[data-block="testimonials"][data-tone="dark"] .tn-name,
[data-block="testimonials"][data-tone="brand"] .tn-name,
[data-block="testimonials"][data-tone="dark"] .tn-quote,
[data-block="testimonials"][data-tone="brand"] .tn-quote,
[data-block="testimonials"][data-tone="dark"] .tn-platform-rating,
[data-block="testimonials"][data-tone="brand"] .tn-platform-rating,
[data-block="testimonials"][data-tone="dark"] .tn-pc-score,
[data-block="testimonials"][data-tone="brand"] .tn-pc-score,
[data-block="testimonials"][data-tone="dark"] .tn-logo-icon,
[data-block="testimonials"][data-tone="brand"] .tn-logo-icon {
  color: #fff !important;
}
[data-block="testimonials"][data-tone="dark"][data-item-style="card"] .tn-inner,
[data-block="testimonials"][data-tone="brand"][data-item-style="card"] .tn-inner {
  background: rgba(255, 255, 255, 0.06);
  border-color: rgba(255, 255, 255, 0.15);
}
[data-block="testimonials"][data-tone="dark"] .tn-platform,
[data-block="testimonials"][data-tone="brand"] .tn-platform {
  background: rgba(255, 255, 255, 0.08);
}
[data-block="testimonials"][data-tone="dark"] .tn-initials,
[data-block="testimonials"][data-tone="brand"] .tn-initials {
  background: rgba(255, 255, 255, 0.14) !important;
  color: #fff !important;
}
[data-block="testimonials"][data-tone="dark"] .tn-arrow,
[data-block="testimonials"][data-tone="brand"] .tn-arrow {
  background: transparent;
  border-color: rgba(255, 255, 255, 0.35);
  color: #fff;
}
[data-block="testimonials"][data-tone="dark"] .tn-dots [data-bs-target],
[data-block="testimonials"][data-tone="brand"] .tn-dots [data-bs-target] {
  background: rgba(255, 255, 255, 0.35);
}
[data-block="testimonials"][data-tone="dark"] .tn-dots .active,
[data-block="testimonials"][data-tone="brand"] .tn-dots .active {
  background: #fff;
}

/* ── testimonials · cards（预设 Cards）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：testimonials 是一份 markup + 八个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-items-*` / `data-item-*` / `data-quote-size` / `data-tone`，规则全在 `blocks/testimonials/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="testimonials"][data-shape="cards"] {
  --tn-preset: cards;
}

/* ── testimonials · side-intro（预设 Side intro）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：testimonials 是一份 markup + 八个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-items-*` / `data-item-*` / `data-quote-size` / `data-tone`，规则全在 `blocks/testimonials/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="testimonials"][data-shape="side-intro"] {
  --tn-preset: side-intro;
}

/* ── testimonials · big-quote（预设 Big quote）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：testimonials 是一份 markup + 八个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-items-*` / `data-item-*` / `data-quote-size` / `data-tone`，规则全在 `blocks/testimonials/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="testimonials"][data-shape="big-quote"] {
  --tn-preset: big-quote;
}

/* ── testimonials · quote-card（预设 Quote card）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：testimonials 是一份 markup + 八个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-items-*` / `data-item-*` / `data-quote-size` / `data-tone`，规则全在 `blocks/testimonials/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="testimonials"][data-shape="quote-card"] {
  --tn-preset: quote-card;
}

/* ── testimonials · ratings（预设 Ratings）──────────────────────────────────────────────────────────────
   这个形态没有自己的几何：testimonials 是一份 markup + 八个旋钮，排版由**实际生效的旋钮**决定
   （根元素上的 `data-intro-*` / `data-items-*` / `data-item-*` / `data-quote-size` / `data-tone`，规则全在 `blocks/testimonials/block.css`）。
   按形态写规则的话，旋钮拧偏成 custom 时这里的规则仍然按形态名命中，页面就会排成一个跟旋钮说的不一样的样子。
   所以这份文件只有这段说明。 */
[data-block="testimonials"][data-shape="ratings"] {
  --tn-preset: ratings;
}

/* ══ footer ══════════════════════════════════════════════════════════════════════ */
/* footer · 旋钮派生的排版（#1464）—— 六个预设共用这一份

   Section 在根上挂 `ftr-layout-<row|stacked|columns>` · `ftr-cta-<none|centered|boxed|inline>`，外加
   `ftr-reverse`（#1469 起它来自旋钮 `brand=right`，原来是开关 `reverse`；类名没改，`stacked` 下不挂）。排版本身全在 markup 的 Webpixels 工具类里；这里只放工具类表达不了的那几条。
   🔴 为什么不在各预设的 shape.css 里写：旋钮能拧成预设表外的组合（Custom），那种组合没有文件夹；
      而且生成器要求 shape.css 里每条规则以 `[data-shape="<该形态>"]` 开头
      （`scripts/block-build/build-blocks.js` §assertPieceOwnsItsRules），跨形态的规则只能住这里。
   📌 下面三条是 T2.2 各形态 shape.css 里原有的规则，按排布搬过来，内容不变。 */

/* row：中间那组页面链接是 flex 项，默认 `min-width: auto` —— 链接一多它不肯缩，整行被撑出页脚右边。
   缩到 0 之后，链接在自己那一格里换行（它自己的 `ul` 带 `flex-wrap`）。 */
[data-block="footer"].ftr-layout-row .ftr-nav {
  min-width: 0;
}

/* stacked：联系一行 / 链接里的一个**不可断的长词**（真站的邮箱、长网址）会把整行撑出页脚、整页横向滚动 ——
   columns 在 390 上实测撞过（`service@northsideauto.ca` 画到 419）。 */
[data-block="footer"].ftr-layout-stacked .container-lg a,
[data-block="footer"].ftr-layout-stacked .container-lg span {
  overflow-wrap: anywhere;
}

/* columns：列里的一条链接 / 联系方式是一个不可断的长词时，`col-6` 在 390 上只有 ~170px，它会伸出页脚。 */
[data-block="footer"].ftr-layout-columns [data-footer-col] a,
[data-block="footer"].ftr-layout-columns [data-footer-col] span {
  overflow-wrap: anywhere;
}

/* brand=right（`ftr-reverse`，#1519）：row 在 <768 竖着排时链接 / 社交 / 底栏都靠右（`flex-row-reverse`），
   只有店名还在左边 —— 它是被拉满整行的 flex 容器，内容从左排。推到右头，跟 columns 的品牌列（`align-items-end`）一致。
   ≥768 它是一行里按内容宽的 flex 项，这条不改任何位置。 */
[data-block="footer"].ftr-reverse [data-footer-main] > [data-footer-part="brand"] {
  justify-content: flex-end;
}

/* #1469 —— 深底（`bg` 填了深色 / `brand` / 深渐变）上的小字挂 `ftr-muted-on-dark`（跟 `text-body-secondary` 同一个位置换掉）。
   它的颜色（白 .92）全站只有一条：`scripts/lib/site-css.js` §ON_DEEP_MUTED（#1477）。 */

/* ── footer · slim-row = 预设（layout=row · cta=none）────────────────────────────────────────
   🔴 #1464 起这个文件夹**只是一个名字**：预设 = 旋钮的一组值（manifest 的 `presets`；#1469 起三个：layout · brand · cta），排版全在
      `blocks/footer/block.css`，按 Section 从旋钮派生的类（`ftr-layout-*` / `ftr-cta-*`）点名 ——
      旋钮能拧成预设表外的组合（Custom），那种组合没有文件夹，几何按文件夹写就无处可落。
   🔴 下面这条空规则是**锚点**，别删（理由同 `blocks/header/stacked/shape.css`）：`readShapeDir` 拒绝
      空的 shape.css；`theme-pipeline/pool.test.js` §⑪ 要主题选择单里的名字在 `public/shapes.css` 里有一条
      `[data-block][data-shape="<名>"]` 规则。 */
[data-block="footer"][data-shape="slim-row"] {}

/* ── footer · stacked = 预设（layout=stacked · cta=none）────────────────────────────────────────
   🔴 #1464 起这个文件夹**只是一个名字**：预设 = 旋钮的一组值（manifest 的 `presets`；#1469 起三个：layout · brand · cta），排版全在
      `blocks/footer/block.css`，按 Section 从旋钮派生的类（`ftr-layout-*` / `ftr-cta-*`）点名 ——
      旋钮能拧成预设表外的组合（Custom），那种组合没有文件夹，几何按文件夹写就无处可落。
   🔴 下面这条空规则是**锚点**，别删（理由同 `blocks/header/stacked/shape.css`）：`readShapeDir` 拒绝
      空的 shape.css；`theme-pipeline/pool.test.js` §⑪ 要主题选择单里的名字在 `public/shapes.css` 里有一条
      `[data-block][data-shape="<名>"]` 规则。 */
[data-block="footer"][data-shape="stacked"] {}

/* ── footer · columns = 预设（layout=columns · cta=none）────────────────────────────────────────
   🔴 #1464 起这个文件夹**只是一个名字**：预设 = 旋钮的一组值（manifest 的 `presets`；#1469 起三个：layout · brand · cta），排版全在
      `blocks/footer/block.css`，按 Section 从旋钮派生的类（`ftr-layout-*` / `ftr-cta-*`）点名 ——
      旋钮能拧成预设表外的组合（Custom），那种组合没有文件夹，几何按文件夹写就无处可落。
   🔴 下面这条空规则是**锚点**，别删（理由同 `blocks/header/stacked/shape.css`）：`readShapeDir` 拒绝
      空的 shape.css；`theme-pipeline/pool.test.js` §⑪ 要主题选择单里的名字在 `public/shapes.css` 里有一条
      `[data-block][data-shape="<名>"]` 规则。 */
[data-block="footer"][data-shape="columns"] {}

/* ── footer · cta-row = 预设（layout=row · cta=centered）────────────────────────────────────────
   🔴 #1464 起这个文件夹**只是一个名字**：预设 = 旋钮的一组值（manifest 的 `presets`；#1469 起三个：layout · brand · cta），排版全在
      `blocks/footer/block.css`，按 Section 从旋钮派生的类（`ftr-layout-*` / `ftr-cta-*`）点名 ——
      旋钮能拧成预设表外的组合（Custom），那种组合没有文件夹，几何按文件夹写就无处可落。
   🔴 下面这条空规则是**锚点**，别删（理由同 `blocks/header/stacked/shape.css`）：`readShapeDir` 拒绝
      空的 shape.css；`theme-pipeline/pool.test.js` §⑪ 要主题选择单里的名字在 `public/shapes.css` 里有一条
      `[data-block][data-shape="<名>"]` 规则。 */
[data-block="footer"][data-shape="cta-row"] {}

/* ── footer · cta-stacked = 预设（layout=stacked · cta=inline）────────────────────────────────────────
   🔴 #1464 起这个文件夹**只是一个名字**：预设 = 旋钮的一组值（manifest 的 `presets`；#1469 起三个：layout · brand · cta），排版全在
      `blocks/footer/block.css`，按 Section 从旋钮派生的类（`ftr-layout-*` / `ftr-cta-*`）点名 ——
      旋钮能拧成预设表外的组合（Custom），那种组合没有文件夹，几何按文件夹写就无处可落。
   🔴 下面这条空规则是**锚点**，别删（理由同 `blocks/header/stacked/shape.css`）：`readShapeDir` 拒绝
      空的 shape.css；`theme-pipeline/pool.test.js` §⑪ 要主题选择单里的名字在 `public/shapes.css` 里有一条
      `[data-block][data-shape="<名>"]` 规则。 */
[data-block="footer"][data-shape="cta-stacked"] {}

/* ── footer · cta-columns = 预设（layout=columns · cta=boxed）────────────────────────────────────────
   🔴 #1464 起这个文件夹**只是一个名字**：预设 = 旋钮的一组值（manifest 的 `presets`；#1469 起三个：layout · brand · cta），排版全在
      `blocks/footer/block.css`，按 Section 从旋钮派生的类（`ftr-layout-*` / `ftr-cta-*`）点名 ——
      旋钮能拧成预设表外的组合（Custom），那种组合没有文件夹，几何按文件夹写就无处可落。
   🔴 下面这条空规则是**锚点**，别删（理由同 `blocks/header/stacked/shape.css`）：`readShapeDir` 拒绝
      空的 shape.css；`theme-pipeline/pool.test.js` §⑪ 要主题选择单里的名字在 `public/shapes.css` 里有一条
      `[data-block][data-shape="<名>"]` 规则。 */
[data-block="footer"][data-shape="cta-columns"] {}

/* ══ header ══════════════════════════════════════════════════════════════════════ */
/* header · 旋钮派生的排版（#1462）—— 七个预设共用这一份

   Section 在根上挂三个类：`hdr-logo-<left|center|right>` · `hdr-menu-<beside|center|split|gathered|below>` ·
   `hdr-topbar-<on|off>`（#1468）。桌面那三格（`.hdr-a` / `.hdr-b` / `.hdr-c`）里各放
   什么由 Section 按 menu 决定；这里只管三件工具类表达不了的事：网格列宽、每格落哪一栏、992 起才出现的
   那几段（顶条 / 三格网格 / 菜单下沉那一行）。
   🔴 为什么不在各预设的 shape.css 里写：旋钮能拧成预设表外的组合（Custom），那种组合没有文件夹；
      而且生成器要求 shape.css 里每条规则以 `[data-shape="<该形态>"]` 开头
      （`scripts/block-build/build-blocks.js` §assertPieceOwnsItsRules），跨形态的规则只能住这里。
   🔴 Webpixels 的工具类全带 `!important`（票正文「通用规矩」）：这里藏 / 显的规则也带 `!important`，
      选择器的 class 数不少于它。**藏的规则在媒体查询外面**（手机也要藏），只有「显示」进 ≥992。
   🔴 折叠点是 992 不是 768：iPad（768–991）跟手机一样走紧凑条 + 抽屉（Chris 2026-09-27，820 宽真实
      菜单必折行）。紧凑条和抽屉自己带 `d-lg-none`，不在这里。
   🔴 每格同时钉 `grid-row: 1`：logo=right 换栏之后网格自动摆放的光标只往前走，第一格放到第 3 栏以后，
      第二格要放第 2 栏就会掉到下一行（做图册时实测：一反转变成三行）。两侧栏写 `minmax(0, …)`、每格
      `min-width: 0`：网格项默认 `min-width: auto`，塞不下时不缩、直接溢出压到 logo 上。 */

/* 紧凑条（<992）里的店名要能缩：`.navbar-brand` 是 flex 项，默认 `min-width: auto` ⟹ 店名一行多宽它就多宽，
   再怎么 `flex-nowrap` 也只会把电话 / 汉堡挤出去。`flex-nowrap` 在 markup 上（Bootstrap 的
   `.navbar > .container-lg { flex-wrap: inherit }` 会从 `.navbar` 继承 wrap —— 就是它把汉堡折到了第二行）。 */
[data-block="header"] .hdr-compact > .hdr-brand { min-width: 0; }

/* 紧凑条右边那个电话圆图标（#1519）：它是 flex 项，`w-rem-10` 只钉了宽度，默认 `flex-shrink: 1` 下被挤时会缩成椭圆
   （team 的 `.tm-join-icon` 同一条）。今天它的父容器带 `flex-shrink-0` 挡着，这条让它自己也不缩。 */
[data-block="header"] .hdr-phone { flex: 0 0 auto; }

/* 抽屉（#1519）：联系信息 / 链接里的一个不可断的长词（真站的邮箱、长网址）会把整页撑宽 ——
   320 宽实测 `appointments@northsidecollisionandautobody.com` 画到 369，整页跟着撑到 369、横向滚动（footer
   stacked / columns 同一条）。 */
[data-block="header"] .hdr-drawer a,
[data-block="header"] .hdr-drawer span {
  overflow-wrap: anywhere;
}

/* logo=right（#1468）：紧凑条两头对调 —— logo 到右头，汉堡到最左（电话图标在汉堡和 logo 之间）；顶条那两段
   （Section 上挂 `hdr-flip` 的两个 flex 容器，各两个子项）也对调。抽屉内容顺序不动。
   用 `order` 把第一个子项挪到最后：这几处都只有两个子项（电话图标不在时紧凑条右边那组只剩汉堡），效果跟整条
   反向相同，而 `justify-content` 不跟着翻。 */
[data-block="header"].hdr-logo-right .hdr-compact > :first-child,
[data-block="header"].hdr-logo-right .hdr-compact > div > :first-child,
[data-block="header"] .hdr-topbar .hdr-flip > :first-child { order: 1; }

[data-block="header"] > .hdr-topbar,
[data-block="header"] .navbar > .hdr-grid,
[data-block="header"] .navbar > .hdr-below {
  display: none !important;
}

@media (min-width: 992px) {
  [data-block="header"] > .hdr-topbar,
  [data-block="header"] .navbar > .hdr-below {
    display: block !important;
  }
  [data-block="header"] .navbar > .hdr-grid {
    display: grid !important;
    grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
    align-items: center;
    column-gap: 1.5rem;
  }
  [data-block="header"] .hdr-grid > .hdr-a,
  [data-block="header"] .hdr-grid > .hdr-b,
  [data-block="header"] .hdr-grid > .hdr-c { grid-row: 1; min-width: 0; }
  [data-block="header"] .hdr-grid > .hdr-a { grid-column: 1; justify-self: start; }
  [data-block="header"] .hdr-grid > .hdr-b { grid-column: 2; justify-self: center; }
  [data-block="header"] .hdr-grid > .hdr-c { grid-column: 3; justify-self: end; }
  [data-block="header"].hdr-logo-right .hdr-grid > .hdr-a { grid-column: 3; justify-self: end; }
  [data-block="header"].hdr-logo-right .hdr-grid > .hdr-c { grid-column: 1; justify-self: start; }
  [data-block="header"].hdr-logo-right .hdr-grid > .hdr-c { justify-content: flex-start !important; }

  /* menu=beside：中间那格是空的，两栏就够 —— logo 一栏 auto，菜单 + CTA 吃掉其余全部宽度（三栏 1fr auto 1fr
     会让菜单只拿到一半，6 项真实菜单在 1440 上照样折行）。 */
  [data-block="header"].hdr-menu-beside .navbar > .hdr-grid { grid-template-columns: auto minmax(0, 1fr); }
  [data-block="header"].hdr-menu-beside .hdr-grid > .hdr-c { grid-column: 2; }
  [data-block="header"].hdr-menu-beside.hdr-logo-right .navbar > .hdr-grid { grid-template-columns: minmax(0, 1fr) auto; }
  [data-block="header"].hdr-menu-beside.hdr-logo-right .hdr-grid > .hdr-a { grid-column: 2; }
  [data-block="header"].hdr-menu-beside.hdr-logo-right .hdr-grid > .hdr-c { grid-column: 1; }

  /* menu=center：中间那栏是整条菜单，两侧是 logo 和 CTA —— 这两样不能缩（缩了就被菜单盖住：992 实测
     菜单压在店名和「Book a service」上）。所以两侧最小值是 max-content，中间 `minmax(0, auto)` 让菜单在
     自己那栏里换行；宽度够的时候两侧仍是等宽的 1fr，菜单真居中。 */
  [data-block="header"].hdr-menu-center .navbar > .hdr-grid {
    grid-template-columns: minmax(max-content, 1fr) minmax(0, auto) minmax(max-content, 1fr);
  }

  /* menu=gathered：跟 split 同一份 DOM，三栏都是 auto、整组居中（Bootstrap 的 `.navbar > .container-lg
     { justify-content: space-between }` 会把三栏撑到两边，这里压回 center —— 做图册时踩到的坑 2）。 */
  [data-block="header"].hdr-menu-gathered .navbar > .hdr-grid {
    grid-template-columns: auto auto auto;
    justify-content: center !important;
    column-gap: 2.5rem;
  }
  [data-block="header"].hdr-menu-gathered .hdr-grid > .hdr-a { justify-self: end; }
  [data-block="header"].hdr-menu-gathered .hdr-grid > .hdr-c { justify-self: start; }
}

/* #1476 —— 深底（`bg` 填了深色 / `brand` / 深渐变）上的弱化文字（顶条联系信息、抽屉里那一段）挂 `hdr-muted-on-deep`。
   它的颜色（白 .92）全站只有一条：`scripts/lib/site-css.js` §ON_DEEP_MUTED（#1477）。 */

/* #1476 —— `bg=brand`：主色底上主色按钮看不见 ⟹ 实心那种翻成白底主色字（hero / cta 同一条）。 */
[data-block="header"] .btn-primary.hdr-cta-on-brand {
  background: #fff !important;
  border-color: #fff !important;
  color: var(--x-primary) !important;
}
[data-block="header"] .btn-primary.hdr-cta-on-brand:hover {
  background: #f1f5f9 !important;
  border-color: #f1f5f9 !important;
  color: #0f172a !important;
}

/* ═══ 汉堡的两枚图标（#1514：抽屉是 Bootstrap Collapse，aria-expanded 由它翻）：收着露 list、开着露 x-lg ═══════════ */
[data-block="header"] .hdr-burger[aria-expanded="true"] .hdr-ic-open,
[data-block="header"] .hdr-burger:not([aria-expanded="true"]) .hdr-ic-close {
  display: none !important;
}

/* ── header · logo-left = 预设（logo=left · menu=beside · topbar 关）──────────────────────
   🔴 #1462 起这个文件夹**只是一个名字**：预设 = 两个旋钮 + topbar 开关的一组值（manifest 的 `presets`，#1468），排版全在
      `blocks/header/block.css`，按 Section 从旋钮派生的类（`hdr-logo-*` / `hdr-menu-*` / `hdr-topbar-*`）
      点名 —— 旋钮能拧成预设表外的组合（Custom），那种组合没有文件夹，几何按文件夹写就无处可落。
   🔴 下面这条空规则是**锚点**，别删：① `readShapeDir`（`scripts/lib/block-manifest.js`）拒绝空的 shape.css；
      ② `theme-pipeline/pool.test.js` §⑪ 要求主题选择单里写的名字在 `public/shapes.css` 里有一条
      `[data-block][data-shape="<名>"]` 规则。两道都是在问「这个名字是不是真的」，而这里名字是真的 ——
      戴上它 Section 就按这组旋钮值画（`Section.tsx` §resolveKnobs）。 */
[data-block="header"][data-shape="logo-left"] {}

/* ── header · menu-center = 预设（logo=left · menu=center · topbar 关）──────────────────────
   🔴 #1462 起这个文件夹**只是一个名字**：预设 = 两个旋钮 + topbar 开关的一组值（manifest 的 `presets`，#1468），排版全在
      `blocks/header/block.css`，按 Section 从旋钮派生的类（`hdr-logo-*` / `hdr-menu-*` / `hdr-topbar-*`）
      点名 —— 旋钮能拧成预设表外的组合（Custom），那种组合没有文件夹，几何按文件夹写就无处可落。
   🔴 下面这条空规则是**锚点**，别删：① `readShapeDir`（`scripts/lib/block-manifest.js`）拒绝空的 shape.css；
      ② `theme-pipeline/pool.test.js` §⑪ 要求主题选择单里写的名字在 `public/shapes.css` 里有一条
      `[data-block][data-shape="<名>"]` 规则。两道都是在问「这个名字是不是真的」，而这里名字是真的 ——
      戴上它 Section 就按这组旋钮值画（`Section.tsx` §resolveKnobs）。 */
[data-block="header"][data-shape="menu-center"] {}

/* ── header · logo-center-split = 预设（logo=center · menu=split · topbar 关）──────────────────────
   🔴 #1462 起这个文件夹**只是一个名字**：预设 = 两个旋钮 + topbar 开关的一组值（manifest 的 `presets`，#1468），排版全在
      `blocks/header/block.css`，按 Section 从旋钮派生的类（`hdr-logo-*` / `hdr-menu-*` / `hdr-topbar-*`）
      点名 —— 旋钮能拧成预设表外的组合（Custom），那种组合没有文件夹，几何按文件夹写就无处可落。
   🔴 下面这条空规则是**锚点**，别删：① `readShapeDir`（`scripts/lib/block-manifest.js`）拒绝空的 shape.css；
      ② `theme-pipeline/pool.test.js` §⑪ 要求主题选择单里写的名字在 `public/shapes.css` 里有一条
      `[data-block][data-shape="<名>"]` 规则。两道都是在问「这个名字是不是真的」，而这里名字是真的 ——
      戴上它 Section 就按这组旋钮值画（`Section.tsx` §resolveKnobs）。 */
[data-block="header"][data-shape="logo-center-split"] {}

/* ── header · logo-center-gathered = 预设（logo=center · menu=gathered · topbar 关）──────────────────────
   🔴 #1462 起这个文件夹**只是一个名字**：预设 = 两个旋钮 + topbar 开关的一组值（manifest 的 `presets`，#1468），排版全在
      `blocks/header/block.css`，按 Section 从旋钮派生的类（`hdr-logo-*` / `hdr-menu-*` / `hdr-topbar-*`）
      点名 —— 旋钮能拧成预设表外的组合（Custom），那种组合没有文件夹，几何按文件夹写就无处可落。
   🔴 下面这条空规则是**锚点**，别删：① `readShapeDir`（`scripts/lib/block-manifest.js`）拒绝空的 shape.css；
      ② `theme-pipeline/pool.test.js` §⑪ 要求主题选择单里写的名字在 `public/shapes.css` 里有一条
      `[data-block][data-shape="<名>"]` 规则。两道都是在问「这个名字是不是真的」，而这里名字是真的 ——
      戴上它 Section 就按这组旋钮值画（`Section.tsx` §resolveKnobs）。 */
[data-block="header"][data-shape="logo-center-gathered"] {}

/* ── header · topbar = 预设（logo=left · menu=beside · topbar 开）──────────────────────
   🔴 #1462 起这个文件夹**只是一个名字**：预设 = 两个旋钮 + topbar 开关的一组值（manifest 的 `presets`，#1468），排版全在
      `blocks/header/block.css`，按 Section 从旋钮派生的类（`hdr-logo-*` / `hdr-menu-*` / `hdr-topbar-*`）
      点名 —— 旋钮能拧成预设表外的组合（Custom），那种组合没有文件夹，几何按文件夹写就无处可落。
   🔴 下面这条空规则是**锚点**，别删：① `readShapeDir`（`scripts/lib/block-manifest.js`）拒绝空的 shape.css；
      ② `theme-pipeline/pool.test.js` §⑪ 要求主题选择单里写的名字在 `public/shapes.css` 里有一条
      `[data-block][data-shape="<名>"]` 规则。两道都是在问「这个名字是不是真的」，而这里名字是真的 ——
      戴上它 Section 就按这组旋钮值画（`Section.tsx` §resolveKnobs）。 */
[data-block="header"][data-shape="topbar"] {}

/* ── header · stacked = 预设（logo=center · menu=below · topbar 关）──────────────────────
   🔴 #1462 起这个文件夹**只是一个名字**：预设 = 两个旋钮 + topbar 开关的一组值（manifest 的 `presets`，#1468），排版全在
      `blocks/header/block.css`，按 Section 从旋钮派生的类（`hdr-logo-*` / `hdr-menu-*` / `hdr-topbar-*`）
      点名 —— 旋钮能拧成预设表外的组合（Custom），那种组合没有文件夹，几何按文件夹写就无处可落。
   🔴 下面这条空规则是**锚点**，别删：① `readShapeDir`（`scripts/lib/block-manifest.js`）拒绝空的 shape.css；
      ② `theme-pipeline/pool.test.js` §⑪ 要求主题选择单里写的名字在 `public/shapes.css` 里有一条
      `[data-block][data-shape="<名>"]` 规则。两道都是在问「这个名字是不是真的」，而这里名字是真的 ——
      戴上它 Section 就按这组旋钮值画（`Section.tsx` §resolveKnobs）。 */
[data-block="header"][data-shape="stacked"] {}

/* ── header · topbar-stacked = 预设（logo=center · menu=below · topbar 开）──────────────────────
   🔴 #1462 起这个文件夹**只是一个名字**：预设 = 两个旋钮 + topbar 开关的一组值（manifest 的 `presets`，#1468），排版全在
      `blocks/header/block.css`，按 Section 从旋钮派生的类（`hdr-logo-*` / `hdr-menu-*` / `hdr-topbar-*`）
      点名 —— 旋钮能拧成预设表外的组合（Custom），那种组合没有文件夹，几何按文件夹写就无处可落。
   🔴 下面这条空规则是**锚点**，别删：① `readShapeDir`（`scripts/lib/block-manifest.js`）拒绝空的 shape.css；
      ② `theme-pipeline/pool.test.js` §⑪ 要求主题选择单里写的名字在 `public/shapes.css` 里有一条
      `[data-block][data-shape="<名>"]` 规则。两道都是在问「这个名字是不是真的」，而这里名字是真的 ——
      戴上它 Section 就按这组旋钮值画（`Section.tsx` §resolveKnobs）。 */
[data-block="header"][data-shape="topbar-stacked"] {}
