.cs-stage{position:relative;height:100svh;overflow:hidden;background:var(--navy-900);color:#fff}
        /* 视差留白：层比舞台大 22px，图片在里面跟手移动时不会露出边缘 */
        .cs-layer{position:absolute;inset:-22px;will-change:transform;
          transition:transform .55s cubic-bezier(.22,1,.36,1)}
        .cs-set{position:absolute;inset:0}
        .cs-set[hidden]{display:none}
        .cs-img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;opacity:0;
          transition:opacity .8s cubic-bezier(.25,1,.5,1)}
        .cs-img.on{opacity:1}
        /* 2026-10-22：案例封面只有 900 宽，通栏满铺 = 实测 1.65× 放大（Retina 更强）⇒ 糊。
           ≥1024 起改成「居中 1:1 主图 + 同图虚化衬底」：主图严格按像素显示，
           两侧由同一张图放大虚化填满，观感仍是满屏（不引入第二张素材，URL 相同只走缓存）。
           ⚠️ 窄屏保持 cover 不变 —— 390 视口下 900 宽的图本来就是**缩小**，不存在糊的问题。 */
        .cs-bg{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;opacity:0;
          /* 衬底要「环境色」不要「第二张照片」：模糊给到 48px、压暗到 52%、再放大 1.3
             —— 否则同一张图放大后主体轮廓还在，两侧看起来像重影（首版 26px 就是这个毛病） */
          filter:blur(48px) brightness(.52) saturate(1.12);transform:scale(1.3);
          transition:opacity .8s cubic-bezier(.25,1,.5,1)}
        .cs-bg.on{opacity:1}
        @media (max-width:1023px){ .cs-bg{display:none} }
        /* ⚠️ 只写 object-fit:contain 不够 —— contain 仍会把图**放大**去填满盒子
           （本盒 1484×944 ⇒ 944/675=1.4×）。要真正 1:1，必须同时把盒子交给内容尺寸：
           width/height:auto + max-*:100% + margin:auto ⇒ 按原始 900×675 居中，不再放大。 */
        @media (min-width:1024px){
          .cs-img{ inset:0; width:auto; height:auto; max-width:100%; max-height:100%;
                   margin:auto; object-fit:contain;
                   /* 1:1 之后图片成了「画面中间的矩形」，加一层投影让它像有意为之的取景框，
                      否则边缘很硬、像贴上去的 */
                   box-shadow:var(--shadow-modal); }
        }
        /* 舞台版（tools/build_case_stage.py，1536×960 母图另存）⇒ 源比容器宽，铺满**不放大**：
           1440 视口 1484/1536 = 0.97×（缩小），1920 视口 1.28×（仍远好于 900 图的 2.2×）。
           这类图不需要虚化衬底，也不该带取景框投影 —— 它是满屏画面，不是中间的矩形。
           选择器写成 `.cs-img.cs-hi`（0,2,0）压过上面那条 `.cs-img`（0,1,0），并与它同处
           一个断点，避免与 ≤1023 的默认 cover 打架。 */
        @media (min-width:1024px){
          .cs-img.cs-hi{ inset:0; width:100%; height:100%; max-width:none; max-height:none;
                         margin:0; object-fit:cover; box-shadow:none; }
        }
        /* 2026-10-23：下段由 `.78 100%` 一路加深并**提前介入**（62% 起），
           让压图文字落在照片自己的暗角里 —— 不再需要文字底下的方块。 */
        .cs-stage::after{content:'';position:absolute;inset:0;pointer-events:none;
          background:linear-gradient(180deg,rgba(5,9,15,.74) 0%,rgba(5,9,15,.30) 26%,
                     rgba(5,9,15,0) 44%,rgba(5,9,15,.40) 60%,rgba(5,9,15,.94) 100%)}
        /* 左侧暗角（2026-10-23·UI 评审 §五.3 第 2 步）：只压**左缘**，给标题让出背景。
           ⚠️ 上面那条 `::after` 是**竖直**渐变，管不到水平方向：它的 0%（=顶部 0.74）
           到 26%（=y 234）之间正好覆盖标题区，但 alpha 是**按行**给的 —— 同一行里
           左、中、右一样黑。标题恰恰是最左那一列，于是满屏最亮的那张底图
           （012 的白墙、013 的大窗）上，白字只剩 2.76:1。
           🔴 数值不是手调的：把 34 张 `assets/cases/stage/*.webp` 按 1440×900 铺满、
           叠上 `::after` 的逐行 alpha 逐像素算 WCAG 对比度（标题 bbox 64,117–424,252，
           取区域 p90 当最坏情况）——
             现状 15/34 不达标，最差 2.76:1（case-fuzhe-012）
             本曲线 0/34 不达标，最差 4.53:1（仍是 012）
           更轻的曲线（0.56/0.22@18/0@40）只有 3.85:1，够「大字号 AA 3:1」但不够 4.5。
           ⚠️ 曲线停到 46% 就收：再往右会压到中景沙发与窗，整幅读作「左半边蒙了一层」。
           ⚠️ 必须写 `z-index`：`::before` 是第一个子元素，不写就排到 `.cs-layer`
              **下面**（图片层是定位元素，按文档序后画）—— 症状是「加了渐变但看不见」，
              控制台全绿，实测踩过一次。
           ⚠️ 别改成 `.cs-top` 自己的背景：盒子只有文字那么大，渐变贴着字边走，
              读作一块深色方块（2026-10-21 已经因此撤过一次压暗板）。 */
        .cs-stage::before{content:'';position:absolute;inset:0;z-index:2;pointer-events:none;
          background:linear-gradient(90deg,rgba(5,9,15,.60) 0%,rgba(5,9,15,.26) 22%,
                     rgba(5,9,15,0) 46%)}
        .cs-top{position:absolute;z-index:3;left:clamp(var(--space-6),5vw,var(--space-16));
          top:clamp(var(--space-16),13svh,140px)}
        .cs-eyebrow{margin:0 0 var(--space-4);font-size:var(--fs-nano);letter-spacing:.18em;
          text-transform:uppercase;color:rgba(255,255,255,.72)}
        .cs-title{margin:0;font-family:var(--font-display);font-weight:800;
          font-size:var(--fs-d2);line-height:1.02;letter-spacing:-0.02em;
          text-shadow:0 2px 4px rgba(5,9,15,.55),0 0 32px rgba(5,9,15,.42)}
        .cs-meta{position:absolute;z-index:3;left:clamp(var(--space-6),5vw,var(--space-16));
          bottom:clamp(var(--space-24),15svh,132px)}
        /* 2026-10-23（客户）：**去掉文字底下的压暗板** —— 有方块边、破坏画面。
           对比度改由首屏自身的下半段渐晕承担（见 .cs-stage::after），
           它铺满整幅、没有边界，读起来是照片的暗角而不是 UI 色块。 */
        .cs-meta b{display:block;font-size:clamp(1.1rem,2vw,1.5rem);font-weight:700;letter-spacing:-0.01em}
        /* 2026-10-21：原来 75% 白压图，实测最亮 5% 只有 3.31:1（需 4.5）。
           提到 95% + 两层影（近硬 + 远柔）—— 亮窗帘/白墙背景上才站得住。 */
        .cs-meta span{display:block;margin-top:var(--space-2);font-size:var(--fs-small);
          color:rgba(255,255,255,.95);text-shadow:0 1px 2px rgba(5,9,15,.85),0 0 16px rgba(5,9,15,.55)}
        .cs-meta em{font-style:normal;color:rgba(255,255,255,.86);margin-left:var(--space-2);
          text-shadow:0 1px 2px rgba(5,9,15,.8)}
        /* 同风格缩略带：跟着当前风格走，一格一套 */
        .cs-strip{position:absolute;z-index:4;left:clamp(var(--space-6),5vw,var(--space-16));
          bottom:clamp(2.125rem,7svh,3.75rem);display:flex;gap:var(--space-1-5);align-items:flex-end;
          max-width:min(62vw,720px);overflow-x:auto;scrollbar-width:none}
        .cs-strip::-webkit-scrollbar{display:none}
        .cs-strip button{flex:none;width:62px;height:44px;padding:0;border:0;cursor:pointer;
          background:rgba(255,255,255,.12);overflow:hidden;opacity:.5;
          outline:1.5px solid transparent;outline-offset:2px;
          /* 触摸端「第二下 = 打开」必须压掉 iOS 的双击缩放手势，否则两下会被手势吃掉 */
          touch-action:manipulation;
          transition:opacity var(--duration-normal), transform var(--duration-normal), outline-color var(--duration-normal)}
        .cs-strip button img{width:100%;height:100%;object-fit:cover}
        .cs-strip button:hover{opacity:.9;transform:translateY(-2px)}
        /* 触摸端没有真正的 hover：点过之后 :hover 会粘住，缩略图一直悬着 2px */
        @media (hover:none){
          .cs-strip button:hover{transform:none}
        }
        .cs-strip button.on{opacity:1;outline-color:#fff}
        .cs-strip button:focus-visible{outline-color:var(--color-interactive)}
        .cs-styles{position:absolute;z-index:3;right:clamp(var(--space-6),5vw,var(--space-16));
          top:50%;transform:translateY(-50%);display:flex;flex-direction:column;gap:0;text-align:right}
        .cs-styles button{font:inherit;cursor:pointer;background:none;border:0;padding:var(--space-2) 0;
          min-width:44px;   /* 2026-10-23（触控）：实测 30.6×44 —— 高度够、宽度不够点 */
          color:rgba(255,255,255,.86);font-size:clamp(1rem,1.6vw,1.35rem);font-weight:600;
          letter-spacing:.02em;
          /* 右侧常压在亮窗帘/白墙上：两层影（近硬 + 远柔）才站得住，不用压暗块 */
          text-shadow:0 1px 3px rgba(5,9,15,.92),0 0 22px rgba(5,9,15,.72);
          transition:color var(--duration-normal), transform var(--duration-normal)}
        .cs-styles button small{display:block;font-size:var(--fs-nano);font-weight:400;
          letter-spacing:.06em;opacity:.7;margin-top:2px}
        .cs-styles button:hover,.cs-styles button.on{color:#fff;transform:translateX(-6px)}
        .cs-styles button:focus-visible{color:#fff;outline:2px solid var(--color-interactive);outline-offset:4px}
        /* WCAG 2.2.2：自动播放必须给得出「暂停」。悬停暂停不算（触摸端没有悬停）。
           🔴 2026-10-28（客户）：文字胶囊 → **图标按钮**（44×44 圆）。
           两态由 `aria-pressed` 派生（JS 的 paintPause() 已经在维护它，不必动 JS）：
             false（正在播）→ 两条竖线「暂停」；true（已暂停）→ 三角「播放」。
           ⚠️ 按钮里的文字**不能删** —— paintPause() 会把 textContent 在
              「暂停轮播 / 继续轮播」之间切换，那是读屏唯一的状态播报。
              这里用 `font-size:0` 只做视觉隐藏：文字仍在无障碍树里（已用 accessibility
              snapshot 验过名字可达），所以**不要**改成 `display:none` 或 `visibility:hidden`。
           ⚠️ 图标用 ::before 的纯 CSS 画（与站头汉堡同法），不引 SVG / 字体图标。 */
        .cs-pause{position:absolute;z-index:5;right:clamp(var(--space-6),5vw,var(--space-16));
          bottom:calc(clamp(2.125rem,7svh,3.75rem) + 42px);
          width:44px;height:44px;min-height:44px;padding:0;border-radius:50%;
          display:flex;align-items:center;justify-content:center;
          cursor:pointer;font:inherit;font-size:0;letter-spacing:0;overflow:hidden;
          border:1px solid rgba(255,255,255,.46);background:rgba(5,9,15,.34);color:#fff;
          -webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);
          transition:background var(--duration-normal), border-color var(--duration-normal)}
        /* 暂停态：两条竖线（11×13，间距 8，与 44 盒居中对齐） */
        .cs-pause::before{content:"";width:11px;height:13px;
          background:linear-gradient(#fff,#fff) 0 0/3px 13px no-repeat,
                     linear-gradient(#fff,#fff) 8px 0/3px 13px no-repeat}
        /* 已暂停 → 三角（视觉重心偏左，往右推 4px 才像居中） */
        .cs-pause[aria-pressed="true"]::before{width:0;height:0;background:none;margin-left: var(--space-1);
          border-left:11px solid #fff;border-top:7px solid transparent;border-bottom:7px solid transparent}
        .cs-pause:hover{background:rgba(5,9,15,.55);border-color:#fff}
        .cs-pause:focus-visible{outline:2px solid var(--color-interactive);outline-offset:3px}
        .cs-hint{position:absolute;z-index:3;right:clamp(var(--space-6),5vw,var(--space-16));
          bottom:clamp(2.125rem,7svh,3.75rem);margin:0;font-size:var(--fs-nano);
          color:rgba(255,255,255,.72);letter-spacing:.04em;text-align:right;
          text-shadow:0 1px 3px rgba(5,9,15,.75)}
        .cs-hint a{color:#fff;text-decoration:underline;text-underline-offset:3px}
        @media (max-width:900px){
          /* 🔴 2026-10-27 V-16：≤900 砍掉「同风格缩略带」。
             实测缩略图 56×44（PC 62×44）—— 那个尺寸看不清是哪套房子，
             点击价值低于它的干扰成本；而它换的是 hero 里同一个位置，
             与底部风格词条、下方案例网格三者语义重叠。
             砍掉后底部只剩两行：案例名 + 风格词条，一屏一套导航。
             ⚠️ 缩略条 DOM 保留（桌面要用），这里只隐藏 —— display:none
                不进无障碍树、也不可 Tab，不会制造「点不到的控件」。
             🔴 2026-10-28（客户拍板 ③b）：**恢复显示**，与风格词条**共存**。
                客户的原话是宁可看缩略图，也要那个首屏量房按钮 —— 所以两者都留：
                底部重排成三层「案例名 / 缩略图 / 风格词」，每层间距 8px，从 646 排到 810。
                V-16 当年说的「认不出是哪套房子」是真的，但客户的判断是
                **「看得出还有其他六套」比「认得清是哪一套」更重要** —— 这一段的作用是暗示有货，
                真要认房子往下滚就是 36 格网格。别再按 V-16 的理由把它藏回去。 */
          .cs-meta{bottom:138px;left:var(--space-5);right:var(--space-5)}
          /* 缩略带夹在中间：左右各让 space-5，与上下两层的文字左缘同在 20 位 */
          .cs-strip{display:flex;left:var(--space-5);right:var(--space-5);bottom:86px;
            max-width:none;gap:var(--space-1-5)}
          .cs-styles{left:0;right:0;top:auto;bottom:34px;transform:none;flex-direction:row;
            flex-wrap:nowrap;gap:var(--space-5);max-width:none;overflow-x:auto;
            scrollbar-width:none;padding:0 var(--space-5);justify-content:flex-start;text-align:left}
          .cs-styles::-webkit-scrollbar{display:none}
          .cs-styles button{font-size:var(--fs-body-sm);padding:var(--space-1-5) 0;white-space:nowrap;flex:none;
            min-height:var(--tap-min, 44px);display:inline-flex;align-items:center}
          .cs-styles button small{display:none}
          .cs-hint{display:none}
          /* 🔴 2026-10-28（客户拍板 ③b）：标题区上移。
             基态是 clamp(space-16, 13svh, 140px) ⇒ 389 宽下取 110；这里改成 space-16+20 = 84。
             同时标题区与底部三层之间留出整片画面（原来 110→261 挤在画面上半）。
             ⚠️ 2026-10-29：暂停钮只在 ≤767 隐藏；本块到 900，768–900 的平板仍显示它，
                所以「与按钮齐平」这条在 768–900 依然成立。767 以下没有对齐对象，
                保留 84 是因为标题自己需要（站头下 12px）。 */
          .cs-top{top:calc(var(--space-16) + 20px)}
          /* 🔴 2026-10-28（客户）：首屏的「免费上门量房」按钮退役。
             它是 V-7（10-23）为补「手机首屏零 CTA」加的，客户看过实拍后判断
             **这个位置的价值不如让出画面**（宁可要缩略图）。留资入口仍在：
             页头 CTA（桌面）、悬浮件（滚 55% 起）、下方留资区；只是首屏不再放。
             ⚠️ 想恢复：删掉这一条即可，其余样式都还在。 */
          .hero-quick-cta{display:none}
          .cs-pause{right:var(--space-6);bottom:auto;top:calc(var(--space-16) + 20px)}
        }
        @media (max-width:767px){
          /* 🔴 2026-10-29（客户方案「手机端不播放、平板继续」）：≤767 不自动轮播
             ⇒ 不需要暂停钮 ⇒ 只在**这一档**隐藏。
             ⚠️ 必须单独写 767：上一块是 max-width:900，把 display:none 放进去会连
                768–900 的平板一起藏掉，而平板仍自动播 ⇒ 直接违反 WCAG 2.2.2「可暂停」
                （本轮实测 768 抓到的就是这个）。平板/桌面那个 44×44 按钮照旧。
             按钮**只隐藏、不删 DOM**：平板档仍要用它。 */
          .cs-pause{display:none}

          /* 🔴 2026-10-29（客户选定方案）：首屏轮播 100svh → 75svh，让第一张案例卡进首屏。
             单压轮播不够 —— 它与首卡之间还隔着 586px（列表区留白 80 + 介绍段 91 + 筛选区
             311 + 网格边距 32）⇒ 必须同时把这段压到 ~203px，首卡才从 1.66 屏落到 0.99 屏。
             实测（390×844）：轮播 844→633，首卡 1430→836，露出 8px 卡边。
             ⚠️ 折线必须落在**模块之间**：80% 那版会切在面积胶囊中间，读起来像没加载完
                （客户已否），所以取 75%。
             ⚠️ 下面这几条只影响 ≤767；1440 档逐像素不变。 */
          .cs-stage{height:75vh;height:75svh;min-height:430px}
          /* ↑ 先给 vh 回退：iOS <15.4 / 老 Chrome 不认 svh，整条声明会被丢弃 ⇒
             舞台高度塌成 min-height(430)，首屏比例全变。两行写法在支持 svh 的
             浏览器里后者胜出，行为与单写 svh 完全一致。 */
          /* ⚠️ 选择器必须带 `.section--roomy`：裸 `section[aria-label="案例列表"]` 是 (0,1,1)，
             压不住 shared 的 `.section.section--roomy`(0,2,0)，实测 padding 仍算 80px（白丢 60px）。 */
          section.section--roomy[aria-label="案例列表"]{padding-top:var(--space-5)}
          .section--roomy > .container > .cases-intro{display:none}
          .style-filter{margin-bottom:var(--space-3)}
          /* 标题行整行撤掉：「中古 6 套」这类胶囊本身已经说明是风格筛选，标题是重复的。
             「全部 36 套」不能跟着消失 ⇒ shared.js 在本档把它挪进胶囊行首位。 */
          .style-filter-head{display:none}
          /* 「全部 36 套」的后缀在手机档隐藏（只留「全部」），否则 7 个 tab 放不进 342。 */
          .all-n{display:none}
          /* 面积筛选 → **纯文字行**（去掉胶囊）。
             5 档 + 标签在 390 下内容宽刚好 342 = 容器宽 ⇒ 一眼看全、零横滑，
             不需要 mask 渐隐（有溢出才需要；原来 555/342 时那版是靠 mask 遮的）。 */
          /* ⚠️ 选择器加 `.style-filter` 前缀：裸 `.area-filter`(0,1,0) 压不住 critical 里
             序列化出来的同名规则（critical 排在 page-cases-body.css 之后），实测 flex-wrap
             仍是 wrap ⇒ 「面积」标签被挤到第二行。R48 那条「产物级 vs 源码级」双保险也认这个。 */
          .style-filter .area-filter{
            margin-top:0;padding-top:var(--space-1);border-top:0;gap:var(--space-1-5);
            flex-wrap:nowrap;align-items:center;
          }
          /* 「面积」标签在手机档撤掉：第二行全是「≤120m² / 121–160m²」这种带单位的区间，
             一眼就是面积；留着它反而占 36px，把 5 档挤成需要压缩字号的局面。
             撤掉后 5 档用 space-between 铺满 342，两端与上面一行 tab 左右对齐，成为整齐的两列。 */
          .style-filter .area-filter-label{display:none}
          .style-filter .area-filter{justify-content:space-between}
          .style-filter .area-pick{
            flex:0 0 auto;min-height:44px;padding:0;border:0;border-radius:0;background:transparent;
            font-size:var(--fs-small);color:var(--gray-600);
          }
          .style-filter .area-pick small{display:none}
          .style-filter .area-pick.active{background:transparent;color:var(--navy-900);font-weight:600}
          #casesGrid{margin-top:var(--space-1)}
        }
        @media (prefers-reduced-motion:reduce){
          .cs-layer{transition:none}
          .cs-img{transition:none}
        }
