/* =========================================================================
   QiWeiSheng — Plan C（原型，不进线上 src/）

   与方案 B 的差别只有一条：**一块 canvas 从头挂到尾**，
   所有幕都在同一个舞台上换机位、换模型，中间不断层。

   配色、字体、间距沿用 home-v2 的 token，不新造色相。
   ========================================================================= */
:root{
  --brand-50:#eff7fc;
  --brand-600:#0b5c8a;
  --brand-900:#062f47;
  --accent:#ff7a00;
  /* ⛔ 必须跟线上 globals.css 的 --color-accent-600 一模一样。
     原型里原来写成 #e56d00，差 3 个色阶，hover 和线上不是同一个橙。 */
  --accent-d:#e56a00;

  --dark:#0a0e13;
  --dark-2:#0d1218;
  --dark-3:#070a0e;

  --txt:#e9eff5;
  --txt-2:rgba(233,239,245,.66);
  --txt-3:rgba(233,239,245,.42);
  --ln:rgba(255,255,255,.11);
  --ln-2:rgba(255,255,255,.06);

  --wrap:1280px;
  --pad:clamp(20px,4vw,40px);
  --hdr-h:70px;

  /* 浅色版块的 token。
     ⛔ 整站通篇深色会闷 —— 中段插一块"纸"出来，深→浅→深，明暗节奏才立得住。
        这里只给「纸」和「墨」两档，不加色相，免得跟品牌色打架。 */
  --paper:#f2f0ec;
  --ink:#0b0f14;
  --ink-2:rgba(11,15,20,.66);
  --ink-3:rgba(11,15,20,.42);

  --sans:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,
         "PingFang SC","Microsoft YaHei",sans-serif;
  --mono:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,"Liberation Mono",monospace;
}

*,*::before,*::after{box-sizing:border-box}
/* ⛔ 不要给 html 加 scroll-behavior:smooth。
   整页动画都是「读滚动位置」驱动的，平滑滚动会让浏览器在后台补一段很长的
   缓动动画，滚动值和渲染进度对不上（实测程序化 scrollTo 到位后还会自己漂 1000px+）。
   锚点跳转的平滑交给各自的交互，不放在根元素上。 */
html{scroll-behavior:auto}
body{
  margin:0;background:var(--dark);color:var(--txt);
  font-family:var(--sans);font-size:16px;line-height:1.65;
  -webkit-font-smoothing:antialiased;
}
img{display:block;max-width:100%}
a{color:inherit;text-decoration:none}
h1,h2,h3{margin:0;line-height:1.02;letter-spacing:-.034em;font-weight:700}
p{margin:0}
code{font-family:var(--mono);font-size:.86em;color:var(--txt-2)}
.wrap{width:100%;max-width:var(--wrap);margin:0 auto;padding:0 var(--pad)}

.mono{
  font-family:var(--mono);font-size:.6rem;font-weight:500;
  letter-spacing:.22em;text-transform:uppercase;color:var(--txt-3);
}

/* ------------------------------ 导航 ------------------------------ */
#hdr{
  position:fixed;inset:0 0 auto;height:var(--hdr-h);z-index:60;
  display:flex;align-items:center;
  transition:background .3s ease,border-color .3s ease,backdrop-filter .3s ease;
  border-bottom:1px solid transparent;
}
#hdr.is-solid{
  background:rgba(8,11,16,.82);
  border-bottom-color:var(--ln-2);
  backdrop-filter:blur(14px) saturate(1.2);
}
.hdr__in{display:flex;align-items:center;gap:clamp(18px,3vw,44px);width:100%}
.hdr__logo{font-weight:700;font-size:1.02rem;letter-spacing:-.02em}
.hdr__nav{display:flex;gap:clamp(14px,2vw,26px);margin-left:auto}
.hdr__nav a{font-size:.88rem;color:var(--txt-2);transition:color .2s}
.hdr__nav a:hover{color:var(--txt)}
.hdr__cta{
  font-size:.84rem;font-weight:600;padding:.58em 1.1em;border-radius:5px;
  background:var(--accent);color:#150c02;transition:background .2s;
}
.hdr__cta:hover{background:var(--accent-d)}

/* 压在浅色版块上时反色（JS 加 is-light）。必须排在 is-solid 后面才盖得住。 */
#hdr.is-light{
  background:rgba(242,240,236,.86);
  border-bottom-color:rgba(11,15,20,.09);
}
#hdr.is-light .hdr__logo{color:var(--ink)}
#hdr.is-light .hdr__nav a{color:var(--ink-3)}
#hdr.is-light .hdr__nav a:hover{color:var(--ink)}

/* ------------------------------ 舞台 ------------------------------
   .film 只负责给"滚动距离"；真正的画面钉在 .film__pin 里。
   高度差 = 滚多久走完这一条时间线。
   ------------------------------------------------------------------ */
.film{position:relative;height:calc(100vh + 660vh)}
/* 遮罩底下的厂房实拍。
   素材本身带英文卖点字幕 + 阿里水印（原片就是阿里国际站的产线片），
   所以这里必须把它压成「看不清字、只看得见厂房在动」：
   ⛔ 别把 opacity 提到 .5 以上、也别关掉 blur —— 字幕会变成可读的白字，那就出事了。
   ⛔ 光靠 blur 不够：字幕压在画面底部 70~88%，所以再用 mask 把底部三分之一抹掉，
      这样画面上部（厂房结构、机械臂）才敢给到 .42 的亮度。
   模糊用 CSS 做而不是烘进视频，随时能调，视频文件也不用重压。 */
.film__bg{
  position:absolute;inset:0;width:100%;height:100%;display:block;
  object-fit:cover;object-position:center 40%;
  opacity:.42;
  filter:blur(4px) brightness(.6) saturate(.55) contrast(1.08);
  -webkit-mask-image:linear-gradient(to top,transparent 0%,rgba(0,0,0,.12) 24%,#000 46%);
  mask-image:linear-gradient(to top,transparent 0%,rgba(0,0,0,.12) 24%,#000 46%);
  /* 放大一点，把 blur 在四边啃出来的透明边推出画面 */
  transform:scale(1.08);
  pointer-events:none;
}

.film__pin{
  position:sticky;top:0;height:100vh;overflow:hidden;
  background:#0a0e13;
}
#gl{
  position:absolute;inset:0;width:100%;height:100%;display:block;
  opacity:0;transition:opacity .8s ease;
}
#gl.is-ready{opacity:1}

/* 暗角 + 底部压暗：让白模边缘从背景里"浮"出来，同时给底部卡片托底。
   ⛔ 不做纯黑遮罩 —— 会把白模一起吃掉。 */
.film__scrim{
  position:absolute;inset:0;pointer-events:none;
  background:
    radial-gradient(120% 90% at 62% 44%,transparent 38%,rgba(6,9,13,.55) 100%),
    linear-gradient(to top,rgba(6,9,13,.72) 0%,rgba(6,9,13,0) 26%),
    linear-gradient(to right,rgba(6,9,13,.58) 0%,rgba(6,9,13,0) 42%);
}

.film__lines{position:absolute;inset:0;width:100%;height:100%;pointer-events:none;z-index:2}
.film__lines polyline{fill:none;stroke:rgba(255,255,255,.34);stroke-width:1}
.film__lines circle{fill:#ff7a00;stroke:none}

.film__ui{position:absolute;inset:0;z-index:3}

/* 进度条：底部一条极细的琥珀线 */
.film__bar{
  position:absolute;left:0;right:0;bottom:0;height:2px;
  background:rgba(255,255,255,.07);z-index:4;
}
.film__bar i{display:block;height:100%;width:0;background:var(--accent)}

.film__hint{
  position:absolute;right:var(--pad);bottom:26px;z-index:4;
  font-family:var(--mono);font-size:.62rem;letter-spacing:.22em;text-transform:uppercase;
  color:var(--txt-3);transition:opacity .4s ease;
}
.film__hint.is-off{opacity:0}

/* 降级：不 sticky、不跟滚动，只显示一帧静图（JS 那边同步加了 body.is-reduce） */
body.is-reduce .film{height:100vh}
body.is-reduce .film__pin{position:relative}
body.is-reduce .film__bar,
body.is-reduce .film__hint{display:none}

/* ------------------------- 面板定位（不进动画）-------------------------
   ⛔ 定位交给 .panel，动画只动里面的 [data-in]。
      两者写在一起会互相覆盖 transform —— 方案 B 里就吃过这个亏。
   ------------------------------------------------------------------ */
.panel{position:absolute;pointer-events:none}

.panel[data-pos="left"]{
  top:50%;left:var(--pad);
  transform:translateY(-50%);
  width:min(520px,46vw);
}
.panel[data-pos="right"]{
  top:50%;right:var(--pad);
  transform:translateY(-50%);
  width:min(440px,40vw);
}
.panel[data-pos="bottom"]{
  left:var(--pad);right:var(--pad);bottom:clamp(34px,7vh,74px);
  display:flex;justify-content:center;
}
/* 细节幕的标题放左下。
   ⛔ 不能还用 data-pos="left"（垂直居中）：摇把那条引线标签的落点在画面纵向 40% 一带，
      正好压在标题上，两段文字叠在一起（实测"Hand-crank lift"直接糊在
      "Adjust, don't replace." 上面）。标题下移之后三条标注全在它上方，互不打架。 */
.panel[data-pos="bottomleft"]{
  left:var(--pad);bottom:clamp(44px,8vh,88px);
  width:min(460px,44vw);
}

/* 统一入场动画：k = 1 表示完全隐藏，0 表示就位。
   --dx / --dy 各自声明位移方向，[data-in] 只负责算。 */
[data-in]{
  opacity:0;
  transform:translate3d(
    calc(var(--dx,0px) * var(--k,1)),
    calc(var(--dy,18px) * var(--k,1)),
    0
  );
  will-change:opacity,transform;
}

/* ------------------------------ 文案 ------------------------------ */
.h1{font-size:clamp(2.2rem,5vw,3.9rem);margin:22px 0 26px}
.h2{font-size:clamp(1.9rem,3.8vw,3.1rem);margin:20px 0 22px}
.lead{font-size:.93rem;line-height:1.72;color:var(--txt-2);max-width:36ch}
.lead--sm{font-size:.94rem;max-width:36ch}
.box--tight{max-width:34ch}

.facts{list-style:none;margin:26px 0 0;padding:0;display:flex;gap:clamp(16px,2.4vw,32px);flex-wrap:wrap}
.facts li{display:flex;flex-direction:column;gap:2px}
.facts b{font-size:1.22rem;font-weight:700;letter-spacing:-.02em}
.facts span{font-family:var(--mono);font-size:.6rem;letter-spacing:.16em;text-transform:uppercase;color:var(--txt-3)}

/* 引线标注 */
.call{
  position:absolute;width:min(264px,30vw);opacity:0;pointer-events:none;
}
.call__t{font-size:.9rem;font-weight:600;line-height:1.35}
.call__s{margin-top:4px;font-size:.78rem;line-height:1.45;color:var(--txt-2)}

/* 装配步骤清单 */
.steps{list-style:none;margin:24px 0 0;padding:0;display:grid;gap:2px;counter-reset:s}
.steps li{
  display:grid;grid-template-columns:auto 1fr;gap:0 12px;align-items:baseline;
  padding:9px 0;border-top:1px solid var(--ln-2);
  opacity:.38;transition:opacity .45s ease;
}
.steps li:last-child{border-bottom:1px solid var(--ln-2)}
.steps li.is-on{opacity:1}
.steps i{font-family:var(--mono);font-style:normal;font-size:.6rem;letter-spacing:.14em;color:var(--accent)}
.steps b{font-size:.9rem;font-weight:600;grid-column:2}
.steps em{
  grid-column:2;font-style:normal;font-size:.76rem;line-height:1.4;color:var(--txt-3);
}

/* 认证清单 */
.certs{list-style:none;margin:24px 0 0;padding:0;display:grid;gap:1px}
.certs li{
  display:flex;justify-content:space-between;gap:16px;align-items:baseline;
  padding:9px 0;border-top:1px solid var(--ln-2);
}
.certs li:last-child{border-bottom:1px solid var(--ln-2)}
.certs__k{font-size:.86rem;font-weight:600}
.certs__v{font-family:var(--mono);font-size:.62rem;letter-spacing:.1em;color:var(--txt-2);white-space:nowrap}

/* 询盘 */
.spec{margin:22px 0 0;display:grid;gap:1px}
.spec div{display:flex;justify-content:space-between;gap:16px;padding:8px 0;border-top:1px solid var(--ln-2)}
.spec div:last-child{border-bottom:1px solid var(--ln-2)}
.spec dt{font-size:.8rem;color:var(--txt-3);text-transform:uppercase;letter-spacing:.12em;font-family:var(--mono);font-size:.62rem}
.spec dd{margin:0;font-size:.86rem;font-weight:600}

.cta{display:flex;gap:10px;margin:24px 0 0;flex-wrap:wrap}
.btn{
  display:inline-flex;align-items:center;justify-content:center;
  padding:.8em 1.35em;border-radius:5px;font-size:.86rem;font-weight:600;
  border:1px solid transparent;transition:.2s ease;white-space:nowrap;
}
.btn--accent{background:var(--accent);color:#150c02}
.btn--accent:hover{background:var(--accent-d)}
.btn--ghost{border-color:var(--ln);color:var(--txt)}
.btn--ghost:hover{border-color:rgba(255,255,255,.3);background:rgba(255,255,255,.04)}
.fine{margin-top:16px;font-size:.72rem;line-height:1.5;color:var(--txt-3);max-width:40ch}

/* 产品分流 */
.split{
  width:100%;display:grid;grid-template-columns:1fr 1fr;
  gap:clamp(12px,1.6vw,20px);
}
.split__head{grid-column:1/-1;margin-bottom:2px}
.card{
  display:grid;grid-template-columns:88px 1fr;align-items:center;gap:16px;
  padding:12px;border-radius:8px;
  background:linear-gradient(160deg,rgba(255,255,255,.067),rgba(255,255,255,.022));
  border:1px solid var(--ln-2);
  transition:border-color .25s ease,background .25s ease,transform .25s ease;
  pointer-events:auto;
}
.card:hover{border-color:rgba(255,122,0,.42);background:rgba(255,255,255,.085);transform:translateY(-2px)}
.card img{
  width:88px;height:88px;object-fit:contain;border-radius:5px;
  background:rgba(255,255,255,.05);
}
.card__txt{display:flex;flex-direction:column;gap:1px;min-width:0}
.card__txt b{font-size:1.14rem;letter-spacing:-.02em}
.card__txt em{font-style:normal;font-size:.76rem;line-height:1.4;color:var(--txt-3)}

/* ------------------------------ 微距细节（唯一的浅色版块）------------- */
.macro{
  background:var(--paper);color:var(--ink);
  padding:clamp(76px,12vh,150px) 0 clamp(66px,10vh,120px);
}
.macro__hd{max-width:var(--wrap)}
.macro .mono{color:var(--ink-3);font-size:.6rem;letter-spacing:.22em}
.macro .h2{margin:22px 0 20px;color:var(--ink)}
.macro .lead{color:var(--ink-2);max-width:44ch;font-size:.96rem}

.macro__row{
  width:100%;max-width:var(--wrap);margin:clamp(44px,6vh,78px) auto 0;
  padding:0 var(--pad);
  display:grid;grid-template-columns:repeat(4,1fr);
  gap:clamp(10px,1.5vw,22px);
}
.macro figure{margin:0;display:flex;flex-direction:column}
.macro img{
  display:block;width:100%;aspect-ratio:4/5;object-fit:cover;
  border-radius:3px;background:#e3e0da;
}
.macro figcaption{display:flex;flex-direction:column;gap:5px;padding-top:15px}
.macro .n{
  font-family:var(--mono);font-size:.56rem;font-weight:500;
  letter-spacing:.22em;text-transform:uppercase;color:var(--ink-3);
}
.macro b{font-size:.92rem;font-weight:600;letter-spacing:-.012em;line-height:1.3}
.macro em{
  font-style:normal;font-size:.77rem;line-height:1.55;color:var(--ink-2);
}

/* 四张微距图「依次从框里擦出来」。
   ⛔ 通用的 --i 一步是 90ms，而 wipe 的 clip 过渡是 1.05s —— 四张只差 90ms
      就等于同时揭开，看不出「一张张看过去」的节奏。这一块单独放大到 170ms。
   ⛔ 选择器必须带 [data-rv] 才压得住 `.js [data-rv]` 那条通用规则：
      (0,2,0) 比 `.macro__row figure` 的 (0,1,1) 高，光加类名没用，延迟会被吃掉。
      实测过：写 `.macro__row figure` 时四张的 delay 还是 0/90/180/270ms。 */
/* ⛔ hover 的放大要裁在框里，否则会压到隔壁那张。这条不带 .js ——
   脚本没跑时 hover 照样在，裁切也得在。 */
.macro__row figure{overflow:hidden}
.js .macro__row figure[data-rv]{transition-delay:calc(var(--i,0) * 170ms)}
/* 揭示的同时图片从 1.07 收回原大 —— 只做 clip 像一个帘子掀开，加了收束才有纵深。
   ⛔ 入场用**独立的 scale 属性**，hover 用 transform:scale()：
      两个属性各自合成，不用为"谁压谁"去数选择器权重。 */
.js .macro__row figure[data-rv] img{scale:1.07}
.js .macro__row figure[data-rv].is-in img{scale:1}
.macro img{
  transition:scale 1.05s cubic-bezier(.2,.7,.2,1),
             transform .7s cubic-bezier(.2,.7,.2,1),
             filter .5s ease;
}
.macro__row figure:hover img{transform:scale(1.045)}
/* 图注编号前那道短线，悬停时拉长。用 scaleX 而不是改 width：
   改 width 会把后面的文字一起顶走，读起来像抖了一下。 */
.macro .n::before{
  content:'';display:inline-block;width:22px;height:1px;margin-right:9px;
  background:currentColor;vertical-align:middle;
  transform-origin:left center;
  transition:transform .5s cubic-bezier(.2,.7,.2,1);
}
.macro__row figure:hover .n::before{transform:scaleX(1.75)}

/* ------------------------------ 工厂手风琴 ------------------------------
   五张并排，其中一张变宽、其余收窄。宽度靠 flex-grow 过渡：
   浏览器自己按容器宽度算 px，不用 JS 量、不用 grid 轨道（Safari 网格轨道过渡不稳）。
   flex-grow 只定"比例"，图本身 object-fit:cover 填满，怎么拉都不变形。
   ------------------------------------------------------------------ */
.shop{padding:clamp(64px,11vh,120px) 0 clamp(48px,8vh,84px);background:var(--dark-3)}
.shop__hd{padding-bottom:clamp(26px,4vh,44px)}
.shop__hd .lead{margin-top:14px}

.band{
  display:flex;gap:1px;
  margin:0 0 clamp(28px,4vw,48px);background:var(--ln-2);
}
.band figure{
  position:relative;margin:0;overflow:hidden;background:var(--dark-3);
  flex-basis:0;                      /* ⛔ 必须配 min-width:0，否则内容宽度会顶住 flex-grow 失效 */
  min-width:0;
  transition:flex-grow .85s cubic-bezier(.22,.7,.2,1);
}
/* ⛔ 明暗关系不能反：展开的那张必须是「正常曝光」，收起的才压暗当底色。
   反过来会出现「五张全暗、展开那张因为最宽反而像被压得最狠」。 */
.band img{
  width:100%;height:clamp(300px,39vw,560px);object-fit:cover;
  filter:saturate(.72) brightness(.62) contrast(1.04);
  transition:transform .8s cubic-bezier(.2,.7,.2,1),filter .6s ease;
}
.band figure[data-open] img{
  filter:saturate(.88) brightness(.98) contrast(1.05);
  transform:scale(1.015);
}
.band figcaption{
  position:absolute;left:0;right:0;bottom:0;padding:2.4rem 1.1rem 1rem;
  font-size:.8rem;font-weight:600;color:#fff;white-space:nowrap;
  background:linear-gradient(transparent,rgba(4,8,12,.92));
  display:flex;align-items:baseline;gap:.7rem;
  transition:opacity .5s ease .1s;
}
.band figcaption .mono{color:rgba(255,255,255,.55);font-size:.68rem;flex:0 0 auto}
/* ⛔ .mono 在 flex 里会被当块级撑开 → 编号和标题分两行。必须显式 inline-block。 */
.band figcaption .mono{display:inline-block}
.band figure:not([data-open]) figcaption{opacity:0}
.band figure:not([data-open]) figcaption .mono{display:none}

/* ------------------------------ 页脚 ------------------------------ */
.tail{padding:clamp(60px,10vh,110px) 0;background:var(--dark-3);border-top:1px solid var(--ln-2)}
.tail__in{max-width:660px}
.tail__t{margin-top:10px;font-size:1.05rem;font-weight:600}
.tail__s{margin-top:8px;font-size:.88rem;line-height:1.65;color:var(--txt-3)}

/* =========================================================================
   后半页（08–11）：数字 / 流程 / 常见问题 / 页脚
   动效统一走 motion.js，这里只写"动的起止状态"。
   ⛔ 隐藏态一律挂在 html.js 下面：脚本没加载成功时页面必须是正常的，
      不能因为没跑 JS 就整页空白。这条比动效本身重要。
   ========================================================================= */

/* 统一的入场状态。type 只有四种，别再加：
   up    位移 + 淡入（正文、卡片）
   mask  从下沿"擦"出来（大标题、品牌大字）
   wipe  从左往右揭开（图片）
   line  横线从左边画出去（分隔线） */
.js [data-rv]{
  transition:opacity .9s cubic-bezier(.2,.7,.2,1),
             transform .9s cubic-bezier(.2,.7,.2,1),
             clip-path 1.05s cubic-bezier(.2,.7,.2,1);
  transition-delay:calc(var(--i,0) * 90ms);
}
.js [data-rv="up"]{opacity:0;transform:translate3d(0,26px,0)}
.js [data-rv="mask"]{opacity:1;transform:translate3d(0,16px,0);clip-path:inset(0 0 104% 0)}
.js [data-rv="wipe"]{opacity:1;clip-path:inset(0 100% 0 0)}
.js [data-rv="line"]{transform:scaleX(0);transform-origin:left center}
.js [data-rv].is-in{opacity:1;transform:none;clip-path:inset(0 0 0 0)}

.sec__hd .h2{margin-bottom:0}

/* ------------------------------ 07b · 产品（一张张蹦出来）--------------------
   参考 open-design.ai 的 capabilities 板块：sticky 舞台 + 长滚动区，
   每张卡按滚动进度从 scale 0 用 back 缓动弹到 1，已出场的随后缩小后退。
   ⛔ 缩放写在卡片自己的 transform 上（--s 由 motion.js 逐帧写），
      不要挂到祖先元素 —— 会让整段跟着重算样式，滚动就卡了。
   ⛔ 舞台必须 overflow:hidden：卡片 scale 超过 1 时会顶出去。
   ---------------------------------------------------------------------------- */
.shelf{
  position:relative;
  height:calc(100svh + 240vh);
  background:var(--dark-2);
  border-top:1px solid var(--ln-2);
}
.shelf__pin{
  position:sticky;top:0;height:100svh;overflow:hidden;
  display:flex;flex-direction:column;
  padding:clamp(58px,9vh,104px) 0 clamp(30px,5vh,56px);
}
.shelf__hd{
  margin:0 auto;flex:0 0 auto;
  /* 标题和说明并排 —— 竖着排会把舞台挤掉 150px，卡片就装不下了 */
  display:grid;grid-template-columns:minmax(0,1fr) minmax(0,.66fr);
  column-gap:clamp(26px,4vw,64px);align-items:end;
}
.shelf__hd .mono{grid-column:1 / -1}
.shelf__hd .h2{grid-column:1;margin:10px 0 0}
.shelf__hd .lead{grid-column:2;margin:0 0 8px;max-width:40ch}

.shelf__stage{
  position:relative;flex:1 1 auto;min-height:0;
  margin:0 auto;
}

/* 十张卡的摆位：宽度百分比相对舞台，纵向相对舞台高度，
   所以换屏幕尺寸不会互相压到溢出。位置是手工排的「看起来随机」——
   大小混搭、上下错开，别排成规规矩矩的网格，那就没有撒落感了。
   ⛔ --y 别排到 52% 以上：卡高约 1.3×宽，超出去会被 .shelf__pin 的
      overflow:hidden 切掉半张。 */
.shelf__card[data-card="0"]{--w:24%;--x:1%;--y:1%;--r:-1.6deg;--ar:4/5}
.shelf__card[data-card="1"]{--w:19%;--x:27%;--y:0%;--r:1.2deg;--ar:1/1}
.shelf__card[data-card="2"]{--w:18%;--x:47%;--y:7%;--r:-2.2deg;--ar:1/1}
.shelf__card[data-card="3"]{--w:19%;--x:66%;--y:0%;--r:1.8deg;--ar:1/1}
.shelf__card[data-card="4"]{--w:16%;--x:78%;--y:25%;--r:-1deg;--ar:1/1}
.shelf__card[data-card="5"]{--w:17%;--x:26%;--y:33%;--r:2.1deg;--ar:1/1}
.shelf__card[data-card="6"]{--w:20%;--x:45%;--y:39%;--r:-1.4deg;--ar:1/1}
.shelf__card[data-card="7"]{--w:19%;--x:8%;--y:53%;--r:1.5deg;--ar:1/1}
.shelf__card[data-card="8"]{--w:19%;--x:70%;--y:48%;--r:-1.9deg;--ar:1/1}
.shelf__card[data-card="9"]{--w:15%;--x:-1%;--y:11%;--r:2.4deg;--ar:1/1}

.shelf__card{
  position:absolute;
  width:var(--w);left:var(--x);top:var(--y);
  margin:0;
  background:#fff;border-radius:13px;overflow:hidden;
  box-shadow:0 32px 64px -30px rgba(0,0,0,.72), 0 2px 0 rgba(255,255,255,.06) inset;
  /* ⛔ --s 初值 0：脚本没跑起来时卡片是隐藏的，但 .js 之外要给 1，
     否则无 JS 的浏览器整段空白。见下面的 :not(.js) 兜底。 */
  transform:translate3d(0,var(--dy,0px),0) rotate(var(--r,0deg)) scale(var(--s,0));
  transform-origin:center center;
  will-change:transform;
}
.shelf__card img{
  display:block;width:100%;aspect-ratio:var(--ar,1/1);object-fit:cover;
  background:#fff;
  opacity:0;transition:opacity .55s ease;
}
.shelf__card img.is-loaded,
html:not(.js) .shelf__card img{opacity:1}
/* 图注只写型号：十张卡里最小的一行只有 175px 宽，
   再塞「Study desk」就会被 overflow 裁成半个词。型号才是 B2B 买家要的。 */
.shelf__card figcaption{
  display:block;
  padding:8px 11px 10px;
  font-family:var(--mono);font-size:.62rem;letter-spacing:.1em;
  text-transform:uppercase;color:#12171d;font-weight:600;
  border-top:1px solid rgba(10,14,19,.07);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}

.shelf__hint{
  position:absolute;right:var(--pad);bottom:clamp(16px,2.6vh,28px);z-index:5;
  font-size:.6rem;letter-spacing:.2em;color:rgba(255,255,255,.34);
  pointer-events:none;
}

/* 没有 JS / 动效敏感：卡片就正常摆着，不参与缩放 */
html:not(.js) .shelf__card,
.shelf.no-anim .shelf__card{transform:rotate(var(--r,0deg))}
html:not(.js) .shelf{height:auto}
html:not(.js) .shelf__pin{position:static;height:auto}
html:not(.js) .shelf__stage{min-height:78vh}

/* ------------------------------ 08 · 数字 ------------------------------ */
/* 用户要「PPT 的一页」：铺满一屏、内容垂直居中，滚到这儿是一次停顿而不是路过。
   ⛔ 用 100svh 不用 100vh：手机上 vh 按地址栏收起后的高度算，
      写成 vh 会多出半条，后面白板块提前露头。 */
.nums{
  background:var(--dark-2);
  border-top:1px solid var(--ln-2);
  min-height:100svh;
  display:flex;align-items:center;
  padding:clamp(70px,11vh,130px) 0;
}
.nums>.wrap{width:100%}
.nums__grid{
  list-style:none;margin:clamp(40px,6vh,72px) 0 0;padding:0;
  display:grid;grid-template-columns:repeat(5,minmax(0,1fr));
}
.nums__grid li{
  padding:22px 22px 0 0;
  border-top:1px solid var(--ln);
}
.nums__grid b{
  display:flex;align-items:baseline;gap:.3em;
  font-size:clamp(2.1rem,3.5vw,3.05rem);font-weight:700;
  letter-spacing:-.045em;line-height:1;
  /* 数字滚动时宽度会变，等宽数字才不抖 */
  font-variant-numeric:tabular-nums;
}
.nums__grid i{
  font-style:normal;font-family:var(--mono);font-size:.6rem;font-weight:500;
  letter-spacing:.18em;text-transform:uppercase;color:var(--accent);
}
/* 数字转轮：每一位是一个 1em 高的窗口，里面一条 0-9 的竖带在转。
   ⛔ 窗口和逗号都要 height:1em + line-height:1em + vertical-align:bottom，
      三者缺一个，逗号就会浮到数字中间或者掉下去半行。
   ⛔ 不要给 .dg 设固定宽度。等宽字体下每位本来一样宽，写死反而会
      和后面的单位（m² / people）对不齐。 */
.roll{display:inline-block;line-height:1em;white-space:nowrap;font-variant-numeric:tabular-nums}
.dg{
  display:inline-block;height:1em;line-height:1em;
  overflow:hidden;vertical-align:bottom;
}
.dg__strip{
  display:flex;flex-direction:column;
  transition:transform 1.8s cubic-bezier(.16,.84,.24,1);
  will-change:transform;
}
.dg__strip>span{display:block;height:1em;line-height:1em}
.sep{display:inline-block;height:1em;line-height:1em;vertical-align:bottom}
.nums__grid p{
  margin-top:12px;font-size:.78rem;line-height:1.55;
  color:var(--txt-3);max-width:21ch;
}

/* ------------------------------ 09 · 买家评价（纸底，两排反向滚动）-------------
   动效参考网易 UU 远程官网的「口碑认证，真的好用」：两排卡片铺满整幅，
   一排往左、一排往右，匀速无缝循环，鼠标悬停暂停。
   ⛔ 这跟证书段完全是两回事：证书段是「钉住 + 跟随滚动位置」，这里是一条
      duration 固定的 CSS 动画 —— 页面滚不滚它都在动，别掺进 moveCerts 那套进度计算。
   ⛔ 无缝循环 = 整组卡片复制一份 + 动画走 -50%。
   ⛔ 所以卡片间距必须用 margin-right，不能用 flex 的 gap：12 张卡用 gap 只有 11 个
      间隙，-50% 正好差半个间隙，接缝处会跳一下。
   ⛔ 边缘还是不做渐隐遮罩（跟证书段同一原则），卡片撞到视口就是硬切。 */
.voices{
  background:var(--paper);color:var(--ink);
  padding:clamp(70px,11vh,124px) 0 clamp(44px,6vh,68px);
}
.voices .mono{color:var(--ink-3)}
.voices .h2{color:var(--ink);margin:20px 0 16px}
.voices__note{
  margin:0;font-family:var(--mono);font-size:.6rem;letter-spacing:.16em;
  text-transform:uppercase;color:var(--ink-3);
}
.voices__rows{
  margin-top:clamp(34px,5vh,58px);
  display:grid;gap:18px;
}
.vrow{overflow:hidden}
.vtrack{
  display:flex;width:max-content;
  animation:vscroll 52s linear infinite;
  will-change:transform;
}
/* 反向 + 错相位 + 稍微不同的速度：
   ⛔ 两排如果同步，看上去像一整块在平移；
   ⛔ 速度完全一样又会长期锁死在同一相位上，经常出现「上下两张一模一样」，
      所以第二排 duration 故意给 58s（第一排 52s），让它慢慢漂开。 */
.vrow--b .vtrack{animation-direction:reverse;animation-duration:58s;animation-delay:-23s}
/* 鼠标停在哪一排，哪一排停住 —— 不然长文案根本读不完 */
.vrow:hover .vtrack{animation-play-state:paused}
@keyframes vscroll{
  from{transform:translateX(0)}
  to{transform:translateX(-50%)}
}

.vcard{
  flex:0 0 auto;
  width:clamp(280px,27vw,400px);
  /* ⛔ 间距写 margin，见上面那条：用 gap 会让 -50% 差半个间隙 */
  margin-right:18px;
  background:#fff;
  border:1px solid rgba(11,15,20,.1);
  border-radius:3px;
  padding:clamp(18px,1.6vw,26px) clamp(18px,1.6vw,24px);
  display:flex;flex-direction:column;gap:13px;
}
.vcard__hd{display:flex;align-items:center;gap:10px}
.vcard__mono{
  flex:0 0 auto;width:36px;height:36px;border-radius:50%;
  display:grid;place-items:center;
  font-family:var(--mono);font-size:.68rem;letter-spacing:.04em;
  background:rgba(11,15,20,.055);color:var(--ink-2);
}
.vcard__who{min-width:0;display:flex;flex-direction:column;line-height:1.32}
.vcard__who b{font-size:.87rem;font-weight:650;color:var(--ink)}
.vcard__who span{font-size:.71rem;color:var(--ink-3)}
.vcard__stars{display:flex;gap:2px;margin-left:auto;color:var(--accent)}
.vcard__stars i{display:block;line-height:0}
.vcard__quote{font-size:.87rem;line-height:1.72;color:var(--ink-2);margin:0}

/* ------------------------------ 10 · 认证（深色，两条 45° 斜链逆向平移） ------
   参考机能风作品站（bilibili BV1eBz5BcETa）：两串证书各沿一条 45° 斜线排开，
   滚动时一串往右上、一串往左下，整串平移。
   ⛔⛔ 不要再「排成一排 + 整排斜移」。横向排成一条，肉眼读到的永远是「一根横线在动」，
      位移量算得再准也没用（被连问两次，第一次说 45°、第二次说还是水平线）。
   ⛔ 必须 sticky：页面本身在垂直滚，不钉住的话屏幕上看到的角度会被压陡（实测约 76°）。
   ⛔ 类名必须叫 `.certsec` 而不是 `.certs` —— 首屏七幕面板里早有一个
      `<ul class="certs">`（三行认证有效期），撞名会把下面这段深色背景
      套到首屏那块列表上，首屏直接被撑成大黑块、把 3D 椅顶下去。
   ⛔ `.certsec` 上**不能写 overflow:hidden** —— 任何祖先带 overflow 都会让 sticky 失效，
      裁剪交给 `.certsec__stage` 自己做。
   ------------------------------------------------------------------ */
.certsec{
  background:var(--dark);color:var(--txt);
  /* ⛔ 底部 padding 必须归零：舞台要一直贴到这一段的下沿，
     否则白层推到顶之后还会露出一条黑边，跟下一个白板块接不上。 */
  padding:clamp(76px,12vh,140px) 0 0;
  /* 舞台高度和退场距离提到变量上：pin 的高度要靠这两个数算出来。
     ⛔ 别再写死 82vh —— 三个数必须联动，单独改一个会让 sticky 的解除点错位，
        表现是「白层推到一半就停住」或者「白层推到顶了证书还在」。 */
  /* ⛔⛔ 舞台必须**顶满导航以下的整个视口**，不能留 82vh。
     原因：sticky 钉住之后，pin 里 stage 之外那段空间会一直暴露在视口下方 ——
     那段是空的（露出 section 的深色底），白层推到 82vh 就停，底下永远挂一条黑带，
     再接下一个白板块就成了「白块 → 黑带 → 白块」。用户截图里那条横切就是这个。
     现在 stage 底边正好落在一个视口底，pin 多出来的 --exitH 全在视口外，看不到。 */
  --stageH:calc(100vh - var(--hdr-h));
  --exitH:760px;
  /* ⛔ isolation 必须有：下面 ::after 用 z-index:-1 垫底色，
     不建层叠上下文的话它会跑到更外层去，垫不到这一段下面。 */
  position:relative;
  isolation:isolate;
}
/* 整段背景补间：stage 只覆盖钉住的那一块视口，舞台往上一滚，
   上面露出的是 .certsec 自己的深色底 —— 白层推到顶之后还会闪一条黑出来。
   这一层跟同一个 --wash 一起变白，把整段兜住，退场结束就是纯粹的白。 */
.certsec::after{
  content:'';position:absolute;inset:0;z-index:-1;
  background:var(--paper);
  opacity:var(--wash,0);
  pointer-events:none;
}
.certsec .mono{color:var(--accent)}
.certsec .h2{color:var(--txt);margin:20px 0 18px}
.certsec__lead{
  color:var(--txt-2);font-size:.94rem;line-height:1.75;max-width:46ch;margin:0;
}
.certsec__lead b{color:var(--txt);font-weight:600}

/* ⛔⛔ 这里又回到 sticky 了，但目的跟上一版完全不同（2026-10-20 第二轮）：
   上一版的 sticky 是「钉住 + 按滚动进度推两串证书」，已经被废弃（那样悬停停不住）。
   现在 sticky 只用来**撑出一段退场时间轴**：pin 比 stage 高出 --exitH，
   这段额外高度里 stage 一动不动，motion.js 拿它当进度条，让证书飞散、白层推上来。
   自动漂 + 悬停暂停不受影响 —— 那套在 .chain 上，两者写的是不同的属性。 */
.certsec__pin{
  position:relative;
  height:calc(var(--stageH) + var(--exitH));
  margin-top:clamp(20px,3vw,40px);
}
.certsec__stage{
  position:sticky;
  top:var(--hdr-h);
  height:var(--stageH);
  overflow:hidden;
  /* ⛔ 必须给不透明底色。不给的话 stage 是透明的，会透出下面 .certsec::after
     那层白（它跟着 --wash 一起变白）—— 舞台还没推就被整体染成灰的，
     白层从下沿推上来时和这层灰叠在一起，很脏。给了之后 ::after 只在舞台滚走时才看得见。 */
  background:var(--dark);
  /* 把重绘 / 布局范围锁在舞台里：两串十六张卡每帧都在动，
     不锁的话上面标题区、说明行也会跟着重算。和 overflow:hidden 不冲突，
     sticky 也不受影响（contain 不影响自身定位）。 */
  contain:paint;
}
/* ⛔ 边缘不做渐变，直接硬切（漫画出血线的做法）：卡片撞到舞台边界就是一刀切掉，
   边缘的「透视感」来自卡片被边界裁断，而不是从深到浅地淡出去。
   早先用过两侧 / 上下各盖一道同色渐变，结果整块看着雾蒙蒙的，
   远端那串也一起被吃掉 —— 用户明确要「不要渐变」。
   ⛔ 别改回 mask（要前缀、叠 will-change 出条纹），也别用 transparent
   （按「透明黑」插值，中间泛一层灰）。 */

/* 两条斜链：每条链自己沿 45° 排开（下一张往右 X、往上 X），链内相对位置固定，
   整条链沿自己的 45° 方向来回漂，两条链方向相反。 */
.certsec__field{position:absolute;inset:0}

/* 上沿「融进黑里」：把卡片被上边界切断的那一刀化掉。
   ⛔ 只有这一道。下沿绝对不要加 —— 下沿是卡片朝你飞出来的地方，
      那里必须是硬切，加了就变成「被框住的一张图」。
   ⛔ 高度别再往上加：超过 150px 会把远端那串整片吃掉，
      又回到被否掉的「糊了一层雾」。120 左右刚好只咬掉最上面一张的边。
   ⛔ 颜色用 --dark 的同色 rgba，不要用 transparent 收尾（会按透明黑插值，中间泛灰）。
   ⛔ 白层推上来时它必须跟着退掉，否则白底上挂一条黑雾。 */
.certsec__stage::before{
  content:'';position:absolute;top:0;left:0;right:0;
  height:clamp(80px,13vh,128px);
  background:linear-gradient(to bottom,rgba(10,14,19,.96) 6%,rgba(10,14,19,0) 100%);
  z-index:5;pointer-events:none;
  /* ⛔ 2026-10-20 二轮：--in 已经下放到 .certsec__field（为了缩小变量重算范围），
     stage 自己的伪元素读不到它了。这里本来也只需要「进场的头一下不突兀」，
     直接恒亮、跟着 --wash 退掉就够 —— 舞台是从视口下方升上来的，
     露头那一刻黑雾全亮反而更像「幕布往上拉」。 */
  opacity:calc(1 - var(--wash,0));
}

/* ---------------------------- 退场层 ----------------------------
   每条链外面套一层 .exit，motion.js 按退场进度写 --out（0 → 1）。
   职责分工：.exit 管「飞出去 + 淡出」，.chain 管「自己漂 + 悬停暂停」。
   ⛔ 千万别把两件事写进同一层：transform 只有一个，后者会盖掉前者。
   ⛔ A 往右上、B 往左下，各续各的漂移方向 —— 退场看起来才像「顺着力道甩出去」，
      而不是两串一起往同一个方向平移。 */
.exit{
  position:absolute;inset:0;
  /* ⛔⛔ 这一句不能少。.exit 是 inset:0 铺满整个舞台的空盒子，
     默认 pointer-events:auto 会把自己的整片矩形当成 hit target，
     把**下面那一串**的卡片全挡住 —— 表现是「只有一串能悬停放大」。
     实际是老板真机实测发现的，之前我用 CDP 测的是加 .exit 之前的版本，所以没暴露。
     命中要留给 .cert（它自己 pointer-events:auto）。 */
  pointer-events:none;
  will-change:transform,opacity;
  transform:
    translate3d(
      /* ⛔ 进场**不做横向偏移**，只做纵向：舞台是从视口下方升上来的，
         卡片再从左右两边横向飞入，两个方向叠在一个人身上就成了乱飘。
         横向那 16vw 只留给退场（对角撕开）。
         加速段（--surg，2026-10-20 二轮）也走横向：乘 -ox，
         配上纵向的 oy，两串各自沿 45° 朝自己的漂移方向滑出去一截。 */
      calc(var(--out,0) * 16vw * var(--ox,1) + var(--surg,0) * -12vw * var(--ox,1)),
      /* ⛔ 纵向进场**不乘 --oy**，两串统一从下往上涌：
         按 --oy 分左右的话，A 串会从上方掉下来，和「幕布往上拉」的物理方向打架。 */
      calc(var(--out,0) * 13vh * var(--oy,1) + (1 - var(--in,1)) * 26vh
           + var(--surg,0) * 15vh * var(--oy,1)),
      0
    )
    /* ⛔ 退场靠**放大冲出去**，不是靠平移走开。
       两串是 45° 斜链、只占对角线，平移走开会在画面正中留一大片空黑，
       要等白层慢慢推上来填 —— 那一段「人走楼空」是最难看的。
       放大则是卡片自己膨胀到出画，中间一直有东西，白层推上来正好盖住。
       进场就用同一个参数倒着放：从放大 1.28 倍收回到 1.0。 */
    scale(calc((1 + var(--out,0) * .62) * (1 + (1 - var(--in,1)) * .28)));
  /* ⛔⛔ 不要「一上来就跟着 out 线性变淡」。A/B 是两个独立的 .exit，
     各自 opacity 半透明时，两串重叠的地方会互相看穿 —— 十六张证书糊成一坨。
     做法：前 68% 完全不透明（纯位移，卡与卡正常遮挡），最后 32% 才快速淡掉；
     那时位移已经过半，两串也各自甩出了画面中心，看不出重叠。
     ⛔ 前面乘的 --in 走的是「前 17% 就淡完」（×6 立刻到 1）：
        进场要是也慢慢淡，两串会在半透状态下重合一整段，跟退场那个坑一模一样。
        卡片本来就大多在画外，只要一开始那一下不突兀就够了。
     ⛔ 负值/超过 1 的部分浏览器会自己 clamp，不用写 min/max。 */
  opacity:calc(min(1, var(--in,1) * 6) * (1 - (var(--out,0) - .68) * 3.2));
}
/* ⛔ 两串要往**对角撕开**：A 在画面左侧，走左上；B 在右侧，走右下。
   按各自漂移方向（A 往右上、B 往左下）会让两串在画面正中撞满怀，
   十六张卡叠成一坨 —— 这一版第一次做就是这个错。
   进场时反着来：A 从左上飞入、B 从右下飞入，正好和退场对称。 */
.exit--a{--ox:-1;--oy:-1;z-index:2}
.exit--b{--ox:1;--oy:1;z-index:1}

/* 白色补间层：从舞台下沿整块往上推，把黑推成白。
   ⛔ 用 translateY 而不是改 opacity：opacity 读起来是「画面变灰、褪色」，
      从下沿推上来才是「下一页纸在盖过来」，也是用户要的「PPT 动画窗格」那味。
   ⛔ 必须盖在 ::before 的黑雾之上（z-index 6 > 5），否则白层顶上永远挂一条黑边。 */
.certsec__wash{
  position:absolute;inset:0;z-index:6;pointer-events:none;
  background:var(--paper);
  transform:translateY(calc((1 - var(--wash,0)) * 100%));
}
/* ⛔ 立体感全靠这一句 perspective：卡片按 --i 往画面深处退（见 .cert 的 --z）。
   远处（画面上沿）小、近处（画面下沿）大 —— 小的那张被上边界切掉是「离得远」，
   大的那张被下边界切掉才像「从你身边飞过去」。硬切本身没错，错在整条链等大：
   等大的卡片被边界切开，读起来就是「一张图被裁了」。
   ⛔ perspective 必须给 .cert 的**直接父级**（.chain），给 .field 不生效。
   ⛔ 漂移做在 .chain 上（不是 .cert），这样卡片的 rotate / --z / hover 放大互不干扰。 */
.chain{
  position:absolute;inset:0;pointer-events:none;
  perspective:1200px;perspective-origin:50% 46%;
  /* ⛔ 用 alternate 来回漂，不做「跑满一圈再跳回起点」：
     链路没有复制内容，跳回去会看到卡片换脸。smooth 的 easeInOut 让折返点不突兀。 */
  animation:certDriftA 24s cubic-bezier(.45,0,.55,1) infinite alternate;
  will-change:transform;
}
/* ⛔ 悬停只停「这一串」：.chain 本身 pointer-events:none，但卡片是 auto，
   所以鼠标落在某条链的卡片上时，:hover 会沿祖先链冒到对应的 .chain。 */
.chain:hover{animation-play-state:paused}

@keyframes certDriftA{
  from{transform:translate3d(-150px,150px,0)}
  to{transform:translate3d(150px,-150px,0)}
}
/* 第二串的 keyframes 是镜像的：任何时刻两串都在往相反方向走 */
@keyframes certDriftB{
  from{transform:translate3d(150px,-150px,0)}
  to{transform:translate3d(-150px,150px,0)}
}

/* --x0/--y0 是链起点（相对舞台的百分比），--step 是每往上一张的位移（px，横竖同一个数 = 45°）。
   ⛔ step 别小于卡片宽：小了每张都被下一张挡住，看起来不是「一串」是一「叠」。
   ⛔ y0 压在 100%（起点在画面下沿），--i 从 -2 起，链从画面下面外面进来、走到画面上沿外面，
     两头都不露端点。
   ⛔ 两条链的垂直间距 ≈ (x0b - x0a) / √2。
     2026-10-20 二轮按用户要求收紧：step 265 → 240（间隙从 65 收到 45 左右）。
     ⛔⛔ 试过 215（间隙只剩 15px），结果同一串的卡首尾相接、读成一条实心斜带，
        「两串」的结构直接没了 —— 用户说的「紧凑」不是「贴死」。
        下限是卡片宽 + 40px 左右，再小就不是一串了。 */
.chain--a{--x0:2%;--y0:100%;--step:240px}
.chain--b{--x0:45%;--y0:100%;--step:240px;animation-name:certDriftB}

.cert{
  position:absolute;
  /* ⛔ 这里靠 --i 排斜链：left 加、top 减，同一个 --step 才落在 45° 线上。
     百分比基点必须是 .chain（inset:0 铺满），给 0 尺寸的容器算百分比会全部塌到 0。 */
  left:calc(var(--x0) + var(--i) * var(--step));
  top:calc(var(--y0) - var(--i) * var(--step));
  /* ⛔ 宽度别给太大：证书竖长条（宽高比 ≈ 1:1.42），240 宽就 480 高，一屏只放得下一张半。
     ⛔ 宽度必须 < --step，否则每张被下一张压住。现在 step=265，宽度封顶 200。 */
  width:clamp(160px,13.5vw,200px);
  /* ⛔ margin 必须清掉：figure 浏览器默认 16px 40px，留着每张左右各多 40px，
     斜链的间距会被撑歪。 */
  margin:0;
  background:#fff;
  padding:8px 8px 0;
  border-radius:2px;
  box-shadow:0 26px 52px -16px rgba(0,0,0,.72),0 2px 7px rgba(0,0,0,.3);
  /* 这一层只负责三件事：居中、按 --i 的深度、随手摊的那点角度。
     ⛔ 漂移在 .chain 上、退场在 .exit 上、悬停放大用独立的 `scale` 属性 ——
        三件事分三处写。全塞进这个 transform 就会互相覆盖，只剩最后一个生效。 */
  transform:
    translate(-50%,-50%)
    translate3d(0,0,var(--z,0px))
    rotateY(calc(var(--i) * -1.6deg))
    rotate(var(--rot,0deg));
  will-change:transform;
  /* ⛔ 放大用独立的 `scale` 属性，不要塞进 transform（会和 rotate / --z 打架）。
     曲线用 easeOutExpo 那一档：起手快、尾巴长，停得住，不像 ease-in-out 那样软塌塌。 */
  transition:scale .58s cubic-bezier(.16,1,.3,1),box-shadow .58s ease;
  pointer-events:auto;
}
/* 每张给一点角度差，像随手摊的一沓；⛔ 用 --i 算，别用 nth-child
   ⛔ --z 是深度：i 越大越往画面深处退（画面上沿=远、下沿=近）。
      配合 .chain 上的 perspective:1200px，近处那张放大约 1.18 倍、远处约 0.73 倍。
      改 --z 的系数要连着 perspective 一起看，只调一个会变成「一头特别大」。 */
.cert{--rot:calc((var(--i) - 1) * 1.7deg);--z:calc(var(--i) * -150px)}
.cert:hover{
  scale:1.14;z-index:9;
  box-shadow:0 44px 78px -20px rgba(0,0,0,.9),0 2px 10px rgba(0,0,0,.4);
}

/* 第二串是同一批证书的重复件，压成后景层：暗一点，别跟前景抢。
   ⛔ 别压太狠：.52 的亮度在 #0a0e13 上基本等于看不见，用户直接说「只有一串」。
   ⛔⛔ 2026-10-20 二轮：原来这里写的是 filter:brightness(.72) saturate(.9)。
      filter 挂在一个每帧都在做 transform / scale 的元素上，浏览器只能反复重新光栅化
      整张卡 —— 十六张一起滚，就是用户说的「卡卡的」。
      改成盖一层纯色遮罩（rgba 叠加），不碰图片本身，合成阶段就完成了。
   ⛔ 也别改回 opacity —— 这条链自己内部就有重叠，半透明叠起来会糊成一团。 */
.chain--b .cert{box-shadow:0 20px 40px -14px rgba(0,0,0,.65)}
.chain--b .cert::after{
  content:'';position:absolute;inset:0;border-radius:2px;
  background:rgba(10,14,19,.34);
  pointer-events:none;
  transition:background .5s ease;
}
/* 悬停第二串的某张时把它提亮回来 —— 不然压暗状态下根本看不清内容 */
.chain--b .cert:hover{box-shadow:0 36px 64px -18px rgba(0,0,0,.8)}
.chain--b .cert:hover::after{background:rgba(10,14,19,.06)}
/* 前后层次挂在 .exit 上（不在 .chain 上）：退场层各自有 transform 和 opacity，
   在同一层里再抢 z-index 容易打架。 */

.cert img{display:block;width:100%;height:auto;background:#fff}
.cert figcaption{padding:13px 6px 15px;color:var(--ink)}
.cert figcaption b{
  display:block;font-family:var(--mono);font-size:.8rem;letter-spacing:.09em;
  color:var(--ink);margin-bottom:4px;
}
.cert figcaption span{
  display:block;font-size:.78rem;line-height:1.4;color:var(--ink-2);
}
.cert figcaption em{
  display:block;font-style:normal;font-family:var(--mono);font-size:.66rem;
  letter-spacing:.05em;color:var(--ink-3);margin-top:7px;line-height:1.5;
}
/* 说明行放在标题下面（正常流里）。
   ⛔ 别放进钉住的舞台：卡片又长又大，放进去一定会被某张证书压住，怎么调 z-index 都别扭。 */
.certsec__note{
  margin:18px 0 0;
  font-family:var(--mono);font-size:.6rem;letter-spacing:.16em;
  text-transform:uppercase;color:var(--txt-3);
}

/* ------------------------------ 跑马灯 ------------------------------
   轨道里放**两份一模一样**的内容，位移 -50% 正好接上，看不出接缝。
   ⛔ 只放一份会在末尾露出空档。
   ------------------------------------------------------------------ */
.tick{
  overflow:hidden;background:var(--dark-2);
  border-top:1px solid var(--ln-2);border-bottom:1px solid var(--ln-2);
  padding:15px 0;
}
.tick__track{
  display:flex;align-items:center;gap:clamp(20px,3vw,40px);
  width:max-content;
  animation:tk 36s linear infinite;
  will-change:transform;
}
.tick span{
  font-family:var(--mono);font-size:.64rem;letter-spacing:.2em;
  text-transform:uppercase;color:var(--txt-2);white-space:nowrap;
}
.tick em{font-style:normal;color:var(--accent);opacity:.55}
.tick:hover .tick__track{animation-play-state:paused}
@keyframes tk{to{transform:translate3d(-50%,0,0)}}

/* ------------------------------ 09 · 下单流程（白底）------------- */
.flow{
  background:var(--paper);color:var(--ink);
  padding:clamp(76px,12vh,150px) 0;
}
.flow .mono{color:var(--ink-3)}
.flow .h2{color:var(--ink)}
.flow__in{
  display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.06fr);
  gap:clamp(30px,5vw,80px);align-items:start;
}
.flow__media{position:sticky;top:calc(var(--hdr-h) + 24px)}
.flow__media figure{
  margin:0;position:relative;overflow:hidden;border-radius:3px;background:#e3e0da;
  aspect-ratio:3/4;
}
/* 五张叠着放，靠 .is-on 交叉淡入（2026-10-20 二轮，用户要「渐变换图，跟流程对应」）。
   ⛔ 原来这里是一张图 + 26px 视差。轮换之后视差没意义了，
      而且 data-px 写的是 transform，会和下面的 scale 抢同一个属性。
   ⛔ 放大用独立的 `scale` 属性，别塞进 transform（两者各自合成，谁也不盖谁）。
   ⛔ 别用 clip-path 做转场：五张竖图同时裁剪会掉帧。opacity / scale 都是合成层操作。 */
.flow__media img{
  position:absolute;inset:0;
  width:100%;height:100%;object-fit:cover;
  opacity:0;scale:1.05;
  transition:opacity .7s cubic-bezier(.2,.7,.2,1),
             scale 1.5s cubic-bezier(.2,.7,.2,1);
}
/* 到位的那张：不透明、缩回原大。scale 给更长的时长，淡入完了还在慢慢沉下来。 */
.flow__media img.is-on{opacity:1;scale:1}
.flow__media .mono{display:block;margin-top:14px}

.steps2{list-style:none;margin:clamp(28px,4vh,48px) 0 0;padding:0;position:relative}
/* 竖轨 + 已走过的部分（--p 由 JS 写，0→1） */
.steps2::before,
.steps2::after{
  content:'';position:absolute;left:0;top:7px;width:1px;
}
.steps2::before{bottom:7px;background:rgba(11,15,20,.14)}
.steps2::after{
  background:var(--accent);
  height:calc(var(--p,0) * (100% - 14px));
}
.step{
  position:relative;
  display:grid;grid-template-columns:auto 1fr;gap:0 16px;
  /* ⛔ 2026-10-20 二轮：原来是 clamp(22px,3.4vh,38px)。五步加起来只有 500 出头，
     滚 500px 就把整段走完了 —— 用户说「动效不明显」，一部分原因就是节奏太快、
     左侧换图根本来不及看。实测 40px 还是不够（一次滚 380px 就走到第 5 步），
     再加一档到每步约 184px，滚 900px 走完，一张图大约占 180px 滚动。 */
  padding:0 0 clamp(62px,11.4vh,124px) 26px;
  opacity:.4;transition:opacity .5s ease;
}
.step:last-child{padding-bottom:0}
.step.is-on{opacity:1}
/* 点落在竖轨上：step 的边框盒左边就是轨的位置 */
.step::before{
  content:'';position:absolute;left:-3px;top:.42em;
  width:7px;height:7px;border-radius:50%;
  background:var(--paper);border:1px solid rgba(11,15,20,.28);
  transition:background .4s,border-color .4s,box-shadow .4s;
}
.step.is-on::before{
  background:var(--accent);border-color:var(--accent);
  box-shadow:0 0 0 5px rgba(255,122,0,.13);
}
.step i{
  font-style:normal;font-family:var(--mono);font-size:.6rem;font-weight:500;
  letter-spacing:.16em;color:var(--ink-3);padding-top:.28em;
  transition:color .45s ease;
}
/* 当前这一步的编号转成主色：只靠整体透明度 .4→1 差别太弱，
   用户的反馈是「有动效但不明显」。 */
.step.is-on i{color:var(--accent)}
.step b{display:block;font-size:1.02rem;font-weight:700;letter-spacing:-.022em;line-height:1.3}
.step em{
  display:block;margin-top:6px;font-style:normal;
  font-size:.84rem;line-height:1.62;color:var(--ink-2);max-width:40ch;
}

/* ------------------------------ 10 · 常见问题 ------------------------------ */
.faq{
  background:var(--dark);border-top:1px solid var(--ln-2);
  padding:clamp(72px,11vh,128px) 0;
}
.faq__in{
  display:grid;grid-template-columns:minmax(0,.82fr) minmax(0,1.18fr);
  gap:clamp(30px,5vw,84px);align-items:start;
}
.faq__hd{position:sticky;top:calc(var(--hdr-h) + 26px)}
.faq__list{border-top:1px solid var(--ln-2)}
.q{border-bottom:1px solid var(--ln-2)}
.q button{
  width:100%;display:flex;justify-content:space-between;align-items:center;gap:20px;
  padding:clamp(18px,2.4vh,24px) 0;
  background:none;border:0;color:var(--txt);
  font:inherit;font-size:1rem;font-weight:600;letter-spacing:-.01em;
  text-align:left;cursor:pointer;transition:color .2s;
}
.q button:hover{color:#fff}
/* 加号：两根 1px 的线，展开时竖线收掉 → 变减号 */
.q button i{position:relative;flex:0 0 auto;width:13px;height:13px}
.q button i::before,
.q button i::after{
  content:'';position:absolute;background:var(--txt-3);
  transition:transform .38s cubic-bezier(.2,.7,.2,1),background .3s;
}
.q button i::before{left:0;top:6px;width:13px;height:1px}
.q button i::after{left:6px;top:0;width:1px;height:13px}
.q.is-open button i::before{background:var(--accent)}
.q.is-open button i::after{transform:scaleY(0)}
/* 0fr → 1fr 是现在唯一不用量高度的展开写法 */
.q__a{display:grid;grid-template-rows:0fr;transition:grid-template-rows .45s cubic-bezier(.2,.7,.2,1)}
.q.is-open .q__a{grid-template-rows:1fr}
.q__a>div{overflow:hidden}
.q__a p{
  padding:0 40px 22px 0;font-size:.9rem;line-height:1.72;
  color:var(--txt-2);max-width:48ch;
}

/* ------------------------------ 11 · 页脚 ------------------------------ */
.foot{
  background:var(--dark-3);border-top:1px solid var(--ln-2);
  padding:clamp(70px,11vh,130px) 0 0;overflow:hidden;
}
.foot__big{font-size:clamp(2.1rem,5.2vw,4.2rem);max-width:17ch;margin-top:20px}
.foot__cta .cta{margin-top:clamp(26px,4vh,38px)}
.foot__cols{
  display:grid;grid-template-columns:repeat(4,minmax(0,1fr));
  gap:clamp(20px,3vw,44px);
  margin-top:clamp(56px,8vh,96px);padding-top:clamp(28px,4vh,44px);
  border-top:1px solid var(--ln-2);
}
.foot__cols .mono{display:block;margin-bottom:12px}
.foot__cols a,.foot__cols span{
  display:block;padding:5px 0;font-size:.86rem;color:var(--txt-2);
  transition:color .2s;word-break:break-word;
}
.foot__cols a:hover{color:var(--txt)}

/* 社交外链：圆形字母徽章（2026-10-20 二轮）。
   ⛔ 不用品牌 logo 的 SVG 路径 —— 手画容易走形，这些标识本身也有使用规范。
      字母 + aria-label 更稳，读屏也读得出来。
   ⛔ 缩放/上浮用独立的 `translate` 属性，别写 transform：
      以后要是给这几个加别的动效，两者不会互相盖掉。
   ⛔ 这条必须写在 .foot__cols a 之后：同权重，后写的才压得住 display/padding。 */
.foot__social{display:flex;gap:10px;margin-top:clamp(26px,4vh,40px)}
.foot__social a{
  display:grid;place-items:center;
  width:40px;height:40px;padding:0;
  border:1px solid rgba(255,255,255,.16);border-radius:50%;
  font-family:var(--mono);font-size:.68rem;letter-spacing:.04em;
  color:var(--txt-2);
  transition:color .3s,border-color .3s,background .3s,translate .3s;
}
.foot__social a:hover{
  color:var(--txt);border-color:rgba(255,255,255,.4);
  background:rgba(255,255,255,.07);translate:0 -2px;
}
.foot__mark{margin-top:clamp(44px,7vh,84px);overflow:hidden}
/* 品牌大字只做很淡的一层，是纹理不是主角。
   ⛔ 字号不能只按 vw 给：容器有 max-width，vw 一放大字就顶出右沿被裁掉半个字母
      （实测 15.2vw 时最后的 g 直接切平）。
   ⛔ 也不能给太小 —— 11.8vw 时只占容器 79%，右边空一大块，看着像没对齐。
      实测 14.6vw 时文字宽 1171 / 容器 1200，基本齐边且不裁。 */
.foot__mark span{
  display:block;font-weight:700;letter-spacing:-.055em;line-height:.8;
  font-size:clamp(2.6rem,14.6vw,14.2rem);color:rgba(255,255,255,.075);
  white-space:nowrap;will-change:transform;
}
.foot__base{
  display:flex;justify-content:space-between;align-items:baseline;gap:20px;
  margin-top:clamp(18px,3vh,30px);padding:20px 0 26px;
  border-top:1px solid var(--ln-2);
}
.foot__base p{font-size:.72rem;color:var(--txt-3)}

/* =========================================================================
   ⛔ 窄屏规则必须写在文件最后：同权重下后写的才生效。
   ⛔ 窄屏不显示引线（.film__lines 隐藏），标注改成固定槽位。
   ========================================================================= */
@media (max-width:1023px){
  .film{height:calc(100vh + 520vh)}
  .macro__row{grid-template-columns:repeat(2,1fr);gap:clamp(10px,2vw,18px)}
  .panel[data-pos="left"],.panel[data-pos="right"],.panel[data-pos="bottomleft"]{
    top:var(--hdr-h);left:var(--pad);right:var(--pad);
    width:auto;transform:none;
    padding-top:clamp(18px,4vh,40px);
  }
  .panel[data-pos="bottom"]{bottom:clamp(18px,3vh,32px)}
  .h1{font-size:clamp(1.72rem,6.4vw,2.4rem)}
  .h2{font-size:clamp(1.38rem,5vw,1.9rem)}
  .lead{max-width:none;font-size:.92rem}
  .facts{margin-top:18px;gap:14px 22px}
  .facts b{font-size:1.05rem}
  .film__lines{display:none}
  .call{
    width:auto;left:var(--pad);right:var(--pad);
    /* ⛔ 固定槽位：标题下方、模型上方那一带，不要跟着锚点走 */
  }
  .steps,.certs,.spec{margin-top:16px}
  .split{grid-template-columns:1fr}
  .card{grid-template-columns:64px 1fr;gap:12px;padding:10px}
  .card img{width:64px;height:64px}
  .film__hint{display:none}
}

@media (max-width:560px){
  .hdr__nav{display:none}
  .hdr__cta{margin-left:auto}
  .facts{display:none}
  .fine{display:none}
  .card__txt em{display:none}
}

/* ⛔ 手机档必须写在文件最后：同权重下后写的才生效。
   ≤767px 不做手风琴，改成横滑 + 吸附（触屏没有 hover，展开/收起读不出来）。 */
@media (max-width:767px){
  .band{
    display:flex;gap:10px;overflow-x:auto;
    scroll-snap-type:x mandatory;overscroll-behavior-x:contain;
    -webkit-overflow-scrolling:touch;
    padding:0 var(--pad) 4px;
    scrollbar-width:none;
  }
  .band::-webkit-scrollbar{display:none}
  .band figure{
    flex:0 0 min(78vw,420px);scroll-snap-align:center;
    transition:none;
  }
  .band figcaption,
  .band figure:not([data-open]) figcaption{opacity:1}
  .band figure:not([data-open]) figcaption .mono{display:inline}
  /* 微距图在窄屏保持两列：四张竖图堆成一列会拉出一整屏的空滚动 */
  .macro__row{grid-template-columns:repeat(2,1fr);gap:10px}
  .macro figcaption{padding-top:10px}
  .macro em{font-size:.72rem}
  .band img,
  .band figure[data-open] img{
    height:clamp(247px,68vw,338px);
    filter:saturate(.82) brightness(.88) contrast(1.05);
    transform:none;
  }
}

/* ------------------------- 后半页的窄屏（必须在这）------------------------- */
@media (max-width:1023px){
  .nums__grid{grid-template-columns:repeat(3,minmax(0,1fr));row-gap:8px}
  .nums__grid li{padding-right:14px}
  /* 两栏一拆，sticky 就没有意义了（图会卡在中间不动），改回普通流 */
  .flow__in,.faq__in{grid-template-columns:1fr;gap:clamp(26px,5vw,44px)}
  .flow__media{position:static}
  .flow__media figure{aspect-ratio:16/10}
  .flow__media img{height:calc(100% + 36px);margin-top:-18px}
  .faq__hd{position:static}
  .foot__cols{grid-template-columns:repeat(2,minmax(0,1fr));row-gap:28px}
}

@media (max-width:767px){
  .nums__grid{grid-template-columns:repeat(2,minmax(0,1fr))}
  .nums__grid li:nth-child(3){border-top:1px solid var(--ln)}
  .nums__grid b{font-size:clamp(1.9rem,9vw,2.6rem)}
  .nums__grid p{max-width:none}
  .tick__track{animation-duration:26s}
  .step{padding-bottom:24px}
  .step em{max-width:none}
  .q__a p{padding-right:0}
  .faq__hd{position:static}
  .foot__mark span{font-size:clamp(2.6rem,13.5vw,6rem)}
  .foot__base{flex-direction:column;gap:8px;align-items:flex-start}
}

/* 手机：还是两条斜链、逆向走，只是链短一点、卡片小一点 —— 别退回竖排列表，
   那样就没「两串逆向」这回事了，跟桌面完全两种观感。
   ⛔ step 必须跟着卡片宽一起缩：卡片 130 宽、step 还是 150 的话，三张会全散开断链。 */
@media (max-width:767px){
  /* ⛔ 这里原来还有一条 .certsec__pin{height:calc(100vh + 480px)}，是滚动驱动时代的
     「钉住够久」用的。现在驱动改成纯时间动画、pin 桌面档已经是 height:auto，
     这条留着会让手机上白白多出 480px 的死滚动，已删。 */
  /* ⛔ 别等比缩到 130 出头 —— 那么窄证号根本看不清；但也别超过 step，超了会叠 */
  .cert{width:clamp(128px,34vw,145px);padding:6px 6px 0}
  .cert figcaption{padding:9px 4px 11px}
  .cert figcaption b{font-size:.66rem}
  .cert figcaption span{font-size:.62rem}
  .cert figcaption em{font-size:.56rem;margin-top:5px}
  /* ⛔ 手机必须给负的 x0：45° 意味着「升高多少像素就往右移多少」，
     390 宽的屏最多只能升 390px，起点压在半屏右侧时上半屏全空。
     给负值，链从屏幕左下外侧进来，斜穿整个画面。
     ⛔⛔ 2026-10-20 重调：舞台高度从 82vh 改成「满视口」，手机上一口气高了 80px，
        链整体被压到下半屏、y0 106% 会把最后的卡也顶出去。现在 y0 收到 88%：
        链两端各留 80~90px 余量，i=0..4 五张正好从底部斜贯到顶部。
     ⛔ step 也要跟着收（150 → 132）：45° 链在竖屏上横向只能走 390px，
        step 太大的话 i 一涨就飞出右边界，上半屏照样没人。 */
  .chain--a{--x0:-45%;--y0:88%;--step:132px}
  /* ⛔ 手机上 --z 要收：屏窄，同样的深度差会让最近那张直接顶满整屏 */
  .cert{--z:calc(var(--i) * -45px)}
  /* ⛔ 手机上只留一串。两条 45° 斜链在 390 宽里垂直间距只有 100 出头，
     卡片本身厚度接近 300，必然叠成一坨 —— 同一张证书糊两遍。 */
  .chain--b{display:none}
}

/* ---------------------- 13 · OEM / ODM（深色，两栏）----------------------
   左边一张窄卡：标题压在斜叠卡堆上，卡堆自己慢速换位；
   右边三行无限滚动，滚的是工序 / 认证 / 品类。
   版式照 accio.com/work 那块「工具生态」抄的骨架（2026-10-20 实测参数）。

   ⛔ 全部纯 CSS，一张动图都没有：
      跑马灯 = keyframes 推 translate(-50%)，内容原样复制两份；
      斜叠 = 四张卡共用一套 keyframes，各错开 -3.2s（一个周期 12.8s）。
   ⛔ 跑马灯的间距只能写在 margin-right 上，不能给 .otrack 加 flex gap：
      复制的是整组内容，-50% 要正好等于一个周期，用 gap 会少半个间隙、接缝抖一下。
   ⛔ .ostack 是铺满整张卡的绝对定位空盒子，必须 pointer-events:none ——
      不然它会把底下那条滚动的行整片挡住（证书段踩过一模一样的坑）。
   ⛔ 卡片底色不能半透明：两张卡换位时会互相看穿，糊成一坨。
   ---------------------------------------------------------------------- */
.oem{
  background:var(--dark-2);border-top:1px solid var(--ln-2);
  padding:clamp(72px,11vh,128px) 0;
}
.oem__in{
  display:grid;gap:12px;align-items:stretch;
  grid-template-columns:minmax(0,.66fr) minmax(0,1fr);
}

/* 左卡 ------------------------------------------------------------------ */
.oem__lead{
  position:relative;isolation:isolate;overflow:hidden;
  min-height:480px;padding:clamp(22px,2.6vw,32px);
  border-radius:22px;
  background:
    radial-gradient(130% 100% at 12% 0%,rgba(11,92,138,.92) 0%,rgba(11,92,138,0) 62%),
    linear-gradient(160deg,#08222f 0%,#05151d 100%);
  border:1px solid rgba(255,255,255,.09);
}
/* ⛔ 这里不能写 max-width:24ch —— ch 是按容器自己的 16px 字号算的（约 214px），
   标题会被硬挤成三行，压到下面的卡堆上。断行交给 h2 里的 <br>。 */
.oem__hd{position:relative;z-index:2}
.oem__lead .h2{font-size:clamp(1.6rem,2.2vw,2.05rem);margin:14px 0 10px}
.oem__sub{font-size:.82rem;line-height:1.55;color:var(--txt-2);max-width:30ch}

/* ⛔ 卡堆只占左卡的下半（top:44%），上半留给标题：
   铺满整张卡的话，卡片会直接糊在 "Four things" 上 —— accio 那边能用是因为
   它的徽章是一张图、标题白字压在图上还读得清；我们卡片是浅色实底，一压就花。 */
/* ⛔ 卡堆贴底（bottom 定位），不居中：居中会变成「一张卡飘在卡中间」，
   贴底才像「一摞单子从下沿抽上来」。卡片高度写死 190，四张不一样高叠起来会露齿。 */
.ostack{position:absolute;left:0;right:0;bottom:0;height:58%;z-index:1;overflow:hidden;pointer-events:none}
.ostack__card{
  position:absolute;bottom:16px;left:52%;width:64%;height:162px;overflow:hidden;
  display:flex;flex-direction:column;
  padding:16px 16px 18px;border-radius:16px;
  background:#f7f6f2;color:var(--ink);
  box-shadow:0 24px 34px rgba(0,0,0,.4);
  opacity:0;
  animation:ostack 12.8s cubic-bezier(.34,1.15,.6,1) infinite;
  animation-delay:calc(var(--i) * -3.2s);
}
.ostack__n{
  display:block;font-family:var(--mono);font-size:.52rem;letter-spacing:.2em;
  color:var(--ink-3);
}
.ostack__card b{
  display:block;margin-top:9px;font-size:1.12rem;line-height:1.04;
  letter-spacing:-.02em;color:var(--ink);
}
/* 描述贴底：四张卡内容长短不一，不推到底的话短的那张下面会空一大块 */
.ostack__card p{margin:auto 0 0;padding-top:9px;font-size:.72rem;line-height:1.5;color:var(--ink-2)}
/* 一摞卡往上抽：从下沿等着 → 升到正中 → 停住让你读 → 继续往上飞、淡出。
   ⛔ 三个相位都只动 translateY，横向一律 -50% 不偏 ——
      横向也错位的话，边缘会露出半张卡的角，读起来像「图片被裁了」而不是「一摞」。
   ⛔⛔ 进入时**不用 opacity 淡入**，靠 .ostack 的 overflow 裁切从下沿抽上来。
      早先是边升边淡入，淡入那 0.5 秒里卡片是半透明灰色的，
      能透出底下另一张卡的字 —— 像「图没加载完」。位置拉开之后根本不需要淡入。
   ⛔⛔ 每张卡的可见区间必须收在自己的 25% 里（4 张 × 25% = 12.8s 一圈）。
      早先淡出写到 25%~28%，跨过了 25% 这条线，和下一张的淡入叠了 0.38 秒 ——
      两张卡同时半透明，文字互相穿透糊成一坨（手机档实测才看出来，桌面正好截在交界外）。
   ⛔ 0% 和 100% 必须完全同状态（opacity 0 + 在下方），否则循环到头的瞬间会闪一下。
   ⛔ keyframes 里不要写 z-index：它不是可动画属性，各浏览器只在关键帧边界应用，
      会造成层级跳变。同一时刻只有一张不透明，靠 DOM 顺序就够。 */
@keyframes ostack{
  0%{opacity:0;transform:translate(-50%,110%) rotate(6deg) scale(.94)}
  1%{opacity:1;transform:translate(-50%,110%) rotate(6deg) scale(.94)}
  6%{opacity:1;transform:translate(-50%,0) rotate(1.2deg) scale(1)}
  20%{opacity:1;transform:translate(-50%,-12px) rotate(0deg) scale(1)}
  25%{opacity:0;transform:translate(-50%,-80px) rotate(-7deg) scale(.96)}
  32%,100%{opacity:0;transform:translate(-50%,110%) rotate(6deg) scale(.94)}
}

/* 右栏三行 -------------------------------------------------------------- */
.oem__runs{display:grid;grid-template-rows:repeat(3,1fr);gap:12px;min-width:0}
.orow{
  display:flex;align-items:stretch;overflow:hidden;
  border-radius:18px;background:rgba(255,255,255,.035);
}
/* ⛔ 行上不要边框。早先有 border:1px，卡片贴着圆角被切断时，
   那条 1px 线会被读成「框」，看起来像内容溢出被裁、像布局 bug。
   去掉之后同一刀就变成「卡片从画面外滑进来」，正是跑马灯该有的读法。 */
/* ⛔ height:100% + stretch 是配套的：.orow 是等高的 1fr，不给轨道明确高度的话
   卡片的 align-self:stretch 会退化成内容高度，三行又缩回一小条。 */
.otrack{
  display:flex;align-items:stretch;width:max-content;height:100%;
  will-change:transform;
  animation:oemrun 42s linear infinite;
}
.otrack--rev{animation-direction:reverse}
.orow:hover .otrack{animation-play-state:paused}
@keyframes oemrun{to{transform:translate3d(-50%,0,0)}}

/* ⛔ 高度不要写死：三行是等高的 1fr，卡片撑满行高才不显得三行中间空一大截。
   ⛔ margin 用简写并且**左右对称**：早先写 11px 10px 11px 0（左边 0），
      卡片一进视口就贴着行的左边缘被切断，左边框直接没了。
      改成左右各 10 之后间距变 20，但 -50% 仍然精确 —— 它等于「一份内容的宽度」，
      每张卡占 width+20，两份正好 2 倍，不受影响。
   ⛔ 别改成给 .otrack 加 padding-left：那会把周期撑长，-50% 就接不上了。 */
.ocard{
  flex:0 0 auto;display:flex;align-items:center;gap:12px;
  align-self:stretch;margin:11px 10px;padding:0 18px;
  border-radius:14px;
  background:rgba(255,255,255,.055);
  border:1px solid rgba(255,255,255,.08);
}
/* 序号做成 44 的方块，替掉 accio 那颗 40×40 的圆形图标：
   我们一张图标素材都没有，纯色块 + 序号撑起「图标位」，卡片里才不会只剩一行字。 */
.ocard__n{
  flex:0 0 auto;display:grid;place-items:center;
  width:44px;height:44px;border-radius:11px;
  background:rgba(255,255,255,.06);
  border:1px solid rgba(255,255,255,.07);
  font-family:var(--mono);font-size:.68rem;letter-spacing:.06em;
  color:var(--accent);
}
.ocard b{display:block;font-size:.86rem;font-weight:600;white-space:nowrap;line-height:1.25}
.ocard em{
  display:block;font-style:normal;font-size:.66rem;color:var(--txt-3);
  white-space:nowrap;line-height:1.3;
}

/* 平板 / 手机：这一段**整体降级**，不做动画，只保证读得清、划得动。
   ⛔⛔ 手机档（390 宽）第一次真机实测抓到三个问题：
      ① 卡堆顶越过副标题底边 33px —— 桌面档 342 对 343，本来就只有 1px 余量，窄屏一挤就压上了；
      ② 卡堆换位时两张卡同时半透明，文字互相穿透糊成一坨；
      ③ 跑马灯卡片比行还宽，每行只看到一张半，还被行的圆角硬切成半张。
      与其在窄屏上调参数，不如直接换成「静态一张卡 + 手指横滑」。 */
@media (max-width:1000px){
  .oem__in{grid-template-columns:1fr}
  .oem__lead{min-height:400px}
  /* ⛔ 卡堆只留一张静态卡：不轮播就没有透明重叠，也不用管 12.8s 的时序。
     ⛔ 高度必须收到 48% —— 58% 时卡堆顶正好压在副标题上。 */
  .ostack{height:48%}
  .ostack__card{
    width:70%;height:auto;min-height:118px;bottom:14px;
    animation:none;opacity:0;
  }
  /* ⛔ animation 关掉之后卡片没有任何 transform，位置会跑到 left:52% 右边去，
     这里必须把居中那一下补回来。 */
  .ostack__card:first-child{
    opacity:1;transform:translate(-50%,0) rotate(1.2deg);
  }
  /* 跑马灯降级成手指横滑：去掉自动位移，改成 scroll-snap，一次停一张。
     ⛔ 不能只把 animation 停掉就完事：track 是 width:max-content，
        超出行宽的部分没有任何办法看到，等于把后面几张卡直接藏了。 */
  .otrack{animation:none;transform:none}
  .orow{
    overflow-x:auto;overflow-y:hidden;
    scroll-snap-type:x proximity;-webkit-overflow-scrolling:touch;
    scrollbar-width:none;
    /* 右缘淡出：不然卡片被容器边缘一刀切，读起来像渲染坏了，不像「还能往右滑」 */
    -webkit-mask-image:linear-gradient(to right,#000 0,#000 84%,transparent 100%);
    mask-image:linear-gradient(to right,#000 0,#000 84%,transparent 100%);
  }
  .orow::-webkit-scrollbar{display:none}
  .ocard{scroll-snap-align:start}
  .oem__runs{grid-template-rows:repeat(3,72px)}
}
@media (max-width:560px){
  .oem__lead{min-height:360px}
  /* ⛔ 窄屏副标题换行更多、标题区更高，卡堆要再收一档，不然又会压上去 */
  .ostack{height:40%}
  .ostack__card{width:82%;padding:15px 16px 17px}
}

/* ------------------------------ 产品段 · 窄屏 ------------------------------
   手机上一屏只有 346px 宽，五张卡挤在一起会读不出产品。
   直接只留三张（后两张 display:none），摆成竖着的错落。
   ⛔ 隐藏的卡在 motion.js 里还会占出场窗口，没关系 ——
      可见的三张节奏是对的，后面那段空转只是多停一会儿。
   ---------------------------------------------------------------------------- */
@media (max-width:900px){
  .shelf{height:calc(100svh + 190vh)}
  .shelf__hd{grid-template-columns:minmax(0,1fr)}
  .shelf__hd .h2{grid-column:1;font-size:clamp(1.85rem,7.4vw,2.5rem)}
  .shelf__hd .lead{grid-column:1;margin:12px 0 0;max-width:44ch}

  /* 手机只留五张 —— 346px 宽塞十张，每张还没一个指甲盖大，读不出产品。
     留下的 0/1/2/5/7 在 SHELF_SEQ 里正好是 at 0.09 0.47 0.24 0.40 0.17，
     节奏还是散开的，不会空等半天。 */
  .shelf__card[data-card="3"],
  .shelf__card[data-card="4"],
  .shelf__card[data-card="6"],
  .shelf__card[data-card="8"],
  .shelf__card[data-card="9"]{display:none}

  .shelf__card[data-card="0"]{--w:49%;--x:1%;--y:2%;--r:-1.2deg;--ar:4/5}
  .shelf__card[data-card="2"]{--w:36%;--x:58%;--y:8%;--r:-1.8deg;--ar:1/1}
  .shelf__card[data-card="5"]{--w:34%;--x:28%;--y:34%;--r:1.6deg;--ar:1/1}
  .shelf__card[data-card="1"]{--w:40%;--x:4%;--y:48%;--r:1deg;--ar:1/1}
  .shelf__card[data-card="7"]{--w:32%;--x:62%;--y:62%;--r:-1.4deg;--ar:1/1}

  .shelf__card figcaption{font-size:.56rem;padding:7px 9px 9px;gap:5px}
}

/* 关掉动效的机器：不要位移、不要视差，直接就是最终状态。
   motion.js 里也做了同样的判断，两边都要有 —— 少了任何一边都会漏。
   ⛔ 这条放在最末，别被上面的媒体查询盖掉。 */
@media (prefers-reduced-motion:reduce){
  .js [data-rv],
  .js [data-rv="up"],
  .js [data-rv="mask"],
  .js [data-rv="wipe"],
  .js [data-rv="line"]{
    opacity:1;transform:none;clip-path:none;transition:none;
  }
  .tick__track{animation:none}
  /* 买家评价：不滚了，改成可以手动横向拖（不然第二排有一半永远看不到） */
  .vtrack{animation:none}
  .voices__rows .vrow{overflow-x:auto;scrollbar-width:thin}
  .flow__media img,.foot__mark span{transform:none !important}
  /* 微距图：不推近、不淡化。宽度过渡也一并关掉，hover 就是硬切。
     ⛔ 入场那个 scale:1.07 必须显式压回 1 —— REDUCE 只清了 clip-path，
        图片会停在放大态被框裁掉一圈。 */
  .macro img{transition:none;scale:1 !important;transform:none !important}
  .band figure{transition:none}
  /* 证书段：不漂、不放大，直接给一张静态的斜链图。
     ⛔ 只关动画不改布局的话，链条会停在 keyframes 的 from 位置（整体偏出去一截）。 */
  .chain{animation:none;transform:none}
  .cert{transform:translate(-50%,-50%) rotate(var(--rot,0deg));transition:none}
  .cert:hover{scale:1}
  .certsec__note{opacity:1}
  /* OEM 段：跑马灯停住并把整条轨道靠左裁齐，斜叠只留最上面那张不动 */
  .otrack{animation:none;transform:none}
  .ostack__card{animation:none;opacity:0}
  .ostack__card:first-child{
    opacity:1;z-index:3;transform:translate(-50%,0) rotate(1.2deg);
  }
}
