/* 令牌回落值一律与 vendor/tokens.css 的真值对齐（2026-08-01）。
   之前 `var(--radius-200, 12px)` 的回落写 12px、令牌真值是 16px——回落**永远不会触发**
   （令牌有定义），所以屏上一直是 16px。不是渲染 bug，是「写的和看到的不是一回事」：
   读代码的人按回落值推断圆角，推出来的是错的。同类还有 --z-sticky(90→真值100，90 是 --z-stuck)、
   --dur-base(.3s→.25s)、--dur-slow(.5s→.4s)。
   另有 `var(--ease-out, ease)`：--ease-out **全站没有定义**，恒退回 `ease`——
   那才是真正上屏的曲线，而站里能用的是 --ease-standard / --ease-emphasis / --ease-entrance。
   已改用 --ease-emphasis（与 portal 的强调过渡同款）。 */
/* catalog.css — 商业目录层（列表 / 详情 / 对比 / 询单）。
   规则同 portal/plum/ember：只消费 vendor 语义变量，不碰 vendor 文件，不造 vx- 类。

   语法与卷轴刻意对位：卷轴是满幅沉浸、一屏一幅、镜头在动；
   目录是版式、密度、可扫视、静止——图一律装进固定比例的框，信息是主角。
   页面级主题取玄关的 violet（中立一级层），每张卡/每列自带 data-theme，
   accent 跟着它属于的世界走：同一张表上并列，但颜色仍分属两个世界。 */

/* ── 字脸（2026-08-03）──
   在此之前，本层**一个 font-family 都没设**：标题、代号、正文、数字全是同一张
   系统 UI 字脸，"设计感"整个压在 letter-spacing 上（标题 .2em、维度名 .4em、
   代号 .34em、按钮 .2em）。中文在小字号上被这么拉，读起来是散的，不是宽松的。

   下面三条是**实测本机装了才写进链的**（页内 span 宽度探针，100px "ABCabc123"）：
     Futura 548 / Avenir Next Condensed 403 / Didot 525 / sans-serif 536
   Oswald 量出来 536＝与 sans 同宽，说明它根本没装、声明了也是回落，所以不写。
   —— 「声明了就当有」是这类活最常见的假绿：屏上从来没变过，代码里看着很讲究。 */
/* ── 08-03 改苹果路线：一张字脸走完全部层级 ──
   去 apple.com.cn/shop/buy-mac 实测下来的做法，不是印象：
     大标题 80px / 600 / letter-spacing **normal**
     导航筛选 17px / 400 / normal，与标题同一条左边线，间距 40px
     节标题 28px / 600
     字脸自始至终一张 "SF Pro SC"，页面上一条分隔线都没有
   层级全部由**字号和字重**产生，留白负责分节。这跟本层原来的路子正好相反——
   原来是细线分节、字距造气质。两套不能混着用，混出来就是半苹果。
   于是 Futura（代号）与 Didot（序号）一并退场：换家族做区分是另一套语法。
   -apple-system 在 macOS 上解析到 SF Pro，中文落到苹方，与实测那张同源。 */
:root {
  --cat-face: -apple-system, BlinkMacSystemFont, "SF Pro SC", "SF Pro Text",
              "PingFang SC", "Helvetica Neue", Arial, sans-serif;
  --cat-face-code: var(--cat-face);   /* 代号 */
  --cat-face-no: var(--cat-face);     /* 在册序号 */
}

/* 代号（EMBER / PLUM / LIPS…）曾是全站唯一真实的产品标识；品牌方 2026-08-04/07
   回填正式名之后，列表、详情、对比三页已改成「正式名大标、代号小标」（08-04 列表、
   08-12 详情与对比），询单与选购指南仍以代号为行标识。无论大字小字，
   同一元素类在四个目录页必须是同一张字脸，否则同一个标识在列表、详情、对比、
   询单四处长了四副样子。
   字距归零：苹果把 MacBook Air / iMac 这些产品名设成 SF Semibold、字距 normal，
   靠字号立住。拉开字距是上一套语法的遗留，跟这套不兼容。 */
.world-code, .obj-code, .item-code, .cmp-code, .inq-code, .guide-code {
  font-family: var(--cat-face-code);
  font-weight: 600;
  letter-spacing: -.01em;
}
/* ⚠ 上面这条只是兜底：六个类后面各自还有一条更靠后的规则，字距会被再次改写。
   08-03 改苹果路线时只顺手改了 .world-code / .obj-code，另外四条还留着 .16em——
   于是列表页归零、详情页 8px，同一个 EMBER 在两页宽度差一截，
   正好破掉上一段自己写下的承诺。**声明处写了不等于终态是它**。
   _cat-others.mjs 量的是四页的实际计算值，不是这条声明。 */

/* ── 字距（2026-08-03 一并收紧）──
   中文没有词间空格，字距一大就把词拆散：「规 格」「加 入 询 单 篮」「更 多 筛 选 维 度」
   读到的是一个个孤立的字，不是一个词。本层原先普遍在 .2–.5em。
   收到 .04–.18em（标签略宽于正文），拉开的活交给字号、留白和分隔线。

   **三处刻意不动**：
     · .world-nav-links a —— 导航在 portal/ember/plum 各自的 CSS 里也有一份，
       只改这一份会让同一条导航在不同页长得不一样；要改就四个文件一起改，那是另一次改动。
     · .plate-kicker / .cat-kicker —— 内容是 CATALOG / INQUIRY 这类全大写拉丁词，宽字距正是它该有的样子。
       （对比页的 kicker 是**一句会被翻译的话**，例外走 .cat-kicker--words，见该处。
        起初这里写的是「kicker 一律是拉丁大写」——不成立，屏上就有一个反例。）
     · .dev-note-tag —— 只在 ?preview=1 出现，不对外。 */

/* ── 通用 ── */

/* 占位符：文字一个字不改（红线 3 要的就是显式写出「待品牌方」），改的是它凭什么排第一顺位。
   改版前它取 --text-low，而每张卡带 data-theme，ember 主题下 --text-low = #c86b69 砖红——
   于是「这里还没有信息」成了整页最扎眼的颜色，一屏 11 处，比产品代号还抢眼。
   现在钉死一个不随主题漂移的中性色：对比度 5.83（旧值 5.29，**更高不是更低**），
   降的是色相显著性，不是亮度——:200 那条老坑记的正是「区分待定要靠形状，别调暗字」。 */
.ph {
  color: #8e8aa8;
  font-family: var(--cat-face);
  font-size: clamp(13px, 1.1vw, 15px);
  letter-spacing: 0;
}
.ph::before { content: "〔待品牌方：" attr(data-ph) "〕"; }
/* i18n 接上后由 applyLang 写 data-ph-label（前缀跟语言走，占位项名保持中文——
   读者是品牌方）。没有 JS 时上面那条就是终态，两者中文写法刻意一致，不会闪一下 */
.ph[data-ph-label]::before { content: "〔" attr(data-ph-label) "〕"; }

/* vendor 的 .vx-nav 是 position:fixed，不要收进任何 position:relative 覆写里 */
.cat-shell, .vx-footer { position: relative; z-index: 1; }

/* 导航保底压暗（2026-07-30 全站 26 点对比度实测后加）：
   vendor 的 .vx-nav 默认全透明，滚动后也只加一层很淡的毛玻璃，链接色是 --text-mid。
   导航压在亮画面上时对比度实测掉到 1.08–1.13（玄关双门那扇天光窗、
   胶片横廊的白瓷浴缸、partner 的亮底幅）——那三个字在截图里根本认不出来。
   用渐变而非实底色：保留「悬浮在画面上」的观感，往下多铺 30px 淡出，不留硬边；
   底色取 --bg-base，跟着分区主题走（亮色模式下自然变成亮 scrim，方向仍然正确）。
   ::before 必须 position:absolute —— .vx-nav 是 flex 容器，
   静态定位的伪元素会变成 flex item，把品牌与链接挤开。 */
.vx-nav::before {
  content: "";
  position: absolute;
  /* 往下多铺 46px 而不是 30px：盒子越高，尾巴越长，亮画面上才不会看出一条硬边的黑带。
     链接行落在盒子 15%–41% 处（导航高约 71px），所以 44% 之前必须还是满强度。 */
  inset: 0 0 -46px 0;
  z-index: -1;
  pointer-events: none;
  background: linear-gradient(180deg,
    color-mix(in oklab, var(--bg-base) 92%, transparent) 0%,
    color-mix(in oklab, var(--bg-base) 84%, transparent) 44%,
    color-mix(in oklab, var(--bg-base) 34%, transparent) 72%,
    transparent 100%);
}

.world-nav-links { display: flex; gap: clamp(18px, 3vw, 40px); }
.world-nav-links a {
  color: var(--text-mid);
  text-decoration: none;
  font-size: calc(12.5px * var(--ratio, 1));
  letter-spacing: .22em;
  transition: color var(--dur-base, .25s);
}
.world-nav-links a:hover,
.world-nav-links a[aria-current="page"] { color: var(--text-hi); }
.world-nav .vx-nav__brand img { width: 34px; display: block; }

body { background: var(--bg-base); }

.cat-shell {
  max-width: var(--content-max, 1440px);
  margin: 0 auto;
  padding-inline: var(--page-gutter, clamp(20px, 4vw, 64px));
  /* 顶距跟着导航实测高度走：导航是 fixed，换长语种会换行变高，写死的顶距会被它压住。
     --nav-h 由 js/lang.js 量出来写在 :root；没有 JS 时退回原来的写死值 */
  padding-top: max(clamp(84px, 11svh, 118px), calc(var(--nav-h, 64px) + clamp(20px, 3svh, 34px)));
  padding-bottom: clamp(90px, 14svh, 160px);
}

/* kicker 降到苹果卡片上「新款」那一档：小、半粗、带色，不再是拉开 .52em 的图录封面字。
   .52em 那版正是用户圈出来要换掉的东西之一。 */
.cat-kicker {
  font-family: var(--cat-face);
  font-size: calc(12px * var(--ratio, 1));
  font-weight: 600;
  letter-spacing: .06em;
  text-indent: 0;
  color: var(--accent);
}
/* 内容是一句会被翻译的话（对比页的「并排对比」），不是 CATALOG / INQUIRY 那种固定拉丁词。
   判据挂在内容上而不是页面上：切到英文变成 Side by side，仍然是一句话，字距仍该是紧的。 */
.cat-kicker--words {
  font-family: inherit;
  font-weight: 400;
  letter-spacing: .16em;
  text-indent: .16em;
}

.cat-head { margin-bottom: clamp(22px, 3.6svh, 42px); }
/* 08-01 提大一档：玄关 34px、ember 92px、plum 62px 都是满幅沉浸首屏，
   catalog 40px 左对齐顶对齐——从上一屏切过来落差像换了个站，而不是换了个模式。
   56px 接得住，且仍明显低于叙事页，保住「这里是台账不是电影」的分工。

   08-03 把字距从 .2em 收到 .05em。.2em 下「在 册 的 全 部」是五个各自为政的字，
   中文没有词间空格，字距一大就把词拆散了——那不是气质，是读不成词。
   拉开的活交给字号和留白，不交给字距。 */
/* 苹果实测：80px / 600 / 字距 normal / 行高 1.09。
   原来是 54px / 字重 200 / 字距 .05em——细、小、带字距，靠下面一条细线撑住版面。
   这套里没有那条线，标题得自己立得住，所以字号和字重一起上去。 */
.cat-title {
  margin: clamp(8px, 1.4svh, 14px) 0 0;
  font-family: var(--cat-face);
  font-weight: 600;
  font-size: clamp(34px, 5.4vw, 76px);
  letter-spacing: -.015em;
  text-indent: 0;
  line-height: 1.08;
  color: var(--text-hi);
}
/* 苹果那一页 H1 底下**没有**引子段。这里保留，因为它讲的是这一册怎么用
   （放进询单篮、一次问清），是读者真的需要的一句——但按苹果的正文档设：
   21px / 400 / 字距 0 / 行高 1.6，不再是 13px 拉 .06em 行高 1.95 的说明体。 */
.cat-sub {
  margin: clamp(12px, 2svh, 22px) 0 0;
  max-width: 34ch;
  font-size: clamp(16px, 1.5vw, 21px);
  line-height: 1.6;
  color: var(--text-mid);
  letter-spacing: 0;
}

/* 文字链＝苹果那种带主题色的纯文字链，不画下划线。
   原先每条链底下一道 accent 细线，在「一条分隔线都没有」的版面里显得格外多余。 */
.cat-link {
  display: inline-block;
  color: var(--accent);
  text-decoration: none;
  font-family: var(--cat-face);
  font-size: clamp(14px, 1.2vw, 16px);
  letter-spacing: 0;
  transition: opacity var(--dur-base, .25s);
}
.cat-link:hover { opacity: .72; }
.cat-link--quiet { color: var(--text-mid); }
.cat-link--quiet:hover { color: var(--text-hi); opacity: 1; }

.cat-error {
  color: var(--text-hi);
  font-size: clamp(15px, 1.3vw, 17px);
  letter-spacing: 0;
  line-height: 1.6;
  margin-bottom: 18px;
}

/* 入篮按钮＝苹果的购买键：实心药丸、无边框、字压在实心上。
   原先是「透明底 + accent 描边」的幽灵按钮——那是次级动作的长相，
   而这颗是本层的目的。实心底取 accent，字取该主题最深的 tone，
   两者都跟着卡自己的 data-theme 走，所以两个世界的按钮是各自的颜色。 */
.cat-add {
  appearance: none;
  cursor: pointer;
  border: 0;
  background: var(--accent);
  color: var(--tone-0, #08080f);
  font-family: var(--cat-face);
  font-size: clamp(14px, 1.2vw, 16px);
  font-weight: 500;
  letter-spacing: 0;
  padding: 11px 22px;
  border-radius: var(--radius-pill, 999px);
  transition: opacity var(--dur-base, .25s), background var(--dur-base, .25s);
}
.cat-add:hover { opacity: .85; }
/* 已在篮里＝退回描边态：动作已经发生，它不该再抢主视线 */
.cat-add.is-in {
  background: transparent;
  color: var(--text-hi);
  box-shadow: inset 0 0 0 1px var(--accent);
}
.cat-add--lg { padding: 14px 30px; font-size: clamp(15px, 1.3vw, 17px); }

/* ── 视图 1 · 列表 ── */

/* 2026-08-03 第二稿。第一稿只是把侧栏挪到了上面——底色、方框、圆角、描边、
   图下横排小字全没变，看起来还是任何一个电商列表。真正的两处病：

   ① **它不属于这个站。** 玄关和两条卷轴一路是满幅影像 + 竖排中文压在边距 +
      零卡片零圆角零描边；只有目录页是深色底上一堆带描边圆角的方框。
   ② **这一册最重要的事实在版面上看不见。** 册子里躺着两个「她」，各是一个世界
      （Ember 暖 / Plum 冷，全站顶层结构就是二选一）；改版前她们是两张一模一样的灰卡。

   现在分两种登记法（判据在 catalog.js 的 registerOf：有没有自己的一条叙事线）：
     · 幅 .cat-sec--world —— 一个她一整幅，满幅出血、无框、自带世界色场，
       那句话竖排落在外侧边距。和玄关同一套语言。
     · 架 .cat-sec--shelf —— 三件器物坐在同一条台面线上。静物本来就拍在台面上。

   **图一律不调色**：目录页的图必须是这件东西真实的颜色，冷暖靠图周围的场和幕分。

   已删：.cat-card* / .cat-grid / .cat-facets / .cat-results / .facet-more*
   （侧栏、卡片网格连同它们的折叠容器一起没了，DOM 里不再有对应节点）。 */

.cat-view { display: grid; gap: clamp(30px, 5svh, 62px); }
/* 节与节之间的呼吸：幅是满幅出血的，节距要够大才分得开两种登记法 */
.cat-body { display: grid; gap: clamp(46px, 8svh, 104px); }

/* ── 报头 ── */

/* 报头改成**一条左边线上的单栏**：eyebrow → 标题 → 引子 → 筛选 → 计数。
   苹果 /shop/buy-mac 就是这个排法：标题 x=140，底下那排导航 x=145，同一条边线；
   右上角只挂两条极小的次级链接，页面上一条分隔线都没有。
   原来那版是「标题在左 + 一簇控件贴右」的双栏，两边基线对不齐、筛选被挤成密排索引行——
   用户圈出来要换掉的就是它。换法不是把那一簇挪个位置，是**取消这一簇**：
   筛选自己站到标题的左边线上，成为版面的第二层，而不是标题旁边的附件。 */
/* 留白要给够，但报头不能把第一件商品挤出首屏：
   1440×900 实测过一次 报头 417px、首幅顶 y=681 —— 900 高的屏上只剩 219px 能看到图。
   苹果那页 H1 落在 y=105，到第一排卡是 303，中间只隔 110px。
   这里多了引子和两组筛选，压不到 110，但间距和下边距都收了一档。 */
.cat-mast {
  display: grid;
  justify-items: start;
  row-gap: clamp(16px, 2.4svh, 28px);
  margin-bottom: clamp(28px, 4.6svh, 58px);   /* 分节靠留白，不靠线 */
}
.cat-mast-say { display: grid; gap: 8px; justify-items: start; }
.cat-mast .cat-sub { margin: 0; }
.cat-mast-tools {
  display: grid;
  justify-items: start;
  gap: clamp(10px, 1.6svh, 18px);
}

/* 筛选＝苹果那排本地导航：纯文字、无框无线、17px，与标题同一条左边线。
   选中态只改颜色 + 一枚很浅的实底，不加下划线（理由见下面 .facet-opt.is-on）。

   ── 2026-08-05 改版：横排改竖排 ──
   原来三组并排成一条横带，甲方的原话是「这块分类太乱」。查下来不是审美问题，是
   **邻近律没做**：组间距 clamp(28,3.4vw,56) 与组内距 clamp(18,2.2vw,40) 只差 1.4 倍，
   而组标签与选项同字号同色、还压在同一条基线上。于是那一行读起来是
   「品类 全部 整娃 小件 世界 全部 Ember Plum 材质 全部 全硅胶」——11 个等权的词，
   三枚一模一样的深色「全部」胶囊是全场最显眼的东西，而它们的含义是「什么都没筛」。
   横向再怎么调间距都救不回来：一旦组数 ≥3，横排的分组只能靠间距，而间距同时还要
   兼顾整行宽度，两个约束互相打架。竖排把分组交给换行，天然不打架。 */
.cat-filters { display: grid; justify-items: start; gap: clamp(12px, 1.6svh, 18px); }
/* 一组＝标签一行、选项一行、占位再一行，全部左对齐到同一条边线。
   （写成 flex+flex-basis:100% 时 .ph 不换行，只会变成同排第三个元素，
   〔待品牌方：品类名〕会悬在两组之间——实测过，grid 才真的能开第二行。） */
.cat-filters .facet {
  display: grid;
  justify-items: start;
  gap: 6px;
}
.cat-filters .facet[hidden] { display: none; }
.cat-filters .facet-label { margin: 0; }

/* 从属维度（有 applies_to 的，眼下是「世界」——只在整娃里成立）。
   缩进 + 一条起首短线：它不是与「品类」并列的第二把尺子，是品类之下的一层。
   不用嵌套 DOM 表达从属，是因为它会随品类整组显隐，嵌进去时父容器会留一个空壳。 */
.cat-filters .facet--sub { padding-left: clamp(12px, 1.4vw, 20px); position: relative; }
.cat-filters .facet--sub::before {
  content: "";
  position: absolute;
  left: 0; top: .55em; bottom: .3em;
  width: 1px;
  background: color-mix(in oklab, var(--text-hi) 14%, transparent);
}
/* 〔待品牌方：品类名〕必须显式看得见——红线 3。
   版面挤不是藏的理由，挤就换排法（第一稿写过 display:none，那是错的）。 */
.cat-filters .facet > .ph {
  grid-column: 1 / -1; justify-self: start;
  font-size: calc(12px * var(--ratio, 1));
}

.facet-label {
  margin: 0;
  font-weight: 400;
  font-size: calc(13px * var(--ratio, 1));
  letter-spacing: 0;
  color: var(--text-low);
  white-space: nowrap;
}
/* 一组筛选内部不折行：「全部 整娃 小件」拆成三行就不像一排导航了。
   要折就整组折（.cat-filters 的 wrap 管这件事）。 */
.facet-opts { display: flex; flex-wrap: nowrap; gap: clamp(18px, 2.2vw, 40px); }
.facet-opt {
  appearance: none;
  cursor: pointer;
  border: 0;
  background: none;
  color: var(--text-low);
  font-family: var(--cat-face);
  font-size: clamp(15px, 1.3vw, 17px);   /* 苹果那排导航实测 17px */
  font-weight: 400;
  letter-spacing: 0;
  padding: 0;
  transition: color var(--dur-base, .25s), font-weight var(--dur-base, .25s);
}
.facet-opt:hover { color: var(--text-hi); }
/* 选中态＝亮色 + 一枚很浅的实底，没有下划线（下划线是上一套细线语法的记号）。
   一度写成「选中转 600 字重」，那是苹果做标题的手法，不是做选中态的：
   字重一变文字就变宽，整排横向抖动。补宽度要靠 ::before content:attr(data-label)
   再排一份粗体撑开——那就多了一个**必须跟着语言切换同步**的真值来源，
   一旦 i18n 只改 textContent 不改属性，宽度就按旧语言留着。
   实底不改变任何字的度量，也是苹果选中 chip 的原样，没有这个隐患。 */
.facet-opt {
  border-radius: var(--radius-pill, 999px);
  padding: 4px 12px;
  margin: -4px 0;
  display: inline-flex;
  align-items: baseline;
  gap: 6px;
}
.facet-opt.is-on {
  color: var(--text-hi);
  background: color-mix(in oklab, var(--text-hi) 12%, transparent);
}

/* 数目：比选项名小两档、低一级对比度，跟在名字后面而不是上标——
   上标在 15px 的字上只有 9px，等于没有。它要能读，不然加了也白加。 */
.facet-n {
  font-size: .74em;
  font-variant-numeric: tabular-nums;   /* 等宽数字：切筛选时数目变了宽度不抖 */
  color: var(--text-low);
  letter-spacing: 0;
}
.facet-opt.is-on .facet-n { color: color-mix(in oklab, var(--text-hi) 62%, transparent); }

/* 0 款的选项禁用。不是隐藏——隐藏会让人以为这个维度只有那么几项；
   留着并压暗，说的是「这一项在，但和你当前的选择合不到一起」。 */
.facet-opt.is-dead {
  cursor: not-allowed;
  opacity: .38;
}
.facet-opt.is-dead:hover { color: var(--text-low); }

.cat-mast-foot { display: flex; align-items: baseline; flex-wrap: wrap; gap: 8px clamp(20px, 2.4vw, 34px); }
.cat-count {
  margin: 0;
  font-size: clamp(14px, 1.2vw, 16px);
  letter-spacing: 0;
  color: var(--text-mid);
}
.cat-count-of { color: var(--text-low); }
/* 次级动作按苹果的做法给主题色，不给下划线（他们右上角那两条就是这样） */
.cat-cmp { font-size: clamp(14px, 1.2vw, 16px); letter-spacing: 0; color: var(--accent); }

/* ── 节头 ──
   苹果的节标题是一句话两个声部：「**全部机型，**随你挑。」——同字号同位置，
   前半粗黑、后半转灰。这里正好对得上：品类名是前半，形态提示是后半。
   实测 28px / 600；分隔线取消，节与节之间靠 .cat-body 的留白分。
   在册序号留着但降成 eyebrow（苹果卡片上「新款」那一档）——它是台账事实，
   不是装饰：01—02 说明这两件是册子里的第 1、2 件，筛选时不重编。 */
.cat-sec { display: grid; gap: clamp(18px, 3svh, 34px); }
.cat-sec-head { display: grid; justify-items: start; gap: 6px; }
.cat-sec-no {
  margin: 0;
  font-family: var(--cat-face-no);
  font-size: calc(12px * var(--ratio, 1));
  font-weight: 600;
  letter-spacing: .06em;
  line-height: 1.2;
  font-variant-numeric: lining-nums tabular-nums;
  color: var(--accent);
}
.cat-sec-label {
  margin: 0;
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 0 .4em;
  font-family: var(--cat-face);
  font-weight: 600;
  font-size: clamp(21px, 2.2vw, 30px);
  letter-spacing: -.01em;
  line-height: 1.2;
  color: var(--text-hi);
}
/* 第二声部：同字号，转常规字重与灰色 */
.cat-sec-hint {
  font-size: inherit;
  font-weight: 400;
  letter-spacing: inherit;
  color: var(--text-low);
}

/* ── 幅：一个她一整幅 ──
   **不再满幅出血**（08-03 改）。原来是 width:100vw + margin-inline 负值撑到视口两边，
   而 .cat-shell 是 max-width:1440 居中的——两个左边缘于是不是同一条：
   2560 宽的屏上实测，节头「整娃 全身」落在 x≈624，幅里的「01 EMBER」落在 x≈56，
   差 568px。近处看每一块都对，退开看整页没有一条能对齐的竖线，就是「怪」的来源。
   （1440 及以下两条边差不了多少，所以这毛病只在宽屏上现形——
     我一直在 1440 截图验收，漏了它。）
   现在收进同一条内容栏，并给圆角：苹果首页那些整块图的模块也是这个形状——
   居中定宽的栏里放一块圆角容器，图在容器里出血。图一点没变小，
   变的是它和其他东西共用一条边线。 */
.worlds {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 380px), 1fr));
  gap: 1px;                                  /* 这 1px 就是两个世界之间的缝 */
  background: var(--line-subtle, rgb(255 255 255 / .14));
  border-radius: var(--radius-200, 16px);
  overflow: clip;                            /* 让两块图跟着圆角一起裁 */
}

.world {
  position: relative;
  isolation: isolate;
  min-height: clamp(420px, 74svh, 760px);
  display: grid;
  align-content: end;
  background: var(--tone-0, #08080f);
  overflow: clip;
}
.world-plate { position: absolute; inset: 0; display: block; }
.world-plate img {
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: 50% var(--fy, 34%);
  /* 只压亮度不动色相：商品颜色必须是真的，冷暖两个世界靠下面那道幕分 */
  filter: brightness(.92);
  transition: transform 1.1s var(--ease-emphasis, cubic-bezier(.645,.045,.355,1)), filter var(--dur-slow, .4s);
}
.world:hover .world-plate img { transform: scale(1.035); filter: brightness(1); }

/* 自下而上的幕。它干两件事，缺一不可：
   ① 给压在图上的字一个地基，否则对比度随画面漂移；
   ② 让两个世界一眼分冷暖——**颜色只加在幕上，不加在图上**。
      商品图不许调色（客人要看到真实颜色），冷暖差别只能来自它周围的色场。
   取的是这一幅自己的色阶：data-theme 挂在 .world 上，vendor 主题表在这一小块里
   重绑整套 --tone-*（ember 的 tone-4 是 #321532 偏紫红，azure 是 #1c1e40 偏藏青）。
   **各档的位置是照字块实际落点定的，不是拍脑袋的好看数字**：
   _tmp-cat.mjs 量出 .world-id 占这一幅的 50%–100%（自上往下，含内边距），
   实际有字的一段是自下往上的 5%–45%，所以 94% 那档必须一直撑到 30%、84% 撑到 42%。
   前两版退得太早，contrast-site.py 逐条打出来：
   〔待品牌方：对外产品名〕3.32、〔定价与规格〕4.30，都压在打亮的皮肤上。
   ⚠ 改字块的行数/行距/内边距，这几档要跟着重新量——它们是绑在一起的。
   代价是照片底下 45% 被盖住；那一段本来就是肩胸，盖掉对目录页只有好处。 */
.world::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: linear-gradient(to top,
    color-mix(in oklab, var(--tone-0, #08080f) 97%, transparent) 0%,
    color-mix(in oklab, var(--tone-0, #08080f) 94%, transparent) 30%,
    color-mix(in oklab, var(--tone-2, #111127) 84%, transparent) 42%,
    color-mix(in oklab, var(--tone-4, #1c1e40) 44%, transparent) 54%,
    color-mix(in oklab, var(--tone-5, #21254c) 14%, transparent) 64%,
    transparent 76%);
}

/* 竖排那句话落在幅的外侧边距上，而边距上是什么颜色**由照片说了算**：
   Ember 那幅外侧正好是一堵打亮的灰墙，实测字/底 = 4.07，够不着 4.5。
   text-shadow 不解决这件事——它只在字周围一圈，量的是字底下那块像素。
   所以给外侧一道竖直的暗角，和底下那道幕同一个道理：**先有地基再放字**。
   顺带它把两幅从外面收了一下边，双联的形状更立得住。 */
.world::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  width: clamp(72px, 10%, 150px);
  z-index: 1;
  pointer-events: none;
  background: linear-gradient(to right,
    color-mix(in oklab, var(--tone-0, #08080f) 68%, transparent), transparent);
}
.worlds:has(> .world:nth-child(2):last-child) > .world:last-child::before {
  inset: 0 0 0 auto;
  background: linear-gradient(to left,
    color-mix(in oklab, var(--tone-0, #08080f) 68%, transparent), transparent);
}

/* 那句话竖排，落在幅的外侧边距。玄关与两条卷轴一路是这个写法。
   ⚠ 这里必须用物理的 left/right。逻辑属性按**元素自己的** writing-mode 解析，
     在 vertical-rl 下 inset-inline-start 指的是 top 不是 left——
     第一版写 inset-inline-start，实测字贴死在幅的左边缘（x 与幅同值），
     镜像那条规则看着像"没生效"，其实是两条规则都在改 top。 */
.world-say {
  position: absolute;
  z-index: 2;
  top: clamp(26px, 6svh, 60px);
  left: clamp(16px, 2.4vw, 38px);
  margin: 0;
  writing-mode: vertical-rl;
  max-height: 56%;
  font-size: calc(13px * var(--ratio, 1));
  letter-spacing: .2em;
  color: var(--text-hi);
  text-shadow: 0 1px 14px var(--tone-0, #08080f), 0 0 4px var(--tone-0, #08080f);
}
/* 两幅相对时字在外侧：右边那幅的字贴右边缘。
   :has 限定「正好两幅」——只剩一幅时它是 :last-child，不该被镜像到右边。 */
.worlds:has(> .world:nth-child(2):last-child) > .world:last-child .world-say {
  left: auto;
  right: clamp(16px, 2.4vw, 38px);
}

.world-id {
  position: relative;
  z-index: 2;
  display: grid;
  justify-items: start;
  gap: 8px;
  /* 横向内缩与 .obj 卡取同一档：两种登记法是两种卡，但卡里的字要在同一条内缩线上 */
  padding: clamp(22px, 4svh, 44px) clamp(20px, 2.2vw, 32px);
}
.world-name { text-decoration: none; display: grid; justify-items: start; gap: 2px; }
/* 序号降成 eyebrow，和节头那一档同款：小、半粗、主题色，压在代号上面 */
.world-no {
  font-family: var(--cat-face-no);
  font-size: calc(12px * var(--ratio, 1));
  font-weight: 600;
  letter-spacing: .06em;
  font-variant-numeric: lining-nums tabular-nums;
  color: var(--accent);
}
/* 苹果把 MacBook Air 这类产品名设成 SF Semibold、字距 normal，靠字号立住 */
.world-code {
  font-family: var(--cat-face-code);
  font-size: clamp(32px, 4vw, 60px);
  font-weight: 600;
  line-height: 1.05;
  letter-spacing: -.02em;
  color: var(--text-hi);
}
.world-nm, .world-meta { margin: 0; }
.world-acts { margin-top: clamp(12px, 2svh, 22px); }

/* ── 有正式名的卡：大标题里装的就是名字（代号已全站退场，08-12）──
   中文名比 EMBER 这类拉丁词占宽得多，同字号会顶到卡缘：60px × 2 个汉字 = 120px 起，
   四个字就 240px——所以中文名单独压一档字号，并允许在窄卡里换行。 */
.world-id.has-name .world-code {
  font-size: clamp(28px, 3.2vw, 46px);
  letter-spacing: .02em;          /* 汉字负字距会粘连，拉丁词那套不能照搬 */
  line-height: 1.15;
}

/* ── 架：三张商品卡 ──
   照苹果 /shop 的卡：一块抬起来的圆角面，名字在**图的上面**，图坐在卡里，
   卖点与价格在图下，购买动作压在卡底。卡与卡之间只有间距，没有分隔线。
   之前是「器物坐在一条台面线上」——那是上一套（细线分层）的语法，
   跟报头改成苹果路线之后的版面不是一套东西，留着就是半苹果。
   卡底色用 --text-hi 兑出来而不是写死灰值：每张卡自带 data-theme，
   底色跟着它那一档的文字色走，暗色主题下不会出现一块发闷的死灰。 */
.shelf {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
  gap: clamp(16px, 1.6vw, 24px);
  align-items: stretch;                     /* 卡等高：苹果那一排卡是齐的 */
}
/* 一节只有一件时，别让卡撑满整行。
   2026-08-07 新增第三档「头身一体」，那一节只有一件——auto-fit 会把空轨道折掉，
   于是这一张卡吃掉整行 1300px，一件 45cm 的半身件在版面上比上面那两个 172cm 的
   「幅」还大，层级读反了。限宽到 34%，与小件那排三张卡同宽（实测 1440 档下
   三栏各 417px，34% ≈ 416px），一节多少件都排在同一条视觉基线上。
   860px 以下不限：那一档本来就是单栏，卡满宽才对。 */
@media (min-width: 861px) {
  .shelf:has(> .obj:only-child) > .obj { max-width: 34%; }
}
.obj {
  display: grid;
  grid-template-rows: auto auto 1fr;        /* 名 / 图 / 正文，正文吃掉剩下的，动作贴底 */
  gap: clamp(14px, 2svh, 20px);
  padding: clamp(20px, 2.2vw, 32px);
  border-radius: var(--radius-200, 16px);
  background: color-mix(in oklab, var(--text-hi) 6%, transparent);
  transition: background var(--dur-base, .25s);
}
.obj:hover { background: color-mix(in oklab, var(--text-hi) 9%, transparent); }

.obj-head { display: grid; justify-items: start; gap: 4px; }
/* 序号压在代号**上面**，跟节头和幅里的同一个写法。
   原来是并排（03 LIPS），于是同一个 eyebrow 在页面上有两种排法，
   代号的起始位置也跟着差出一个序号的宽度（实测 幅 32 / 架 61）。 */
.obj-name { text-decoration: none; display: grid; justify-items: start; gap: 2px; }
.obj-no {
  font-family: var(--cat-face-no);
  font-size: calc(12px * var(--ratio, 1));
  font-weight: 600;
  letter-spacing: .06em;
  font-variant-numeric: lining-nums tabular-nums;
  color: var(--accent);
}
.obj-code {
  font-family: var(--cat-face-code);
  font-size: clamp(19px, 1.7vw, 24px);
  font-weight: 600;
  letter-spacing: -.01em;
  color: var(--text-hi);
}
/* 同 .world-id.has-name：大标题装名字（代号已退场）。
   小件卡本来就窄（三张并排），中文名不压字号会把「加入询单篮」挤下去。 */
.obj-head.has-name .obj-code {
  font-size: clamp(18px, 1.55vw, 22px);
  letter-spacing: .02em;
}

.obj-plate {
  display: block;
  position: relative;
  aspect-ratio: 4 / 3;
  overflow: clip;
  border-radius: calc(var(--radius-200, 16px) - 6px);
  background: var(--bg-inset, #111);
}
.obj-plate img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: 50% var(--fy, 48%);
  filter: brightness(.92);
  transition: transform var(--dur-slow, .4s) var(--ease-emphasis, cubic-bezier(.645,.045,.355,1)), filter var(--dur-slow, .4s);
}
.obj:hover .obj-plate img { transform: scale(1.04); filter: brightness(1); }

/* 三行：卖点 / 价格占位 / 动作。第三行 1fr + align-self:end，
   让「加入询单篮」不论上面两行多高都贴在卡底同一条线上——
   苹果那排卡的购买按钮就是齐的，卡里文案长短不影响。 */
.obj-body { display: grid; justify-items: start; gap: 6px; grid-template-rows: auto auto 1fr; }
.obj-nm, .obj-meta { margin: 0; }
.obj-say {
  margin: 0;
  font-size: clamp(14px, 1.2vw, 16px);
  line-height: 1.5;
  letter-spacing: 0;
  color: var(--text-mid);
}
.obj-acts { align-self: end; padding-top: clamp(10px, 1.6svh, 16px); }

/* ── 动作组（幅与架共用）── */
.cat-acts { display: grid; justify-items: start; gap: clamp(8px, 1.4svh, 13px); }
.cat-acts-links { display: flex; flex-wrap: wrap; gap: 4px clamp(14px, 1.8vw, 24px); }

/* 形体事实行：「这件东西长什么样」，是逐张看图核实过的客观描述，不是卖点。
   必须和待填的卖点占位在排版上分得开——两者长得一样，会让人以为占位那句
   也是已确认的信息。事实走正文色 + 主题色引边，占位仍是全站统一的虚框。
   （列表页 08-01 起不再显示 form，只有详情页用；规则留着给 .item-form。） */
.item-form {
  margin: 0;
  color: var(--text-mid);
  line-height: 1.62;
  letter-spacing: .04em;
  font-size: calc(14px * var(--ratio, 1));
  padding-left: 12px;
  border-left: 2px solid color-mix(in oklab, var(--accent) 42%, transparent);
}

/* ── 册尾：还没定的维度 ──
   图录把体例与待补条目放末页——看完货再看规矩。 */
.cat-colophon {
  margin-top: clamp(24px, 5svh, 60px);
  padding: clamp(22px, 2.6vw, 36px);
  border-radius: var(--radius-200, 16px);
  background: color-mix(in oklab, var(--text-hi) 4%, transparent);
}
.cat-colophon-head {
  margin: 0 0 clamp(14px, 2.2svh, 22px);
  display: flex; align-items: center; gap: 9px;
  font-weight: 500;
  font-size: clamp(14px, 1.2vw, 16px);
  letter-spacing: 0;
  color: var(--text-mid);
}
/* 册尾从一节变成两节（册例 / 还筛不了的）之后才暴露的：原来的标题只有下外边距，
   第二个标题于是紧贴着上一节的最后一行，两节读成一节。 */
.cat-colophon-list + .cat-colophon-head { margin-top: clamp(20px, 3.2svh, 34px); }
.cat-colophon-n {
  font-family: var(--cat-face-no);
  font-size: calc(11px * var(--ratio, 1));
  letter-spacing: 0;
  color: var(--text-low);
  border: 1px solid var(--line-subtle, rgb(255 255 255 / .16));
  border-radius: var(--radius-pill, 999px);
  padding: 1px 7px;
}
.cat-colophon-list {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 168px), 1fr));
  gap: clamp(12px, 1.8svh, 20px) clamp(16px, 2.4vw, 36px);
}
/* 区分「待定」靠形状（虚线引边），不靠把字调暗——
   老坑记在文件开头 .ph 那段：容器透明度是把说明文字一起压的钝器，实测压到过 3.34。 */
.cat-colophon .facet.is-pending {
  padding-left: 11px;
  border-left: 1px dashed var(--line-subtle, rgb(255 255 255 / .14));
}
.cat-colophon .facet-label { margin: 0 0 4px; letter-spacing: 0; font-size: calc(13px * var(--ratio, 1)); }
.cat-colophon .ph { font-size: calc(12.5px * var(--ratio, 1)); line-height: 1.6; display: block; }
.cat-colophon .facet[hidden] { display: none; }

/* ── 册例（ready 但只有一个选项的维度）──
   与「还筛不了的」用**实线**区分虚线：那边是还没有的东西，这边是已经确定的事实。
   引边同色系但不同笔法，比调字色更能说清两者不是一类。 */
.cat-colophon .facet.is-fact {
  padding-left: 11px;
  border-left: 1px solid color-mix(in oklab, var(--accent) 46%, transparent);
}
.facet-fact {
  margin: 0;
  display: flex;
  align-items: baseline;
  gap: 7px;
  font-size: calc(14px * var(--ratio, 1));
  letter-spacing: .02em;
  color: var(--text-hi);
}
/* 这一行不是按钮，别让它长得像按钮：无胶囊底、无 hover */
.facet-fact-v { color: var(--text-hi); }

/* 筛空时的出口。不套 .cat-roster-note 那套灰字叙述——这里要的是一个动作。 */
.cat-empty {
  display: grid;
  justify-items: start;
  gap: 16px;
  padding: clamp(28px, 6svh, 60px) 0;
}
.cat-empty-line {
  margin: 0;
  font-size: calc(14px * var(--ratio, 1));
  letter-spacing: .08em;
  color: var(--text-mid);
}
.cat-empty-reset {
  appearance: none;
  cursor: pointer;
  font: inherit;
  font-size: calc(12.5px * var(--ratio, 1));
  letter-spacing: .12em;
  color: var(--text-hi);
  background: color-mix(in oklab, var(--accent) 14%, transparent);
  border: 1px solid var(--accent);
  border-radius: var(--radius-pill, 999px);
  padding: 9px 20px;
}

/* 预览备注块：只在 ?preview=1 时出现。做成「明显不属于这一页」的样子是故意的——
   它一旦长得像正文，就会重演 08-01 之前那件事：内部工作记录（含「甲方」「items[]」
   「L3/L2」「全库最薄」）当商品说明译成五语种推给顾客。
   虚线框 + 主题色角标 + 只有中文，三重信号说明它是给自己人看的。 */
.dev-note {
  display: block;
  margin: clamp(32px, 6svh, 64px) 0 0;
  max-width: 68ch;
  padding: 16px 18px;
  border: 1px dashed color-mix(in oklab, var(--accent) 46%, transparent);
  border-radius: var(--radius-200, 16px);
  background: rgb(255 255 255 / .02);
}
.dev-note-tag {
  margin: 0 0 10px;
  font-size: calc(11px * var(--ratio, 1));
  letter-spacing: .26em;
  color: var(--accent);
}
.dev-note-body {
  margin: 0 0 8px;
  font-size: calc(12px * var(--ratio, 1));
  line-height: 1.95;
  color: var(--text-low);
}
.dev-note-body:last-child { margin-bottom: 0; }

/* ⚠ 2026-08-01 起零匹配：唯一用它的两段（roster_note / media_note）已改为
   只在 ?preview=1 下渲染、且套 .dev-note。这条规则**故意留着不删**——
   品牌方补出对外措辞后，「在册几款、其余型号何时上」是该有一段正文说明的，
   到时把那段接回来就用这套样式。留着的成本是十行 CSS，删了再重写的成本是重新调一遍。
   （项目无 git，删除即永久丢失，这也是不删的理由之一。） */
.cat-roster-note {
  margin: clamp(28px, 5svh, 56px) 0 0;
  max-width: 62ch;
  font-size: calc(12.5px * var(--ratio, 1));
  line-height: 2.1;
  letter-spacing: .05em;
  color: var(--text-low);
  padding-top: clamp(16px, 2.6svh, 26px);
  border-top: 1px solid var(--line-subtle, rgb(255 255 255 / .1));
}

/* ── 视图 2 · 详情 ── */

/* 商详两列：图钉在左列（滚动时跟着，规格再长也一直看得见她），
   右列自上而下是身份 → 定制 → 规格；画廊与页脚横跨两列 */
.item-view {
  display: grid;
  grid-template-columns: minmax(0, 36%) 1fr;
  column-gap: clamp(24px, 4.5vw, 70px);
  row-gap: clamp(30px, 5svh, 60px);
  align-items: start;
}
.item-view > .item-media-wrap { grid-column: 1; grid-row: 1 / span 2; align-self: stretch; min-width: 0; }
.item-view > .item-side  { grid-column: 2; grid-row: 1; }
.item-view > .item-specs { grid-column: 2; grid-row: 2; }
.item-view > .item-gallery,
.item-view > .item-plates,
.item-view > .item-guide,
.item-view > .item-foot  { grid-column: 1 / -1; }

.item-media {
  /* sticky 挂在 .item-media-wrap（真正的网格项）里而不是网格项自身：
     网格项直接 sticky 时 Chrome 的活动范围约束失效，实测越过两行格区
     压进画廊 174px。壳是普通块级包含块，天花板/地板都清清楚楚。 */
  position: sticky;
  top: clamp(88px, 13svh, 132px);
  aspect-ratio: 2 / 3;
  max-height: 72svh;
  overflow: clip;
  isolation: isolate;
  border-radius: var(--radius-200, 16px);
  border: 1px solid color-mix(in oklab, var(--accent) 20%, transparent);
  background: var(--bg-inset, #111);
  box-shadow: 0 30px 90px rgb(0 0 0 / .5);
}
.item-media img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: 50% var(--fy, 40%); }

/* 代号是当前唯一真实的标识，就让它当主标题；正式产品名还没有，
   占位小字排在它下面——不给一个空标题占着版面最大的字号 */
.item-side { display: grid; gap: clamp(14px, 2.2svh, 26px); }
.item-code {
  margin: 0;
  font-weight: 600;
  font-size: clamp(28px, 3.6vw, 50px);
  letter-spacing: -.02em;
  text-indent: 0;
  color: var(--text-hi);
}
.item-name { margin: -8px 0 0; font-size: calc(12px * var(--ratio, 1)); }
/* 有正式名（JS 加 .has-name，2026-08-12）：h1 名字接任大标——沿用被撤代号
   那一档字号，汉字用 +.02em 字距（负字距对汉字是粘连，列表卡 08-04 已记）；
   代号这一页一个字母都不留。 */
.item-side.has-name .item-name {
  margin: 0;
  font-weight: 600;
  font-size: clamp(28px, 3.6vw, 50px);
  letter-spacing: .02em;
  line-height: 1.15;
  color: var(--text-hi);
}
.item-tag { margin: 0; font-size: calc(15px * var(--ratio, 1)); letter-spacing: .04em; color: var(--text-mid); }
.item-acts { display: flex; flex-wrap: wrap; align-items: center; gap: clamp(14px, 2vw, 28px); }

.item-h2 {
  margin: 0 0 clamp(14px, 2.2svh, 22px);
  font-weight: 400;
  font-size: calc(12.5px * var(--ratio, 1));
  letter-spacing: .18em;
  color: var(--text-mid);
}
.item-custom { padding-top: clamp(10px, 1.8svh, 20px); border-top: 1px solid var(--line-subtle, rgb(255 255 255 / .1)); }
.item-custom-list { margin: 0; padding-left: 1.2em; color: var(--text-hi); font-size: calc(14px * var(--ratio, 1)); line-height: 2.1; }

.item-specs { margin-top: 0; }
.spec-group { margin-bottom: clamp(28px, 5svh, 54px); }
.spec-group-label {
  margin: 0 0 clamp(10px, 1.6svh, 16px);
  font-weight: 400;
  font-size: calc(13px * var(--ratio, 1));
  letter-spacing: .14em;
  color: var(--text-hi);
}
.spec-table { margin: 0; max-width: 76ch; }
.spec-row {
  display: grid;
  grid-template-columns: minmax(88px, 148px) 1fr;
  gap: clamp(14px, 2.4vw, 32px);
  align-items: baseline;
  padding: clamp(12px, 1.9svh, 20px) 2px;
  border-bottom: 1px solid var(--line-subtle, rgb(255 255 255 / .08));
}
.spec-row:first-child { border-top: 1px solid var(--line-subtle, rgb(255 255 255 / .08)); }
.spec-row dt { color: var(--text-mid); font-size: calc(13px * var(--ratio, 1)); letter-spacing: .12em; }
.spec-row dd { margin: 0; color: var(--text-hi); font-size: calc(14px * var(--ratio, 1)); letter-spacing: .05em; }
.spec-fieldset-note {
  margin: 12px 0 0;
  font-size: calc(11.5px * var(--ratio, 1));
  letter-spacing: .12em;
  color: var(--text-low);
}

.item-gallery { margin-top: clamp(36px, 7svh, 80px); }
.item-gallery-strip {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr));
  gap: clamp(12px, 1.8vw, 22px);
}
.item-shot {
  margin: 0;
  position: relative;
  aspect-ratio: 4 / 3;
  overflow: clip;
  border-radius: var(--radius-150, 12px);
  border: 1px solid var(--line-subtle, rgb(255 255 255 / .1));
  background: var(--bg-inset, #111);
}
.item-shot img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; filter: brightness(.92); }

.item-foot {
  display: flex;
  flex-wrap: wrap;
  gap: clamp(20px, 4vw, 56px);
  margin-top: clamp(40px, 7svh, 84px);
  padding-top: clamp(20px, 3.4svh, 34px);
  border-top: 1px solid var(--line-subtle, rgb(255 255 255 / .1));
}

/* ── 视图 3 · 对比 ── */

.cmp-head { margin-bottom: clamp(26px, 4.5svh, 52px); }
.cmp-note {
  margin: clamp(12px, 2svh, 20px) 0 0;
  max-width: 58ch;
  font-size: calc(13.5px * var(--ratio, 1));
  line-height: 2;
  color: var(--text-mid);
  letter-spacing: .05em;
}

/* 同一行=同一字段：标签列固定，条目列等分，横向溢出自己滚（表格永不撑破页面） */
.cmp-table { display: grid; overflow-x: auto; }
.cmp-row {
  display: grid;
  grid-template-columns: minmax(84px, 132px) repeat(var(--cols, 2), minmax(140px, 1fr));
  gap: clamp(12px, 2vw, 28px);
  align-items: baseline;
  padding: clamp(11px, 1.7svh, 18px) 2px;
  border-bottom: 1px solid var(--line-subtle, rgb(255 255 255 / .08));
}
.cmp-cell { min-width: 0; color: var(--text-hi); font-size: calc(13.5px * var(--ratio, 1)); letter-spacing: .05em; }
.cmp-cell--label { color: var(--text-mid); font-size: calc(12.5px * var(--ratio, 1)); letter-spacing: .12em; }

.cmp-row--head { align-items: end; border-bottom-width: 1px; padding-bottom: clamp(16px, 2.6svh, 26px); }
.cmp-cell--card { display: grid; gap: 10px; justify-items: start; }
/* 表头图只是「这一列是谁」的标识，不是主角——固定矮框，
   让第一屏就能同时看见两列的头与前几行规格（2:3 高框实测吃掉整屏） */
.cmp-media {
  display: block;
  position: relative;
  width: 100%;
  height: clamp(160px, 24svh, 240px);
  overflow: clip;
  border-radius: var(--radius-200, 16px);
  border: 1px solid color-mix(in oklab, var(--accent) 20%, transparent);
  background: var(--bg-inset, #111);
}
.cmp-media img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: 50% var(--fy, 40%); filter: brightness(.9); }
.cmp-code { margin: 0; font-size: clamp(19px, 1.6vw, 22px); font-weight: 600; letter-spacing: -.01em; color: var(--text-hi); }
.cmp-name { margin: 0; }
/* 同 .world-id.has-name：大标装正式名，内部代号一个字母都不留（2026-08-12）。
   中文名不换字号只松字距——卡宽比列表卡宽，压字号会让
   两列的名字比下面整张规格表还小，层级反而倒。 */
.cmp-cell--card.has-name .cmp-code { letter-spacing: .02em; line-height: 1.25; }

.cmp-row--group {
  border-bottom-color: color-mix(in oklab, var(--accent) 28%, transparent);
  padding-top: clamp(22px, 3.6svh, 40px);
}
.cmp-row--group .cmp-cell--label { color: var(--text-hi); letter-spacing: .14em; }

/* ── 视图 4 · 询单 ── */

.inq-view { display: grid; gap: clamp(30px, 5.5svh, 62px); max-width: 74ch; }
.inq-empty { margin: 0 0 16px; color: var(--text-mid); font-size: calc(14px * var(--ratio, 1)); letter-spacing: .06em; }
.inq-list { list-style: none; margin: 0; padding: 0; }
.inq-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  padding: clamp(12px, 2svh, 18px) 2px;
  border-bottom: 1px solid var(--line-subtle, rgb(255 255 255 / .08));
}
.inq-row:first-child { border-top: 1px solid var(--line-subtle, rgb(255 255 255 / .08)); }
.inq-name { text-decoration: none; display: flex; align-items: baseline; gap: clamp(12px, 2vw, 22px); }
.inq-code { font-size: clamp(16px, 1.4vw, 18px); letter-spacing: -.01em; color: var(--text-hi); }
.inq-tag { font-size: calc(12.5px * var(--ratio, 1)); letter-spacing: .12em; color: var(--text-mid); }
.inq-remove {
  appearance: none; cursor: pointer; border: 0; background: transparent;
  color: var(--text-low); font: inherit; font-size: calc(12px * var(--ratio, 1)); letter-spacing: .2em;
  transition: color var(--dur-base, .25s);
}
.inq-remove:hover { color: var(--text-hi); }

.inq-topic-row { display: flex; flex-wrap: wrap; gap: 10px; }
.inq-topic {
  appearance: none; cursor: pointer;
  border: 1px solid var(--line-subtle, rgb(255 255 255 / .12));
  background: transparent; color: var(--text-mid); font: inherit;
  font-size: calc(12.5px * var(--ratio, 1)); letter-spacing: .14em;
  padding: 9px 16px; border-radius: var(--radius-pill, 999px);
  transition: color var(--dur-base, .25s), border-color var(--dur-base, .25s), background var(--dur-base, .25s);
}
.inq-topic:hover { color: var(--text-hi); border-color: color-mix(in oklab, var(--accent) 40%, transparent); }
.inq-topic.is-on { color: var(--text-hi); border-color: var(--accent); background: color-mix(in oklab, var(--accent) 14%, transparent); }

.inq-memo-input, .inq-out {
  width: 100%;
  box-sizing: border-box;
  background: var(--bg-inset, #111);
  border: 1px solid var(--line-subtle, rgb(255 255 255 / .12));
  border-radius: var(--radius-150, 12px);
  color: var(--text-hi);
  font: inherit;
  font-size: calc(13.5px * var(--ratio, 1));
  line-height: 1.9;
  letter-spacing: .04em;
  padding: 14px 16px;
  resize: vertical;
}
.inq-memo-input:focus, .inq-out:focus { outline: 1px solid color-mix(in oklab, var(--accent) 55%, transparent); }
.inq-out { font-family: var(--font-mono, monospace); color: var(--text-mid); }
.inq-output { display: grid; gap: clamp(14px, 2.4svh, 24px); justify-items: start; }
.inq-channel { margin: 0; }

/* ── 常驻询单条 ── */

#basket-bar {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: var(--z-sticky, 100);
  display: flex;
  align-items: center;
  gap: clamp(14px, 2.4vw, 30px);
  padding: 14px var(--page-gutter, clamp(20px, 4vw, 64px));
  background: color-mix(in oklab, var(--bg-base) 88%, transparent);
  /* ⚠ 原写法 `backdrop-filter: var(--blur-glass, blur(14px))` **从未生效过**：
     tokens.css 的 --blur-glass 是 `13px`——一个长度，不是滤镜函数。
     `backdrop-filter: 13px` 在计算期非法 ⇒ 整条声明归零（计算值实测 none），
     而回落 `blur(14px)` 因为令牌**有定义**，永远轮不到。
     结果：这条常驻底栏只剩 88% 不透明底色、零模糊，正文从它身下滚过去时清晰可辨，
     字压在乱纹上。要用的是 --glass-filter（= blur(...) saturate(1.4)），
     与 navtools.css / portal.css 同款，并补上全站只有这里漏掉的 -webkit- 前缀。 */
  -webkit-backdrop-filter: var(--glass-filter);
  backdrop-filter: var(--glass-filter);
  border-top: 1px solid color-mix(in oklab, var(--accent) 26%, transparent);
}
#basket-bar[hidden] { display: none; }
.basket-count {
  font-size: calc(12.5px * var(--ratio, 1));
  letter-spacing: .1em;
  color: var(--text-hi);
  white-space: nowrap;
}
.basket-names {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: calc(12px * var(--ratio, 1));
  letter-spacing: .16em;
  color: var(--text-mid);
}
.basket-open {
  color: var(--text-hi);
  text-decoration: none;
  font-size: calc(12.5px * var(--ratio, 1));
  letter-spacing: .1em;
  white-space: nowrap;
  border-bottom: 1px solid color-mix(in oklab, var(--accent) 40%, transparent);
  padding-bottom: 3px;
}
.basket-open:hover { border-color: var(--accent); }

/* ── 窄屏 ── */

/* ── 小件的商详：图是主角 ────────────────────────────────────
   甲方清单文档「结论 A」：小件是低单价、看图就决定的冲动型；整娃是长决策、
   反复比较规格。**别用同一个模板硬套**——整娃那套「图钉在左 36%、规格占右 2/3」
   照搬到小件上，会让一件靠画面成交的东西看起来像一张待填的空表（实测截图如此）。
   ⇒ 小件版式：大图 + 短信息并排 → 整幅画廊 → 规格表退到最后。
   顺序靠显式 grid-row 改，不动 DOM 顺序（DOM 顺序对读屏与无 CSS 时仍是对的）。 */
body[data-category="piece"] .item-view { grid-template-columns: minmax(0, 46%) 1fr; }
body[data-category="piece"] .item-view > .item-media-wrap { grid-row: 1; }
body[data-category="piece"] .item-view > .item-side { grid-row: 1; align-self: center; }
/* 阅读顺序 = 决策顺序：先看清它长什么样（证据板）→ 再看其余机位 →
   然后才是参数表 → 拿不定主意就看三款横比。
   规格表夹在中间不是笔误：它眼下十行全是占位，放在最前面等于开场先给一张空表。 */
body[data-category="piece"] .item-view > .item-plates   { grid-row: 2; grid-column: 1 / -1; }
body[data-category="piece"] .item-view > .item-gallery  { grid-row: 3; grid-column: 1 / -1; }
body[data-category="piece"] .item-view > .item-specs    { grid-row: 4; grid-column: 1 / -1; }
body[data-category="piece"] .item-view > .item-guide    { grid-row: 5; grid-column: 1 / -1; }
/* 不粘、不夹高度：信息栏很短，图没有"被规格表拖着走"的问题；
   ⚠ 这里不能给 max-height——.item-media 是 aspect-ratio 撑的，
     高度一旦被 max 夹住，宽度会跟着缩回去（门厅双开门踩过同一个坑）。 */
body[data-category="piece"] .item-media { position: relative; top: auto; max-height: none; }
/* 画廊放大到两张一排：小件的做工细节（螺纹、转折、光泽）在小图里看不出来 */
body[data-category="piece"] .item-gallery-strip {
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 340px), 1fr));
}
body[data-category="piece"] .item-shot { aspect-ratio: 3 / 2; }

/* 首屏向下的钩子 */
.item-cue {
  display: inline-flex;
  align-items: baseline;
  gap: 10px;
  text-decoration: none;
  font-size: calc(12.5px * var(--ratio, 1));
  letter-spacing: .16em;
  color: var(--text-mid);
  transition: color .2s;
}
.item-cue:hover { color: var(--accent); }
.cue-arrow { color: var(--accent); }

/* ── 证据板 ────────────────────────────────────────────────
   一图一句，图与句左右并排、逐块左右互换。为什么不做成三张一排的网格：
   这几张的信息全在微观纹理里，缩到三分之一宽就只剩一片肉色，正好把
   「凑近看」这件事取消掉。宁可一页只走三块，也要每块大到看得见颗粒。 */
.item-plates { margin-top: clamp(36px, 7svh, 80px); display: grid; gap: clamp(28px, 5svh, 64px); }
.plate {
  margin: 0;
  display: grid;
  grid-template-columns: minmax(0, 1.55fr) minmax(0, 1fr);
  gap: clamp(18px, 3vw, 44px);
  align-items: center;
}
.plate:nth-of-type(even) { grid-template-columns: minmax(0, 1fr) minmax(0, 1.55fr); }
.plate:nth-of-type(even) > .plate-img { order: 2; }

.plate-img {
  overflow: clip;
  border-radius: var(--radius-200, 16px);
  border: 1px solid color-mix(in oklab, var(--accent) 18%, transparent);
  background: var(--bg-inset, #111);
  box-shadow: 0 24px 70px rgb(0 0 0 / .45);
}
/* ⚠ 这里绝不能用 object-fit: cover + 固定 aspect-ratio。
   五张图的裁框是逐张开到 100% 像素看过、按分级规程定下来的；CSS 再裁一刀
   等于让浏览器替我重新构图，验过的那一版就不再是屏幕上这一版。
   自然高度 = 屏幕上看到的就是我核过的那个画面。 */
.plate-img img,
.plate-img video { display: block; width: 100%; height: auto; }

/* 动态板给得比静图大一点：它是这条线唯一能直接给出「手感」的东西，
   缩到和静图一样宽就没了优势。同时取消左右互换——视频永远在左，
   进页面第一眼落在动的东西上。 */
.plate--video { grid-template-columns: minmax(0, 1.9fr) minmax(0, 1fr); }
.plate--video > .plate-img { order: 0; }

.plate-cap { display: grid; gap: clamp(8px, 1.4svh, 14px); align-content: center; }
.plate-kicker {
  font-size: calc(11px * var(--ratio, 1));
  letter-spacing: .34em;
  text-transform: uppercase;
  color: var(--accent);
}
.plate-line {
  margin: 0;
  font-size: calc(17px * var(--ratio, 1));
  line-height: 1.85;
  color: var(--text-hi);
  text-wrap: pretty;
}
/* 「原图裁切 · 未放大」：这一类的页面里放大插值与渲染图是常态，
   所以这行小字是有信息量的声明，不是装饰——配方在 _build/build-assets.sh。 */
.plate-meta {
  font-size: calc(11px * var(--ratio, 1));
  letter-spacing: .2em;
  color: var(--text-mid);
  padding-top: 2px;
}

/* ── 三款怎么选 ─────────────────────────────────────────── */
.item-guide { margin-top: clamp(36px, 7svh, 80px); }
.guide-table {
  margin-top: clamp(14px, 2.4svh, 24px);
  border: 1px solid var(--line-subtle, rgb(255 255 255 / .1));
  border-radius: var(--radius-200, 16px);
  overflow: clip;
}
.guide-row {
  display: grid;
  grid-template-columns: minmax(96px, 168px) repeat(var(--cols, 3), minmax(0, 1fr));
  border-top: 1px solid var(--line-subtle, rgb(255 255 255 / .08));
}
.guide-row:first-child { border-top: 0; }
.guide-cell {
  padding: clamp(11px, 1.7svh, 17px) clamp(10px, 1.6vw, 20px);
  font-size: calc(14px * var(--ratio, 1));
  color: var(--text-hi);
  min-width: 0;
}
.guide-cell--label { color: var(--text-mid); letter-spacing: .08em; }
.guide-cell--head { display: grid; gap: 3px; align-content: center; text-decoration: none; }
.guide-code { font-size: clamp(16px, 1.4vw, 18px); letter-spacing: -.01em; color: var(--text-hi); }
/* 当前这一款整列点亮：读者的第一个问题是「我在看的是哪一列」 */
.guide-cell.is-cur { background: color-mix(in oklab, var(--accent) 11%, transparent); }
.guide-cell--head.is-cur .guide-code { color: var(--accent); }
a.guide-cell--head:hover .guide-code { color: var(--accent); }
/* 待品牌方的整行：一条虚线格横跨所有款，不逐列重复同一句「待填」 */
.guide-cell--span { grid-column: 2 / -1; }
.guide-row--pending .guide-cell--label { opacity: .78; }
.guide-note {
  margin: clamp(10px, 1.6svh, 16px) 0 0;
  font-size: calc(12.5px * var(--ratio, 1));
  line-height: 1.9;
  color: var(--text-mid);
}

@media (max-width: 760px) {
  /* 窄屏图文左右并排会把图压成一条，颗粒又没了——竖着来，图仍是满宽 */
  .plate,
  .plate:nth-of-type(even) { grid-template-columns: 1fr; }
  .plate:nth-of-type(even) > .plate-img { order: 0; }
  /* 三列横比在窄屏里每列不到 90px，标签行先掉到自己一行 */
  .guide-row { grid-template-columns: repeat(var(--cols, 3), minmax(0, 1fr)); }
  .guide-cell--label { grid-column: 1 / -1; padding-bottom: 2px; font-size: calc(12px * var(--ratio, 1)); }
  .guide-cell--span { grid-column: 1 / -1; }
}

@media (max-width: 860px) {
  /* 报头本来就是单栏左对齐了（改苹果路线后不再有右侧那一簇），这里只收留白。
     纵向留白是直接从「第一屏能看到几件东西」里扣的——390×844 实测过一次
     筛选区 650px＝77% 视口、首屏 0 件商品。 */
  .cat-mast {
    row-gap: clamp(14px, 2.4svh, 22px);
    margin-bottom: clamp(24px, 4svh, 40px);
  }
  .cat-mast-tools { gap: clamp(10px, 1.8svh, 16px); }
  .cat-sub { line-height: 1.6; }

  /* 竖排那句话在 390 下要占掉大半屏高——退回横排，压在幅的底部、代号上方。
     这是版式退化不是内容删减：字一个没少，只是躺下来了。 */
  .world-say {
    position: static;
    writing-mode: horizontal-tb;
    max-height: none;
    padding: 0 clamp(18px, 2.6vw, 42px);
    margin-bottom: 4px;
    letter-spacing: .04em;
    line-height: 1.7;
  }
  .world { min-height: clamp(360px, 62svh, 560px); }
  /* 那句话已经横过来了，外侧暗角没有要垫的东西——留着只是把 390 宽的幅
     无故啃掉左边 18%。它是为竖排字服务的，字不竖排就该撤。 */
  .world::before { display: none; }
  /* 横排后左右两边都不再定位，镜像规则必须一起撤干净 */
  .worlds:has(> .world:nth-child(2):last-child) > .world:last-child .world-say {
    left: auto; right: auto;
  }

  /* 小件的显式行列指派同样要撤——否则单列下画廊还钉在第 2 行、规格在第 3 行，
     而其余块都成了 auto，顺序会乱成一团（比不改还糟） */
  body[data-category="piece"] .item-view { grid-template-columns: 1fr; }
  body[data-category="piece"] .item-view > .item-media-wrap,
  body[data-category="piece"] .item-view > .item-side,
  body[data-category="piece"] .item-view > .item-specs,
  body[data-category="piece"] .item-view > .item-plates,
  body[data-category="piece"] .item-view > .item-guide,
  body[data-category="piece"] .item-view > .item-gallery { grid-column: 1; grid-row: auto; align-self: auto; }

  /* 单列：图在最上，其余按 DOM 顺序落下来。显式行列指派必须一起撤，
     只改 grid-template-columns 会让 .item-specs 仍被钉在第 2 列 */
  .item-view { grid-template-columns: 1fr; }
  .item-view > .item-media-wrap,
  .item-view > .item-side,
  .item-view > .item-specs,
  .item-view > .item-gallery,
  .item-view > .item-foot { grid-column: 1; grid-row: auto; }
  /* 窄屏不粘：图一粘住，正下方的规格表就得从图底下钻过去。
     必须写 relative 不能写 static——里面那张 img 是 absolute inset:0，
     父级一退成 static，它的包含块就换成初始包含块，整张图会盖满全页 */
  .item-media { position: relative; top: auto; max-width: 380px; margin-inline: auto; width: 100%; }
}

@media (max-width: 640px) {
  .cat-shell { padding-bottom: clamp(120px, 18svh, 180px); }
  .spec-row { grid-template-columns: 1fr; gap: 4px; }
  .cmp-row { grid-template-columns: minmax(76px, 96px) repeat(var(--cols, 2), minmax(120px, 1fr)); }
  /* 表头那一格标签是空的，窄屏别让它白占 96px——头行让两列吃满整宽 */
  .cmp-row--head { grid-template-columns: repeat(var(--cols, 2), minmax(0, 1fr)); }
  .cmp-row--head > .cmp-cell--label { display: none; }
  /* 按钮在窄列里会折成两行，把两张卡的底边推得高低不齐 */
  .cmp-cell--card .cat-add { white-space: nowrap; padding-inline: clamp(14px, 4vw, 22px); }
  .inq-row { flex-wrap: wrap; }
}

/* 长语种下导航容易换行：窄屏先收紧间距，实在放不下再换行——
   换行本身不再是 bug（顶距已跟着 --nav-h 走），只是尽量别发生 */
@media (max-width: 620px) {
  .world-nav-links { gap: clamp(10px, 3vw, 18px); row-gap: 4px; flex-wrap: wrap; justify-content: flex-end; }
  .world-nav-links a { font-size: calc(11.5px * var(--ratio, 1)); letter-spacing: .14em; }
}


/* ── 进场与跨页切换（2026-07-30，品牌方点名：滚动动画与切换升级） ── */

/* 渲染宿主的进场：keyframe 对 JS 新插入的顶层块自动播一次，backwards 补住
   延迟期的第一帧不闪白。列表页筛选只换 #view 里的节，顶层不重播。
   滚动揭示（.vx-reveal，随 jb:rendered 由 VX.init 补挂）管折叠线以下，
   这段管首屏进场，两者不重叠。

   08-03：列表页的顶层孩子从「筛选栏 + 结果区」变成「正文 + 册尾」两块，
   分节后真正该逐块进场的是节（#view 的孙辈）与卷首里 JS 填进去的工具。
   只留 `#view > *` 会让整页正文当成一块一起淡入——节奏没了，也看不出分节。 */
@keyframes cat-enter {
  from { opacity: 0; transform: translateY(16px); }
}
#view > *,
#inquiry-view > *,
.cat-mast-tools > *,
.cat-body > .cat-sec {
  animation: cat-enter .55s var(--ease-entrance, cubic-bezier(.16, 1, .3, 1)) backwards;
}
#view > :nth-child(2), #inquiry-view > :nth-child(2) { animation-delay: 90ms; }
#view > :nth-child(3), #inquiry-view > :nth-child(3) { animation-delay: 160ms; }
#view > :nth-child(4), #inquiry-view > :nth-child(4) { animation-delay: 220ms; }
#view > :nth-child(n+5), #inquiry-view > :nth-child(n+5) { animation-delay: 270ms; }
.cat-mast-tools > :nth-child(2) { animation-delay: 70ms; }
.cat-body > .cat-sec:nth-child(2) { animation-delay: 120ms; }
.cat-body > .cat-sec:nth-child(n+3) { animation-delay: 190ms; }

/* 跨页切换：同源跨文档 View Transition。四个目录页共用本文件=彼此之间都开门；
   主图挂了 view-transition-name: hero-<sku>（catalog.js），列表 ⇄ 详情之间
   那张图做连续变形，其余内容旧页淡出新页淡入。不支持的浏览器 = 普通跳转，零成本降级。 */
@view-transition { navigation: auto; }
::view-transition-old(root) { animation-duration: .22s; }
::view-transition-new(root) { animation-duration: .3s; }

@media (prefers-reduced-motion: reduce) {
  /* 减弱动效下要把上面新加的两条一起关掉——只关 `#view > *` 会漏掉节与卷首工具，
     那正是这一版真正在动的两块（改判据就得回头扫一遍沿用它的地方）。 */
  #view > *, #inquiry-view > *,
  .cat-mast-tools > *, .cat-body > .cat-sec { animation: none; }
  @view-transition { navigation: none; }
}
