/* ==========================================================================
   风格子页骨架 · style-page.css   2026-10-01
   --------------------------------------------------------------------------
   适用：style-yuanmu / style-zhonggu / style-yishi / style-qingfashi /
        style-bauhaus / style-duobaan（与 style-page.js 配套使用）

   页面侧只需提供三样东西，其余全在本文件：
     ① .sb 上的颜色变量 —— 每页主色不同，公共层给的是中性兜底值
     ② --sb-serif —— 本页衬线字体栈（配合页面内联的 @font-face）
     ③ .sb-hero-sketch 的 background-image —— **6 页均已配真针管笔线稿**（10-04 补完，
        此前「去色实景」只是无手稿时的临时兜底，现已无页在用）。
        🔴 线稿必须与同页 hero.webp **同比例**（做到同尺寸最省心）：页面把线稿叠在实景上
        （实景 <img object-fit:cover> + 线稿 background-size:cover），只有同比例，
        两层在任意视口下的裁切方式才一致，开孔处「线稿的沙发」才对得上「实景的沙发」。
        ⚠️ 实测六页比例均一致（1.615–1.617）⇒ 渲染无错位；其中原木/意式/中古/轻法式
        **同尺寸**，包豪斯 1526×944 与多巴胺 1150×712 尺寸不同但比例一致 ⇒ 维持原样
        （强行放大到 1536 反而会让线稿发糊，得不偿失）。

   🔴 收编进生成器时本文件整块搬进 assets/css/page-style.css，页面侧三样照旧。
   ========================================================================== */

.sb {
  /* 文字 */
  --sb-ink: var(--navy-800);
  --sb-ink-rgb: 20, 34, 28;
  /* 次级文字（骨架里 12 处：章节序号 / 说明 / tab 标签 / caption / 提示 / 图例 /
     元素 / 房间 / 色卡附注 / 风险正文 / 尾注）。
     🔴 2026-10-08 评审 §五 + 风格子页专项：原为**冷灰** #5A6572（R−B = −24），
     而骨架自己的中性系**全是暖的** —— paper #EDECE5 (+8) / line #E9E9DD (+12) /
     card #F5F6F0 (+5) / viewer #EFEEE6 (+9) ⇒ 六页都成了「冷灰次级文字压在暖纸面上」，
     暖主色页（意式 +113 / 中古 +103 / 轻法式 +100）色温差最刺眼。
     改为同族暖灰 #635F58（R−B = +11）：亮度基本持平（L .1270 → .1154），
     对 paper #EDECE5 由 5.01:1 → **5.36:1**，12 处只升不降。
     ⚠️ 在**骨架里统一改**，不开页面侧覆盖 —— 否则六页会出现 6 个不同的中性值，
        正是这次要消除的那类漂移。 */
  --sb-mut: #635F58;
  /* 纸：首屏压在文字下的纸面、指引行的胶囊底 */
  --sb-paper: #EDECE5;
  --sb-paper-rgb: 237, 236, 229;
  /* 主色（每页覆盖这五个） */
  --sb-accent: #1D5549;
  --sb-accent-2: #C1521F;
  --sb-accent-dark: #17453C;
  --sb-accent-soft: #EDF1EC;
  --sb-accent-wash: #C9D2BC;
  /* 主色「作底 / 作字」专用档位：承载白字或压在白底上时必须够深（AA 4.5:1）。
     默认跟随主色 ⇒ 不覆盖的页面行为不变；浅主色页在页面里覆盖成压暗档。
     🔴 与 --sb-accent 分开的理由：--sb-accent 还用于边框 / 热点圆点 / 图例点 / focus 环，
        那些场合要的是品牌色本身，不能为了文字对比度被一起压暗。 */
  --sb-accent-bg: var(--sb-accent);
  /* 中性浅底 */
  --sb-line: #E9E9DD;
  /* 控件边框。🔴 与 --sb-line 分开的理由：--sb-line 是**装饰性**分隔线 / 卡片描边
     （.sb-el / .sb-sw / .sb-room 都用它），可以很淡；未选中的 .sb-tab 是**需要被看见的
     可点控件**，白底上要 #D8DCE2（1.35:1）而不是 #E9E9DD（1.19:1）。
     ⚠️ 2026-10-08 评审 §五「.sb-tab 边框硬编码 #D8DCE2」的原建议是「统一到 --sb-line」——
     实测那样会让控件边界**变淡**（1.35→1.19），是退步；正解是**令牌化而不换值**。 */
  --sb-ctrl-line: #D8DCE2;
  /* 06 段「最容易翻车的三个点」卡片的记号（圆角 + 顶栏粗细）。
     🔴 2026-10-19 客户口径：**六个风格页统一成中古风那版**（直角 + 3px 顶栏），
        不再按各风格几何语言分叉（此前的「上平下圆 14px / 20px 大圆角 / 8px 厚顶栏」
        在同站里读作三种不同的卡片组件）。六个页面的同名覆盖已删除 ⇒ 值只此一处。
     ⚠️ 仍保留为变量：`.sb-risk` 靠它们取几何，将来要改只改这里。 */
  --sb-risk-r: 0;
  --sb-risk-t: 3px;
  /* 06 段序号列（`.sb-risk b.no`）的字号 —— **令牌化而不换值**（同 --sb-ctrl-line 的原则）：
     fs 令牌表最大只到 --fs-h3（20–26px），而序号是装饰性大数字、本就跨档；
     写裸字面会被设计令牌审计的 B1「阶梯外基线冻结」判红 ⇒ 按本文件既有 --sb-* 模式收进来。 */
  --sb-risk-no: 34px;
  --sb-card: #F5F6F0;
  --sb-viewer: #EFEEE6;
}
.sb-display { font-family: var(--sb-serif, 'Noto Serif SC', 'Songti SC', serif); }

/* ---- hero：一屏 · 滚动即走（2026-10-25 取消钉住）----
   历史：2026-10-02 起是「220vh 钉住 + 两段进度」——滚动前 0.7 屏画面不动、只淡文案；
   2026-10-25 先收成 170vh + 一次性揭图，业主实机仍反馈「按上去拖不动 / logo 在闪」⇒ 整段钉住删除。
   现在只有一条规则：用户一动手指（scrollY > 14）就加 `.is-revealing`，手稿 0.8s 淡出露效果图；
   滑回顶部（< 6px）摘掉，手稿淡回来、上色可再用。**没有任何逐帧写入**
   （--sb-p1 / --sb-p2 / --sb-out 全部删除，滚动只剩一次 class 切换）。
   ⚠️ `assets/styles/**` 的版本号由 `tools/stamp_assets.py` 维护（md5）⇒ 改完必须跑它，
      否则六个风格页仍吃旧缓存。 */
/* 驻留段高度 = 一屏 + 0.34 屏。🔴 上限就是 0.4 屏：再长就回到「按住拖不动」那一版
   （门禁里有结构判据卡这一条，不是靠人记）。 */
.sb-hero-pin { position: relative; height: 134vh; height: 134svh; }
/* 🔴 驻留段只有 **0.34 屏**（2026-10-25 第三版）：sticky 保留，但行程从 0.7 屏砍到 0.34 屏。
   三轮实机反馈的取舍：
     v1 `170vh`（0.7 屏行程）⇒ 「按上去拖不动」：591px 里只有标题在淡，画面几乎不动；
     v2 去 sticky         ⇒ 「一滑就走」：开屏没有停留感，揭晓还没看清就进正文了；
     v3 现在 0.34 屏      ⇒ 有停留、但每一段都有反馈，且越不过一屏（见下）。
   每段的反馈都排满了，没有「什么都不动」的空档：
     0 → 0.34×屏高  标题/副标按进度线性淡出（--sb-p1，逐帧写入，只此一个变量）
     0.10×屏高      揭晓（一次性 0.55s 过渡，手稿淡出露效果图）
     余下            「继续下滑」提示淡入（延迟 0.55s）
   ⚠️ 页头在驻留段内保持透明，由 shared.js 按 `.sb-hero-pin` 的实际高度算阈值（不再有 pinHold
      与页头逻辑互相摘挂 .scrolled 的老问题：那才是「logo 在闪」的真因）。 */
.sb-hero { position: sticky; top: 0; height: 100vh; height: 100svh; display: flex; align-items: flex-end; color: var(--sb-ink); overflow: hidden;
  /* 触屏 / 触控笔：横向手势交给上色，纵向手势仍归页面滚动 */
  touch-action: pan-y; -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; }
.sb-hero-bg { position: absolute; inset: 0; }
.sb-hero-bg img { width: 100%; height: 100%; object-fit: cover; }
/* scrim 只压实景图：不留可读性兜底职责（文字兜底 = 贴字的纸色柔光 text-shadow），只留一点层次感。 */
.sb-hero-scrim { position: absolute; inset: 0; z-index: 2; background:
    linear-gradient(94deg, rgba(20,24,19,.34) 0%, rgba(20,24,19,.28) 38%, rgba(20,24,19,.08) 60%, rgba(20,24,19,0) 76%),
    linear-gradient(180deg, rgba(20,24,19,.04) 0%, rgba(20,24,19,.03) 44%, rgba(20,24,19,.34) 100%); }

/* 2026-10-27 V-13：文案压在针管笔线稿上 —— 白字落在稿线亮处直接失读。
   在文字块背后补一块暖白衬底（文字是深墨色，压暗会让字消失）；
   pointer-events:none，不挡「划过上色」与按钮。 */
/* 2026-10-27 V-13：标题与副标压在针管笔线稿上，笔画与稿线互相穿插。
   ⚠️ 试过两种衬底都不行：整幅压暗会把地面糊成脏带；暖白面板在浅色稿纸上等于没压。
   最后用**纸色光晕**（文字外两圈暖白 shadow）—— 只把字背后的线抹掉，不落一块面板。 */
.sb-h1,
.sb-hero-sub,
.sb-hero-hint {
  text-shadow:
    0 0 6px rgba(250, 248, 241, .98),
    0 0 14px rgba(250, 248, 241, .92),
    0 0 26px rgba(250, 248, 241, .72);
}


/* 顶部压暗必须独立于手稿层：手稿被「上色」开孔后，孔内会露出浅色实景（浅天花板）
   把白 logo / 白导航吃掉。它常驻，因此也顺带修掉了「线稿收起时导航压浅色实景」的老问题。 */
.sb-hero-topguard { position: absolute; left: 0; right: 0; top: 0; height: 176px; z-index: 4; pointer-events: none;
  background: linear-gradient(180deg, rgba(20,24,19,.78) 0%, rgba(20,24,19,.58) 34%, rgba(20,24,19,.2) 68%, rgba(20,24,19,0) 100%); }
/* 标题/副标跟着驻留段进度线性淡出（`--sb-p1` = 0→1，JS 逐帧写，**全站只此一个逐帧变量**）。
   整个 0.34 屏行程里它一直在变 ⇒ 没有「什么都不动」的空档，这正是 v1 被投诉的地方。
   滚动期必须关掉 transition（`.is-scrolling`），否则淡出会拖半拍；
   点「看完整效果图」时（`.is-painted`）仍走 transition。 */
.sb-hero .sb-h1, .sb-hero .sb-hero-sub {
  opacity: clamp(0, calc(1 - var(--sb-p1, 0)), 1); transition: opacity .4s ease; }
.sb-hero.is-scrolling .sb-h1, .sb-hero.is-scrolling .sb-hero-sub { transition: none; }
/* 进入全图态后文案收掉 */
.sb-hero.is-painted .sb-h1, .sb-hero.is-painted .sb-hero-sub,
.sb-hero.is-painted .sb-hero-hint { opacity: 0; }
/* 揭图后收掉提示与「看完整效果图」：先淡出、再摘可点性
   （只归零 opacity 的话，键盘还能 Tab 到一个看不见的按钮）。
   ⚠️ visibility 必须 0s + 延迟到淡出结束：立刻 hidden 会让两个元素「啪」地消失。 */
.sb-hero.is-revealing .sb-hero-hint,
.sb-hero.is-revealing .sb-hero-paint {
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity .35s ease, visibility 0s linear .35s; }
.sb-hero-in { position: relative; z-index: 5; width: 100%; max-width: 1200px; margin: 0 auto; padding: 0 var(--space-6) var(--space-16); }
/* 手稿层：整幅底稿，靠遮罩开一个「上色孔」——孔内透出下层（scrim 压过的实景）= 已上色，
   孔外仍是未上色态。孔位由 JS 写 px 变量 --sb-mx/--sb-my（默认在屏外 ⇒ 未交互时是干净底稿）。
   ⚠️ background-image 由页面提供：
     · 有手稿线稿 → url('assets/styles/<页>/sketch.webp')
     · 没有 → 去色实景（实景图 + grayscale 滤镜 + 一层纸色薄雾），占位期也能完整演示上色
   后者记得在页面里加 filter: grayscale(1) brightness(1.1) contrast(.92); */
.sb-hero-bg .sb-hero-sketch { position: absolute; inset: 0; z-index: 3; pointer-events: none;
  background-color: var(--sb-paper);
  background-position: center; background-size: cover; background-repeat: no-repeat;
  transition: opacity .55s cubic-bezier(.33, 0, .2, 1);
  -webkit-mask-image: radial-gradient(circle var(--sb-r, 300px) at var(--sb-mx, -900px) var(--sb-my, -900px), transparent 0 52%, rgba(0,0,0,.8) 74%, #000 100%);
          mask-image: radial-gradient(circle var(--sb-r, 300px) at var(--sb-mx, -900px) var(--sb-my, -900px), transparent 0 52%, rgba(0,0,0,.8) 74%, #000 100%);
  /* 揭图 = **一次性**过渡（2026-10-25 改）：
     原先是 `opacity: calc(1 - var(--sb-rev))` 由 JS 逐帧写变量，等于每帧都让一个整屏、
     带 mask 的图层重新合成/重栅格化。改成「越阈值加 .is-revealing ⇒ 0.8s 过渡」后，
     每帧成本归零，观感不变（手稿淡出、效果图露出）。
     📉 实测（12× 降频）关掉逐帧揭图对帧率**零改善**；同日又把 sticky 钉住整段删除
        （34ms/31 帧超时 → 18ms/1 帧）⇒ 本层现在每次揭图只做一次 .8s 合成，滚动期零成本。 */
  opacity: 1; }
.sb-hero.is-revealing .sb-hero-sketch { opacity: 0; }
/* 过渡期临时提升为合成层（只在这 1 秒内有效，结束后由 JS 摘掉类，避免长期占一张整屏纹理） */
.sb-hero.is-melting .sb-hero-sketch { will-change: opacity; }
.sb-hero.is-painted .sb-hero-sketch { opacity: 0; }
/* 看完整效果图 / 回手绘稿：键盘与不便精细操作的用户靠它拿到全图 */
.sb-hero-paint { margin-top: var(--space-3-5); font: inherit; font-size: var(--fs-small); letter-spacing: .05em; color: #fff;
  padding: var(--space-3) var(--space-6); border: 1px solid var(--sb-accent-bg); border-radius: var(--radius-full);
  background: var(--sb-accent-bg);
  cursor: pointer; opacity: 1;
  transition: background-color .25s ease, border-color .25s ease, color .25s ease, opacity .25s ease; }
.sb-hero-paint:focus-visible { outline: 2px solid rgba(var(--sb-ink-rgb), .55); outline-offset: 3px; }
/* 🔴 引导条常驻：与滚动进度彻底解耦。从开屏第一帧起就可见，
   直到用户真的划过（.is-touched）或进全图态（.is-painted）才收。
   胶囊底给整行垫一层「干净纸」—— 小字压在手稿线 / 实景上本来就弱，
   垫底之后才读得出来，而且与左下角的按钮同族，视线接得住。
   ⚠️ 胶囊底必须用 ::before 的 inset 撑出来、不占布局：用 padding 撑的话，初始态这一行会涨高，
   把下面的按钮整体推下去。
   ⚠️ 必须块级 flex + fit-content：用 inline-flex 的话，下面的 <button>（inline-block）
   会跟它排到同一行，左下角的「引导 + 按钮」垂直节奏就散了。
   行内排布（demo 走 inline-block + margin-right）—— 不要用 flex：
   flex item 是块级盒，Range.getClientRects() 取不到文字行盒，探针会报「未测到」。
   🔴 2026-10-09：胶囊底**开屏态也常驻一档**（`max(--sb-guide, .5)` ⇒ alpha .44）。
   深墨 12.5px 小字无底时压在手稿线 / 浅色实景上会不达标（6 页 × 3 断点里 7/18，最差 2.64，门槛 4.5）。
   2026-10-25 去钉住后 `--sb-guide` 不再由 JS 写 ⇒ 恒取默认 0 ⇒ 实际恒为 .5（就是这一档），
   胶囊不再有「亮起」过程；要恢复必须在 JS 里重新给一个写入口。
   🔴 2026-10-28（V-13，客户拍板「就 ⑦」）：**整句文案退役，只留这条演示笔触**。
   客户原话是「下面一大坨，用户基本不会看」⇒ 把 21 个字的操作说明删掉，只留那条来回扫的线
   （它自己就在演示「左右划」）。
   ⇒ 同时**拆掉胶囊底**：那层 `::before` 本来就是为了让 12.5px 小字过 4.5:1 门槛才加的
     （见上方 10-09 那条），文字没了就没有可读性问题，底也就没有必要 ——
     而且客户明确要「不要额外遮罩」。真要有字，先看 `_preview/v13-preview.html` 第 ③ 格：
     无底的小字在这个画面上**根本读不出来**，所以别再把它加回来。 */
.sb-hero-hint { position: relative; display: block; width: fit-content; max-width: 100%;
  margin: var(--space-3) 0 0; font-size: var(--fs-nano); line-height: 17px; letter-spacing: .04em;
  opacity: .92; transition: opacity .4s ease; }
/* （`.sb-hero-hint::before` 胶囊底已于 2026-10-28 随文案一起退役，见上） */
/* 🔴 2026-10-25：这三条是**补上原本缺失的实现**。注释里一直写着「划过（.is-touched）就收」，
   但 CSS 里从来没有 `.is-touched` 规则 ⇒ 引导条与它的无限动画在整屏 hero 上永远跑着
   （`sbHintSweep` 3.6s infinite，合成器无法闲置，滚动时白抢一帧）。JS 侧同步改成只对触屏加这个类：
   鼠标用户靠这段提示学会「移动光标即上色」，划一下就把提示收掉反而没学会。 */
.sb-hero.is-touched .sb-hero-hint { opacity: 0; visibility: hidden; }
.sb-hero.is-touched .sb-hint-demo,
.sb-hero.is-revealing .sb-hint-demo { animation: none; }
/* 🔴 2026-10-28（V-13）：操作说明改为**视觉隐藏、读屏可读**。
   「只留动画」是视觉决定，不能把无障碍一起删掉 —— 同一段 <p> 里演示笔触是 `aria-hidden`，
   文案是读屏用户唯一能知道「可以划」的信息源。用 sr-only（1px + clip-path）而不是
   `display:none` / `font-size:0`：后两者会把内容从无障碍树里摘掉。 */
.sb-hint-sr { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
/* 引导演示：一条「灰 → 主色 → 跳色」的笔触 + 末端指针，左右轻移 = 「划过去就上色」 */
.sb-hint-demo { display: inline-block; position: relative; z-index: 1; vertical-align: middle;
  width: 100px; height: 15px; margin-right: 0;
  animation: sbHintSweep 3.6s cubic-bezier(.45,.05,.55,.95) infinite; }
.sb-hint-trail { position: absolute; left: 0; top: 50%; width: 74px; height: 3px; margin-top: -1.5px;
  border-radius: var(--radius-sm);
  background: linear-gradient(90deg, rgba(var(--sb-ink-rgb), 0) 0%, rgba(var(--sb-ink-rgb), .42) 30%,
              var(--sb-accent) 64%, var(--sb-accent-2) 100%); }
.sb-hint-cursor { position: absolute; left: 70px; top: 50%; width: 9px; height: 9px; margin-top: -4.5px;
  border-radius: 50%; background: var(--sb-accent-2);
  box-shadow: 0 0 0 2px rgba(255,255,255,.9); }
@keyframes sbHintSweep { 0%, 100% { transform: translateX(0); } 50% { transform: translateX(13px); } }
/* 上色孔会在纸上打洞（否则画面左半的纸块区看不见上色），中小字就会短暂压在实景上。
   给它们一层「纸色柔光」兜底：阴影色 = 纸色 ⇒ 纸面上与底色同色、肉眼不可见，
   只在实景上把字从沙发 / 棋盘格里托出来。大标题不加 —— 墨色大字本身就压得住。
   ⚠️ 2026-10-02 撤掉了「柔雾」大底（用户要通透感）：兜底只剩这一层贴字的柔光，
   不再有任何铺在文字块下方的色块 / 光晕。要调可读性只动这两个球。 */
.sb-hero-sub, .sb-hero-hint {
  text-shadow: 0 1px 3px rgba(var(--sb-paper-rgb), .9), 0 0 14px rgba(var(--sb-paper-rgb), .62); }
/* 「继续下滑」提示 —— 第二段把整幅效果图露完之后，压在屏幕下方提示继续往下。
   ⚠️ 节点由脚本注入（style-page.js），六页 HTML 零改动。
   显隐跟着揭图状态走（2026-10-25 改）：`.is-revealing` 一出现就淡入，`transition-delay .55s`
   —— 让手稿先淡完、眼睛落回画面，提示再出现，两件事同时发生会显得急。
   点「看完整效果图」（.is-painted）时同样亮起 —— 那也进了一次全图态。
   不垫任何底、不加胶囊（要通透）：可读性只靠「白字 + 深色柔光」。
   🔴 配色方向按实测定的，别再改回深墨：1440×900 六页 hero 底带（此提示所在的那一条）
     实测对比度 —— 白字 4.4 / 11.3 / 11.2 / 11.6 / 16.9 / 19.2，深墨字只有 1.0 ~ 3.7。
     hero 图全是深色图（地板 / 深家具压底），深字在上面直接糊掉。
   纯视觉提示：不接收指针、不进 Tab 序、不读屏。 */
.sb-hero-scrollcue { position: absolute; left: 50%; bottom: 26px; z-index: 6;
  transform: translateX(-50%);
  display: flex; flex-direction: column; align-items: center; gap: 7px;
  pointer-events: none; color: #fff; font-weight: 500;
  font-size: var(--fs-small); line-height: 1; letter-spacing: .14em; text-indent: .14em;
  /* 2026-10-25：提示的显隐不再由 --sb-rev 逐帧推导（那个变量已随逐帧揭图一起删除），
     改成跟着「揭图是否已启动」一次性淡入，并**延后 .55s** —— 让手稿先淡掉、眼睛落回画面，
     提示再出现，否则两件事同时发生会显得急。 */
  opacity: 0;
  transition: opacity .5s ease .55s; }
.sb-hero.is-revealing .sb-hero-scrollcue { opacity: 1; }
.sb-hero.is-painted .sb-hero-scrollcue { opacity: 1; transition-delay: 0s; }
/* ⚠️ 别再给这里加「滚动期关 transition」：`.is-scrolling` 已随逐帧驱动一起删除，
   唯一保留的过渡就是这个延迟淡入，掐掉等于提示不出现。 */
.sb-cue-t { text-shadow: 0 1px 3px rgba(20,24,19,.72), 0 0 14px rgba(20,24,19,.5); }
.sb-cue-ic { display: block;
  filter: drop-shadow(0 1px 3px rgba(20,24,19,.72));
  animation: sbCueBob 1.8s ease-in-out infinite; }
@keyframes sbCueBob { 0%, 100% { transform: translateY(-2px); } 50% { transform: translateY(3px); } }
.sb-h1 { font-size: clamp(3.5rem, 9vw, 6rem); line-height: 1.06; letter-spacing: .02em; margin: 0 0 var(--space-4-5); font-weight: 700; }
/* 🔴 2026-10-24（UI 评审 §五.5）：上限 7.375rem(118px) → **6rem(96px)**。
   全站 H1 实测只有三档：**大 88–96**（81 页 + cases 89.6）/ **中 72–78**（styles 72 /
   首页 77.76 / craft 78.4）/ **内容 34–49**（services 34.56 / 资讯 40 / about 48.96）。
   118 是唯一的例外 —— 从 cases 点进风格页，主字号突然大 30%，读作「换了一套模板」。
   96 是「大」档的上限：风格页是视觉展示页，仍该是全站最大，但不能自成一档。
   ⚠️ 只改**上限**，不动 `clamp()` 的下限 3.5rem(56) —— 那是移动端的实测字号，
      390 下 9vw 只有 35px、必然落到下限，改下限等于改手机版。
   ⚠️ 中间项 9vw 不用动：1440 下 9vw=129.6 本来就被上限管住，改完上限才轮到它说话
      （1067px 以下开始生效，768 处 ≈69px，仍在上限与下限之间）。 */
.sb-hero-sub { font-size: clamp(1rem, 2vw, 1.3125rem); line-height: 1.75; max-width: 620px; text-wrap: balance; opacity: .9; margin: 0 0 var(--space-5-5); }

/* ---- 首屏开场「百叶揭幕」：效果图 → 设计手稿（2026-10-05）----
   与首页 hero-landing 同一套语法（SVG mask + 横向百叶错峰），叙事方向按子页的两张图定：
   ① 起手看效果图（本层 8 条满高 ⇒ 整幅盖住下层手稿）→ ② 百叶奇偶交错收起，手稿逐条
   透出来 → ③ 1.66s 后本层彻底撤场，画面完全交回 .sb-hero-sketch。
   ⇒ 一进页面就亲眼看到「同一空间有两张图：效果图 + 设计手稿」，不必先读一段解释。
   ⚠️ 本层承载的是**效果图**（不是手稿）：手稿层 .sb-hero-sketch 自始至终 opacity 不动，
      由本层盖住再揭开 —— 这样「滚动揭图」与「划过上色」两条既有链路一行都不用改。
   ⚠️ 为什么用 SVG mask 而不是 8 个 div 各做 scaleY：div 的 scaleY 会把自身背景一起
      压扁（图会变形），mask 只改可见区域、图不变形 —— 这正是 index 的原始机制。
   ⚠️ 🔴 viewBox 必须与本页效果图**同比例**（页面侧按图的真实像素写），<image> 的
      width/height 取满 viewBox：这样 `preserveAspectRatio="slice"` 与 CSS `cover` 的
      裁切方式才逐像素一致。若 viewBox 写成正方形（100×100），会先按正方形裁一遍再映射
      到视口 ⇒ 整幅被放大一档（本轮实测踩过：画面里的书本、沙发明显变大）。
   ⚠️ 撤场必须写 visibility（不能只做 opacity）：划过上色开的洞在下层 sketch 上，
      本层要真正退出绘制，否则洞会被整片挡住。
   ⚠️ 压暗不走 CSS filter / 不再铺一层 scrim：filter 是均匀的压不平 scrim 的方向性
      （实测左 1/3 偏亮 12.6%、右 1/3 偏暗 3.8%）；整层 scrim 又会把「已经透出来的手稿」
      一起压暗。正解是把它画进 svg 内部、与百叶共用同一 mask —— 见页面侧那两个渐变矩形。
   ⚠️ 纯 CSS 驱动、不依赖 JS：无 JS 照演，演完即终态（手稿 + 文案 + 按钮，内容零缺失）；
      prefers-reduced-motion ⇒ 本层与角标整块隐藏 ⇒ 直接手稿态。
   ⚠️ 页面侧只需在 .sb-hero-bg 里补一段 <div class="sb-hero-blinds"><svg …></svg></div>，
      image 的 href 用本页 hero —— 与 <img> 同 URL ⇒ 复用同一份缓存，零额外请求。 */
.sb-hero-blinds { position: absolute; inset: 0; z-index: 4; pointer-events: none;
  animation: sbBlindsAway .3s ease 1.66s forwards; }
.sb-hero-blinds svg { display: block; width: 100%; height: 100%; }
/* 🔴 静态基态写「收 0 + 全透明」，而不是「满高」—— 这一条是安全网：
   animation 若被取消 / 未启动 / 浏览器不支持，样式回落到静态声明 ⇒ 本层不可见 ⇒
   画面直接落在手稿态（而不是停在满铺实景上把「划过上色」的洞一起挡住）。
   起手那 0.26s 由 `both` 的 backwards fill 顶着 from（满高）⇒ 首帧仍是效果图。 */
.sb-hero-blinds mask rect { transform-box: fill-box; transform-origin: 50% 0; transform: scaleY(0); opacity: 0;
  animation: sbBlindsClose .78s cubic-bezier(.4,0,.2,1) both; }
/* 奇偶锚点相反 ⇒ 相邻两条朝相反方向收（贴上缘 / 贴下缘），读起来是「百叶」 */
.sb-hero-blinds mask rect:nth-child(even) { transform-origin: 50% 100%; }
.sb-hero-blinds mask rect:nth-child(1) { animation-delay: .26s; }
.sb-hero-blinds mask rect:nth-child(2) { animation-delay: .32s; }
.sb-hero-blinds mask rect:nth-child(3) { animation-delay: .38s; }
.sb-hero-blinds mask rect:nth-child(4) { animation-delay: .44s; }
.sb-hero-blinds mask rect:nth-child(5) { animation-delay: .50s; }
.sb-hero-blinds mask rect:nth-child(6) { animation-delay: .56s; }
.sb-hero-blinds mask rect:nth-child(7) { animation-delay: .62s; }
.sb-hero-blinds mask rect:nth-child(8) { animation-delay: .68s; }
/* 终点补 opacity:0 不是修饰：刻度收到 0 之后，rect 的边缘在非整数缩放下仍会留一行
   抗锯齿像素（mask 里是半透明白 ⇒ 实景残留 → 画面上横着几道细线）。归零才真正退场。 */
@keyframes sbBlindsClose {
  from { transform: scaleY(1); opacity: 1; }
  96% { opacity: 1; }
  to { transform: scaleY(0); opacity: 0; }
}
@keyframes sbBlindsAway { to { opacity: 0; visibility: hidden; } }
/* 2026-10-06 用户指令：首屏右下角的两枚角标胶囊（「效果图 · 设计意向」/「设计手稿 ·
   同机位线稿」）与其交接动画已整块移除；角标的类名不再出现在本文件的任何位置。 */
/* 🔴 顶部导航带必须让给手稿：实景层在顶部 176px 内渐隐，露出下层的纸白手稿。
   原因：子页未滚动时导航是「深色字」配色（本来压在浅色手稿上），而效果图是深色图 ——
   深色字压深色图读不出来。让顶部保持手稿，导航可读性与改动前完全一致。
   高度与 .sb-hero-topguard 的 176px 对齐；60 → 176px 走渐变，不出硬接缝。
   ⚠️ 这是 CSS mask，与 svg 内部那条 SVG mask 各作用各的元素 ⇒ 两层正好相乘，不冲突。 */
.sb-hero-blinds { -webkit-mask-image: linear-gradient(180deg, transparent 0, transparent 60px, #000 176px);
  mask-image: linear-gradient(180deg, transparent 0, transparent 60px, #000 176px); }
/* 文案随揭幕淡入，而不是起手就压上去：前 0.9s 只有画面 + 角标，0.9–1.45s 文案淡入 ——
   与手稿露出的节奏重合，深墨字自始至终落在纸白上。
   ⚠️ 仍走 filter 而不是 opacity：`.is-painted` / `.is-revealing` 用 opacity 控制这几个元素，
      两条链路的属性分开，改一条不会把另一条顶掉。 */
.sb-hero-in { animation: sbIntroText .55s ease .9s both; }
@keyframes sbIntroText { from { filter: opacity(0); } to { filter: none; } }

/* ---- 同机位对照（首屏之后第一块，2026-10-25）----
   为什么存在：首屏的「手稿 → 效果图」要用户停下来玩才看得到，快速划走的人一句都没带走
   （实测一次轻扫 ≈600px，首屏驻留只有 287px）。这里把**同一件事放进滚动路径**：
   滚到就自动播一次，整块是一个按钮，点一下就重播。
   ⚠️ 重播**不能**用「摘类 + 强制 reflow + 加类」：同一任务里摘了又加，目标值没变，
      Chrome 不会重启 transition —— 实测点了完全没反应。改用 WAAPI 重播（见 style-page.js）。
   ⚠️ 两张图必须同源同机位（`<slug>/hero.webp` + `<slug>/sketch.webp`），否则「同机位」是假的。 */
.sb-cmp-sec { padding-top: var(--space-16); }
.sb-cmp { position: relative; display: block; width: 100%; margin: 0; padding: 0; border: 0;
  overflow: hidden; border-radius: var(--radius-xl); aspect-ratio: 1536 / 950;
  background: var(--sb-paper); cursor: pointer; -webkit-tap-highlight-color: transparent; }
.sb-cmp img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
.sb-cmp-sk { clip-path: inset(0 0 0 0); }
.sb-cmp.is-in .sb-cmp-sk { clip-path: inset(0 0 0 100%); }
.sb-cmp:focus-visible { outline: 2px solid rgba(var(--sb-ink-rgb), .55); outline-offset: 3px; }
.sb-cmp-cap { margin: var(--space-5) 0 0; font-size: var(--fs-body); line-height: 1.9;
  color: var(--sb-mut); max-width: 46em; }
.sb-cmp-cap strong { color: var(--sb-ink); font-weight: 700; }
.sb-cmp-tip { margin: var(--space-2) 0 0; font-size: var(--fs-nano); letter-spacing: .1em;
  color: var(--sb-mut); opacity: .72; }
@media (prefers-reduced-motion: reduce) { .sb-cmp-sk { transition: none; } }

/* ---- 通用节 ---- */
.sb-sec { max-width: 1200px; margin: 0 auto; padding: var(--space-24) var(--space-6) 0; }
.sb-sec-head { display: flex; align-items: baseline; gap: var(--space-5-5); margin-bottom: var(--space-3-5); }
.sb-sec-no { font-family: 'Outfit', sans-serif; font-weight: 700; font-size: var(--fs-body-sm); letter-spacing: .18em; color: var(--sb-mut); }
.sb-sec-title { font-size: clamp(1.75rem, 4vw, 2.75rem); line-height: 1.2; margin: 0; color: var(--sb-ink); font-weight: 700; }
.sb-sec-desc { color: var(--sb-mut); font-size: var(--fs-body); line-height: 1.9; max-width: 720px; margin: 0 0 var(--space-10); }
/* 03 区说明按断点分流：桌面说「拖」，手机说「点」（手机是 2×3 静态格，拖拽体验弱） */
.only-mob { display: none; }


/* ---- 导语 ---- */
.sb-lead { font-size: clamp(1.25rem, 2.6vw, 1.75rem); line-height: 2.05; color: var(--sb-ink); max-width: 880px; margin: 0; }
.sb-lead strong { font-weight: 700; }

/* ---- 多视角 tabs ---- */
.sb-tabs { display: flex; gap: var(--space-2-5); flex-wrap: wrap; margin-bottom: var(--space-5-5); }
/* transition 只列真正会变的属性：all 会把 box-shadow / transform 一并纳入监听 */
.sb-tab { border: 1px solid var(--sb-ctrl-line); background: #fff; border-radius: var(--radius-full); padding: var(--space-3) var(--space-4-5); font-size: var(--fs-caption); color: var(--sb-ink); cursor: pointer; transition: border-color .22s ease, background-color .22s ease, color .22s ease; display: inline-flex; gap: 9px; align-items: baseline; }
.sb-tab b { font-family: 'Outfit', sans-serif; font-weight: 600; font-size: var(--fs-nano); color: var(--sb-mut); letter-spacing: .08em; }
.sb-tab:focus-visible { outline: 2px solid var(--sb-accent); outline-offset: 2px; }
.sb-tab[aria-selected="true"] { background: var(--sb-accent-bg); border-color: var(--sb-accent-bg); color: #fff; }
/* 2026-10-17：0.92 白压在风格主色上只有 4.21:1（12px 需 4.5）⇒ 纯白 4.64。 */
.sb-tab[aria-selected="true"] b { color: #fff; }
.sb-viewer { position: relative; border-radius: var(--radius-xl); overflow: hidden; background: var(--sb-viewer); }
.sb-viewer figure { margin: 0; display: none; }
.sb-viewer figure.on { display: block; animation: sbFade .45s ease; }
@keyframes sbFade { from { opacity: 0; } to { opacity: 1; } }
.sb-viewer img { width: 100%; display: block; aspect-ratio: 16/10; object-fit: cover; }
.sb-viewer figure:focus-visible { outline: 2px solid var(--sb-accent); outline-offset: -3px; }
.sb-cap { display: flex; justify-content: space-between; gap: var(--space-4); padding: var(--space-3-5) var(--space-1) 0; font-size: var(--fs-caption); color: var(--sb-mut); }
.sb-cap b { color: var(--sb-ink); font-weight: 600; }

/* ---- 俯视热点 ---- */
.sb-top-wrap { display: grid; grid-template-columns: minmax(0, 640px) minmax(280px, 1fr); gap: var(--space-12); align-items: start; }
.sb-top-map { position: relative; border-radius: var(--radius-xl); overflow: hidden; box-shadow: 0 18px 50px rgba(46,40,28,.16); }
.sb-top-map img { width: 100%; display: block; }
/* 🔴 2026-10-08 评审 §五：单色描边对**任何**底图都无解 —— 实测 36 个热点采样底图像素：
   白边对亮地板最低 1.66:1（qingfashi 底 rgb(208,200,187)）、主色实心圆对底最低 1.01:1
   （18/36 低于 1.5 = 视觉消失）；反过来在包豪斯/多巴胺的深色底上白边 15:1、深圈只有 1.04:1。
   ⇒ 正解是**双向描边**：白内圈（对深底可辨）+ 深外圈（对亮底可辨），取两者较大值。
   实测「max(白,深)」最坏点从 1.66 → 3.23（alpha .7），六页 36 个热点全部 ≥3.2:1。 */
.sb-hot { position: absolute; width: 26px; height: 26px; margin: -13px 0 0 -13px; border-radius: 50%; border: 2px solid #fff; box-shadow: 0 0 0 2px rgba(var(--sb-ink-rgb), .7); background: var(--sb-accent); cursor: pointer; z-index: 2; transition: transform .18s ease; }
.sb-hot::after { content: ""; position: absolute; inset: -7px; border-radius: 50%; border: 1.5px solid rgba(var(--sb-ink-rgb), .45); animation: sbPulse 2.2s ease-out infinite; }
@keyframes sbPulse { 0% { transform: scale(.6); opacity: .9; } 100% { transform: scale(1.5); opacity: 0; } }
.sb-hot.on { transform: scale(1.25); }
.sb-hot:focus-visible { outline: 2px solid var(--sb-ink); outline-offset: 3px; }
.sb-hot.on { background: var(--sb-accent-2); }
/* 🔴 pointer-events:none 不是可选优化 —— 气泡 z-index:3 高于 .sb-hot 的 2，
   展开后**下方的热点与图例全部点不动**（10-04 15:15 记录过桌面端症状，
   10-04 22:55 回归时在「图例逐个点击」上被 Playwright 当场抓到：
   `<b id="sbTipT">… intercepts pointer events`）。气泡是纯展示层，不需要接收指针。 */
.sb-tip { position: absolute; z-index: 3; width: 232px; background: #fff; border-radius: var(--radius-xl); box-shadow: 0 14px 40px rgba(46,40,28,.24); padding: var(--space-3); display: none; gap: var(--space-3); align-items: center; pointer-events: none; }
.sb-tip.on { display: flex; animation: sbFade .25s ease; }
.sb-tip img { width: 64px; height: 64px; border-radius: var(--radius-md); object-fit: cover; flex: none; }
.sb-tip b { display: block; font-size: var(--fs-caption); color: var(--sb-ink); margin-bottom: var(--space-1); }
.sb-tip span { font-size: var(--fs-nano); line-height: 1.65; color: var(--sb-mut); }
.sb-top-side h3 { font-size: var(--fs-body-lg); color: var(--sb-ink); margin: 0 0 var(--space-3); }
.sb-legend { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-1); }
.sb-legend button { display: flex; gap: var(--space-3); align-items: center; width: 100%; text-align: left; background: none; border: none; padding: var(--space-2-5) var(--space-3); border-radius: var(--radius-md); cursor: pointer; font-size: var(--fs-caption); color: var(--sb-ink); transition: background-color .18s ease; }
.sb-legend button.on { background: var(--sb-accent-soft); }
.sb-legend button:focus-visible { outline: 2px solid var(--sb-accent); outline-offset: -2px; }
.sb-legend i { width: 12px; height: 12px; border-radius: 50%; background: var(--sb-accent); flex: none; }
.sb-legend button.on i { background: var(--sb-accent-2); }
.sb-legend small { color: var(--sb-mut); margin-left: auto; font-size: var(--fs-nano); }

/* ---- 元素墙 ---- */
.sb-els { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-5-5);
  /* 03 区深底：卡片是「纯黑底 + 白描边」的贴纸，白卡容器会把描边吃掉（贴纸浮不起来） */
  background: #14120F; padding: var(--space-5); border-radius: var(--radius-xl); }
.sb-el { background: #1B1815; border: 1px solid rgba(237,230,218,.14); border-radius: var(--radius-xl); overflow: hidden; transition: transform .28s ease, box-shadow .28s ease; padding: var(--space-2-5); }
.sb-el-ph { position: relative; border-radius: var(--radius-md); overflow: hidden; }
.sb-el img { width: 100%; aspect-ratio: 4/3; object-fit: cover; display: block; }
.sb-el-in { padding: var(--space-3-5) var(--space-2) var(--space-4); }
.sb-el b { font-size: var(--fs-body); color: #EDE6DA; display: block; margin-bottom: var(--space-1-5); }
.sb-el span { font-size: var(--fs-small); line-height: 1.75; color: #9C948A; }

/* ---- 全屋空间 ----
   2026-10-10 换版式：白卡网格 → 无框满幅图墙。上一节 .sb-els 是「白底 + 边框」的卡片墙，
   本节原样照抄同一外形（只把 3 列改 4 列）⇒ 仍是「卡片网格」。现去掉白底与描边，
   并从 8/5 收到 4/3 让文字有处落。
   ⚠️ 8 张 + 2 行的条件下做不了「首图跨 2×2」的马赛克（4 格主图 + 7 张 = 11 格 > 8 格），
      只能排成 3 行、反而更高；故走「满幅拼贴」而不是跨格马赛克。

   🔴 2026-10-19 客户口径 A「图外题注」：题注从图上移到图下，图面零遮挡。
      旧做法是底部墨色渐变一路压到 92% 不透明度 + 上内边距 64px ⇒ **盖住图高约 43%**，
      且蒙罩色 #071223 是冷藏青，压在暖木 / 洞石上会把整幅调子拉冷。本节 8 张是
      「八个空间一套材质」的图录、不是首屏主视觉，最该看的恰是被蒙住的那条台面与收口。
      文字改用正文色（--sb-ink / --sb-mut），对比度不再依赖图底，无需任何压暗。
   ⚠️ 圆角与裁切随之从 .sb-room 下沉到 .sb-room-ph：外层若仍 `overflow:hidden` + 圆角，
      图外题注会被一起框进那块浅底里（变成一张带灰底的卡）。
   ⚠️ 行距必须明显大于「图↔题注」的 10px，否则题注会被读成下一行的图说 ⇒ 行 20px / 列 8px。 */
.sb-rooms { display: grid; grid-template-columns: repeat(4, 1fr);
  column-gap: var(--space-2); row-gap: var(--space-5); }
.sb-room { position: relative; margin: 0; border: 0; background: none; }
.sb-room-ph { overflow: hidden; border-radius: var(--radius-xl); background: var(--sb-viewer);
  transition: box-shadow .28s ease; }
.sb-room img { width: 100%; aspect-ratio: 4/3; object-fit: cover; display: block; transition: transform .55s cubic-bezier(.22,1,.36,1); }
.sb-room figcaption { padding: var(--space-2-5) 0 0; }
.sb-room b { display: block; font-size: var(--fs-body-sm); font-weight: 600; color: var(--sb-ink); margin-bottom: 2px; }
.sb-room span { font-size: var(--fs-nano); line-height: 1.6; color: var(--sb-mut); }

/* ---- 色彩语言 + 材质搭配 ---- */
.sb-swatches { display: grid; grid-template-columns: repeat(5, 1fr); gap: var(--space-3-5); }
.sb-sw { border-radius: var(--radius-xl); overflow: hidden; border: 1px solid var(--sb-line); background: #fff; }
/* 浅色卡（奶白 / 米白一类）压在白卡片上几乎看不出边界 ⇒ 统一加一道 1px 内描边：
   深色卡上这道线隐没（10% 墨色压在深色上不可见），浅色卡上刚好勾出一条界线。 */
.sb-sw i { display: block; height: 96px; box-shadow: inset 0 0 0 1px rgba(var(--sb-ink-rgb), .1); }
.sb-sw p { margin: 0; padding: var(--space-2-5) var(--space-3) var(--space-3); font-size: var(--fs-small); color: var(--sb-ink); }
/* 2026-10-08 评审 §五#2：这里原本写 font-family:'Outfit'，但实测六页 68/68 条内容
   全是中文（基底留白 / 地面主色 / 木作收口 …）⇒ Outfit 根本没有中文字形，浏览器在
   这里一直回退系统字体 —— 原注释说的「Outfit 在这个字号下发虚」其实是**字体配错**的
   症状，不是字号的错。故：去掉 Outfit（继承页面中文栈）+ 字号抬到 12.5px。 */
.sb-sw p small { display: block; color: var(--sb-mut); letter-spacing: .04em; margin-top: 2px;
  font-size: var(--fs-nano); line-height: 1.5; }
.sb-mats { margin-top: 44px; }
.sb-mats-head { margin-bottom: var(--space-5); }
.sb-mats-head h3 { font-size: var(--fs-h4); color: var(--sb-ink); margin: 0 0 var(--space-2); }
.sb-mats-head p { margin: 0; color: var(--sb-mut); font-size: var(--fs-caption); line-height: 1.85; max-width: 760px; }
.sb-swatches--6 { grid-template-columns: repeat(6, 1fr); }
.sb-swatches--7 { grid-template-columns: repeat(7, 1fr); }
.sb-sw img { display: block; width: 100%; aspect-ratio: 3/2; object-fit: cover; }

/* ---- 翻车三点 ----
   2026-10-10 换版式：3 列卡 → 横排清单。上一节 .sb-els 是「3 列白卡 × 2 行」，
   本节原为「3 列白卡」（同列数、同外形，只少一张图）⇒ 七节连读节奏平。
   改为**一列三行通栏**（原 3 列 → 1 列），卡内左边巨号编号、右边标题正文。
   ⚠️ 保留 --sb-risk-t / --sb-risk-r 两钩子：六页靠它们做形制差异（有常驻判据锁），
      改版式不能把它们变成死令牌。 */
.sb-risks { display: grid; grid-template-columns: 1fr; gap: var(--space-5); }
.sb-risk { display: grid; grid-template-columns: auto 1fr; column-gap: var(--space-6); align-items: start; border-top: var(--sb-risk-t) solid var(--sb-accent); background: var(--sb-card); border-radius: var(--sb-risk-r); padding: 26px var(--space-6) var(--space-7); }
.sb-risk b.no { grid-row: 1 / 3; grid-column: 1; font-family: 'Outfit', sans-serif; font-weight: 700; font-size: var(--sb-risk-no); line-height: 1;
  /* 2026-10-23（对比度）：--sb-accent-wash 是给底纹用的浅色调，当**文字**色只有 1.3:1
     （装饰序号 aria-hidden，axe 仍判 serious）。用 color-mix 压深到 3:1 以上，同时保留每页的色相。 */
  color: color-mix(in srgb, var(--sb-accent) 55%, var(--navy-900)); }
.sb-risk h3 { grid-column: 2; font-size: var(--fs-body-lg); color: var(--sb-ink); margin: 0 0 var(--space-2-5); }
.sb-risk p { grid-column: 2; font-size: var(--fs-caption); line-height: 1.85; color: var(--sb-mut); margin: 0; max-width: 78ch; }

/* ---- 收口 CTA 横幅 ---- */
.sb-cta { margin: 110px auto 0; max-width: 1200px; padding: 0 var(--space-6); }
.sb-cta-in { background: var(--sb-accent-bg); border-radius: var(--radius-2xl); color: #fff; padding: var(--space-16) var(--space-10); text-align: center; }
.sb-cta-in h2 { font-size: clamp(1.625rem, 3.4vw, 2.375rem); margin: 0 0 var(--space-3-5); font-weight: 700; }
.sb-cta-in p { opacity: .92; margin: 0 0 30px; font-size: var(--fs-body-sm); }
.sb-cta-btns { display: flex; gap: var(--space-3-5); justify-content: center; flex-wrap: wrap; }
/* 2026-10-24（客户拍板「CTA 收成两种形态 = 实心 / 描边」）：
   这两个原本是第三、第四套按钮实现 —— 白胶囊主按钮（白底 + 页面强调色字）+ 56 高 15px 字。
   现在统一到站内 `.btn` 体系的两档：**主 = 交互蓝实心，次 = 白描边**；
   尺寸对齐 `.btn-lg`（56 高 / 17px），不再自己写 padding 凑高度。
   ⚠️ 强调色 `--sb-accent` 仍只用于标题/装饰，**不要**再给按钮上色 ——
      一页两个强调色（蓝主 CTA + 风格 accent 标题）是刻意的分工。 */
.sb-btn { display: inline-flex; align-items: center; justify-content: center;
  min-height: var(--btn-h-lg); padding: 0 var(--btn-pad-x);
  border-radius: var(--radius-full); font-size: var(--fs-body-lg); text-decoration: none;
  transition: transform .22s ease, border-color .22s ease, background-color .22s ease, color .22s ease; }
.sb-btn-pri { background: var(--color-interactive); color: #fff; font-weight: 500; }
.sb-btn-sec { border: 1px solid rgba(255,255,255,.5); color: #fff; }

.sb-note { max-width: 1200px; margin: 26px auto 0; padding: 0 var(--space-6); font-size: var(--fs-nano); color: var(--sb-mut); line-height: 1.8; }

/* ---- 仅指针设备启用悬停反馈 ----
   触摸端悬停效果抬手后不回位（卡片抬起、图片放大、按钮位移），真机上会永久粘住。
   全站规则见 shared.css「触摸端 hover 残留复位」。
   ⚠️ 带 .on 状态类的规则不能整条搬进来（.on 在触摸端也要亮），必须拆成两条。 */
@media (hover: hover) and (pointer: fine) {
  .sb-hero-paint:hover { background: var(--sb-accent-dark); border-color: var(--sb-accent-dark); }
  .sb-tab:hover { border-color: var(--sb-accent); }
  .sb-hot:hover { transform: scale(1.25); }
  .sb-legend button:hover { background: var(--sb-accent-soft); }
  .sb-el:hover { transform: translateY(-5px); box-shadow: 0 18px 44px rgba(46,40,28,.12); }
  /* 投影只落在图盒上：题注已移到图外，投在整个 figure 上会把文字也框成一张卡 */
  /* ⚠️ 2026-10-29：删掉 `.sb-room:hover` 的图盒投影与 1.045 缩放。
   风格页的房间图**没有点击行为**（无链接、无 lightbox —— 实测点图零反应），
   悬停却放大 + 投影，是在把「可放大/可点」写在脸上。要恢复缩放，先给它一个真实的点击出口。 */
    .sb-btn-pri:hover { transform: translateY(-2px); }
  .sb-btn-sec:hover { border-color: #fff; }
}

/* 断点取 1023（项目白名单 640/767/768/1023/1024 内的值）：
   901–1023 这一档（平板竖屏、窄窗口）单列排布。 */
@media (max-width: 1023px) {
  .sb-top-wrap { grid-template-columns: 1fr; }
  .sb-els, .sb-risks { grid-template-columns: 1fr; }
  .sb-rooms { grid-template-columns: repeat(2, 1fr); }
  .sb-swatches { grid-template-columns: repeat(2, 1fr); }
  .sb-swatches--6 { grid-template-columns: repeat(2, 1fr); }
  .sb-swatches--7 { grid-template-columns: repeat(2, 1fr); }
  .sb-sec { padding-top: var(--space-16); }
  .sb-tip { width: 200px; }
}
@media (max-width: 767px) {
  /* 手机：演示笔触缩短。
     （原本这里还要在「光标/手指」两套文案之间切换 —— 2026-10-28 文案退役后只剩笔触，切换随之删除） */
  .sb-hint-demo { width: 76px; }
  .sb-hint-trail { width: 56px; }
  .sb-hint-cursor { left: 52px; }
  /* 手机俯视图只有 ~342px 宽，26px 的圆点六个挤在一起不好点。
     圆点视觉放到 30px（仍靠 left/top 百分比定位 ⇒ margin 同步 -15px 才不偏移），
     点击热区交给 ::before 撑 —— ⚠️ inset 是相对 **padding box**：30px 里含 2px 边框，
     padding box 只有 26px，所以要 -12px 才是 50×50 的实热区（-7px 只有 40px，实测点不稳）。
     🔴 **「最小圆心距 60px」是 10-03 旧图的实测值，已失效** —— 换图换坐标后必须复量，
     且**间距随容器宽度等比缩放**：桌面 640px 容器下 60px ⇒ 手机 342px 下只剩 32px，
     必然跌破 54px 判据。⇒ **「54px 达标」不是坐标的固有属性，是「某容器宽下的测量值」**，
     换图 / 换坐标 / 改栅格宽度，三者任一变动都要重测，别引用旧数字。
     ✅ 六点同屏在 342px 内**数学无解**（所需容器宽 ≥488px > 手机可用宽），且挪点也不行 ——
     相邻的两个物件（如沙发与圆几）在图上本来就贴近，两点都要压在各自目标上时可行域不相交。
     正解 = **减点**（原木页 10-04 减到 4 点：留 h1/h2/h4/h5，实测 62.2px），
     写在**页面级 <style>** 里、**只对本页生效**，骨架不动、其余五页零影响。 */
  /* ⚠️ 2026-10-29：尺寸与 margin 必须**成对**。原先写 30px / -15px —— 但触控合规规则
     把按钮顶到 44×44（min-width / min-height），margin 仍是 -15 ⇒ 圆心相对数据坐标
     固定偏 (+7, +7)。342px 宽的图上 7px 已足够把圆点压出图区（memphis 的 h3 底缘被裁 3px）。
     现在显式写 44 与 -22，不再依赖「谁把尺寸抬到 44」这个隐含前提；圆心 = left/top 数据点。 */
  .sb-hot { width: 44px; height: 44px; margin: -22px 0 0 -22px; }
  .sb-hot::before { content: ""; position: absolute; inset: -12px; border-radius: 50%; }
  /* 02 段三颗胶囊在 342px 里换行堆成 **3 行 146px**（实测六页一致），首屏被白吃掉 104px；
     与站内 `.case-tabs--sticky` 同源做法（那条注释写的是同一个理由）。
     ⚠️ `.sb-cap` 必须同时改纵向：横向时左列标题被右列说明挤到 47–62px（实测），
        改后左右各 334px。 */
  .sb-tabs { flex-wrap: nowrap; justify-content: flex-start; overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch; }
  .sb-tabs::-webkit-scrollbar { display: none; }
  .sb-tab { white-space: nowrap; flex: none; }
  .sb-cap { flex-direction: column; gap: var(--space-1); }
}
@media (prefers-reduced-motion: reduce) {
  /* 开场百叶整块撤掉：不演「效果图 → 手稿」，直接给终态（手稿 + 文案 + 按钮） */
  .sb-hero-blinds { display: none; }
  /* 文案的「随揭幕淡入」也要停 —— 百叶都不演了，文案再淡入就只剩突兀 */
  .sb-hero-in { animation: none; }
  .sb-hot::after { animation: none; }
  .sb-viewer figure.on, .sb-tip.on { animation: none; }
  .sb-el, .sb-el:hover { transition: none; transform: none; }
  .sb-hero-sketch, .sb-hero-paint, .sb-h1, .sb-hero-sub, .sb-hero-hint { transition: none; }
  .sb-hint-demo { animation: none; }
  .sb-cue-ic { animation: none; }
}

/* ===== 「该档位的案例」注入块（tools/inject_style_axis.py）=====
   2026-10-22 第二次修订。前两版是「借站上既有类」——先借 `.section-title`（4.5rem 大标题）
   + `.cases-intro`（案例大页的居中导语），对齐一左一中；再只换导语为 `.section-desc`，
   对齐对了但 `.section-desc` 的间距靠 `.section-heading` 父级给，单用时上下边距为 0
   ⇒ 标题 / 导语 / 网格 / 「看全部」四层贴成一片（业主：「字都堆在了一起，没了设计感」）。

   现在整块改用**本页自己的编号小节语汇**（`.sb-sec-head` + `.sb-sec-no` + `.sb-sec-title`，
   与 01–06 各节同字号、同颜色、同基线），节奏写在这里，不再依赖别的文件的父级：
     标题 → 导语 14+16 折叠后 16  ·  导语 → 网格 36  ·  网格 → 看全部 32
   ⚠️ 本文件是风格子页的骨架样式：改完必须手工 bump 六页 `?v=`（md5，非 sha1）。 */
.style-axis-lede {
  max-width: 46em;
  margin: var(--space-4) 0 0;      /* 与 .sb-sec-head 的 14px 下边距折叠 ⇒ 实际 16px */
  font-size: var(--fs-body);
  line-height: 1.9;
  color: var(--gray-700);
}
.style-axis-lede b { color: var(--navy-900); font-weight: 600; }
.style-axis .cases-grid { margin-top: var(--space-9); }
.style-axis-more { margin: var(--space-8) 0 0; }
.style-axis-more a { font-weight: 600; }

/* 断点文案（.only-desk / .only-mob）
   ⚠️ 这条 767 块必须留在文件末尾：冒烟判据取**第一个** 767 媒体块当「手机端主块」，
   插在前面会让主块判据误命中这一段。
   ⚠️ 但样式必须在主 767 块之后才能生效于 .sb-sec-desc 的隐藏逻辑，所以只能放末尾。 */
@media (max-width: 767px) {
  .only-desk { display: none; }
  .only-mob { display: inline; }
}

/* ---------- 落地速查（2026-10-31）----------
   「原木风要注意什么」这类问句，页面上原本只有散文（lead / desc / 三条翻车长文），
   引擎摘不到一处可整行对照的答案。四个维度全部由 styles.json 派生，
   **不新增事实**（没有「工期」「适合户型」——数据里没这两个字段）。
   断点只用 1023 / 640（白名单内）。 */
.sb-facts-title { margin: var(--space-16) 0 var(--space-4); font-size: var(--fs-h4); font-weight: 700; }
.sb-facts { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--space-4); margin: 0; }
.sb-facts > div { background: var(--sb-card); border-radius: var(--sb-risk-r, 12px); padding: var(--space-5) var(--space-5); }
.sb-facts dt { margin: 0 0 var(--space-2); font-size: var(--fs-caption); letter-spacing: .08em; color: var(--sb-mut); }
.sb-facts dd { margin: 0; font-size: var(--fs-body-sm); line-height: 1.75; }
@media (max-width: 1023px) {
  .sb-facts { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 640px) {
  .sb-facts { grid-template-columns: minmax(0, 1fr); }
  .sb-facts-title { margin-top: var(--space-10); }
}
