/* ==========================================================================
   站点层：顶栏、页脚、内容块、各页版式
   ========================================================================== */

/* ---------------------------------------------------------------- 顶栏 */

.site-header {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: var(--z-header);
  /* ★ 高度不写死：= 顶部横幅（内容决定）+ 44px 导航行。
     实测高度由 core.js 写回 --header-h，main 的 offset 与首屏高度跟着它走。 */
  height: auto;
  display: flex;
  flex-direction: column;
  background: var(--c-bg);
  transition: background var(--tr), border-color var(--tr), transform var(--tr);
  border-bottom: 1px solid transparent;
}

/* ---- 顶部横幅（原站 vib-top-banner）----
   原站真值（从 Essentials_files/*.css 抽出）：
     padding: var(--vib-spacing-sm)=8px var(--vib-spacing-xl)=48px
     font-size: var(--vib-font-size-xs)=10px
     关闭按钮 right: var(--vib-spacing-md)=16px、图标 --vib-icon-size-xs=14px
   8+8+行高 ≈ 30px —— 正好对上线上实测的那条 31px 横幅。
   字号这里取 11px 而不是原站的 10px：原站文案是英文大写，中文 10px 会糊。
   横幅可以关掉（sessionStorage 记住），关掉后整条移除，页头自动收起。 */
.site-banner {
  position: relative;
  width: 100%;
  min-height: var(--header-banner);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  padding: 8px 48px;
  background: var(--c-banner);
  font-size: var(--fs-11);
  line-height: 1.4;
  text-align: center;
}
.site-banner__link {
  color: var(--c-ink-2);
  letter-spacing: var(--ls-wide);
  transition: color var(--tr);
}
.site-banner__link:hover { color: var(--c-ink); }
.site-banner__close {
  position: absolute;
  right: var(--sp-4);
  top: 50%;
  transform: translateY(-50%);
  width: 20px; height: 20px;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--c-muted-ink);
  transition: color var(--tr);
}
.site-banner__close:hover { color: var(--c-ink); }
.site-banner__close svg { width: 14px; height: 14px; display: block; }
.site-header.is-solid {
  background: rgba(252, 251, 252, 0.94);
  backdrop-filter: saturate(160%) blur(8px);
  border-bottom-color: var(--c-line);
}
.site-header.is-hidden { transform: translateY(-100%); }

.site-header__inner {
  width: 100%;
  height: var(--header-nav);
  display: flex;
  align-items: center;
  gap: 0;
  background: var(--c-bg);
}

/* 品牌位固定 240px —— 参考站「导航第一项」落在容器左缘 +240 处
   （2560 实测：容器左缘 380，Collections 起点 620）。 */
.brand { display: inline-flex; align-items: center; flex: none; width: min(240px, 44vw); }
.brand img, .brand svg { height: 26px; width: auto; }
.brand__text { font-size: var(--fs-16); font-weight: var(--fw-medium); letter-spacing: var(--ls-wider); }

/* 导航不再靠 margin-left:auto 甩到右边：它紧跟在品牌位之后（与参考站一致） */
.site-nav { display: flex; align-items: center; gap: var(--sp-6); }
.site-nav__item { position: relative; }
.site-nav__link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: var(--sp-2) 0;
  font-size: var(--fs-14);
  font-weight: var(--fw-normal);
  letter-spacing: var(--ls-tight);
  color: var(--c-ink-2);
  transition: color var(--tr);
}
.site-nav__link:hover, .site-nav__item.is-active > .site-nav__link { color: var(--c-ink); }
.site-nav__link::after {
  content: "";
  display: block;
  position: absolute;
  left: 0; right: 0; bottom: -2px;
  height: 1px;
  background: var(--c-ink);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--tr);
}
.site-nav__item:hover > .site-nav__link::after,
.site-nav__item.is-active > .site-nav__link::after { transform: scaleX(1); }

/* 下拉指示：内联 SVG 小箭头（不用「▾」字符，那个字体会回退到衬线体）。
   只作用于导航，`.caret` 在别处（手风琴、目录侧栏）另有各自主体的样式。 */
.site-nav__link .caret {
  display: inline-flex;
  width: 10px;
  height: 10px;
  margin-top: 1px;
  opacity: 0.5;
  transition: transform var(--tr), opacity var(--tr);
}
.site-nav__link .caret svg { width: 100%; height: 100%; }
.site-nav__item:hover > .site-nav__link .caret { opacity: 1; transform: rotate(180deg); }

/* 二级菜单 */
.subnav {
  position: absolute;
  top: calc(100% + var(--sp-4));
  left: calc(-1 * var(--sp-4));
  min-width: 200px;
  background: var(--c-bg);
  border: 1px solid var(--c-line);
  padding: var(--sp-3) 0;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-4px);
  transition: opacity var(--tr), transform var(--tr), visibility var(--tr);
}
.site-nav__item:hover .subnav,
.site-nav__item:focus-within .subnav { opacity: 1; visibility: visible; transform: none; }
.subnav a {
  display: block;
  padding: var(--sp-3) var(--sp-5);
  font-size: var(--fs-13, 13px);
  color: var(--c-ink-2);
  transition: background var(--tr), color var(--tr);
}
.subnav a:hover { background: var(--c-bg-alt); color: var(--c-ink); }

/* ---- 导航下拉（有分组时）：通栏面板，照原站 vib-desktop-menu ----
   原站真值（`Vibia _ Plusminus Essentials_files/*.css`）：
     __viewport--position { width:100%; background: primary-1; overflow:hidden }
     .vib-desktop-menu-content { min-height: 30.1875rem (=483px); padding-block: lg }
     __wrapper  { display:flex; justify-content:space-between; gap: lg }
     __group    { display:grid; grid-template-rows:repeat(2,auto); grid-auto-flow:column;
                  column-gap: 2xl; row-gap: 2rem }        ← 两行一列、横向流动
     __section  { flex column; gap: sm; max-width: 10.875rem (=174px) }
     __products { display:grid; grid-template-columns:repeat(2,1fr); gap: md; max-width:33.33% }
   —— 是一条与头部同宽同底色的**通栏面板**，不是小卡片浮层。
   `top: var(--header-h)` 让它永远贴在导航行下缘（头部高度随横幅变，这里不用改）。
   注意：`.is-solid` 上的 backdrop-filter 会给 fixed 后代建 containing block，
   此时 left/right/top 相对 header —— 而 header 正好贴视口顶，两种情况下位置都对。 */
.megapanel {
  position: fixed;
  left: 0; right: 0;
  top: var(--header-h);
  background: var(--c-bg);
  border-bottom: 1px solid var(--c-line);
  padding-block: var(--sp-6);          /* 原站 --vib-spacing-lg = 32px */
  min-height: 30.1875rem;              /* 原站真值 483px */
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transform: translateY(-8px);
  transition: opacity var(--tr), transform var(--tr), visibility var(--tr);
}
.site-nav__item.has-panel:hover > .megapanel,
.site-nav__item.has-panel:focus-within > .megapanel {
  opacity: 1; visibility: visible; pointer-events: auto; transform: none;
}
/* 头部滚出屏幕时，面板一起收走（面板是 fixed，不随 header 的 transform 走） */
.site-header.is-hidden .megapanel { opacity: 0; visibility: hidden; pointer-events: none; }

.megapanel__inner {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;    /* 左链接列组 / 右产品图，中间自然留白（原站 __wrapper 也是这样） */
  gap: var(--sp-6);
}
/* 列组横向排开：每组宽 174px（原站 __section 的 max-width: 10.875rem）。
   ★ 这里没有照抄原站 __group 的 `grid-template-rows:repeat(2,auto); grid-auto-flow:column`：
   那是给「一个 group 里塞很多 section」准备的；我们的一组本身就是一个 section（标题 + 链接），
   再套两行一列会把整个左半区压成 84px 宽的一条细列（实测过）。 */
.megapanel__groups {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-8);                  /* 原站 __group 的 column-gap: 2xl = 64px */
}
.megapanel__group { width: 10.875rem; flex: none; }
/* 组标题：原站用 `--vib-text-lg`（= font-size-md 14px / 行高 140%）+ 更深的颜色，
   不是大号字也不是小灰字。 */
.megapanel__gt {
  font-size: var(--fs-14);
  line-height: 1.4;
  color: var(--c-ink);
  margin-bottom: var(--sp-2);      /* 原站 __section 的 gap: sm = 8px */
}
.megapanel__list { display: flex; flex-direction: column; gap: var(--sp-2); }
.megapanel__list a {
  display: inline-block;
  padding: 1px 0;
  font-size: var(--fs-14);
  line-height: 1.4;                /* 原站 --vib-line-height-lg */
  color: var(--c-ink-2);
  transition: color var(--tr);
}
.megapanel__list a:hover { color: var(--c-ink); text-decoration: underline; text-underline-offset: 4px; }

/* 右侧产品图网格：一列 2 张、最多占面板 1/3（原站 max-width:33.33%） */
.megapanel__products {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-4);                  /* 原站 __products 的 gap: md = 16px */
  width: 50%;
  max-width: 33.33%;
  flex: none;
}
.megapanel__prod { display: block; }
.megapanel__prodimg {
  margin: 0;
  /* 2026-09-28：原 4:5（09-27 曾一度改 1:1）→ 3:4。余总更正「竖版位置用 3:4」。 */
  aspect-ratio: 3 / 4;
  overflow: hidden;
  background: var(--c-bg-alt);
}
.megapanel__prodimg img { width: 100%; height: 100%; object-fit: cover; display: block; }
.megapanel__prodlabel {
  display: block;
  margin-top: var(--sp-3);
  font-size: var(--fs-12);
  color: var(--c-ink-2);
}
.megapanel__prod:hover .megapanel__prodlabel { color: var(--c-ink); }
/* 面板只在桌面用；窄屏走汉堡菜单，避免通栏面板在小屏上撑出超高 */
@media (max-width: 1100px) { .megapanel { display: none; } }

/* 右侧组：搜索 / 获取报价 / 中英切换。整体贴容器右缘，项间距 48px（参考站实测 48）。 */
.site-actions { display: flex; align-items: center; gap: var(--sp-7); flex: none; margin-left: auto; }
.site-actions__link {
  font-size: var(--fs-14);
  font-weight: var(--fw-normal);
  letter-spacing: var(--ls-tight);
  color: var(--c-ink-2);
  white-space: nowrap;
  transition: color var(--tr);
}
.site-actions__link:hover { color: var(--c-ink); }
@media (max-width: 1400px) { .site-actions { gap: var(--sp-5); } }

.lang { display: inline-flex; align-items: center; gap: var(--sp-2); font-size: var(--fs-14); letter-spacing: var(--ls-tight); }
.lang button {
  padding: 0;
  color: var(--c-muted-2);
  transition: color var(--tr);
}
.lang button.is-on { color: var(--c-ink); }
.lang button:hover { color: var(--c-ink); }
.lang button + button::before { content: "/"; margin-right: var(--sp-2); color: var(--c-line-2); }

.burger { display: none; }
@media (max-width: 1023px) {
  .site-nav, .site-actions .lang, .site-actions__link, .site-actions .btn { display: none; }
  .burger { display: inline-flex; }
}

/* ---------------------------------------------------------------- 移动菜单 */

.mmenu {
  position: fixed;
  inset: 0;
  z-index: var(--z-drawer);
  background: var(--c-bg);
  display: flex;
  flex-direction: column;
  transform: translateY(-100%);
  transition: transform var(--tr-slow);
  visibility: hidden;
}
.mmenu.is-open { transform: none; visibility: visible; }
.mmenu__head {
  height: var(--header-h);
  display: flex; align-items: center; justify-content: space-between;
  padding-inline: var(--gutter);
  border-bottom: 1px solid var(--c-line);
  flex: none;
}
.mmenu__body { flex: 1; overflow-y: auto; padding: var(--sp-7) var(--gutter); }
.mmenu__list > li { border-bottom: 1px solid var(--c-line); }
.mmenu__link {
  display: flex; align-items: center; justify-content: space-between;
  padding: var(--sp-5) 0;
  font-size: var(--fs-18);
  font-weight: var(--fw-light);
}
.mmenu__sub { padding: 0 0 var(--sp-4); display: grid; gap: var(--sp-3); }
.mmenu__sub a { font-size: var(--fs-14); color: var(--c-muted); }
.mmenu__foot { flex: none; padding: var(--sp-5) var(--gutter) var(--sp-7); border-top: 1px solid var(--c-line); }

/* ---------------------------------------------------------------- 页脚 */

/* 页脚与页面同色（2026-09-19 用户要求：底部不要黑色）。
   原来是 --c-dark-bg 一整块近黑，跟「极简克制、弱化叫卖」的调子不搭 ——
   深色大色块会把视线钉在页面最底下，浅色页脚才是把内容收住的做法。
   只留一条发丝线把页脚和正文分开：同色不等于没有边界，
   没有边界的话页脚会像正文的尾巴，看不出是个独立的结束区。 */
.site-footer {
  background: var(--c-bg);
  color: var(--c-ink);
  border-top: 1px solid var(--c-line);
  padding-top: clamp(48px, 6vw, 96px);
}
.site-footer .label { color: var(--c-muted-ink); }
/* ★ v17：页脚网格按**参考站实测**重排（2026-09-22；tools/_v17_live_spec.py）。
   参考站的页脚容器自己就是一个 grid，里面**只有 2 个内容块** + 1 条横跨的底行：
     col1 = nav.vib-footer__nav   col2 = （**空着，没有任何元素**）   col3 = div.vib-footer__about
     第二行 = div.vib-footer__bottom（横跨）
   实测 gtc：1920 → `900px 300px 600px`；1440 → `704px 234.656px 469.344px`；
             1355 → `661.5px 220.5px 441px` —— 比例恒为 **3 : 1 : 2**，column-gap 0；
             row-gap 实测 `96px`（原文 `gap: 96px 0px`）。
   折叠（实测）：≥1280 三列 → 768–1279 两列（`338px 350px`，列距 48）→ <768 单列（行距 48）。
   以前我们写的是 `minmax(240px,1.4fr) repeat(4,1fr)`：品牌在最左、4 组并排 ——
   和参考站的「链接在左、品牌在右」**正好相反**，已改。
   ⚠️ 我上一轮跟用户说「参考站 Footer 是 8 列」也是错的：顶层只有 2 块，
      「8」是 `__nav` 内部 3 列 × 若干 section 数出来的分组数，不是列数。 */
.footer__grid {
  display: grid;
  grid-template-columns: 3fr 1fr 2fr;
  column-gap: 0;
  row-gap: var(--sp-9);          /* 96px = 参考站行距实测 */
  padding-bottom: var(--sp-9);
}
/* 链接区在第 1 列、品牌区在第 3 列 —— 中间那列**故意留空**（参考站就是空的）。
   必须显式写列号：靠 auto 摆放会把品牌塞进空着的第 2 列，比例就变了。 */
.footer__nav {
  grid-column: 1; grid-row: 1;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--sp-7) var(--sp-4);  /* 48px 16px = 参考站 __nav 实测 gap */
}
.footer__col { display: flex; flex-direction: column; gap: var(--sp-7); }
.footer__section h4 { margin-bottom: var(--sp-4); }

.footer__about { grid-column: 3; grid-row: 1; }
.footer__about img, .footer__about svg { height: 30px; width: auto; }
.footer__tagline { margin-top: var(--sp-5); font-size: var(--fs-14); color: var(--c-muted-ink); max-width: 30ch; line-height: var(--lh-normal); }
/* 品牌块里的联系方式：后台把某一组的「位置」选成「品牌块（右侧）」就落在这里。
   参考站那 600px 的 about 块里**一条链接都没有**，所以我们把邮箱/电话/微信
   这类非链接条目放这儿最合适（它们本来也渲染成 .footer__text 而不是 <a>）。 */
.footer__contact { margin-top: var(--sp-6); }
.footer__contact h4 { margin-bottom: var(--sp-4); }

@media (max-width: 1279px) {
  /* 参考站实测：768–1279 折成两列，右列恒 350px、列距 48 —— 768 下算出 338/350，与实测一致 */
  .footer__grid { grid-template-columns: minmax(0, 1fr) 350px; column-gap: var(--sp-7); }
  .footer__about { grid-column: 2; }
  .footer__nav { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 767px) {
  /* 参考站实测：<768 单列、行距 48 */
  .footer__grid { grid-template-columns: minmax(0, 1fr); row-gap: var(--sp-7); column-gap: 0; }
  .footer__nav, .footer__about { grid-column: 1; grid-row: auto; }
  .footer__nav { grid-template-columns: minmax(0, 1fr); }
}

.footer__links { display: grid; gap: var(--sp-3); }
.footer__links a {
  font-size: var(--fs-14);
  color: var(--c-ink-2);
  transition: color var(--tr);
}
.footer__links a:hover { color: var(--c-muted); }

/* 没有真实地址的条目（后台留空 / 只有 "#"）：保持同样的排版，但明确「不是链接」——
   不能给它 pointer 和 hover 变化，否则用户会一直点。 */
.footer__text {
  font-size: var(--fs-14);
  color: var(--c-ink-2);
  cursor: default;
}
.footer__legal .footer__text { font-size: var(--fs-12); color: var(--c-muted); }

.footer__bottom {
  border-top: 1px solid var(--c-line);
  padding-block: var(--sp-5);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-5);
  flex-wrap: wrap;
  font-size: var(--fs-12);
  color: var(--c-muted-ink);
}
.footer__legal { display: flex; gap: var(--sp-5); flex-wrap: wrap; }
.footer__legal a:hover { color: var(--c-ink); }
/* 备案号：法定必显示项，跟在版权后同一行（2026-10-16 备案通过后加）。
   颜色继承 .footer__bottom 的弱化色，hover 才变深 —— 与右侧法务链接观感一致，不喧宾夺主。 */
.footer__icp { margin-left: var(--sp-4); color: inherit; }
.footer__icp:hover { color: var(--c-ink); }

/* 主内容偏移（顶栏固定） */
main { padding-top: var(--header-h); }
main.no-offset { padding-top: 0; }

/* ---------------------------------------------------------------- 内容块：首屏 */

.blk-hero { position: relative; overflow: hidden; background: var(--c-bg-alt); }
.blk-hero--full   { min-height: calc(100vh - var(--header-h)); }
.blk-hero--tall   { min-height: clamp(520px, 72vh, 820px); }
.blk-hero--normal { min-height: clamp(380px, 46vh, 520px); }

.blk-hero__media { position: absolute; inset: 0; }
.blk-hero__media img { width: 100%; height: 100%; object-fit: cover; }
.blk-hero--overlay-soft .blk-hero__media::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(1,1,1,0.10) 0%, rgba(1,1,1,0.42) 100%);
}
.blk-hero--overlay-strong .blk-hero__media::after {
  content: ""; position: absolute; inset: 0;
  background: rgba(1,1,1,0.52);
}

.blk-hero__inner {
  position: relative;
  min-height: inherit;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: flex-end;
  padding-block: clamp(48px, 8vh, 120px);
  gap: var(--sp-5);
  color: var(--c-dark-ink);
}
.blk-hero--align-center .blk-hero__inner { align-items: center; text-align: center; }
.blk-hero--align-center .blk-hero__text { margin-inline: auto; }
.blk-hero__eyebrow { color: rgba(255,255,255,0.72); }
.blk-hero__title { max-width: 18em; }
.blk-hero__text { max-width: 40em; color: rgba(255,255,255,0.8); font-size: var(--fs-lead); font-weight: var(--fw-light); }
.blk-hero__ctas { display: flex; gap: var(--sp-3); flex-wrap: wrap; margin-top: var(--sp-3); }

/* 无图时的首屏（浅色） */
.blk-hero--plain { background: var(--c-bg-alt); }
.blk-hero--plain .blk-hero__inner { color: var(--c-ink); }
.blk-hero--plain .blk-hero__eyebrow { color: var(--c-muted); }
.blk-hero--plain .blk-hero__text { color: var(--c-muted); }

/* ---------------------------------------------------------------- 内容块：陈述句 */

.blk-statement { padding-block: var(--sec-pad); }
.blk-statement__text {
  font-size: clamp(19px, 2.1vw, 30px);
  font-weight: var(--fw-light);
  line-height: 1.75;
  letter-spacing: 0.035em;
  color: var(--c-ink);
  max-width: 46em;
}
.blk-statement--indent .blk-statement__text { text-indent: 18vw; }
@media (max-width: 900px) { .blk-statement--indent .blk-statement__text { text-indent: 9vw; } }
@media (max-width: 640px) {
  .blk-statement--indent .blk-statement__text { text-indent: 2.2em; font-size: 19px; line-height: 1.85; }
}

/* ---------------------------------------------------------------- 内容块：图文引言 / 分栏 */

.blk-intro__grid, .blk-split__grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(32px, 5vw, 96px);
  align-items: center;
}
.blk-intro--img-left .blk-intro__media, .blk-split--img-left .blk-split__media { order: -1; }
.blk-intro__media img, .blk-split__media img { width: 100%; height: 100%; object-fit: cover; }
.blk-intro__body > * + *, .blk-split__body > * + * { margin-top: var(--sp-5); }
@media (max-width: 860px) {
  .blk-intro__grid, .blk-split__grid { grid-template-columns: minmax(0, 1fr); gap: var(--sp-7); }
  .blk-intro--img-left .blk-intro__media, .blk-split--img-left .blk-split__media { order: 0; }
}

.blk-split__points { display: grid; gap: var(--sp-5); }
.blk-split__point { display: grid; grid-template-columns: 68px minmax(0, 1fr); gap: var(--sp-4); }
.blk-split__point-n {
  font-size: var(--fs-12);
  color: var(--c-muted-2);
  letter-spacing: var(--ls-wide);
  padding-top: 3px;
}
.blk-split__point h4 { margin-bottom: var(--sp-1); }
.blk-split__point p { font-size: var(--fs-14); color: var(--c-muted); }

/* ---------------------------------------------------------------- 内容块：栅格 / 精选 */

.blk-grid__items, .blk-featured__items { display: grid; gap: var(--sp-6) var(--sp-5); }
.blk-grid--2 .blk-grid__items, .blk-featured--2 .blk-featured__items { grid-template-columns: repeat(2, minmax(0,1fr)); }
.blk-grid--3 .blk-grid__items, .blk-featured--3 .blk-featured__items { grid-template-columns: repeat(3, minmax(0,1fr)); }
.blk-grid--4 .blk-grid__items, .blk-featured--4 .blk-featured__items { grid-template-columns: repeat(4, minmax(0,1fr)); }
@media (max-width: 1100px) {
  .blk-grid--4 .blk-grid__items, .blk-featured--4 .blk-featured__items { grid-template-columns: repeat(2, minmax(0,1fr)); }
}
@media (max-width: 860px) {
  .blk-grid__items, .blk-featured__items,
  .blk-grid--2 .blk-grid__items, .blk-grid--3 .blk-grid__items, .blk-grid--4 .blk-grid__items,
  .blk-featured--2 .blk-featured__items, .blk-featured--3 .blk-featured__items, .blk-featured--4 .blk-featured__items {
    grid-template-columns: repeat(2, minmax(0,1fr));
  }
}
@media (max-width: 560px) {
  .blk-grid__items, .blk-featured__items { grid-template-columns: minmax(0,1fr); }
}

/* ---------------------------------------------------------------- 内容块：数字条 */

.blk-stats { border-top: 1px solid var(--c-line); border-bottom: 1px solid var(--c-line); }
.blk-stats__items {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--sp-7) var(--sp-5);
}
@media (max-width: 860px) { .blk-stats__items { grid-template-columns: repeat(2, minmax(0,1fr)); } }
.blk-stats__item { display: flex; flex-direction: column; gap: var(--sp-2); }
.blk-stats__value {
  font-size: clamp(30px, 3.4vw, 52px);
  font-weight: var(--fw-light);
  line-height: 1;
  letter-spacing: var(--ls-tight);
}
.blk-stats__value small { font-size: 0.45em; margin-left: 4px; font-weight: var(--fw-normal); color: var(--c-muted); }
.blk-stats__label { font-size: var(--fs-12); color: var(--c-muted); letter-spacing: var(--ls-wide); }

/* ---------------------------------------------------------------- 内容块：步骤 */

.blk-steps__items {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--sp-7) var(--sp-6);
  counter-reset: step;
}
.blk-steps__item { display: grid; gap: var(--sp-3); padding-top: var(--sp-5); border-top: 1px solid var(--c-line); }
.blk-steps__n {
  font-size: var(--fs-12);
  letter-spacing: var(--ls-wider);
  color: var(--c-accent);
  font-weight: var(--fw-medium);
}
.blk-steps__item p { font-size: var(--fs-14); color: var(--c-muted); line-height: var(--lh-normal); }

/* ---------------------------------------------------------------- 内容块：问答 */

.blk-faq__list { max-width: 900px; }
.blk-faq__a { font-size: var(--fs-14); color: var(--c-muted); line-height: var(--lh-normal); max-width: 60ch; }

/* ---------------------------------------------------------------- 内容块：图片墙 */

.blk-gallery__items { display: grid; gap: var(--sp-4); }
.blk-gallery--2 .blk-gallery__items { grid-template-columns: repeat(2, minmax(0,1fr)); }
.blk-gallery--3 .blk-gallery__items { grid-template-columns: repeat(3, minmax(0,1fr)); }
.blk-gallery--4 .blk-gallery__items { grid-template-columns: repeat(4, minmax(0,1fr)); }
@media (max-width: 860px) {
  .blk-gallery--3 .blk-gallery__items,
  .blk-gallery--4 .blk-gallery__items { grid-template-columns: repeat(2, minmax(0,1fr)); }
}
.blk-gallery--masonry .blk-gallery__items { grid-auto-rows: 120px; }
.blk-gallery--masonry .blk-gallery__item:nth-child(3n+1) { grid-row: span 3; }
.blk-gallery--masonry .blk-gallery__item:nth-child(3n+2) { grid-row: span 2; }
.blk-gallery--masonry .blk-gallery__item:nth-child(3n)   { grid-row: span 4; }
.blk-gallery--row .blk-gallery__items {
  display: flex; overflow-x: auto; scroll-snap-type: x mandatory; padding-bottom: var(--sp-4);
}
.blk-gallery--row .blk-gallery__item { flex: 0 0 clamp(240px, 32vw, 420px); scroll-snap-align: start; }
.blk-gallery__item { position: relative; overflow: hidden; background: var(--c-bg-alt); }
.blk-gallery__item img { width: 100%; height: 100%; object-fit: cover; transition: transform var(--tr-slow); }
.blk-gallery__item:hover img { transform: scale(1.03); }

/* ---------------------------------------------------------------- 内容块：引言 */

.blk-quote { padding-block: var(--sec-pad); }
.blk-quote__text {
  font-size: clamp(22px, 2.4vw, 34px);
  font-weight: var(--fw-light);
  line-height: 1.55;
  letter-spacing: 0.02em;
  max-width: 34em;
}
.blk-quote__by { margin-top: var(--sp-7); font-size: var(--fs-12); letter-spacing: var(--ls-wider); color: var(--c-muted); }
.blk-quote__by span { color: var(--c-muted-2); }

/* ---------------------------------------------------------------- 内容块：行动条 */

.blk-cta { position: relative; overflow: hidden; }
.blk-cta--dark { background: var(--c-dark-bg); color: var(--c-dark-ink); }
/* 「浅色」CTA = 与页面背景同色（2026-09-19 用户定死：底部不要黑色）。
   用 --c-bg-alt 会有肉眼可辨的色差，看起来像没对齐。 */
.blk-cta--light { background: var(--c-bg); }
.blk-cta--photo .blk-cta__bg { position: absolute; inset: 0; }
.blk-cta--photo .blk-cta__bg img { width: 100%; height: 100%; object-fit: cover; }
.blk-cta--photo .blk-cta__bg::after { content: ""; position: absolute; inset: 0; background: rgba(1,1,1,0.5); }
.blk-cta__inner {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-5);
  padding-block: clamp(56px, 7vw, 110px);
}
.blk-cta--photo .blk-cta__inner { color: var(--c-dark-ink); }
.blk-cta__text { max-width: 44em; font-size: var(--fs-lead); font-weight: var(--fw-light); opacity: 0.82; }
.blk-cta__ctas { display: flex; gap: var(--sp-3); flex-wrap: wrap; margin-top: var(--sp-2); }

/* 内容块：下载 —— 2026-09-20 按定死规则「不用下载功能」整段删除。
   块类型也已从 schema.json 摘掉，这里不再需要样式。 */

/* ---------------------------------------------------------------- 内容块：联系 */

.blk-contact__grid {
  display: grid;
  grid-template-columns: minmax(0, 1.2fr) minmax(280px, 0.8fr);
  gap: clamp(32px, 5vw, 80px);
  align-items: start;
}
@media (max-width: 860px) { .blk-contact__grid { grid-template-columns: minmax(0,1fr); } }
.contact-info { display: grid; gap: var(--sp-6); }
.contact-info__item { display: grid; gap: var(--sp-2); }
.contact-info__k {
  font-size: var(--fs-11);
  letter-spacing: var(--ls-wider);
  text-transform: uppercase;
  color: var(--c-muted);
}
.contact-info__v { font-size: var(--fs-16); color: var(--c-ink); }
.contact-info__v a:hover { border-bottom: 1px solid var(--c-ink); }

.form__ok {
  display: flex; flex-direction: column; gap: var(--sp-3);
  padding: var(--sp-7);
  background: var(--c-bg-alt);
  border-radius: var(--r-4);
  text-align: center;
}

/* ---------------------------------------------------------------- 目录页 */

.dir-hero { padding-block: clamp(40px, 5vw, 80px) clamp(24px, 3vw, 48px); }
.dir-hero__crumb { margin-bottom: var(--sp-7); }
.dir-hero__title { max-width: 20em; }
.dir-hero__statement {
  font-size: clamp(18px, 1.9vw, 26px);
  font-weight: var(--fw-light);
  line-height: 1.7;
  letter-spacing: 0.03em;
  text-indent: 18vw;
  max-width: 44em;
  margin-top: var(--sp-5);
}
@media (max-width: 900px) { .dir-hero__statement { text-indent: 8vw; } }
@media (max-width: 640px) { .dir-hero__statement { text-indent: 2em; font-size: 18px; } }

.dir-body {
  display: grid;
  grid-template-columns: 240px minmax(0, 1fr);
  gap: var(--sp-6);
  align-items: start;
  padding-bottom: var(--sp-10);
}
@media (max-width: 1100px) { .dir-body { grid-template-columns: minmax(0, 1fr); } }

.dir-side {
  position: sticky;
  top: calc(var(--header-h) + var(--sp-5));
  max-height: calc(100vh - var(--header-h) - var(--sp-8));
  overflow-y: auto;
  padding-right: var(--sp-2);
}
@media (max-width: 1100px) { .dir-side { display: none; } }

.dir-side__group { padding-block: var(--sp-4); border-bottom: 1px solid var(--c-line); }
.dir-side__group:first-child { border-top: 1px solid var(--c-line); }
/* 侧栏顶部搜索框（2026-09-20 补：st.q 一直在用、输入框却从没渲染过）。
   它现在是侧栏第一个子元素，所以「首组的上边框」要挪到搜索框上，否则顶上会少一条线。 */
.dir-side__search { padding-block: var(--sp-4) var(--sp-5); border-top: 1px solid var(--c-line); }
.dir-side__search-label {
  display: block; margin-bottom: var(--sp-2);
  font-size: var(--fs-12); letter-spacing: var(--ls-wide);
  text-transform: uppercase; color: var(--c-ink);
}
.dir-side__q { width: 100%; min-height: 38px; font-size: var(--fs-14); }
.dir-side__search + .dir-side__group { border-top: 0; }
.dir-side__search + .dir-side__group:first-child { border-top: 0; }
/* type=search 的默认清除叉（Safari/Chrome）风格与整站不搭，隐掉 */
.dir-side__q::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; }
.dir-side__head {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-3); width: 100%;
  font-size: var(--fs-12);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  color: var(--c-ink);
}
.dir-side__head .caret {
  width: 10px; height: 10px; flex: none;
  border-right: 1px solid currentColor; border-bottom: 1px solid currentColor;
  transform: rotate(45deg) translateY(-2px);
  transition: transform var(--tr);
}
.dir-side__group.is-open .caret { transform: rotate(-135deg) translateY(-2px); }
.dir-side__body { display: none; padding-top: var(--sp-4); }
.dir-side__group.is-open .dir-side__body { display: block; }
.dir-side__list { display: grid; gap: var(--sp-1); }
.dir-side__opt {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-3);
  width: 100%;
  padding: var(--sp-2) 0;
  font-size: var(--fs-13, 13px);
  color: var(--c-ink-2);
  text-align: left;
  transition: color var(--tr);
}
.dir-side__opt:hover { color: var(--c-ink); }
.dir-side__opt.is-on { color: var(--c-ink); font-weight: var(--fw-medium); }
.dir-side__opt.is-on::before { content: "·"; margin-right: 4px; color: var(--c-accent); }
.dir-side__n { font-size: var(--fs-11); color: var(--c-muted-2); }
.dir-side__more {
  margin-top: var(--sp-3);
  font-size: var(--fs-12);
  color: var(--c-muted);
  border-bottom: 1px solid var(--c-line-2);
  padding-bottom: 1px;
}
.dir-side__more:hover { color: var(--c-ink); border-color: var(--c-ink); }

.dir-main { min-width: 0; }
.dir-toolbar {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-4);
  padding-bottom: var(--sp-5);
  border-bottom: 1px solid var(--c-line);
  margin-bottom: var(--sp-6);
  flex-wrap: wrap;
}
.dir-count { font-size: var(--fs-12); color: var(--c-muted); letter-spacing: var(--ls-wide); }
.dir-mobile-filter { display: none; }
@media (max-width: 1100px) { .dir-mobile-filter { display: inline-flex; } }

.dir-active { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-bottom: var(--sp-6); }
.dir-active__chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px var(--sp-3);
  border: 1px solid var(--c-line-2);
  border-radius: var(--r-2);
  font-size: var(--fs-12);
  transition: border-color var(--tr), background var(--tr);
}
.dir-active__chip:hover { border-color: var(--c-ink); }
.dir-active__chip b { font-weight: var(--fw-medium); }
.dir-active__clear { font-size: var(--fs-12); color: var(--c-muted); border-bottom: 1px solid var(--c-line-2); }

.dir-grid { display: grid; grid-template-columns: repeat(3, minmax(0,1fr)); gap: var(--sp-6) var(--sp-4); }
@media (max-width: 1400px) { .dir-grid { grid-template-columns: repeat(3, minmax(0,1fr)); } }
@media (max-width: 900px)  { .dir-grid { grid-template-columns: repeat(2, minmax(0,1fr)); } }
@media (max-width: 520px)  { .dir-grid { grid-template-columns: minmax(0,1fr); } }

/* 栅格里的条目壳：**不是**卡片本身，只是卡片外面裹一层用来挂类型徽标。
   ★ 壳不能参与布局（display:contents）—— 否则 .dir-grid 的 3 栏会变成
     「一格一个壳」，而壳里的 .card 又自成一格，卡宽被二次折半。
   所以用 position:relative 让徽标有定位参照，同时不引入任何盒子。 */
.dir-card { position: relative; }
.dir-card__badge {
  position: absolute;
  z-index: 2;
  top: var(--sp-3);
  left: var(--sp-3);
  padding: 3px var(--sp-3);
  font-size: var(--fs-11);
  letter-spacing: var(--ls-wide);
  line-height: 1.4;
  color: var(--c-ink);
  background: rgba(252, 251, 252, .82);
  border: 1px solid var(--c-line-2);
  border-radius: var(--r-2);        /* ★ 2026-09-23 批 0：原写 `--radius-2`（**该令牌不存在**，
                                       tokens 里只有 `--r-1/--r-2/--r-4/--r-pill`）⇒ 这条
                                       `border-radius` 一直静默失效、圆角为 0。改名对齐令牌。 */
  pointer-events: none;      /* 别挡住整块可点卡片的点击热区 */
}
.dir-card__badge--col { border-color: var(--c-ink); }

/* 锁定维度（分类页的「分类」组）：不给点、不给折叠，但保持同一套外观 */
.dir-side__group.is-locked .dir-side__head { cursor: default; color: var(--c-ink); }
.dir-side__group.is-locked .caret { visibility: hidden; }
.dir-side__group.is-locked .dir-side__body { display: block; }
.dir-side__group.is-locked .dir-side__opt { cursor: default; }
.dir-side__group.is-locked .dir-side__opt.is-on::before { color: var(--c-ink); }

/* ---------------------------------------------------------------- 产品分类页 */
/* 与系列详情页的区别：分类页的主角是「这个分类下有哪些灯」，所以首屏克制、
   栅格紧跟简介，陈述句等原本在前的内容（.cat-after）整体下移到栅格之后。 */

.cat-hero {
  position: relative; overflow: hidden;
  background: var(--c-bg-alt);
  min-height: clamp(280px, 38vh, 440px);
}
.cat-hero__media { position: absolute; inset: 0; }
.cat-hero__media img { width: 100%; height: 100%; object-fit: cover; }
.cat-hero__scrim {
  position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(1,1,1,0.10), rgba(1,1,1,0.42));
}
.cat-hero__inner {
  position: relative;
  min-height: inherit;
  display: flex; flex-direction: column; justify-content: flex-end;
  gap: var(--sp-3);
  padding-block: clamp(40px, 6vh, 76px);
  color: var(--c-dark-ink);
}
.cat-hero__crumb { margin-bottom: var(--sp-4); }
.cat-hero__title { max-width: 16em; }   /* 字号/行高/字重见「页面主标题」统一块 */
.cat-hero__sub { max-width: 40em; font-size: var(--fs-lead); font-weight: var(--fw-light); opacity: 0.84; }

.cat-intro { padding-block: clamp(32px, 4vw, 60px) clamp(16px, 2vw, 28px); }
.cat-intro__body { max-width: 52em; color: var(--c-muted); }

.cat-results { padding-block: clamp(16px, 2vw, 28px) clamp(48px, 6vw, 96px); }
.cat-results__head {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--sp-4);
  padding-bottom: var(--sp-5);
  margin-bottom: var(--sp-6);
  border-bottom: 1px solid var(--c-line);
}
.cat-results__title { font-size: clamp(20px, 2.2vw, 28px); font-weight: var(--fw-light); letter-spacing: 0.02em; }
.cat-results__hint { font-size: var(--fs-12); color: var(--c-muted); }
.cat-results__body { padding-bottom: 0; }

/* 栅格之后的正文（陈述句 / 分栏 / 常见问题 …）
   ★ 用户 2026-09-20 批注：这里的陈述句要**左对齐** —— 原本 .blk-statement--indent
     用 text-indent:18vw 把首行推到了中间，分类页不缩进。 */
.cat-after .blk-statement--indent .blk-statement__text { text-indent: 0; }
.cat-after .blk-statement { padding-block: clamp(40px, 5vw, 88px); }

/* ================================================================ 系列详情页（照 Vibia _ Knit 重做）
   ★ 2026-09-20 用户第二次推翻：「系列页按 Vibia _ Knit.html 改，以前多余的全删除，严格按这个」。
     版块顺序与数值全部来自参照页源码（见 _docs/系列页参照真值_Knit.md），不是截图估的。

   已删除的旧结构（参照页完全没有）：01 光与影 / 02 设计语言 / 03 结构工艺 /
   04 材质与肌理 / 05 空间参考 / 06 选配 / 07 规格参数 / 08 探索同系列 + 后台自由模块区。
   所以下面那批 .col-hero / .col-attrs / .col-meta 已是**孤儿样式**，
   留在这里只为不误伤同名前缀，新页面不再使用。 */

/* 首屏：参照页 vib-collection-detail-hero
   height: calc(100dvh - 页头)；标题组水平+垂直居中；面包屑绝对定位左上；
   滚动提示绝对定位底部居中。.pdp-hero 的既有几何规则整条复用，
   这里只补「系列页」与单品页不同的两处：面包屑不带 .container 包裹、标题居中。 */
.pdp-hero--collection .pdp-hero__crumb {
  position: absolute; top: 20px; left: var(--gutter, 16px);
  padding: 5px 8px; border-radius: var(--r-4, 4px);
  background: rgba(198, 198, 197, 0.4);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
}
.pdp-hero--collection .pdp-hero__center { text-align: center; align-items: center; }

/* ① technicalInfo：概览 + 技术信息手风琴
   参照页 @1280 起 `grid-template-columns: 1fr 4fr 2fr 5fr`，
   第 1、3 栏是留白，文字落第 2 栏、手风琴落第 4 栏。
   ★ 手风琴本体复用 .pdp-overview__tech / .pdp-tech__*（单品页同一套），只改外框栅格。 */
.col-overview { padding-block: clamp(48px, 6vw, 104px); }
.col-overview__grid {
  display: grid;
  grid-template-columns: 1fr;
  row-gap: var(--sp-10);
}
@media (min-width: 1024px) {
  .col-overview__grid { grid-template-columns: 1fr 5fr 1fr 5fr; column-gap: var(--sp-6); }
  .col-overview__info { grid-column: 2; }
  .col-overview__specs { grid-column: 4; }
}
@media (min-width: 1280px) {
  .col-overview__grid { grid-template-columns: 1fr 4fr 2fr 5fr; }
}
.col-overview__title {
  display: block;
  margin-bottom: var(--sp-4);
  font-size: clamp(24px, 2.4vw, 32px);
  font-weight: var(--fw-light);
  letter-spacing: 0.01em;
}
.col-overview__designer { margin-bottom: 1.5rem; font-size: var(--fs-14); color: var(--c-muted); }
.col-overview__designer a { color: inherit; text-decoration: underline; text-underline-offset: 0.25em; }
.col-overview__desc { font-size: var(--fs-14); line-height: 1.7; color: var(--c-ink); }
.col-overview__desc p + p { margin-top: 0.6em; }

/* ⑨ 询价段（参照页没有这一段，用户明确要求保留） */
.col-inquiry { padding-block: var(--sec-pad); border-top: 1px solid var(--c-line, rgba(1,1,1,0.08)); }
.col-inquiry__grid { display: grid; grid-template-columns: 1fr; gap: var(--sp-8); }
@media (min-width: 1024px) {
  /* ★ 2026-09-23 批 0：原写 `gap: var(--sp-12)`，而 `--sp-12` **不存在**
     （tokens 的间距阶梯只到 `--sp-11: 300px`）⇒ 这条 gap 一直静默失效、两栏贴在一起。
     ⚠️ 改成 `--sp-10`（160px）是**推断值**（窄屏 `--sp-8` = 64px → 宽屏取更大一档），
     不是从参考站实测来的 —— 待余总复核；要改只是一行。 */
  .col-inquiry__grid { grid-template-columns: 5fr 4fr; gap: var(--sp-10); align-items: start; }
}
.col-inquiry__left .h2 { margin-block: var(--sp-3) var(--sp-4); }
.col-inquiry__form { display: grid; gap: var(--sp-4); max-width: 34em; }
.col-inquiry__form .field { display: grid; gap: 6px; }
.col-inquiry__form .field > span { font-size: var(--fs-12); letter-spacing: var(--ls-wide); color: var(--c-muted); }
.col-inquiry__form input,
.col-inquiry__form textarea {
  width: 100%; padding: 10px 12px;
  border: 1px solid var(--c-line, rgba(1,1,1,0.14));
  border-radius: var(--r-2, 2px);
  background: var(--c-bg); color: inherit;
  font: inherit; font-size: var(--fs-14);
}
.col-inquiry__form textarea { resize: vertical; }
.col-inquiry__form .btn { justify-self: start; }

.backtop {
  position: fixed;
  right: var(--sp-6); bottom: var(--sp-6);
  z-index: 90;
  display: inline-flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-4);
  background: var(--c-bg);
  border: 1px solid var(--c-line-2);
  border-radius: var(--r-2);
  font-size: var(--fs-11);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  opacity: 0; pointer-events: none;
  transform: translateY(6px);
  /* ★ v18：基线补 visibility:hidden —— 原来只有 opacity:0 + pointer-events:none，
     按钮**仍然能被 Tab 聚焦**（一个看不见的焦点框）。visibility 也放进过渡里：
     淡出时它在动画**结束**才翻成 hidden，淡入时立刻变 visible，视觉上没变化。 */
  visibility: hidden;
  transition: opacity var(--tr), transform var(--tr), visibility var(--tr);
}
.backtop.is-on { opacity: 1; pointer-events: auto; transform: none; visibility: visible; }
.backtop:hover { border-color: var(--c-ink); }
/* ★ v18：页脚底行一进视口就淡出（判据在 core.js → initScroll）。
   起因（实测，见 tools/_v18_probe.py）：滚到底时它压住页脚法务条目 ——
   交集 1150.4 / 716.6 / 1744.7 px²（index 在 1440 / 1920 / 390 三视口）。
   ⚠️ 必须排在上一条 `.is-on` **之后**：两者特异性相同（0,2,0），靠顺序决定谁赢。 */
.backtop.is-out { opacity: 0; pointer-events: none; transform: translateY(6px); visibility: hidden; }
/* 返回顶部的箭头是内联 SVG，需要显式给尺寸（SVG 默认 100% 会撑满） */
.backtop__ico { display: inline-flex; width: 11px; height: 11px; }
.backtop__ico svg { width: 100%; height: 100%; }
@media (max-width: 640px) { .backtop { right: var(--sp-4); bottom: var(--sp-4); } }

/* ---------------------------------------------------------------- 页面主标题
   ★ v18 统一：全站 **6 处页面级 H1** 共用参考站实测的**同一条阶梯与行高**。
   统一之前它们各写各的 clamp：
     .cat-hero__title  clamp(30px, 4vw,   54px)   .sp-hero__title  clamp(30px, 4.2vw, 60px)
     .ph__title        clamp(30px, 3.6vw, 52px)   产品名 / 总览 / 块级首屏 走 --fs-h1(30→55)
   → 在 1280–1439 中间档互相差出 10px 以上，同一站的大字节奏是散的。
   参考站口径（令牌与实测过程写在 tokens.css 的 --fs-hero 上）：
     字号 22 / 35（≥768）/ 55（≥1280）｜行高 = 字号 × 1.2｜字重恒 300｜字距 --ls-tight
   ⚠️ 以后再加「页面级 H1」就把选择器挂到这张表里，**别再自己写 clamp** ——
      阶梯验收 tools/check_layout_tiers.py 的 A2 段会逐页逐档盯着。
   ⚠️ 本块放在这些选择器的**前面**也不影响结果：上面那 6 处已经**没有**别的
      font-size/line-height 声明了（v18 一并摘掉），所以胜负不靠顺序，靠唯一来源。 */
.blk-hero__title, .dir-hero__title, .cat-hero__title,
.sp-hero__title, .ph__title, .pdp-hero__name {
  font-size: var(--fs-hero);
  font-weight: var(--fw-light);
  line-height: var(--lh-hero);
  letter-spacing: var(--ls-tight);
}

/* ---------------------------------------------------------------- 系列详情 */

.col-hero { position: relative; overflow: hidden; background: var(--c-bg-alt); min-height: clamp(420px, 56vh, 660px); }
.col-hero__media { position: absolute; inset: 0; }
.col-hero__media img { width: 100%; height: 100%; object-fit: cover; }
.col-hero__media::after { content: ""; position: absolute; inset: 0; background: linear-gradient(180deg, rgba(1,1,1,0.12), rgba(1,1,1,0.48)); }
.col-hero__inner {
  position: relative;
  min-height: inherit;
  display: flex; flex-direction: column; justify-content: flex-end;
  gap: var(--sp-4);
  padding-block: clamp(48px, 7vh, 96px);
  color: var(--c-dark-ink);
}
.col-hero__title { max-width: 16em; }
.col-hero__sub { max-width: 40em; font-size: var(--fs-lead); font-weight: var(--fw-light); opacity: 0.82; }

.col-attrs { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: var(--sp-6); }
.col-attr { display: grid; gap: var(--sp-3); }
.col-attr__k { font-size: var(--fs-11); letter-spacing: var(--ls-wider); text-transform: uppercase; color: var(--c-muted); }
.col-attr__v { display: flex; flex-wrap: wrap; gap: var(--sp-2); font-size: var(--fs-14); }

/* ---------------------------------------------------------------- 产品详情
   版式按《前台完整交互原型补充》第四条：
     Hero → 01 光与影 → 02 设计语言 → 03 结构工艺 → 04 材质与肌理
     → 05 空间参考 → 06 选配 → 07 规格参数 → 询价 → 08 相关产品
   这一页负责把「光」讲清楚，不负责催单：没有价格、没有加购、没有常驻询价条，
   首屏唯一的行动号召是一个克制的文字链。留白要大，字重要细。 */

/* ---- Hero：满屏实景图 + 居中产品名 / Slogan + 右侧速览面板 + 底部滚动提示 ----
   几何全部取自参考站实测（无头浏览器量 vibia 单品页，2560×1405 视口）：

     首屏高度 = 视口高 − 头部，也就是铺满头部以下的全部高度（不溢出到折叠下方）。
     参考站 hero 元素 `vib-collection-detail-page__intro` 两次实测：
       vh=1405 → y 75..1405（h 1330）    vh=2400 → y 75..2400（h 2325）
     即「从头部底到视口底」，与 100svh − 头部 完全一致。

     产品名 + Slogan 这一块**在 hero 内精确垂直居中**，间距 12px：
       vh=1405：h1 y687.8 h66、slogan y765.8 h26.4 → 块 687.8..792.2，中心 740.0 = hero 中心 740.0
       vh=2400：h1 y1185.3        、slogan y1263.3      → 块中心 1237.5 = hero 中心 1237.5
     行高就是字号的 1.2 倍（55→66、22→26.4）。

     ⚠ 这里踩过一次坑，记下来免得再犯：
     线上站首次抓到的头部是 75px（31px 黑横幅 + 44px 导航），h1 顶量到 687.8。
     我把 687.8 当成绝对目标套到了自己的 130px 头部上，于是加了个 `padding-bottom:5vh`
     把标题往上顶 —— 结果标题整体偏高约 29px。
     正确的算法是「在 hero 内居中」，代入 130px 头部：h1 顶 = 130 + (1275 − 104.4)/2 = 715.3。

     面包屑 chip：距头部底 20px、高 24px（padding 5px 8px + 10px 字 × 1.4 行高）
     右侧面板：288px 宽、padding 24px 16px、距右 16px、距底 24px
     面板内部：标签行 48px；动作行 33.8px（1px 上边框 + 8px 内边距 + 16.8 行高 + 8px）
     面板 CTA：256×32、圆角 2px、黑底
     底部滚动提示：32×32、水平居中、距底 20px

     三个玻璃件的底色统一走 --glass（rgba(198,198,197,.4) + blur 4px），与参考站一致。
     分隔线用 --glass-line（深色 12% 发丝线）：我们的实景首图偏浅，浅色线在浅玻璃上看不见。
    */
.pdp-hero {
  position: relative;
  height: calc(100svh - var(--header-h));
  min-height: 520px;
  overflow: hidden;
  background: var(--c-bg-alt);
  /* 首屏与正文之间也要留满 120px（参考站实测：**第一段上方同样是 120**）。
     hero 是全出血的、自身没有上下 padding，所以这半边挂在 hero 的 margin 上 ——
     这样不论 hero 后面接的是哪一块（系列页接型号区 / 单品页接概览 / 空间页接图集），
     规则都成立，不用逐个给「第一段」写特例。 */
  margin-bottom: var(--sec-pad);
}
.pdp-hero__media { position: absolute; inset: 0; }
.pdp-hero__media img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* ---- 首屏遮罩：按底色自动二选一（见 pages.js 的 tagHeroContrast）----
   参考站两种都用了：暗底那张 55px 白字（#FCFBFC），浅底那一段 55px 深字（#010101）。
   我们的产品图是白底棚拍（实测平均亮度 224~246），白字压上去对比只有 1.2、等于看不见；
   所以这里不能写死颜色 —— 页面按「字实际压在图上那一块」的平均亮度自己挑分支。
   暗底分支：竖向梯度，在标题所在的 42%~62% 那条带压到 0.44，
             保证底色一定落到 80 以下（白字对比 ≥3，55px 属大字，AA 只要 3.0）。 */
.pdp-hero__scrim {
  position: absolute; inset: 0;
  background: linear-gradient(180deg,
    rgba(10, 9, 8, 0.22) 0%,
    rgba(10, 9, 8, 0.36) 30%,
    rgba(10, 9, 8, 0.44) 42%,
    rgba(10, 9, 8, 0.44) 62%,
    rgba(10, 9, 8, 0.36) 76%,
    rgba(10, 9, 8, 0.24) 100%);
}
/* 浅底分支：不需要压暗，只留一点点把玻璃件和纯白分开；字改深色 */
.pdp-hero.is-light-bg .pdp-hero__scrim {
  background: linear-gradient(180deg, rgba(10, 9, 8, 0.035) 0%, rgba(10, 9, 8, 0.075) 100%);
}
.pdp-hero.is-light-bg .pdp-hero__center { color: var(--c-ink); }
.pdp-hero__inner { position: relative; height: 100%; }

/* 面包屑 chip：贴在头部正下方 20px、与 logo 同一条左对齐线。
   wrapper 必须是 flex：以前它是块级容器、里面是 inline-flex 的 chip，
   chip 会按行盒基线（而不是容器顶）定位，实测比 target 低了 3px —— 换成 flex 就没有这个行盒偏移。 */
.pdp-hero__crumbwrap { position: absolute; top: 20px; left: 0; right: 0; display: flex; }
.pdp-hero__crumb {
  display: inline-flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2);
  min-height: 24px;
  padding: 5px var(--sp-2);
  background: var(--glass);
  backdrop-filter: var(--glass-blur);
  border-radius: var(--r-2);
  font-size: var(--fs-10);
  line-height: 1.4;                 /* 写死，不用 --lh-*：中文模式会把 --lh-snug 改成 1.6，高度就不对了 */
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  color: var(--c-ink);
}
.pdp-hero__crumb a { color: inherit; }
.pdp-hero__crumb .crumb__sep { opacity: 0.5; }

/* 产品名 + Slogan：水平居中，纵向**精确居中在 hero 里**（见上面那段实测注释）。
   行高一律写死 1.2（55→66、22→26.4）。这里不能用 --lh-tight / --lh-snug：
   那两个令牌在 :root[lang="cn"] 下会被改成 1.3 / 1.6，块高一变、居中位置就跟着变。 */
.pdp-hero__center {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--sp-3);
  padding: 0 var(--gutter);
  text-align: center;
  color: var(--c-bg);
}
.pdp-hero__slogan {
  font-size: clamp(17px, 1.6vw, 22px);
  font-weight: var(--fw-light);
  line-height: 1.2;
  max-width: 32em;
}

/* 右侧速览面板：磨砂玻璃。它是「看技术信息 / 看型号 / 找人」的入口，不是购物车。
   几何全部取自参考站**实测**（Vibia Alba 的 .vib-floating-actions-panel--desktop，2560×1405）：
     面板 288px 宽、padding 24px 16px、右下各留 16px / 24px
     页签区 48px（内容 24 + 下留白 24）→ 动作行 34px×N → 下留白 24 → CTA 32 → 下内边距 24
     底色 rgba(198,198,197,.4) + blur(4px) —— 是中性灰，不是暖白
   ★ 2026-09-21 重做：参考站的面板是「页签区 + 动作行 + 底部 CTA」三段。
     每个动作行是 **文字在左、图标在右**（参考站原样写法 flex-direction:row-reverse +
     justify-content:space-between，实测文字 x=2272、图标 x=2512）。
     ⚠️ 旧注释写过「参考站这几行是左对齐的裸文字、右侧没有箭头」—— 那是错的，已改。
     ⚠️ 参考站动作行里还有一条 Download —— 定死规则①「前台不用下载功能」，整条不做。 */
.pdp-panel {
  position: absolute; right: var(--gutter); bottom: 24px;
  width: 288px;
  padding: var(--sp-5) var(--sp-4);
  background: var(--glass);
  backdrop-filter: var(--glass-blur);
  color: var(--c-ink);
}
/* 页签区（参考站 __tabs-area）：左边一枚 12px 标签、右边一排 12px 大写页签。
   页签之间的竖线是 li 的 ::after **伪元素**（1px × 12px），不塞真的「|」字符 ——
   真字符会跟着字距一起变宽，还会被屏幕阅读器念成「竖线」。 */
.pdp-panel__tabs {
  display: flex; flex-direction: row; align-items: baseline;
  justify-content: space-between; gap: var(--sp-4);
  padding-bottom: var(--sp-5);
  font-size: var(--fs-12);
  line-height: 1.4;
  color: var(--c-ink-2);
}
.pdp-panel__tabs-label { flex: 0 0 auto; }
.pdp-panel__tablist {
  flex: 1 1 auto;
  display: flex; flex-direction: row; align-items: center;
  justify-content: flex-end; flex-wrap: wrap;
  column-gap: var(--sp-2);
  margin: 0; padding: 0; list-style: none;
}
.pdp-panel__tablist li { position: relative; }
.pdp-panel__tablist li:not(:last-of-type) { padding-right: var(--sp-2); }
.pdp-panel__tablist li:not(:last-of-type)::after {
  content: ''; position: absolute; top: 0.5rem; right: 0;
  width: 1px; height: 0.75rem;
  background: var(--glass-rule);
}
/* 页签本身是 <span>（参考站也是）—— 它表达「这款属于哪些品类」，不是一个可点的筛选器。 */
.pdp-panel__tab {
  display: inline-flex; align-items: center;
  /* 参考站那颗页签自身高 24px（实测 .vib-link h=24），页签区 = 24 + 24 下留白 = 48。
     不给行高的话它是继承来的 16.8px，整个页签区只有 40.8 —— 面板整体矮 7px。 */
  line-height: 24px;
  font-size: var(--fs-12);
  text-transform: uppercase;
  letter-spacing: var(--ls-wide);
  color: var(--c-muted-ink);
}
.pdp-panel__tab.is-active { color: var(--c-ink); }
/* 动作行：34px = 1px 上边框 + 8px 内边距 + 12px字×1.4 行高 + 8px 内边距（参考站实测 34/33）。 */
.pdp-panel__actions {
  margin: 0 0 var(--sp-5); padding: 0; list-style: none;
  border-bottom: 1px solid var(--glass-rule);
}
.pdp-panel__actions > li { border-top: 1px solid var(--glass-rule); }
.pdp-panel__row {
  width: 100%;
  display: flex; flex-direction: row-reverse;
  align-items: center; justify-content: space-between;
  gap: var(--sp-2);
  padding: 8px 0;
  font-size: var(--fs-12);
  line-height: 1.4;
  color: var(--c-ink-2);
  text-align: left;
  transition: color var(--tr);
}
.pdp-panel__row:hover { color: var(--c-ink); }
/* ★ 顺序也必须照抄参考站：它的 .vib-link__txt/__icon 带**显式 order**
   （txt=2 / icon=1），配合父级的 flex-direction:row-reverse，图标才落到右边。
   只抄 row-reverse 不抄 order 的话正好相反 —— 实测我们原来量到
   「图标 x=2272（左）、文字 x=2480（右）」，参考站是「文字 2272（左）、图标 2512（右）」。 */
.pdp-panel__rowtxt { flex: 0 1 auto; order: 2; }
.pdp-panel__ico { flex: 0 0 auto; order: 1; display: inline-flex; width: 16px; height: 16px; }
.pdp-panel__ico svg { width: 16px; height: 16px; display: block; }
.pdp-panel__cta {
  display: flex; align-items: center; justify-content: center;
  width: 100%;
  height: 32px;
  background: var(--c-ink);
  color: var(--c-bg);
  border-radius: var(--r-2);
  font-size: var(--fs-12);
  line-height: 1.4;
  letter-spacing: var(--ls-wide);
  transition: opacity var(--tr);
}
.pdp-panel__cta:hover { opacity: 0.86; }

/* 底部滚动提示：32×32、距视口底 20px、水平居中。
   ★ v17：整块按参考站**实测**的 `.vib-btn-icon.vib-scroll-to-anchor-btn` 对齐 ——
   规则原文用 tools/_v17c_cue_probe.py 从 document.styleSheets 抓的（不是看图猜的）：
     width/height = 32；背景 = neutral-1 @ .4；圆角 sm；图标 20px；
     box-shadow: rgba(0,0,0,.15) 0 1px 2px 0；backdrop-filter 磨砂；
     默认**不画**焦点环（::after 36×36 透明），只在 :focus-visible 时才现。 */
.pdp-hero__cue {
  position: absolute; left: 50%; bottom: 20px; transform: translateX(-50%);
  width: 32px; height: 32px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--glass);                  /* 就是参考站实测的 rgba(198,198,197,0.4) */
  backdrop-filter: var(--glass-blur);
  border-radius: var(--r-2);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.15); /* 参考站实测 */
  color: var(--c-ink);                       /* 参考站实测 rgb(1,1,1) */
  transition: color var(--tr);
}
/* 参考站的 hover 是**图标变淡**，不是底色变深（实测：hover → neutral-1 @ .5） */
.pdp-hero__cue:hover { color: rgba(198, 198, 197, 0.5); }
.pdp-hero__cue svg { width: 20px; height: 20px; }

/* ★★ 参考站那条「Scroll to specs」**不是看得见的文字链**，是读屏专用的隐藏标签。
   规则原文（tools/_v17c_cue_probe.py 抓到的）：
     .vib-btn-icon .vib-btn-icon__txt { position: absolute; width: 1px; height: 1px;
       padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; }
   实测佐证：它在页面上占 1×1，elementFromPoint 在它中心点命中的是 .vib-container，
   **不是它自己** → 视觉上根本不存在。所以这里照抄成 sr-only：
   给读屏一个名字，画面上仍然只有那个圆箭头。
   （我上一轮跟用户说「参考站有可见文字链、要补上」——那是错的，实测推翻了。） */
.pdp-hero__cue__txt {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
/* 焦点环：默认透明，只有键盘聚焦才出现（参考站 .vib-btn-icon::after + :focus-visible） */
.pdp-hero__cue::after {
  content: ""; position: absolute; display: block; inset: -2px;
  border-radius: var(--r-2);
  box-shadow: 0 0 0 1px var(--c-ink);
  pointer-events: none; opacity: 0;
  transition: inherit;
}
.pdp-hero__cue:focus-visible::after { opacity: 1; }

/* 2026-09-20：底部悬浮条已按用户要求整条撤掉，
   滚动提示回到基准的「距视口底 20px」（.pdp-hero__cue 的默认值），不再需要让位补偿。 */

/* 窄屏：面板收成底部一条 CTA，滚动提示让位，标题上移避免被压 */
@media (max-width: 900px) {
  .pdp-panel {
    left: var(--gutter); right: var(--gutter); bottom: var(--sp-5);
    width: auto; padding: 0;
    background: none; backdrop-filter: none;
  }
  .pdp-panel__tabs, .pdp-panel__actions { display: none; }
  .pdp-panel__cta { margin-top: 0; background: rgba(252, 251, 252, 0.92); color: var(--c-ink); }
  .pdp-hero__center { padding-bottom: 104px; }
  .pdp-hero__cue { display: none; }
  .pdp-hero__crumb { font-size: var(--fs-10); }
}

/* ★ 2026-09-21：01–08 章节整段退役，这一片 CSS（章节头 / 横滚图组 / 空间拼贴 /
   选配组 / 07 键值对规格 / 询价段）已经没有节点匹配了，整片删掉。
   —— 判据不是 grep：先用 tools/audit_css_usage.py 把**真实 DOM** 里出现的类名
      采了一遍（49 个页面），又把这些名字在「剥掉注释的 JS」里核对过，
      两处都是 0 才删。留着的旧规则会让后来人以为还能挂回去，其实渲染器已经没了。
   ⚠️ 规格表 .pdp-skus* 不在这里面 —— 它是 v9 的规格实体表，仍然活在型号弹层里，已保留。 */



/* ---- 07 规格表（v9 阶段 3.x）：规格实体 variants[] 排成表 ----
   以前 07 只有一张「键值对」列表（产品级 p.specs）；现在那张之后接**规格表**，
   一行 = 一个可售 SKU = 型号 · 版本 · 尺寸 · 颜色（安装形式非空才加列）。
   列会按「隐藏项过滤」自动增减：整列取不到值就整列不出，不出现一列破折号。
   **不出价格 / 包装 / 毛重** —— 本站是 B2B 非电商，那些是报价单内部字段。
   选中态用下划线（与 .models-tab 同一套语汇），不用色块按钮 —— 与「不叫卖」的定调一致。 */
.pdp-skus-wrap { margin-top: clamp(40px, 6vh, 72px); }
.pdp-skus__bar {
  display: flex; align-items: baseline; gap: var(--sp-5);
  flex-wrap: wrap; margin-bottom: var(--sp-5);
}
.pdp-skus__label {
  margin: 0;
  font-size: var(--fs-11); letter-spacing: var(--ls-wider);
  text-transform: uppercase; color: var(--c-muted);
}
.pdp-versbar { display: flex; gap: var(--sp-6); flex-wrap: wrap; }
.pdp-vers {
  appearance: none; background: none; border: 0; padding: 0 0 var(--sp-2);
  font: inherit; font-size: var(--fs-14); font-weight: var(--fw-light);
  color: var(--c-muted); cursor: pointer; position: relative;
  transition: color 200ms ease-out;
}
.pdp-vers:hover { color: var(--c-ink); }
.pdp-vers.is-on { color: var(--c-ink); }
.pdp-vers.is-on::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0;
  height: 1px; background: currentColor;
}
.pdp-skus { max-width: 880px; border-top: 1px solid var(--c-line); }
.pdp-skus__t { width: 100%; border-collapse: collapse; text-align: left; }
.pdp-skus__t th {
  font-size: var(--fs-11); letter-spacing: var(--ls-wider);
  text-transform: uppercase; color: var(--c-muted); font-weight: 400;
  padding: var(--sp-4) var(--sp-5) var(--sp-4) 0;
  border-bottom: 1px solid var(--c-line); white-space: nowrap;
}
.pdp-skus__t td {
  font-size: var(--fs-14); font-weight: var(--fw-light); line-height: var(--lh-snug);
  color: var(--c-ink);
  padding: var(--sp-5) var(--sp-5) var(--sp-5) 0;
  border-bottom: 1px solid var(--c-line);
}
.pdp-skus__t th:last-child,
.pdp-skus__t td:last-child { padding-right: 0; }
.pdp-skus__none { color: var(--c-muted); }
@media (max-width: 640px) {
  /* 窄屏横向滚动而不是把表压扁 —— 规格表压扁后数字会折行，读不出对应关系 */
  .pdp-skus { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .pdp-skus__t { min-width: 420px; }
}

/* ---- 询价入口：一整段话 + 一条下划线链接，不用按钮 ---- */
.pdp-enquire {
  margin-top: clamp(72px, 11vh, 168px);
  padding-block: clamp(48px, 7vh, 104px);
  border-top: 1px solid var(--c-line);
  border-bottom: 1px solid var(--c-line);
}
.pdp-enquire__inner { display: grid; gap: var(--sp-5); max-width: 64em; }
.pdp-enquire__label {
  font-size: var(--fs-11); letter-spacing: var(--ls-wider);
  text-transform: uppercase; color: var(--c-muted);
}
.pdp-enquire__text {
  font-size: var(--fs-lead); font-weight: var(--fw-light);
  line-height: var(--lh-loose); max-width: var(--measure);
}
.pdp-enquire__links { display: flex; align-items: center; gap: var(--sp-6); flex-wrap: wrap; }
.pdp-enquire__meta {
  font-size: var(--fs-12); color: var(--c-muted); letter-spacing: var(--ls-wide);
}

/* ---- 08 探索同系列：原站 COMPLETE YOUR ATMOSPHERE 的裸卡 ----
   只有图 + 名称 + 安装场景小字，没有边框、按钮、色点 —— 不叫卖。 */
.pdp-atmos {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(16px, 1.8vw, 32px);
  max-width: 62%;
}
@media (max-width: 1100px) { .pdp-atmos { max-width: 100%; } }
@media (max-width: 640px) { .pdp-atmos { grid-template-columns: minmax(0, 1fr); } }
.pdp-atmos__card { position: relative; }
.pdp-atmos__media {
  /* 2026-09-28：原 4:5（09-27 曾一度改 1:1）→ 3:4。
     ⚠️ 实测全站无 JS 渲染这个节点（dead CSS），改它只为口径一致、不为观感。 */
  aspect-ratio: 3 / 4;
  overflow: hidden;
  background: var(--c-bg-alt);
}
.pdp-atmos__media img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  transition: transform var(--tr-slow);
}
.pdp-atmos__card:hover .pdp-atmos__media img { transform: scale(1.02); }
.pdp-atmos__body {
  padding-top: var(--sp-4);
  border-top: 1px solid var(--c-line);
  margin-top: var(--sp-4);
}
.pdp-atmos__t { font-size: var(--fs-18); font-weight: var(--fw-light); letter-spacing: var(--ls-tight); }
.pdp-atmos__k {
  margin-top: var(--sp-2);
  font-size: var(--fs-10); letter-spacing: var(--ls-wider);
  text-transform: uppercase; color: var(--c-muted);
}

/* ★ 2026-09-21：01–08 章节退役，05 空间参考（.pdp-atmos*）整片删除。
   空间案例有自己的页面（space.html），不需要通过单品页的章节露出来。 */



/* ---------------------------------------------------------------- 通用页头 */

.page-head { padding-block: clamp(40px, 5vw, 88px) clamp(24px, 3vw, 48px); }
.page-head__crumb { margin-bottom: var(--sp-6); }
.page-head__title { max-width: 20em; }
.page-head__sub { margin-top: var(--sp-5); max-width: 44em; color: var(--c-muted); font-size: var(--fs-lead); font-weight: var(--fw-light); }

/* 加载态 */
.loading { display: grid; place-items: center; padding: var(--sp-10) 0; color: var(--c-muted); font-size: var(--fs-12); letter-spacing: var(--ls-wide); }

/* ==========================================================================
   原站模块（2026-09-19）—— 按参考站的模块结构 1:1 落地
   概览与技术信息手风琴 · 三步配置 · 四工具盒 · 完成氛围 · 设计师引言 ·
   编辑推荐 · 支持服务带 · 常见问题
   遵守全站令牌：色只用暖白/近黑/米色层次，字重只用 300/400/500，
   圆角只用 1/2/4px，动效统一 var(--tr)（ease-out 200ms）。
   ========================================================================== */

/* ---- 概览 + 技术信息手风琴（hero 正下方，不编号，对应截图第 1 张）---- */
.pdp-overview { padding-block: var(--sec-pad); }
.pdp-overview__inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--sp-7);
  align-items: start;
}
/* ★ 2026-09-21：技术信息区改成**参考站的 12 栅格**（实测，不是目测）。
   参考站 .vib-technical-info-section .vib-container 在 2560 视口（容器 1800）下
   解析出的列宽是 146px / 584px / 292px / 730px，column-gap 16px、row-gap 64px
   —— 换算成 12 栅格正好是 1fr 4fr 2fr 5fr（1+4+2+5=12）。
   标题+描述落在第 2 列，手风琴落在第 4 列；**第 1、3 列在参考站里本来就是空列**
   （左边一道留白 + 中间一条呼吸缝），不是我们漏填了东西。
   ⚠️ 别再改回「左文右手风琴」的两栏 —— 那是我们自己的旧版式，不是参考站的。 */
@media (min-width: 1024px) {
  .pdp-overview__inner {
    grid-template-columns: 1fr 4fr 2fr 5fr;
    column-gap: var(--sp-4);
    row-gap: var(--sp-8);
  }
  .pdp-overview__text { grid-column: 2; }
  .pdp-overview__tech { grid-column: 4; }
}

.pdp-overview__eb {
  font-size: var(--fs-11); letter-spacing: var(--ls-wider);
  text-transform: uppercase; color: var(--c-muted);
}
.pdp-overview__title {
  margin-top: var(--sp-4);
  /* ★ v17 修正：旧注释说「2560 与 1440 都是 55px，是固定值」——只对了一半。
     1440 也在 ≥768 那一档里，所以那两个视口**分辨不出**「固定 55」和「阶梯 55」。
     十档视口实测（tools/_v17d_headings.py）：
       ≥768 → 55px（行高 66）；≤767 → 35px（行高 42）；字重恒 300。
     → 用 --fs-sec 表达（它的两档都是实测值）。旧写法 clamp(30px,4vw,55px)
       在 1024 下只有 41px、在 767 下只有 30.7px，都不是参考站的 55 / 35。 */
  font-size: var(--fs-sec);
  font-weight: var(--fw-light);
  line-height: 1.2;
  letter-spacing: var(--ls-tight);
}
.pdp-overview__by {
  margin-top: var(--sp-4);
  font-size: var(--fs-14); color: var(--c-muted); letter-spacing: var(--ls-wide);
}
.pdp-overview__body {
  margin-top: var(--sp-5); max-width: 34em;
  font-weight: var(--fw-light); line-height: var(--lh-loose);
}
.pdp-overview__img { margin-top: var(--sp-6); max-width: 420px; }
.pdp-overview__photo {
  /* 2026-09-28 原 4:3 → 16:9（4:3 不在三档；r=1.333 距 16:9 比距 1:1 近，
     与素材迁移 target_for 同一条规则）*/
  width: 100%; aspect-ratio: 16 / 9; object-fit: cover; display: block;
  background: var(--c-bg-alt);
}

/* 右侧手风琴：行高 48、发丝线分隔。原站这里用的是 + / − 而不是箭头，
   所以用两条细线拼一个加号，展开时收掉竖线变成减号。 */
.pdp-overview__tech { border-top: 1px solid var(--c-line); }
.pdp-tech__row { border-bottom: 1px solid var(--c-line); }
/* 手风琴的表头：参考站实测 button 高 36px、padding 8px 0 12px、
   里面那行字是 **10px 大写**（不是 11px）—— 一行 8+16+12=36。
   旧版是 min-height:48px + 11px，行距明显比参考站松。 */
.pdp-tech__head {
  padding: 8px 0 12px;
  font-size: var(--fs-10);
  line-height: 16px;
  letter-spacing: var(--ls-wider);
  text-transform: uppercase;
  color: var(--c-ink-2);
}
.pdp-tech__row.is-open .pdp-tech__head { color: var(--c-ink); }
.pdp-tech__head .caret {
  width: 11px; height: 11px; position: relative;
  border: 0; transform: none;
}
.pdp-tech__head .caret::before,
.pdp-tech__head .caret::after {
  content: ''; position: absolute; left: 0; right: 0; top: 50%;
  height: 1px; background: currentColor; transform: translateY(-50%);
}
.pdp-tech__head .caret::after { transform: translateY(-50%) rotate(90deg); transition: opacity var(--tr); }
.pdp-tech__row.is-open .pdp-tech__head .caret { transform: none; }
.pdp-tech__row.is-open .pdp-tech__head .caret::after { opacity: 0; }

.pdp-tech__body { padding-bottom: var(--sp-5); }
.pdp-tech__para { font-weight: var(--fw-light); line-height: var(--lh-loose); }
.pdp-tech__swatches { display: flex; flex-wrap: wrap; gap: var(--sp-4) var(--sp-5); }
.pdp-tech__swatch { width: 86px; }
.pdp-tech__sw {
  display: block; width: 100%; height: 46px; border-radius: var(--r-2);
  background: var(--c-bg-sunk);
  border: 1px solid rgba(1, 1, 1, 0.06);
}
.pdp-tech__sw--img { height: auto; padding: 0; overflow: hidden; background: none; }
.pdp-tech__sw--img img { width: 100%; height: 46px; object-fit: cover; display: block; }
.pdp-tech__swl { margin-top: var(--sp-2); font-size: var(--fs-12); font-weight: var(--fw-light); }
.pdp-tech__swc {
  font-size: var(--fs-10); color: var(--c-muted-2); letter-spacing: var(--ls-wide);
}

/* ---- 通用：横向滑动条的箭头（compose / upsell / edit 共用）---- */
.strip-nav {
  display: inline-flex; align-items: center; justify-content: center;
  width: 30px; height: 30px; flex: none;
  border: 1px solid var(--c-line); border-radius: var(--r-2);
  background: transparent; color: var(--c-ink);
  transition: border-color var(--tr), opacity var(--tr);
}
.strip-nav:hover { border-color: var(--c-ink); }
.strip-nav.is-off { opacity: 0; pointer-events: none; }
.strip-nav svg { width: 16px; height: 16px; }

/* ---- 配置方式：图册（缩略列 + 主图）+ 首行缩进的大字 ----
   照原站 vib-inspirational-book-section：
     content  = grid 7fr 1fr 4fr（中间那 1fr 是空列，原站的呼吸感就在这儿）
     book     = grid 4fr 8fr（缩略图列 / 主图）
   原站正文用的是 h3 + vib-text--xl + vib-heading--indent，缩进 2.5rem。 */
.blk-compose__inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--sp-7);
  margin-top: var(--sp-7);
}
@media (min-width: 1024px) {
  .blk-compose__inner { grid-template-columns: 7fr 1fr 4fr; gap: var(--sp-5); }
  .blk-compose__book { grid-column: 1; }
  .blk-compose__text { grid-column: 3; }
}

/* 图册：缩略图列 + 主图 */
.blk-compose__book { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--sp-5); min-width: 0; }
@media (min-width: 768px) {
  .blk-compose__book { grid-template-columns: 4fr 8fr; }
}
.blk-compose__thumbs {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--sp-4);
  align-content: start;
}
@media (min-width: 768px) {
  .blk-compose__thumbs { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.blk-compose__thumb {
  padding: 0; margin: 0; cursor: pointer;
  border: 1px solid transparent; background: var(--c-bg-alt);
  aspect-ratio: 16 / 9; overflow: hidden;   /* 2026-09-28 原 4:3（不在三档）*/
  transition: border-color var(--tr), opacity var(--tr);
  opacity: 0.72;
}
.blk-compose__thumb:hover { opacity: 1; }
.blk-compose__thumb.is-active { border-color: var(--c-ink); opacity: 1; }
.blk-compose__thumb-img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* 主图舞台：切换时淡入（原站用 swiper 的 fade 模式做的同一件事）。
   ⚠ 比例必须挂在**舞台**上，不能挂在每张 slide 上：
   四张 slide 原来叠在同一个网格格里，行高取四者的最大值 —— 一张 3:4 就把整个
   主图区撑到 684px，其余三张只有 289~385px，下面空一大块（实测过）。
   现在舞台定比例、slide 绝对定位铺满，整册高度一致，翻页也不跳。 */
.blk-compose__stage {
  position: relative;
  min-width: 0; width: 100%;
  overflow: hidden;
  background: var(--c-bg-alt);
}
/* 2026-09-28：档位由 5 个并为 3 个（余总点头）。
   `book`/`tall` 原本都是 3:4、`normal`/`wide` 原本都是 16:9 ⇒ 删掉 `tall`/`wide` 两档。
   真源 = data/schema.json → optionSets.tileSpan；全站 26 个图册块的 ratio 值 100% 是 `book`，
   所以本次并档**零数据迁移**。旧值兜底在 assets/js/render.js 的 RATIO_ALIAS（tall→book、wide→normal）。 */
.blk-compose__stage.is-book   { aspect-ratio: 3 / 4; }   /* 3:4 竖幅（余总 09-28 更正的竖版比例；原 4:5）*/
.blk-compose__stage.is-normal { aspect-ratio: 16 / 9; }  /* 16:9 横幅（2026-09-28 原 4:3，不在三档）*/
.blk-compose__stage.is-big    { aspect-ratio: 1 / 1; }   /* 1:1 方形 */
.blk-compose__slide {
  position: absolute; inset: 0;
  margin: 0; overflow: hidden;
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity var(--tr), visibility var(--tr);
}
.blk-compose__slide.is-active { opacity: 1; visibility: visible; pointer-events: auto; }
.blk-compose__img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* 右栏文字：首行缩进的大字（第一条是引导句，字号照原站的 XL，其余降一档） */
.blk-compose__text { min-width: 0; }
.blk-compose__para {
  margin: 0 0 var(--sp-5);
  font-size: var(--fs-22);
  font-weight: var(--fw-light);
  line-height: 1.5;
  color: var(--c-ink);
  text-indent: 2.5rem;
}
.blk-compose__para--lead { font-size: clamp(22px, 2.1vw, 35px); line-height: 1.35; }
.blk-compose__num { font-size: var(--fs-12); letter-spacing: var(--ls-wide); color: var(--c-muted); }
.blk-compose__t { font-weight: var(--fw-normal); }

/* ---- 工具盒明细：标题组 11fr 1fr + 一屏 3 张的卡片滑块 ----
   照原站 vib-collection-features-section：
     __top    = grid 11fr 1fr（标题组 / 箭头，箭头 align-self:flex-end 贴底右）
     __header = grid 1fr 10fr（大写小标题 / 大标题，同一行左对齐）
     __slide  = 4/12（一屏 3 张，gap 16px）
   卡片是 .vib-card 的 column-reverse 效果：媒体在上、文字在下。 */
.blk-compositions__top {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--sp-4);
  margin-bottom: var(--sp-6);
}
.blk-compositions__title { margin: 0; }
.blk-compositions__nav { display: flex; gap: var(--sp-2); }
.blk-compositions__intro { max-width: var(--measure); margin-bottom: var(--sp-6); }
@media (min-width: 768px) {
  .blk-compositions__top {
    grid-template-columns: 11fr 1fr;
    gap: var(--sp-5);
    margin-bottom: var(--sp-8);
  }
  .blk-compositions__head { display: grid; grid-template-columns: 1fr 10fr; gap: var(--sp-5); align-items: start; }
  .blk-compositions__nav { align-self: end; justify-self: end; }
}
.blk-compositions__track {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 78%;
  gap: 16px;
  overflow-x: auto; overflow-y: hidden;
  scroll-snap-type: x proximity;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
}
.blk-compositions__track::-webkit-scrollbar { display: none; }
@media (min-width: 1024px) {
  /* 原站 slide--cols-4：4/12 宽，扣掉自己那份 gap */
  .blk-compositions__track { grid-auto-columns: calc(100% * 4 / 12 - 16px * 8 / 12); }
}
.blk-toolbox {
  display: flex; flex-direction: column; gap: var(--sp-5);
  min-width: 0; scroll-snap-align: start;
}
.blk-toolbox__dias {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-4);
}
.blk-toolbox__body { display: flex; flex-direction: column; gap: var(--sp-3); }
.blk-toolbox__code { margin: 0; }
.blk-toolbox__name { margin: 0; font-size: var(--fs-22); font-weight: var(--fw-light); letter-spacing: var(--ls-tight); }
.blk-toolbox__specs { margin: 0; display: grid; gap: 4px; }
.blk-toolbox__spec { display: flex; gap: var(--sp-3); font-size: var(--fs-12); color: var(--c-ink-2); }
.blk-toolbox__spec dt, .blk-toolbox__spec dd { margin: 0; font-weight: var(--fw-light); }
.blk-toolbox__spec dt { color: var(--c-muted); text-transform: uppercase; letter-spacing: var(--ls-wide); }
.blk-toolbox__cap { margin: 0; font-size: var(--fs-12); color: var(--c-muted); }
.blk-toolbox__dia {
  margin: 0; aspect-ratio: 16 / 9; overflow: hidden;   /* 2026-09-28 原 4:3（不在三档）*/
  background: var(--c-bg-alt);
  display: grid; place-items: center;
}
/* 线稿是 SVG，用 contain 保证不被裁掉 */
.blk-toolbox__img { width: 100%; height: 100%; object-fit: contain; display: block; padding: 6px; }

/* ---- 图文特性（横滑）：原站 vib-collection-features-section（2026-09-20 照 Knit 新增）----
   真值（_docs/系列页参照真值_Knit.md §4）：
     __top    grid 11fr 1fr，箭头 align-self:flex-end / justify-self:flex-end 在右格
     __header grid 1fr 10fr（大写小标题 / 段标题，__subtitle 用 xl 字号 @1024）
     __slide  cols-3 → 3/12、cols-4 → 4/12、cols-5 → 5/12（各扣掉自己那份 16px gap）
   卡是「图在上、标题+说明在下」的裸卡，与 edit/upsell 同源，不额外加边框按钮。 */
.blk-features__top {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--sp-4);
  margin-bottom: var(--sp-6);
}
.blk-features__subtitle { margin: 0; }
.blk-features__nav { display: flex; gap: var(--sp-2); }
.blk-features__intro { max-width: var(--measure); margin-bottom: var(--sp-6); }
@media (min-width: 768px) {
  .blk-features__top {
    grid-template-columns: 11fr 1fr;
    gap: var(--sp-5);
    margin-bottom: var(--sp-8);
  }
  .blk-features__header {
    display: grid; grid-template-columns: 1fr 10fr; gap: var(--sp-5); align-items: start;
  }
  .blk-features__nav { align-self: end; justify-self: end; }
}
@media (min-width: 1024px) {
  .blk-features__subtitle { font-size: var(--fs-22); }
}
.blk-features__track {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 78%;
  gap: 16px;
  overflow-x: auto; overflow-y: hidden;
  scroll-snap-type: x proximity;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
}
.blk-features__track::-webkit-scrollbar { display: none; }
@media (min-width: 1024px) {
  /* 默认 cols-4（4/12 扣 gap）；data-cols 覆盖成 3 或 5 */
  .blk-features__track { grid-auto-columns: calc(100% * 4 / 12 - 16px * 8 / 12); }
  .blk-features[data-cols="3"] .blk-features__track { grid-auto-columns: calc(100% * 3 / 12 - 16px * 9 / 12); }
  .blk-features[data-cols="5"] .blk-features__track { grid-auto-columns: calc(100% * 5 / 12 - 16px * 7 / 12); }
}
.blk-features__card {
  display: flex; flex-direction: column; gap: var(--sp-4);
  min-width: 0; scroll-snap-align: start;
}
.blk-features__media {
  margin: 0; aspect-ratio: 3 / 4; overflow: hidden; background: var(--c-bg-alt);
}
.blk-features__media img { width: 100%; height: 100%; object-fit: cover; display: block; }
.blk-features__body { display: flex; flex-direction: column; gap: var(--sp-2); }
.blk-features__t { margin: 0; font-size: var(--fs-16); font-weight: var(--fw-normal); }
.blk-features__d { margin: 0; font-size: var(--fs-12); color: var(--c-ink-2); line-height: 1.6; }

/* ---- 搭配（完成氛围）：内容整体让到第 2 列 ----
   照原站 vib-upselling-section：容器是 grid 4fr 8fr（≥1280 变 1fr 1fr），
   **标题组与滑块都放 grid-column:2** —— 也就是说左半边整片留白，
   和「编辑推荐」向右缩进 33% 是同一套手法。滑块一屏 2 张（50% - 8px）。 */
.blk-upsell__grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--sp-7); }
.blk-upsell__head {
  display: flex; align-items: flex-start; justify-content: space-between; gap: var(--sp-5);
}
.blk-upsell__nav { display: flex; gap: var(--sp-2); flex: none; }
@media (min-width: 1024px) {
  .blk-upsell__grid { grid-template-columns: 4fr 8fr; column-gap: var(--sp-5); }
  .blk-upsell__head, .blk-upsell__track { grid-column: 2; }
}
@media (min-width: 1280px) {
  .blk-upsell__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.blk-upsell__track {
  display: grid; grid-auto-flow: column;
  grid-auto-columns: calc(50% - 8px);
  gap: 16px;
  overflow-x: auto; overflow-y: hidden;
  scroll-snap-type: x proximity;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
}
.blk-upsell__track::-webkit-scrollbar { display: none; }
.blk-upsell__card {
  position: relative; scroll-snap-align: start; min-width: 0;
}
.blk-upsell__media { aspect-ratio: 3 / 4; overflow: hidden; background: var(--c-bg-alt); }  /* 2026-09-28 原 4:5 → 3:4 */
.blk-upsell__media img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  transition: transform var(--tr-slow);
}
.blk-upsell__card:hover .blk-upsell__media img { transform: scale(1.02); }
.blk-upsell__body { padding-top: var(--sp-3); }
.blk-upsell__t { font-size: var(--fs-18); font-weight: var(--fw-light); letter-spacing: var(--ls-tight); }
.blk-upsell__k {
  margin-top: var(--sp-1);
  font-size: var(--fs-10); letter-spacing: var(--ls-wider);
  text-transform: uppercase; color: var(--c-muted);
}

/* ---- 设计师引言：大写小标题 + 首行缩进的大字，整块左对齐 ----
   照原站 vib-meet-the-designer：header 一行小标题，下面 h4 用
   vib-heading--xl + vib-heading--indent（缩进 2.5rem）。
   ★ 这块**不要居中** —— 原站是左对齐的编辑体。 */
.blk-designer { padding-block: var(--sec-pad); }
.blk-designer__eyebrow { margin: 0 0 var(--sp-5); }
.blk-designer__quote { margin: 0; }
.blk-designer__text {
  margin: 0;
  max-width: 24em;
  font-size: clamp(22px, 2.1vw, 35px);
  font-weight: var(--fw-light);
  line-height: 1.35;
  letter-spacing: var(--ls-tight);
  color: var(--c-ink);
  text-indent: 2.5rem;
}
.blk-designer__by {
  margin-top: var(--sp-6);
  display: flex; align-items: baseline; gap: var(--sp-3);
  font-size: var(--fs-14); color: var(--c-muted);
}
.blk-designer__by cite { font-style: normal; }
.blk-designer__role { letter-spacing: var(--ls-wide); }

/* ---- 编辑推荐：容器右移 1/3，标题行 2fr 5fr 1fr，一屏 2 张卡 ----
   照原站 vib-collection-the-edit：
     .vib-container        padding-left: calc(33.33% - 8px)  ← 整块向右让开 1/3
     __header              grid 2fr 5fr 1fr，baseline 对齐，link 靠右
     swiper-slide          50% - 8px           ← 一屏 2 张
     .vib-slider-arrows    绝对定位到右上角 */
.blk-edit__grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--sp-6); }
.blk-edit__head { display: flex; flex-direction: column; gap: var(--sp-4); }
.blk-edit__headtext > * + * { margin-top: var(--sp-3); }
.blk-edit__title { margin: 0; }
.blk-edit__nav { display: flex; align-items: center; gap: var(--sp-3); }
/* 「1 / 5」这行计数：原站全局把它做成视觉隐藏（.vib-slider-navigation__pages 走
   clip 那一套），只留给读屏软件，画面上只有箭头。
   我们照着隐藏，但**保留在 DOM 里** —— render.js 的滑块每次滚动都会更新它。 */
.blk-edit__count {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
@media (min-width: 768px) {
  .blk-edit__grid {
    grid-template-columns: 2fr 5fr 1fr;
    column-gap: var(--sp-5);
    align-items: baseline;
    /* 原站就是这条：整块向右缩进 1/3 */
    padding-left: calc(33.33% - 8px);
  }
  /* header 用 display:contents 把三个孩子直接交给外层网格，
     于是小标题落在第 1 列、标题落在第 2 列、箭头落在第 3 列，和原站一致 */
  .blk-edit__head { display: contents; }
  .blk-edit__nav { justify-self: end; }
  .blk-edit__track { grid-column: 1 / -1; }
}
.blk-edit__track {
  display: grid; grid-auto-flow: column;
  grid-auto-columns: 100%;
  gap: 16px;
  overflow-x: auto; overflow-y: hidden;
  scroll-snap-type: x proximity;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
}
.blk-edit__track::-webkit-scrollbar { display: none; }
@media (min-width: 768px) {
  .blk-edit__track { grid-auto-columns: calc(50% - 8px); }
}
.blk-edit__card { position: relative; scroll-snap-align: start; min-width: 0; }
/* 卡内文字宽度收到 75%（原站 .vib-collection-edit-card__content-wrapper） */
@media (min-width: 425px) {
  .blk-edit__body { padding-right: calc(25% - 8px); }
}
.blk-edit__media { aspect-ratio: 3 / 4; overflow: hidden; background: var(--c-bg-alt); }
.blk-edit__media img {
  width: 100%; height: 100%; object-fit: cover; display: block;
  transition: transform var(--tr-slow);
}
.blk-edit__card:hover .blk-edit__media img { transform: scale(1.02); }
.blk-edit__body { padding-top: var(--sp-4); }
.blk-edit__k { margin-bottom: var(--sp-2); }
.blk-edit__t { font-size: var(--fs-16); font-weight: var(--fw-light); line-height: 1.45; }
.blk-edit__d { margin-top: var(--sp-2); font-size: var(--fs-12); color: var(--c-muted); line-height: 1.7; }

/* ---- 支持服务带：整幅照片 + 左上定义列表 + 居中大字 ---- */
.blk-support {
  position: relative;
  overflow: hidden;
  background: var(--c-dark-bg);
  padding-block: var(--sec-pad);
  color: #fff;
}
.blk-support__media { position: absolute; inset: 0; }
.blk-support__media img { width: 100%; height: 100%; object-fit: cover; display: block; }
.blk-support__scrim {
  position: absolute; inset: 0;
  /* 遮罩要按「最亮的照片」来定，不能按示例图的暗度来定 ——
     示例图是浅米的占位图（亮度 ~230），早先中间只有 26% 黑，
     结果白字底下实测亮度 101，白字压不住。
     白字要读得清，底下亮度得 ≤80：α ≥ 1 − 80/230 ≈ 0.65。
     所以中段取 0.68，上下再深一点，客户换成任意照片都不会翻车。 */
  background: linear-gradient(180deg,
    rgba(8, 7, 6, 0.74) 0%, rgba(8, 7, 6, 0.68) 45%, rgba(8, 7, 6, 0.84) 100%);
}
.blk-support__inner {
  position: relative;
  display: grid;
  grid-template-rows: auto 1fr auto;
  gap: var(--sp-8);
  padding-block: clamp(56px, 8vw, 130px);
  min-height: clamp(340px, 44vw, 640px);
}
.blk-support__defs {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-7) clamp(24px, 4vw, 72px);
  max-width: 820px;
  margin: 0;
}
@media (max-width: 700px) { .blk-support__defs { grid-template-columns: minmax(0, 1fr); } }
.blk-support__def dt {
  font-size: var(--fs-10); letter-spacing: var(--ls-wider);
  text-transform: uppercase; color: rgba(255, 255, 255, 0.62);
}
.blk-support__def dd {
  margin: var(--sp-2) 0 0; max-width: 36ch;
  font-size: var(--fs-12); line-height: 1.7; color: rgba(255, 255, 255, 0.9);
}
.blk-support__headline {
  align-self: center; text-align: center;
  font-size: clamp(24px, 3vw, 46px);
  font-weight: var(--fw-light);
  line-height: 1.3;
  letter-spacing: var(--ls-tight);
}
.blk-support__text {
  margin: 0; text-align: center;
  font-size: var(--fs-12); color: rgba(255, 255, 255, 0.78);
}

/* ---- 常见问题：大字标题 + 中间一栏问答 + 底部小字 ---- */
.blk-faq__title {
  font-size: clamp(24px, 2.6vw, 42px);
  font-weight: var(--fw-light);
  letter-spacing: var(--ls-tight);
}
.blk-faq__grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--sp-5) var(--sp-6);
  margin-top: var(--sp-7);
}
/* 问答列落在**中间那一栏** —— 原站就是这样偏中的，右侧留白 */
.blk-faq__col { grid-column: 2; }
@media (max-width: 900px) {
  .blk-faq__grid { grid-template-columns: minmax(0, 1fr); }
  .blk-faq__col { grid-column: 1; }
}
.blk-faq__list .acc__head { padding: var(--sp-3) 0; font-size: var(--fs-14); font-weight: var(--fw-light); }
.blk-faq__a { font-weight: var(--fw-light); line-height: var(--lh-loose); }
.blk-faq__a a { text-decoration: underline; text-underline-offset: 3px; }
.blk-faq__disclaimer { margin-top: var(--sp-6); font-size: var(--fs-12); color: var(--c-muted); }

/* ================================================================
   【已作废】版块标题居中（2026-09-19 晚，用户明确取消）
   ----------------------------------------------------------------
   曾在此处把 .head / 陈述句 / 引言块 / CTA / 设计师引言 / 章节抬头
   全部改成居中。用户 2026-09-19 晚指出：这几块与参考站排版不一致，
   并说明「不用再考虑居中对齐那条规则」。
   → 已整段删除，恢复 base.css 里「文字靠左 + 按钮靠右」的原始版式。
   → 参考站的标题层也一律左对齐：见 _docs/_tmp/ref_css_2.txt 的
     .vib-collection-features-section__header（1fr 10fr 左对齐同行）、
     .vib-inspirational-block__header（flex row）、
     .vib-collection-the-edit__header（2fr 5fr 1fr，link 靠右）。
   ★ 别再把它加回来。
   ================================================================ */

/* 工具盒 COMPOSITIONS —— 照原站 vib-inspirational-block：
     标题行 flex row（大写小标题 + 大标题，同一行左对齐）
     → 通栏大图（原站这张图不在容器里，是满宽的）
     → 说明 80% + 按钮 20%（content-row: space-between）
   ★ 不是「左文右图」。 */
.blk-toolboxes__head {
  display: flex; flex-direction: row; align-items: baseline;
  gap: var(--sp-5);
}
.blk-toolboxes__pre { margin: 0; flex: none; }
.blk-toolboxes__title { margin: 0; }
@media (max-width: 424px) {
  .blk-toolboxes__head { flex-direction: column; gap: var(--sp-3); }
}
.blk-toolboxes__hero { margin: var(--sp-7) 0 0; overflow: hidden; }
/* 原站这里是 width:100% + height:auto（走图片自然宽高比）。我们的占位图是竖幅，
   满宽时会撑成两千多像素高的一面墙，所以给一道高度上限并按 cover 裁。
   换成原站那种宽幅照片后，自然高度本来就在上限之内 —— 裁不到。 */
.blk-toolboxes__img {
  width: 100%; height: auto;
  max-height: min(76vh, 820px);
  object-fit: cover; display: block;
}
/* 图注要跟正文一个左边线：满宽图外面补容器宽度 */
.blk-toolboxes__cap {
  max-width: var(--container);
  margin: var(--sp-4) auto 0;
  padding-inline: var(--gutter);
  font-size: var(--fs-12); color: var(--c-muted-ink);
}
/* 图册（参考站 .vib-inspirational-block__gallery--left-aligned）：
   实测 flex-wrap + align-items:flex-start + justify-content:flex-start，
   每块 flex:1 1 min(340px,100%)、max-width:340px —— 就是「左对齐的小图册」。
   ★ 参考站同一个版块有两个变体：单图走 hero-wrapper（满宽），多图走这个左对齐图册。
     我们两个都留着：后台放了 gallery 就出图册，只放一张概括图就出满宽大图。 */
.blk-toolboxes__gallery {
  display: flex; flex-wrap: wrap;
  align-items: flex-start; justify-content: flex-start;
  gap: var(--sp-4);
  margin-top: var(--sp-5);
}
.blk-toolboxes__gal {
  flex: 1 1 min(340px, 100%);
  max-width: 340px;
  display: flex; flex-direction: column;
  margin: 0;
}
.blk-toolboxes__galimg {
  width: 100%; max-width: 100%; height: auto;
  object-fit: cover; display: block;
}

.blk-toolboxes__row {
  display: flex; justify-content: space-between; align-items: flex-start;
  gap: var(--sp-5);
  margin-top: var(--sp-6);
}
/* 原站这里就是硬 80% —— 必须把 base.css 的 max-width: var(--measure) 关掉，
   否则 80% 会被压到 ~52%，右边那 20% 的按钮位也就跟着错了。 */
.blk-toolboxes__body { width: 80%; max-width: none; margin: 0; }
.blk-toolboxes__cta { width: 20%; flex: none; display: flex; justify-content: flex-end; align-items: flex-start; }
@media (max-width: 768px) {
  .blk-toolboxes__row { flex-direction: column; gap: var(--sp-4); }
  .blk-toolboxes__body, .blk-toolboxes__cta { width: 100%; }
  .blk-toolboxes__cta { justify-content: flex-start; }
}

/* ================================================================
   空间案例（spaces.html 列表 + space.html 内页）
   架构图：列表 = 空间实景大图卡片（项目名 + 空间类型）；
          内页 = 连续大图流 + 图文穿插 + 关联灯具 + 询价。
   ================================================================ */

/* ---- 列表卡片：大图占主体，图下只有两行 ---- */
.card--space .card__body { gap: 6px; }
.card--space .card__title { font-size: var(--fs-18); font-weight: var(--fw-light); letter-spacing: var(--ls-tight); }
.card--space .card__meta { font-size: var(--fs-11); letter-spacing: var(--ls-wider); text-transform: uppercase; }

/* ---- 内页 Hero：大图铺满，图上只有「类型 / 项目名 / 一句话 / 项目信息」 ---- */
.sp-hero {
  position: relative;
  height: min(78svh, 820px);
  min-height: 420px;
  display: flex;
  align-items: flex-end;
  overflow: hidden;
  background: #23211E;
  color: #fff;
}
.sp-hero__media { position: absolute; inset: 0; }
.sp-hero__media img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* 由下往上的暗色渐层：浅底图也能压住白字（对照度实测在 check_spaces.py 里） */
.sp-hero::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(to top, rgba(0,0,0,.58), rgba(0,0,0,.20) 48%, rgba(0,0,0,.10));
}
.sp-hero__inner { position: relative; z-index: 1; padding-bottom: clamp(40px, 6vw, 76px); }
.sp-hero__eyebrow { color: rgba(255,255,255,.78); }
.sp-hero__title {
  margin-top: var(--sp-3);
  max-width: 22ch;   /* 字号/行高/字重见「页面主标题」统一块 */
}
.sp-hero__text {
  margin-top: var(--sp-4);
  max-width: 46ch;
  font-size: var(--fs-16);
  line-height: 1.7;
  color: rgba(255,255,255,.88);
}
.sp-hero__meta {
  margin-top: var(--sp-6);
  display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-7);
  font-size: var(--fs-12); letter-spacing: var(--ls-wide);
  color: rgba(255,255,255,.72);
}
.sp-hero__meta span { position: relative; }
.sp-hero__meta span + span::before {
  content: ""; position: absolute; left: -14px; top: 50%;
  width: 3px; height: 3px; border-radius: 50%;
  background: currentColor; opacity: .6; transform: translateY(-50%);
}

/* 浅底分支：量到案例图在文字底下那一块很亮（HM.heroInk 加的 .is-light）——
   这时白字只有 1.6 的对比，等于看不见。换深字，并把渐层反过来（用浅色压亮图）。
   底色也要跟着变浅：万一图加载失败，深字不能落在深底上。 */
.sp-hero.is-light { color: var(--c-ink); background: var(--c-bg-alt); }
.sp-hero.is-light::after {
  background: linear-gradient(to top, rgba(255, 255, 255, 0.86),
                                     rgba(255, 255, 255, 0.42) 52%,
                                     rgba(255, 255, 255, 0.08));
}
.sp-hero.is-light .sp-hero__eyebrow { color: var(--c-muted); }
.sp-hero.is-light .sp-hero__text,
.sp-hero.is-light .sp-hero__meta { color: var(--c-ink-2); }

/* ---- 正文：连续大图流，图文穿插 ---- */
.sp-body { padding-block: clamp(40px, 6vw, 96px) 0; }
.sp-flow { display: grid; gap: clamp(28px, 4.5vw, 76px); }
.sp-shot { margin: 0; }
.sp-shot__box { overflow: hidden; background: var(--c-bg-alt); }
.sp-shot__box img { width: 100%; height: 100%; object-fit: cover; display: block; }
.sp-shot__cap { margin-top: var(--sp-3); font-size: var(--fs-12); color: var(--c-muted); }

/* 图文穿插：左图右文（窄屏改成上下） */
.sp-story {
  display: grid;
  grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
  gap: clamp(24px, 3.5vw, 64px);
  align-items: center;
}
@media (max-width: 900px) { .sp-story { grid-template-columns: minmax(0, 1fr); gap: var(--sp-5); } }
.sp-story__media { margin: 0; }
.sp-story__media .sp-shot__box { overflow: hidden; background: var(--c-bg-alt); }
.sp-story__media img { width: 100%; height: 100%; object-fit: cover; display: block; }
.sp-story__text .body-text { margin-top: var(--sp-4); max-width: 42ch; }

.sp-back { margin-top: clamp(36px, 5vw, 72px); }

/* 案例分类切换（从导航二级 ?type= 进来时出现） */
.sp-tabs {
  display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-6);
  margin: calc(var(--sp-6) * -1) 0 var(--sp-7);
}
.sp-tabs__i {
  font-size: var(--fs-12); letter-spacing: var(--ls-wide);
  color: var(--c-muted); padding-bottom: 4px;
  border-bottom: 1px solid transparent;
  transition: color var(--tr), border-color var(--tr);
}
.sp-tabs__i:hover { color: var(--c-ink); }
.sp-tabs__i.is-on { color: var(--c-ink); border-bottom-color: var(--c-ink); }
.sp-tabs__i--all { margin-left: auto; }


/* ================================================================
   内页首屏（简版）—— .ph
   原站 vib-simple-hero / vib-inner-page-hero：只承载「我在哪一页」，
   比首页矮一截。compact = 纯文字带；tall = 半屏带图（白字压图，
   字色由 HM.heroInk 自动判深浅）。2026-09-20
   ================================================================ */
.ph { padding: 0; }
.ph--compact { padding-block: clamp(64px, 8vw, 132px) 0; }
.ph--tall {
  position: relative;
  min-height: min(52svh, 560px);
  display: flex;
  align-items: flex-end;
  overflow: hidden;
  background: var(--c-bg-alt);
  color: #fff;
}
.ph--tall .ph__media { position: absolute; inset: 0; margin: 0; }
.ph--tall .ph__media img { width: 100%; height: 100%; object-fit: cover; display: block; }
.ph--tall::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(to top, rgba(0,0,0,.46), rgba(0,0,0,.12) 58%, rgba(0,0,0,.04));
}
.ph--tall .ph__inner { position: relative; z-index: 1; padding-bottom: clamp(36px, 5vw, 68px); }
.ph__eyebrow { margin-bottom: var(--sp-3); }
.ph__title {
  max-width: 24ch;   /* 字号/行高/字重见「页面主标题」统一块 */
}
.ph__text { margin-top: var(--sp-4); max-width: 52ch; font-size: var(--fs-16); line-height: 1.7; }
/* 浅底分支（HM.heroInk 加的 .is-light）：亮图换深字 + 浅渐层 */
.ph--tall.is-light { color: var(--c-ink); }
.ph--tall.is-light::after {
  background: linear-gradient(to top, rgba(255,255,255,.84), rgba(255,255,255,.36) 56%, rgba(255,255,255,.06));
}
.ph--tall.is-light .ph__eyebrow { color: var(--c-muted); }
.ph--tall.is-light .ph__text { color: var(--c-ink-2); }
/* 纯文字带也要跟着标准版式走 */
.ph--compact .ph__title { max-width: 28ch; }
.ph--compact .ph__text { color: var(--c-ink-2); }

/* ================================================================
   通用信息表 —— .blk-info
   原站 vib-general-info：左栏「标题 + 备注」、右栏「项目 / 内容」逐行，
   行间用 1px 细线分。用于技术信息、认证、包装这类清单。
   ================================================================ */
.blk-info__inner {
  display: grid;
  grid-template-columns: minmax(0, 0.72fr) minmax(0, 1.28fr);
  gap: clamp(24px, 4vw, 88px);
  align-items: start;
}
@media (max-width: 900px) { .blk-info__inner { grid-template-columns: minmax(0, 1fr); gap: var(--sp-6); } }
.blk-info__note { margin-top: var(--sp-4); max-width: 34ch; font-size: var(--fs-13); color: var(--c-muted); line-height: 1.7; }
.blk-info__rows { margin: 0; border-top: 1px solid var(--c-line); }
.blk-info__row {
  display: grid;
  grid-template-columns: minmax(96px, 0.32fr) minmax(0, 1fr);
  gap: var(--sp-5);
  padding: var(--sp-4) 0;
  border-bottom: 1px solid var(--c-line);
}
.blk-info__row dt { margin: 0; font-size: var(--fs-12); color: var(--c-muted); letter-spacing: var(--ls-wide); }
.blk-info__row dd { margin: 0; font-size: var(--fs-14); line-height: 1.65; }

/* ================================================================
   应用场景 —— .blk-apps
   原站 vib-explore-applications：一屏 3 张卡，卡片走全站统一的 .card，
   所以 hover、比例、边框都跟其它卡一致，不另开一套。
   ================================================================ */
.blk-apps__grid { display: grid; gap: var(--sp-6) var(--sp-5); }
.blk-apps--2 .blk-apps__grid { grid-template-columns: repeat(2, minmax(0,1fr)); }
.blk-apps--3 .blk-apps__grid { grid-template-columns: repeat(3, minmax(0,1fr)); }
.blk-apps--4 .blk-apps__grid { grid-template-columns: repeat(4, minmax(0,1fr)); }
@media (max-width: 1100px) {
  .blk-apps--4 .blk-apps__grid { grid-template-columns: repeat(2, minmax(0,1fr)); }
}
@media (max-width: 860px) {
  .blk-apps--2 .blk-apps__grid, .blk-apps--3 .blk-apps__grid, .blk-apps--4 .blk-apps__grid {
    grid-template-columns: repeat(2, minmax(0,1fr));
  }
}


/* 系列页的「安装方式 / 设计师」一行小字（.col-meta）
   这两个字段原先后台能填、前台没人读（真孤儿）。给个克制的去处：横排小字，
   键用浅色小字号、值正常色，中间用细点分隔 —— 不做卡片、不抢版面。 */
.col-meta {
  margin-top: var(--sp-5);
  display: flex; flex-wrap: wrap; gap: var(--sp-3) var(--sp-8);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--c-line);
}
.col-meta__i { display: flex; align-items: baseline; gap: var(--sp-3); }
.col-meta__k {
  font-size: var(--fs-11); letter-spacing: var(--ls-wider);
  text-transform: uppercase; color: var(--c-muted);
}
.col-meta__v { font-size: var(--fs-14); color: var(--c-ink-2); }
@media (max-width: 640px) { .col-meta { flex-direction: column; gap: var(--sp-2); } }

/* ================================================================
   通用弹层 —— .modal / .modal__overlay / .modal__content
   ★ 2026-09-20 新增。用户批注「系列页面里这个弹出层你没有做」——
     参照页 exploreApplications 标题右侧那颗「View models」。
   真值全部来自参照站源码（见 _docs/系列页参照真值_Knit.md 3b 节）：
     .vib-modal__overlay  position:fixed inset0 z-index:1001
                          background: hsla(0,0%,85%,.8)   ← 浅灰 80%
     .vib-modal__content  fixed 居中 translate(-50%,-50%)
                          width:60rem(@768) / max-height:80dvh
                          padding:1.5rem 16px 3rem 16px
     .vib-modal__header   flex 居中一行标题、margin-bottom:16px
     .vib-modal__close    绝对定位右上
     fadeIn/fadeOut 只动 opacity，ease-out 200ms
   ================================================================ */

.modal[hidden] { display: none; }
.modal { position: fixed; inset: 0; z-index: var(--z-modal, 1200); }

/* 遮罩：浅灰 80%（照抄参照站，不是常见的黑 50%）——
   这一站底色是暖白，黑遮罩会显脏，浅灰压得住又不闷。 */
.modal__overlay {
  position: absolute; inset: 0;
  background: hsla(0, 0%, 85%, 0.8);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
  opacity: 0;
  transition: opacity var(--tr);
}
.modal.is-open .modal__overlay { opacity: 1; }

/* 面板：fixed 居中。参照站是 position:fixed + translate(-50%,-50%) */
/* --- 弹层版式骨架：grid 两行（照 vib-collection-modal）--- */
/* 参照页：display:grid; grid-template-rows:auto 1fr; padding:0
   第 1 行=header（auto），第 2 行=body（1fr，滚动在这里发生）。
   骨架用 grid 而不是 flex 是有原因的：1fr 行会把 body 精确压到剩余高度，
   body 上再挂 overflow:auto 就必然滚；flex 那版 body 没 overflow，
   内容直接溢出盒子、视觉上被切一半（探针量到 body.scrollH 683 > clientH 608）。 */
.modal__content {
  position: fixed; top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  width: 100%;
  max-width: calc(100dvw - var(--sp-4) * 2);
  max-height: calc(100dvh - var(--sp-4) * 2);
  display: grid;
  grid-template-columns: 1fr;
  grid-template-rows: auto 1fr;
  background: var(--c-bg);
  padding: 0;
  overflow: hidden;
  opacity: 0;
  transition: opacity var(--tr);
}
@media (min-width: 768px) {
  .modal__content[data-size="sm"] { width: 25rem; }     /* 400 */
  .modal__content[data-size="md"] { width: 43.75rem; }  /* 700 */
  .modal__content[data-size="lg"] { width: 60rem; }     /* 960 ← 型号弹层用这个 */
  .modal__content { max-height: 80dvh; }
}
.modal.is-open .modal__content { opacity: 1; }

.modal__header {
  position: relative;
  width: 100%;
  display: flex; align-items: center; justify-content: center;
  grid-row: 1;
  /* padding 从 content 搬到 header（照参照页 padding:sp-md sp-xl 0）——
     content 的 padding 归零后，留白必须由 header 自己给，否则标题贴边。
     ★ 下留白用 padding-bottom 而**不是** margin-bottom：
       grid 的 auto 行会把 margin 也算进行高，多出一条 15px 幽灵留白带
       （探针：行高 57.59px 而 header 自身只 42px）。 */
  padding: 24px var(--sp-4) var(--sp-4);
  min-height: 24px;
  /* ★ v5：标题删掉后，头部的切换链靠左、关窗按钮仍在右侧（.modal__close 是 absolute）。
     原 justify-content:center 是给居中标题用的，撤在这里由子元素自己定位。 */
  justify-content: flex-start;
}
.modal__title {
  font-size: var(--fs-11);
  font-weight: var(--fw-normal);
  letter-spacing: var(--ls-wider);
  text-transform: uppercase;
  color: var(--c-ink);
  text-align: center;
}
.modal__close {
  position: absolute; right: 0; top: 50%;
  transform: translateY(-50%);
  z-index: 2;
  width: 24px; height: 24px;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--c-ink-2);
  transition: color var(--tr);
}
.modal__close:hover { color: var(--c-ink); }
.modal__close svg { width: 14px; height: 14px; display: block; }

/* 滚动的责任人在 body 身上 —— 骨架是 grid 1fr 行，body 撑满余高后自己滚。
   少了这句 overflow:auto，长内容会溢出盒子被切（本 bug 的直接成因）。 */
.modal__body { grid-row: 2; min-height: 0; overflow: auto; padding: 0 var(--sp-4) var(--sp-7); }

/* 面板里若嵌了内容（比如型号卡栅格），去掉最外层的顶边距 */
.modal__body > *:first-child { margin-top: 0; }

@media (max-width: 640px) {
  .modal__content { padding: 0; }
  .modal__header { padding: 20px var(--sp-4) var(--sp-3); }
  .modal__body { padding: 0 var(--sp-4) var(--sp-6); }
}


/* ---- 单品页「查看型号」弹层（2026-09-21 照参考站 Alba 的 View models 新增）----
   宽度取参考站**实测**值：.vib-modal__content 在 2560 视口下解析为 960px（= 60rem）。
   ⚠️ 不要照抄系列页型号弹层那套 70.3125vw × 81.8505vh —— 那是用户 2026-09-20 为
      「左列表 + 右详情」两栏式型号选择器单独定的尺寸；这个弹层里只有一张规格表。 */
@media (min-width: 768px) {
  .modal__content--pdp-models { width: 60rem; }
}
.modal__body--pdp-models { padding: 0 var(--sp-4) var(--sp-7); }
/* 规格表上方的型号行（p.sku）—— 和面包屑末项同一个值，给弹层一个上下文锚点。 */
.pdp-models__sku {
  margin: 0 0 var(--sp-5);
  font-size: var(--fs-12);
  letter-spacing: var(--ls-wider);
  text-transform: uppercase;
  color: var(--c-muted-ink);
}

/* ----------------------------------------------------------------
   「View models」文字链 —— 参照页 vib-link--underline
   真值：display:inline-flex / align-items:center
         .vib-link--underline .vib-link__txt { text-decoration:underline;
                                               text-decoration-skip-ink:none }
   ★ 参照页这**是 <button> 不是 <a>**、也**没有图标**
     （带灯泡图标的 models-popup-open-inspirational 是页面顶部那批，不是这颗）。
   ---------------------------------------------------------------- */
.link-underline {
  display: inline-flex; flex-direction: row; align-items: center;
  gap: var(--sp-2);
  font-size: var(--fs-12);
  color: var(--c-ink);
  text-align: center;
  text-decoration: none;
  background: transparent;
  transition: color var(--tr);
}
.link-underline .link-underline__txt {
  text-decoration: underline;
  -webkit-text-decoration-skip-ink: none;
  text-decoration-skip-ink: none;
}
.link-underline:hover { color: var(--c-muted-ink); }

/* .blk-apps 的标题行右侧挂这颗链 —— 区块本身是左缩进的，
   参照页 __header 就是 flex + space-between，所以这里只补「右侧那一格」 */
.blk-apps__head { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-6); }
.blk-apps__head .head { margin-bottom: 0; flex: 1 1 auto; }
.blk-apps__head + .blk-apps__grid { margin-top: var(--sp-7); }
.blk-apps__more { flex: none; }

/* ----------------------------------------------------------------
   系列级「型号」弹层 —— **严格照 `Vibia _ Knit.html` 的 View models 规范**重做
   （2026-09-21 v13，用户逐条拍板；规范原件在 _docs 里有链接）。

   规范实测真值（§3，参考站视口 1355×1250）：
     modal 外宽 1219.5 ≈ 1220px、高 1125px、背景 #FCFBFC、border-radius 0、max-width 锁定
     左栏 .product-main-content   默认宽 0（隐藏），选中型号后 689px
     右栏 .model-selector-panel   默认占满 1219.5，选中型号后收窄 406px
     型号卡 185×260（真站实测）                      选中态 = 2px 近黑描边、不填充
       ↑ 我们**没有**照抄 185×260 —— 它不在三档里，我们用 3/4（见 .model-card__media）
   ★ 1219.5 - 689 - 406 = 124.5 → 两栏之间那段留白不是 gap 摊出来的零头，是真的留白。
     我们按同一比例放成 padding：.models-wrap 左右各 62px → 内容 1096 ≈ 689 + 406。
     ⚠️ 别改成 gap：总量一样，但「留白在两侧」才是规范的样子，两栏会贴到弹层边上。
   ★ 直角（border-radius: 0）：全站其它弹层有 1~4px 圆角，这一个按规范是直角。
   ★ 只改了宽度/结构/选中态这三样（用户明确点的）。字号沿用本弹层自己的 1.4 倍放大
     口径（v5 用户批注过「弹层字体太小、等比加大」），不搬规范里那套 21px 行高 ——
     那是参考站自身字号的产物，硬套会两套尺度打架。
   ---------------------------------------------------------------- */
.modal__content--models,
.modal__content--models[data-size] {
  /* ⚠️ 选择器必须带 [data-size]，否则**压不住**上面那条
     `.modal__content[data-size="lg"] { width: 60rem }` ——
     属性选择器权重 (0,2,0) > 类选择器 (0,1,0)，实测被压回 960px。 */
  /* ★★ v21（2026-09-22）：弹层尺寸 = **视口的 90% × 90%**（用户要求「算弹层占全屏
     尺寸百分比来决定弹层尺寸」→ 那就去量真站的百分比，而不是沿用估出来的数）。
     真站实测（tools/_v21_live_modal.py，三个视口，全部 position:fixed + translate(-50%,-50%)）：
       2560×1405 → 2304.0 × 1264.5   w/vw = 0.9000  h/vh = 0.9000
       1920×1080 → 1728.0 ×  972.0   w/vw = 0.9000  h/vh = 0.9000
       1600× 900 → 1440.0 ×  810.0   w/vw = 0.9000  h/vh = 0.9000
     三个视口**两个方向的比值都精确等于 0.9** —— 这是定比，不是定值，也不是两套比值。
     ⚠️ v19 那版的 70.3125vw × 81.8505vh 来自用户 2026-09-20 口述的「1800*1150」，
        当日实测「Δ=0.0」只证明**我们照他说的做对了**，没证明那个数**本身**对；
        真站是 0.9/0.9 ⇒ 我们一直小了约 22% 宽 / 13% 高（实测 1800×1149.98 = 0.70×0.82）。
     ★ 用 dvw/dvh 而不是 vw/vh：与本文件其余弹层规则（max-width: calc(100dvw …)）同一口径，
       桌面端两者等价，移动端 dvh 能跟着地址栏收放。
     ★ 比值式天然不溢出（0.9 < 1），任何视口下都是同一个观感比例。 */
  width: 90dvw;
  height: 90dvh;
  max-height: none;                    /* 高度已定死，别再被 80dvh 压 */
  border-radius: 0;
}
/* 窄屏退回近全屏：0.9 在桌面够用，但 1280 视口只剩 1152px、平板竖屏只有几百 px，
   两栏挤到左栏不足一行字宽。
   原站移动端形态无从得知（只有桌面录屏，见 _docs/原站型号弹层交互规格 §4），
   这里取「可用性优先」：铺满到留 16px 边。 */
@media (max-width: 1023px) {
  .modal__content--models,
  .modal__content--models[data-size] {
    width: calc(100dvw - var(--sp-4) * 2);
    height: calc(100dvh - var(--sp-4) * 2);
  }
}
.modal__content--models .modal__header {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-6);
  padding: var(--sp-4);
  border-bottom: 1px solid var(--c-line);
  margin-bottom: 0;
}
/* .modal__close 是 absolute + right:0，会贴到 header 的边框上 —— 给它让出内边距 */
.modal__content--models .modal__close { right: var(--sp-4); }

/* ---- 弹层内的滚动条（★ v22：**整条去掉**，只保留滚动能力）----
   演进：v21 先把 Windows 原生条换成有样式的细条（8px / track 透明 / thumb 有色），
   用户 2026-09-22 看完仍要求「弹层滚动条去掉」—— 那就去掉，不留半条。
   ★ 做法是宽度/高度**归零**，不是 display:none：归零时滚动能力照旧（滚轮、触控板、
     键盘、拖拽都在），只是不再画那一条。判据也从「已上样式」改成「已去掉」。
   ⚠️ 仍然**只写 ::-webkit-scrollbar，不写标准属性 scrollbar-width/color**：
      Chromium 里一旦声明标准属性，::-webkit-scrollbar 整套失效，宽度会变成
      我们控制不了的 "thin"（v21 实测踩过这个坑）。
   ★ 左栏**会滚是正常的**：内容本来就是一屏放不下（真站左栏 scrollHeight 3655 /
     clientHeight 1173，我们 2605 / 702~1157）。去掉的是那一条，不是滚动本身。
   ★ 后代选择器 ⇒ 弹层内**所有**可滚区域一起生效（左栏、右侧列表、尺寸筛选浮层），
     不留「一边有一条、另一边没有」的怪相。单品页的规格弹层同款处理。 */
.modal__content--models ::-webkit-scrollbar,
.modal__content--pdp-models ::-webkit-scrollbar { width: 0; height: 0; }

/* 头部那个**静态**「技术信息」标签。
   规范里是 TECHNICAL INFO / DOWNLOADS 两个页签；下载撞定死规则①（前台永不出现下载
   入口），所以只剩一个。用户 2026-09-21 选「照做放一个占位，后面再考虑加什么」。
   ★ 它是 <span> 不是 <button>：只有一个标签、没有可切的目标，做成按钮就是死交互
     （本项目对死按钮有成套检查器）。 */
.models-headtabs { display: flex; align-items: center; gap: var(--sp-5); }
.models-headtab {
  position: relative;
  padding: 0 0 4px;
  font-size: var(--fs-12);
  letter-spacing: var(--ls-wider);
  text-transform: uppercase;
  color: var(--c-muted);
}
.models-headtab.is-current { color: var(--c-ink); }
/* 下划线用 ::after 不用 border-bottom —— border 参与行高计算，会把这一行撑高 1px */
.models-headtab.is-current::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 2px;
  background: currentColor;
}

/* body：纵向列撑满 header 之下 —— 否则内容会把 body 撑出滚动，
   而设计意图是「两侧各栏自己滚」（.models-main / .models-panel__body）。 */
.modal__content--models .modal__body {
  display: flex;
  flex-direction: column;
  min-height: 0;
  padding: 0;
  flex: 1 1 auto;
}

/* --- 两栏容器 --- */
.models-wrap {
  display: flex;
  /* ★ **显式 nowrap** —— 两栏宽度是 %，一旦换行右栏会跑到可视区之外，
     看着就像「右栏没了」（v4b 踩过）。 */
  flex-wrap: nowrap;
  align-items: stretch;
  gap: 0;
  min-height: 0;
  height: 100%;
  padding: var(--sp-5) 62px;           /* 62 = (1219.5 - 689 - 406) / 2，规范那段留白 */
}

/* --- 左栏：选中型号的详情 ---
   ★ 默认宽度 **0**（规范 §5 状态 1：左栏宽 0、是藏着的）。
     点了型号卡才由 modal.js 把 body 的 data-models-open 拨到 1。
   ★ v19（2026-09-22）**开合改成连续宽度过渡** —— 不再 display:none ⇄ flex。
     修前逐帧实测（tools/_v19_probe.py）：左栏 689 → 0 在**两帧之间直接跳**，
     右栏 407 → 1096 同步跳；因为 display 切换时宽度根本不参与过渡。用户 2026-09-22
     的原话就是「我们切换很生硬」。参考站的过渡是 0.3–0.5s ease-out
     （_docs/原站型号弹层交互规格_2026-09-20.md §2），--dur-slow = 420ms 正在区间内。
     ⚠️ 收起态宽度**必须精确 0**：flex-basis 百分比之外若还有 padding，
        盒子至少有 padding 那么宽 → 「宽 > 0」类判据永远绿。所以 padding-right
        只在展开态给（并纳入过渡）。
     ⚠️ min-width 必须是 0：flex 项默认 min-width:auto，会把它撑到内容最小宽度。 */
.models-main {
  flex: 0 0 0%;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
  overflow: hidden;                    /* 收起时把内容裁掉，不许溢到右栏上 */
  padding-right: 0;
  opacity: 0;
  transform: translateX(-18px);        /* 参考站是「向左淡出」，不是原地淡出 */
  /* ★ 0 宽 + opacity:0 的元素**仍然能被 Tab 聚焦**（v18 在 .backtop 上踩过同一个洞）
     → 补 visibility。收起时用 transition-delay 拖到动画结束才隐藏，
     否则内容会在滑动过程中先消失（滑出感就没了）。 */
  visibility: hidden;
  transition: flex-basis var(--dur-slow) var(--ease),
              opacity var(--dur-slow) var(--ease),
              transform var(--dur-slow) var(--ease),
              padding-right var(--dur-slow) var(--ease),
              visibility 0s linear var(--dur-slow);
}
.modal__body[data-models-open="1"] .models-main {
  flex-basis: 62.9%;                   /* 规范 689 / 1096 = 62.9%（与弹层宽无关的比值） */
  padding-right: var(--sp-2);
  opacity: 1;
  transform: none;
  visibility: visible;
  overflow-y: auto;                    /* 滚动责任在左栏（它内容最长） */
  transition-delay: 0s;                /* 展开时立刻可见（覆盖上面给 visibility 的延迟） */
}

/* --- 右栏：型号选择器 ---
   左栏 display:none 时右栏自然拿到全部宽度（= 规范 §2.1 的「全宽 1219.5」）；
   左栏展开后收窄到 37.1%（= 406 / 1096，规范 §2.2）。 */
.models-panel {
  /* ★ v19：`flex: 1 1 0%`（原来 1 1 auto + 一条 [open=1] 覆盖成 `0 0 37.1%`）。
     旧写法里 `auto` → `37.1%` **不可插值**，右栏宽度只能瞬变 —— 这是「生硬」的另一半。
     现在右栏只留一条规则：吃掉左栏剩下的全部（grow 1 / basis 0）。
     左栏 flex-basis 连续变化时右栏自动连续跟随，**不需要给右栏写过渡**。 */
  flex: 1 1 0%;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
}

.models-panel__bar { display: flex; align-items: center; }
/* 形态切换（规范 §5 状态 3）：grid-view ⇄ list-view，纯前端，不重新请求。 */
.models-panel__toggle {
  display: inline-flex; align-items: center; gap: var(--sp-2);
  background: transparent; border: 0; padding: 0;
  font: inherit;
  font-size: var(--fs-12);
  letter-spacing: var(--ls-widest);
  text-transform: uppercase;
  color: var(--c-muted-ink);
  cursor: pointer;
  transition: color var(--tr);
  white-space: nowrap;
}
.models-panel__toggle:hover { color: var(--c-ink); }
.models-panel__chev {
  width: 14px; height: 14px; display: block;
  transition: transform var(--tr);
}
/* 文案随**开合**互换（真站实测）：
     全宽态 = 「Model details」—— 点我去看（当前型号的）详情
     双栏态 = 「All models」 —— 点我回全部型号
   ★ v16 修正：原来按 data-models-view 切、且两段方向写反了（全宽态显示
     「全部型号」，正好与真站相反）。
   ★ 按 open 切而不是按 view 切：语义上这是**开合**的开关，view 只是它的结果。
   ⚠️ 两段必须**成对**写：只写一半会让另一段在两个形态下同时显示（叠字）。 */
[data-models-open="0"] .models-panel__txt--whenSplit,
[data-models-open="1"] .models-panel__txt--whenFull { display: none; }

/* 箭头方向 = 点下去会发生什么：全宽态朝右（展开左栏）、双栏态朝左（收起）。
   源图标是左箭头，全宽态镜像一次即可。 */
[data-models-open="0"] .models-panel__chev { transform: scaleX(-1); }

/* --- 品类页签（规范 §图2 的 HANGING | FLOOR & TABLE）---
   视觉沿用本站既有筛选语汇（.ser-tabs 那一套）：无底色无边框，靠字色 + 1px 下划线
   区分选中，不学参考站的实心胶囊。
   ★ 容器 flex-wrap + 条目 white-space:nowrap 是**必须的**：
     中文标签在窄栏里会被挤成逐字竖排（实测条目高 34px、正常 17px），
     而「元素存在 / 文本对 / 数量对」这类断言**一个都抓不到**它 —— 只能量高度。 */
.models-tabs {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  column-gap: var(--sp-4);
  row-gap: var(--sp-2);
}
.models-tab {
  position: relative;
  background: transparent;
  border: 0;
  padding: 0 0 4px;
  font: inherit;
  font-size: var(--fs-12);
  letter-spacing: var(--ls-wide);
  color: var(--c-muted);
  white-space: nowrap;
  cursor: pointer;
  transition: color var(--tr);
}
/* 条目之间那条竖线用**伪元素**画，不塞真 `|` 字符（参考站也是伪元素） */
.models-tab + .models-tab::before {
  content: '';
  position: absolute;
  left: calc(-0.5 * var(--sp-4));
  top: 2px;
  width: 1px;
  height: 12px;
  background: var(--c-line, rgba(1, 1, 1, 0.18));
}
.models-tab:hover { color: var(--c-ink); }
.models-tab.is-current { color: var(--c-ink); }
.models-tab.is-current::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 1px;
  background: currentColor;
}

.models-panel__body { min-width: 0; overflow-y: auto; flex: 1 1 auto; }

/* --- 型号卡：**一份 DOM 两态** ---
   grid 态 = 185×260 带图卡（规范 §2.1：1219.5 内宽一排 5 张）
   list 态 = 无图纯文字行（规范 §4 的 .model-card--list）
   ★ 为什么不做两套 DOM：本项目一贯「全量出 + CSS 切显隐」。
     两份 DOM 会把图片节点翻倍（每个型号都有实拍图）；而重渲染会丢滚动位置与选中态。 */
.models-list { display: grid; gap: var(--sp-7) var(--sp-6); }
[data-models-view="grid"] .models-list {
  /* minmax(170px,1fr) + gap 32：v19 起弹层宽 70.3125vw，
     2560 视口内宽 1676 → 8 列、每列 181.5 ≈ 规范 185；
     1440 视口内宽 1013 → 4 列；1600 → 5 列。列数**随视口自适应**，不写死。 */
  grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
  align-content: start;
}
[data-models-view="list"] .models-list {
  grid-template-columns: minmax(0, 1fr);
  gap: 0;
}

/* ★★ v20：形态切换（grid ⇄ list）的过渡 —— v19 那版**实测无效**，已换成三段式。
   修前逐帧实测（tools/_v20_probe.py，2560 视口）：卡片几何从 181.5×331.2 在
   **一帧之内**跳到 1676×116（t=1 还是 grid，t=17 已经是 list）；而 v19 只在
   .models-list 上加了 200ms 淡入 —— 用户 2026-09-22 的原话是「形态切换动画没有生效」。
   根因：grid ⇄ list 的差异（行式 ⇄ 卡式、缩略图 181 ⇄ 72、横排文字 ⇄ 竖排）
   **没有一样是可插值的属性**，光靠 CSS 过渡做不到，必须由 JS 排时序：
     ① .models-list 加 .is-morphing → opacity 0（90ms 淡出，见下条 transition）
     ② 等淡出走完再写 data-models-view（此刻列表不可见，瞬切看不见）
     ③ 摘掉 .is-morphing，逐行加 .is-in 播 hmModelIn（animationDelay 每行错开 16ms）
   总时长 ≈ 90 + 200 + 16×n，与左栏 420ms 的滑出同步收尾。
   ⚠️ 时序与**在途守卫**都在 modal.js → morphView()，改这里之前先读那段注释。 */
.models-list { transition: opacity 90ms var(--ease); }
.models-list.is-morphing { opacity: 0; }
@keyframes hmModelIn {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}
.model-card.is-in { animation: hmModelIn var(--dur) var(--ease) both; }

.model-card {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  padding: var(--sp-2);
  background: transparent;
  /* 边框恒为 2px（透明占位），选中时**只换颜色** —— 若写成 1px→2px，
     选中那一刻卡片尺寸会变、整排跟着跳。 */
  border: 2px solid transparent;
  text-align: left;
  font: inherit;
  color: inherit;
  cursor: pointer;
  transition: border-color var(--tr);
}
.model-card:hover { border-color: var(--c-line); }
/* ★ 选中态 = **2px 近黑描边、不填充**（规范 §6）。
   用户 2026-09-21 明确选「照规范」，覆盖早前「别用纯黑、要贴站点色板」的偏好。
   用 --c-ink（全站近黑令牌 #010101）而不是规范里那个 #1A1B1C ——
   本站的近黑就是这一个，两处各写一个近黑必然漂。 */
.model-card.is-current { border-color: var(--c-ink); }
.model-card__media {
  position: relative;
  display: block;
  /* 2026-09-28：原 185/260（≈0.712，规范实测尺寸）→ 3/4。
     185/260 不在三档里：0.712 距 3:4(0.75) 只差 5%，距 1:1 差 29% ⇒ 取 3:4
     （与素材迁移 target_for 同一条规则）。
     ★ 图是 object-fit: contain（见下面 img），改容器只影响留白、**不裁切灯体**。 */
  aspect-ratio: 3 / 4;
  background: var(--c-bg-sunk);
  overflow: hidden;
}
.model-card__media img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; display: block;
  /* 用 contain 不用 cover：产品图是细长吊灯，塞进小方格里 cover 会把灯体裁成
     中间一条实心色（实测过）。宁可留边，也要让灯看得出是什么形状。 */
  object-fit: contain;
}
.model-card__info { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.model-card__num {
  font-size: var(--fs-12);
  letter-spacing: var(--ls-wide);
  color: var(--c-muted-ink);
}
.model-card__name {
  font-size: var(--fs-14);
  font-weight: var(--fw-normal);
  letter-spacing: var(--ls-tight);
  color: var(--c-ink);
}
.model-card__dim { font-size: var(--fs-12); color: var(--c-muted-ink); }

/* list 态：无图、纯文字横排 —— 一行里「型号号 · 名称 · 尺寸」 */
[data-models-view="list"] .model-card {
  flex-direction: row;
  align-items: center;                 /* 有图之后 baseline 会把缩略图对齐到文字基线，很怪 */
  gap: var(--sp-4);
  padding: var(--sp-3) 0;
  border-width: 0 0 1px 0;
  border-bottom-color: var(--c-line);
}
/* ★ v19：list 态**给出缩略图**（v19 之前这里写死 display:none）。
   尺寸 72×96 = 3:4（09-28 余总更正「竖版位置用 3:4」；09-27 曾一度改成 1:1 的 72×72）。
   ★ 这是**可调的一个数** —— 56 是 2026-09-20 那版列表缩略图的口径，
   但那时弹层只有 1220 宽、右栏 406；现在弹层回到 1800/右栏 622（1.53 倍），
   56 在一栏 622 宽里显得空。定 72 是与「全宽卡图 182 / 左栏详情图 240」拉近一档。
   要改只改这一个 width（aspect-ratio 会带着高度走）。
   ★ 这里仍**显式写出** aspect-ratio：现在它与基类（也是 3/4）**同值**，
     但留着是有意的 —— 它把「list 缩略图的比例」这件事钉在本条规则里，
     将来基类再变也不会把这个缩略图带着走。
   ⚠️ 缩略图节点本来就在 DOM 里（render.js → modelCardHTML 恒出 .model-card__media），
      所以这只是「切显隐」，不用改渲染，也不会多请求一张图。 */
[data-models-view="list"] { --ml-thumb: 72px; }
[data-models-view="list"] .model-card__media {
  display: block;
  flex: none;
  width: var(--ml-thumb);
  aspect-ratio: 3 / 4;                 /* 72 × 96（原 4:5 的 72×90 → 09-28 回 3:4）*/
  /* ★ v20：选中时要放大，放大得走过渡否则是瞬跳
     （真站 .model-card__image-container 也是 transition: all）。 */
  transition: width var(--dur) var(--ease);
}
[data-models-view="list"] .model-card__info {
  flex-direction: row;
  align-items: baseline;
  gap: var(--sp-3);
  flex-wrap: wrap;
}
[data-models-view="list"] .model-card:hover { border-bottom-color: var(--c-ink-2); }
/* ★★ v20：list 态选中 = **放大**。
   用户 2026-09-22 原话：「选择某一个行有一个放大的尺寸变化**而不是下划线变粗**」。
   真站实测（tools/_v20c_live_rows.py，1355 视口 / 右栏 406）：
     未选中行 **374.45×118** + 缩略图 **80×80**；
     选中行   **374.45×198** + 缩略图 **160×160**  ⇒ **缩略图正好 2 倍**，行高 118→198。
   我们按同一条**规则**（2 倍）落地，**不抄真站的绝对像素**（真站那个 80 是 406 宽
   右栏下的值，我们右栏 622）：72×90 → **144×180**，行高约 116 → 204
   （真站 118 → 198，几乎一样）。
   ⚠️ v19 那版在这里写 `border-bottom-width: 2px` + `font-weight: medium` —— **已删除**
      （用户明确否掉）。行高会随选中变化（真站也是），这是预期行为。 */
[data-models-view="list"] .model-card.is-current .model-card__media {
  width: calc(var(--ml-thumb) * 2);
}

/* ★ 必须**显式**写 [hidden] 的 display —— .model-card 声明了 display:flex，
   而 display 会盖掉 [hidden] 的 UA 默认 display:none（UA 规则权重最低）。
   症状：JS 明明设了 el.hidden = true，元素照样显示（品类页签切了没反应）。
   ★ 过滤逻辑在 JS（modal.js → filterModelsByCat），不写 CSS 匹配品类名 ——
     写死品类名的 CSS 在数据一变就静默失效。 */
.model-card[hidden] { display: none; }

/* --- 左栏内容：顺序照规范 = 型号头 → 主按钮 → 图 → 简介 → 技术分组 → 规格表 --- */
.models-detail { display: flex; flex-direction: column; gap: var(--sp-2); }
/* ★ v20：表头改成「左型号 / 右主按钮」同一排。
   真站实测（tools/_v20_live_modal.py）：左栏表头 .product-info-header = **689 宽** +
   `justify-content: space-between` —— 左 `.product-info-left` = [7450  MODEL]（y≈170.5）、
   右 `.product-info-right` = [Technical information][Add to project]（y≈172.5、右缘 732.8
   vs 表头右缘 756.8）⇒ **两颗按钮与型号号同一行、贴右**。
   用户 2026-09-22 要求②：「加入报价按钮放在与型号一排右齐」。
   ⚠️ align-items 必须是 center（原来是 baseline）—— 36px 的按钮在 baseline 行里会被顶歪。 */
.models-detail__head {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--sp-4);
  padding-bottom: var(--sp-3);
  border-bottom: 1px solid var(--c-line, rgba(1, 1, 1, 0.08));
}
.models-detail__model { display: flex; align-items: baseline; gap: var(--sp-3); }
/* 表头右半边 = 「装灯方式 + 主按钮」一起靠右。
   ⚠️ 这层不能省：head 是 space-between，直接塞三个孩子会被摊成三份
      （装灯方式会跑到正中间去）。 */
.models-detail__headend { display: flex; align-items: center; gap: var(--sp-4); }
.models-detail__num {
  font-size: var(--fs-31);
  font-weight: var(--fw-normal);
  letter-spacing: var(--ls-tight);
  color: var(--c-ink);
}
.models-detail__unit {
  font-size: var(--fs-14);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  color: var(--c-muted-ink);
}
.models-detail__inst { font-size: var(--fs-15); letter-spacing: var(--ls-wide); color: var(--c-muted-ink); }
.models-detail__name {
  font-size: var(--fs-22);
  font-weight: var(--fw-normal);
  letter-spacing: var(--ls-tight);
  color: var(--c-ink);
}
.models-detail__sub,
.models-detail__intro { font-size: var(--fs-17); color: var(--c-muted-ink); line-height: 1.6; }

/* 主按钮 —— 规范左栏这里是「Technical information（outline）+ Add to project（filled）」
   两颗；我们没有技术图纸可下（定死规则①），所以只保留 filled 那一颗，文案「加入报价」，
   点了滚到本页报价段（行为在 modal.js 的 [data-models-inquire] 上）。
   ★ v20：它现在长在 .models-detail__head 里（与型号同一排、右对齐）→
     `margin-block` 去掉，留着会给表头撑出一段空白（表头自己有 padding-bottom）。 */
.models-detail__actions { display: flex; flex-wrap: wrap; gap: var(--sp-3); }

/* 左栏「页内查看」（批 V · V1 / P2，余总 2026-09-23）——
   「加入报价」是这一栏**唯一**的转化动作，所以它低一档表达：
   复用全站已有的 .btn--ghost .btn--sm（不新造按钮变体），只把描边与字色各压淡一档。
   ⚠️ 只压**静态**态：hover 仍由 `.btn--ghost:hover`（components.css，权重 0,2,0）接管 → 翻黑，
      与「加入报价」同一套反馈 —— 也就是 P1 刚定的口径「悬停翻黑 = 提示可点」。
   ⚠️ 这颗是 <a> 不是 <button>：它是**真跳转**（去单品页的章），不是页内动作。
      更不许给它加 data-modal-open / data-models-inquire 这类弹层钩子。 */
.models-detail__doc {
  align-self: center;
  color: var(--c-muted-ink);
  border-color: var(--c-line);
}

.models-detail__media {
  position: relative;
  aspect-ratio: 3 / 4;                 /* 2026-09-28 原 4:5 → 3:4（09-27 曾一度改 1:1）*/
  max-width: 15rem;                    /* 详情图别抢戏，固定上限 */
  background: var(--c-bg-sunk);
  overflow: hidden;
}
.models-detail__media img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; display: block;
  object-fit: contain;
}

/* 技术信息分组 —— 标签在上、值在下，与全站「标签小字 + 内容」的层次一致
   ★ 组间分割线用 ::before 画，**不用 border-top** —— border 参与盒模型，
     会把每组高度撑 1px、标签基线跟着移。 */
.models-detail__tech {
  display: flex; flex-direction: column;
  gap: var(--sp-5);
  margin-top: var(--sp-4);
}
.models-tech { display: flex; flex-direction: column; gap: var(--sp-2); }
.models-tech + .models-tech { position: relative; padding-top: var(--sp-5); }
.models-tech + .models-tech::before {
  content: '';
  position: absolute;
  left: 0; right: 0; top: 0;
  height: 1px;
  background: var(--c-line, rgba(1, 1, 1, 0.08));
}
.models-tech__label {
  font-size: var(--fs-14);
  letter-spacing: var(--ls-wide);
  text-transform: uppercase;
  color: var(--c-muted-ink);
}
.models-tech__vals { display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-5); }
.models-tech__val { font-size: var(--fs-17); color: var(--c-ink); }
.models-tech--swatches .models-tech__swatches { display: inline-flex; flex-wrap: wrap; gap: var(--sp-2); }
/* 详情区的色点比规格表里的大一档 —— 那里是「附带信息」，这里是主角之一 */
.models-tech--swatches .models-item__dot { width: 18px; height: 18px; border-color: rgba(1, 1, 1, 0.14); }

/* 规格表（技术参数组）—— v21 重做，照真站 .info-section 实测：
   用户 2026-09-22 原话：「技术参数内容如"铁艺+喷漆"应该是左对齐，目前太靠边查看
   影响体验，参数每行都有横线隔开」。修前实测：display:flex + justify-content:space-between，
   dd 起点距行左缘 969.2px（行宽 1046）—— 值被顶到最右边，离标签 900 多像素，
   眼睛要在一条线上横跨整个弹层才能把「材质」和「铁艺+喷漆」对上。
   真站实测（tools/_v21_live_modal.py，真站 .info-section）：
     display: flex / align: flex-start / **padding: 8px 0 16px** / gap: 24px
     标签 .info-section-title 定宽 **200px**（自身字号 14px）
     **border-bottom: 1px rgba(29, 29, 31, 0.2)** ← 每行一条横线
     值 text-align: **left**，起点 = 行左缘 + 224（= 200 定宽 + 24 gap）
   于是三条一起改：① 标签列定宽 → 所有值起点对齐成一列；② 值左对齐；
   ③ 每行一条横线 + 上下内边距（横线之间就有了呼吸，不再是密排）。
   ★ 标签列定宽取 **15rem = 240px**：真站是 200px@标签 14px，我们左栏字号比真站大
     一档（--fs-17 = 17px，v5 用户批注「弹层字体太小、等比加大」）
     ⇒ 200 × 17/14 = 243px。**这是一个可调的数**，要改只改这一处。
   ⚠️ 用 grid 不用 flex：flex 里定宽列要靠 `width` + `flex: none` 两处，
     grid 一行就写清，且 dd 天然落在第 2 列、长值会自己换行不会把标签挤窄。
   ⚠️ gap 从 --sp-2 改成 0：行距已经由 padding 给了，再留 gap 会把横线推得离上一行很远。 */
.models-tech__specs { display: flex; flex-direction: column; gap: 0; }
.models-tech__spec {
  display: grid;
  grid-template-columns: 15rem minmax(0, 1fr);
  gap: var(--sp-5);
  align-items: baseline;
  padding: var(--sp-2) 0 var(--sp-4);
  border-bottom: 1px solid var(--c-line-2);
}
.models-tech__spec dt { font-size: var(--fs-17); color: var(--c-muted-ink); }
.models-tech__spec dd {
  font-size: var(--fs-17);
  color: var(--c-ink);
  /* ★ 左对齐（真站 text-align: left）—— 修前这里是 right，就是用户说的「太靠边」 */
  text-align: left;
}

/* ★ v21 补丁 B：清掉 <dl>/<dd> 的**浏览器默认外边距**。
   实测「值起点距行左缘 304」= 标签列 240 + gap 24 + **40**，
   那 40 是 <dd> 的 UA 样式 `margin-inline-start: 40px` 惄惄加的 ——
   从没写在我们的 CSS 里，却一直在参与布局（真站的值起点
   就是 240 + 24 = 264，没有这 40）。<dl> 同理有 `margin-block: 1em`。
   我们只用它俩的**语义**（dt/dd 成对），不要它的默认排版：
   间距一律由 grid gap 和 padding 显式给，别让 UA 默认值参与计算。 */
.models-tech__specs { margin: 0; }
.models-tech__spec dt,
.models-tech__spec dd { margin: 0; }

/* 规格表（版本 / 尺寸 / 颜色 / 安装形式）—— 就是 pdp-skus 那一套，收进左栏底部 */
.models-detail__skus { margin-top: var(--sp-5); }

/* --- 窄屏：两栏改上下堆叠（横线在竖排里会变成奇怪的断线，所以去掉左栏右边距） --- */
@media (max-width: 900px) {
  .models-wrap {
    flex-direction: column;
    padding: var(--sp-4);
    gap: var(--sp-6);
  }
  .models-main,
  .modal__body[data-models-open="1"] .models-main { flex: 0 0 auto; padding-right: 0; }
  .models-panel,
  .modal__body[data-models-open="1"] .models-panel { flex: 1 1 auto; }
  .models-detail__media { max-width: 100%; }
}

/* 无动画偏好：把过渡与入场动画都关掉，避免闪现 */
@media (prefers-reduced-motion: reduce) {
  .modal__body[data-models-open="1"] .models-main { animation: none; }
  .model-card, .models-panel, .models-panel__chev, .models-tab { transition: none; }
}

/* ==========================================================================
   系列 · v9 双树（2026-09-21）
   系列 = 客户说的「产品名称」（花苞系列 / 飞碟吊灯），是**产品树**的一层。
   一条系列线可以横跨几个品类（花苞系列同时有吊灯 / 壁灯 / 台灯），
   所以 series.html 按品类分组列型号 —— 分组本身就是双树的可视化。
   这一组只负责**排布**：卡沿用全站 .card、徽标沿用 .dir-card__badge，不另立卡样式。
   ========================================================================== */

/* 品类页与系列页共用的三列卡网格。
   独立命名（不复用 .dir-grid）：列表页的验收脚本按 .dir-grid .dir-card 取样，
   共用类名会让「哪些卡算列表条目」变得含糊。 */
.col-series { padding-block: clamp(48px, 6vw, 96px); }
.col-series__head { max-width: 62ch; margin-bottom: var(--sp-8); }
.col-series__head .h2 { margin-block: var(--sp-2) var(--sp-3); }
.col-series__list { grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (max-width: 900px) { .col-series__list { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 520px) { .col-series__list { grid-template-columns: minmax(0, 1fr); } }

/* 系列页：按品类分组的每一段 */
/* ==== v10 阶段 3.5：系列页型号区的「品类页签条」 v1 ==== */
.ser-models { padding-block: var(--sec-pad); }
.ser-models__head { margin-bottom: var(--sp-5); }
.ser-models__head .h2 { margin-block: var(--sp-2) 0; }

/* 单品类系列：页签条不出，改一行静态品类标注（信息不丢）。 */
.ser-models__cat {
  font-size: var(--fs-12);
  letter-spacing: var(--ls-wide);
  color: var(--c-muted-ink);
  margin: 0 0 var(--sp-6);
}

/* 页签条 —— 视觉沿用本站既有筛选语汇（.models-tab / .sp-tabs__i 那一套）：
   无底色无边框，靠字色 + 1px 下划线区分选中，不学原站的实心胶囊。
   ★ 条目之间那条竖线用**伪元素**画，不塞真 `|` 字符
     —— 原站也是伪元素（.vib-floating-actions-panel__tabs li::after）。
   ★ 容器 flex-wrap + 条目 white-space:nowrap 是**必须的**：
     中文标签在窄栏里会被挤成逐字竖排（实测条目高 34px、正常 17px），
     而「元素存在 / 文本对 / 数量对」这类断言**一个都抓不到**它 —— 只能量高度。 */
.ser-tabs {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  column-gap: var(--sp-4);
  row-gap: var(--sp-2);
  margin: 0 0 var(--sp-6);
}
.ser-tab {
  position: relative;
  background: transparent;
  border: 0;
  padding: 0 0 4px;
  font: inherit;
  font-size: var(--fs-12);
  letter-spacing: var(--ls-wide);
  color: var(--c-muted);
  white-space: nowrap;
  cursor: pointer;
  transition: color var(--tr);
}
.ser-tab + .ser-tab::before {
  content: '';
  position: absolute;
  left: calc(-0.5 * var(--sp-4));
  top: 2px;
  width: 1px;
  height: 12px;
  background: var(--c-line, rgba(1, 1, 1, 0.18));
}
.ser-tab:hover { color: var(--c-ink); }
.ser-tab.is-current { color: var(--c-ink); }
/* 选中下划线用 ::after（不是 border-bottom）—— border 会参与行高计算，
   把这一行撑高 1px，下面的网格基线跟着移。 */
.ser-tab.is-current::after {
  content: '';
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 1px;
  background: currentColor;
}

/* ★ 过滤靠 [hidden]，但 .card 在 components.css 里自己声明了 display:flex ——
   不显式补这一条，[hidden] 会被盖过去（同 .models-item[hidden] 的老问题：
   元素看不见、却仍然占位，也仍然被 querySelectorAll 数得到）。 */
[data-ser-list] > [data-ser-cat-item][hidden] { display: none; }

/* ============================================================================
   ★★ v22 规格区（用户 2026-09-22 要求）
   ----------------------------------------------------------------------------
   用户原话：「双栏状态下左栏的版本 tab 点了没变化没数据；『版本』标题字号改和
   『花苞吊灯』一样大小；版本 tab 的数据里要有尺寸 / 颜色 + 一个对应 SKU 号；
   在尺寸表头边做一个尺寸筛选可以选出唯一 SKU；加入报价加一条规则，只有选择了
   唯一的 SKU 才可以加入报价。」
   ========================================================================== */

/* ① 「版本」标题：与左栏品名 .models-detail__name 同级（--fs-22）。
      用户明确要求「改和『花苞吊灯』一样大小」，并选了「标题与 tab 同一行」。
      ⚠️ 只改字号 / 字色 / 字距，**不动 .pdp-skus__bar 的 flex 排布** ——
         那会连带改掉单品页规格弹层的排法（两处共用这一套语汇）。 */
.pdp-skus__label {
  font-size: var(--fs-22);
  font-weight: var(--fw-normal);
  letter-spacing: var(--ls-tight);
  text-transform: none;
  color: var(--c-ink);
  line-height: 1.2;
}

/* ② 尺寸筛选（挂在「尺寸」表头里）
      形态 = 一个下划线小控件 + 自绘浮层（用户 2026-09-22 二选一拍板选了这个）。
      ⚠️ 不用原生 <select>：它的**弹出面板**是系统样式，跟本站的暖白 / 下划线语汇打架。 */
.pdp-skus__th--filter { position: relative; }
.pdp-skus__thinner { display: inline-flex; align-items: baseline; gap: var(--sp-3); }
.skufilter { position: relative; display: inline-block; }
.skufilter__btn {
  appearance: none; background: none; border: 0;
  display: inline-flex; align-items: center; gap: 4px;
  padding: 0 0 2px;
  font: inherit; font-size: var(--fs-12); font-weight: var(--fw-normal);
  letter-spacing: var(--ls-wide); text-transform: none;
  color: var(--c-ink); cursor: pointer;
  border-bottom: 1px solid var(--c-line-2);
  transition: border-color 200ms ease-out;
}
.skufilter__btn:hover { border-bottom-color: var(--c-ink); }
.skufilter__chev { width: 10px; height: 10px; flex: none; }
.skufilter__panel {
  position: absolute; top: calc(100% + 6px); left: 0; z-index: 30;
  display: flex; flex-direction: column;
  min-width: 9rem; max-height: 16rem; overflow-y: auto;
  padding: var(--sp-2);
  background: var(--c-bg);
  border: 1px solid var(--c-line-2);
  box-shadow: 0 10px 28px rgba(1, 1, 1, 0.10);
}
/* ⚠️ 这一条不能省：上面给了 display:flex，会把 [hidden] 的 UA display:none 盖掉
   ——「看不见却仍然存在、也仍然被 querySelectorAll 数得到」的老坑
   （同页脚 .card 与 .models-item[hidden] 那两处，本项目栽过）。 */
.skufilter__panel[hidden] { display: none; }
/* 浮层自己也要能滚：条同样去掉，滚轮照旧 */
.skufilter__panel::-webkit-scrollbar { width: 0; height: 0; }
.skufilter__opt {
  appearance: none; background: none; border: 0; text-align: left;
  font: inherit; font-size: var(--fs-12); letter-spacing: var(--ls-wide);
  color: var(--c-muted-ink); cursor: pointer;
  padding: var(--sp-2) var(--sp-3);
}
.skufilter__opt:hover { color: var(--c-ink); }
.skufilter__opt.is-on { color: var(--c-ink); background: var(--c-bg-sunk); }

/* ③ SKU 号列：它是编码，比尺寸／颜色低一档存在感。
      ⚠️ 必须允许换行 —— SKU 是「型号-版本-尺寸-颜色」拼出来的长串，
         不允许换行会把表撑出左栏。 */
.pdp-skus__td--sku {
  font-size: var(--fs-12);
  letter-spacing: var(--ls-wide);
  color: var(--c-muted-ink);
  overflow-wrap: anywhere;
  /* ★ 余量全给这一列：其它列都声明 width:1px（只按内容占位），
     否则自动表格布局会按内容比例把余量摊给尺寸列 —— 尺寸列会宽得能停飞机，
     真正的长串（SKU）反而被压着换行。 */
  width: 100%;
}
/* ⚠️ width:1px 必须配 white-space:nowrap —— 中文可以在任意两字之间断行，
   只给 width:1px 会让「颜色」列塌成两个字宽、值被折成「白\n色」。 */
.pdp-skus__col--size,
.pdp-skus__col--color,
.pdp-skus__col--install { width: 1px; white-space: nowrap; }

/* ④ 选中一条 SKU：整行可点。
      选中表达用「底色下沉 + 左侧近黑标」—— 表格行里下划线太弱，看不出选的是哪一条。 */
.pdp-skus__t tbody tr[data-sku-idx] { cursor: pointer; }
.pdp-skus__t tbody tr[data-sku-idx]:hover td { color: var(--c-ink); }
.pdp-skus__t tbody tr.is-picked td { background: var(--c-bg-sunk); }
.pdp-skus__t tbody tr.is-picked td:first-child { box-shadow: inset 2px 0 0 var(--c-ink); }

/* ⑤ 门禁提示小字（未选到唯一 SKU 时显示，选到后自动收起） */
.models-detail__pickhint {
  font-size: var(--fs-12);
  letter-spacing: var(--ls-wide);
  color: var(--c-muted-ink);
  align-self: center;
}

/* ⑥ 锁定态的「加入报价」
      ★ 只用**整体透明度**降调，**不改** background / color / border-width ——
        那三样正是 check_modal K3 的判据（去黑底 = 透明底 + 近黑字 + 有描边）。
        改它们等于顺手推翻一条已经验过的判据，而那种改动最难被发现。
      ★ 悬停**照全站翻黑** —— 见本段末尾「批 V · V1 / P1」那条说明（这条被推翻了）。
      ★★ `pointer-events: auto` 是**必需的覆盖**，不是可选项 ——
         components.css 第 42 行有一条全站老规则
           `.btn[disabled], .btn[aria-disabled="true"] { opacity:.4; pointer-events:none; }`
         它把 `aria-disabled="true"` 当成「真·不可操作」处理。而本项目的门禁刻意
         **不用** `disabled` 属性、用 `aria-disabled` 的原因恰恰是：要让它**点得动**，
         点了给出可执行的下一步（滚到规格区 + 闪一下 + 焦点给尺寸筛选）。
         不覆盖的话，锁着的按钮就是**死按钮** —— 用户点它毫无反应，正是全站红线。
         ⚠️ 这条规则的选择器写成**两个**（`.btn.is-locked` 与
            `.btn.is-locked[aria-disabled="true"]`）：后者权重 (0,3,0) 直接压过
            components.css 的 (0,2,0)，不靠「site.css 排在后面」这种文件顺序巧合
            —— 顺序一变就会静默回退成死按钮，而那种回归最难被发现。
         ⚠️ 无障碍语义仍然正确：`aria-disabled` 的规范用法就是「保留可聚焦可点击，
            由脚本给出为什么不给用」，与真的 disabled 属性不是一回事。 */
.btn.is-locked,
.btn.is-locked[aria-disabled="true"] {
  opacity: 0.42;
  cursor: not-allowed;
  pointer-events: auto;     /* 覆盖 components.css 的死按钮规则 —— 见上 */
}
/* ★★ 批 V · V1（P1，余总 2026-09-23 批复 A）：这里**故意没有** `.btn.is-locked:hover` 覆盖。
   历史：v22 曾在这里写一条把锁态悬停「兜回透明底」，理由是「一悬停就翻黑会让人以为点得动」。
   批复把这条推翻了 —— 原话「锁定态是『活的』，悬停翻黑正好提示『可点』」。
   ⇒ 删掉那条之后，锁态悬停走全站 `.btn--ghost:hover`（components.css:31，background: var(--c-ink)）。
   ⚠️ 覆盖面确认过，不是漏写：`:focus-visible` 那条也一起删了 —— 它只改颜色、
      **没有**声明 outline，所以键盘焦点环仍由 base.css 的全局 `:focus-visible` 提供，
      删掉不会把无障碍焦点环弄丢。
   ⚠️ 「锁着 vs 真禁用」的区分仍然成立，只是不再靠悬停：靠 opacity 0.42 + cursor: not-allowed。
   ⚠️ 别再加回来：加回来就是「锁态悬停毫无反馈」的行为倒退，而且这种回归最难被发现
      （hover 只在伸手那一刻存在，截图和断言都容易漏）。 */

/* ⑦ 被门禁挡住时点「加入报价」：滚到规格区并**整块底色闪一下**（不能是死按钮）。
      ⚠️ 用背景脉冲而不用 box-shadow 描一圈：这个块活在一个 overflow:auto 的左栏里，
         box-shadow 会被滚动容器裁掉（实测只剩右边一条竖线，看着像渲染坏了）。
      动画 1400ms 与 render.js 里 flashSkuPicker 的定时器同一个数，改要一起改。 */
.pdp-skus-wrap.is-alert {
  animation: hmSkuAlert 1400ms ease-out;
  border-radius: 2px;
}
@keyframes hmSkuAlert {
  0%   { background: var(--c-bg-sunk); }
  55%  { background: var(--c-bg-sunk); }
  100% { background: transparent; }
}
