/* ============================================================
   CERYé PRO — 动效层
   必须在 pro.css 之后、页面自己那段 <style> 之前引入。
   配套脚本 assets/pro-motion.js，两者要一起引，缺一不可。

   ── 分工 ─────────────────────────────────────
   pro.css 管「一个元素自己的状态变化」：按钮换色、输入框聚焦、
   表格行 hover、抽屉滑出。这些 designer 已经做完，本文件不重复、不覆盖。
   本文件管「元素之间、元素与滚动之间的关系」：
   什么时候出现、按什么顺序出现、页面滚到哪里了、内容换了一批。

   ── 三条自我约束 ─────────────────────────────
   1. 只动 transform / translate / scale / opacity 这几类合成属性。
      唯一例外是数字滚动（改文本）和吸顶栏换底色（纯绘制），
      两处都在下面注明了为什么可以破例。
   2. 会让内容藏起来的规则，一律挂在 html.pm 下。pm 由脚本运行时打上：
      脚本没跑起来，页面就是一张完全可读的静态页。
   3. prefers-reduced-motion: reduce 时全部关闭，内容立即可见。

   ── 手机端三件（参考手机版 Apple Store） ──────
   1. 导航退让：往下滚收起、往上滚回来，走 translateY 不改高度，
      三道防抖，减弱动态时完全不动。只在 900px 以下启用。
   2. 底部面板：升起、把手、下拉关闭。静态样式归设计师，
      这里出 pm-drag / pm-snap 两个状态 class 和把手的 touch-action。
   3. 按压反馈：缩到 --pm-press(.97)，大面用 --pm-press-lg(.985)。
      全站唯一定义处就是本文件的 :root，不允许各页再写一套。

   ── 移动端策略 ───────────────────────────────
   · 视差、卡片抬起这类持续或依赖悬停的效果，用
     (hover:hover) and (pointer:fine) 挡在桌面端，触屏一行都不执行。
   · backdrop-filter 一处都没新增，只复用 pro.css 已有的 fixed/sticky 元素。
   · 低端机由脚本降级（见 pro-motion.js 的 LITE）：
     列表入场的排队数从 20 降到 8、图片淡入整个关掉、骨架屏扫光关掉。
   · 所有按压反馈都关掉了安卓的默认点击高亮，
     否则系统灰块会和我们的缩放叠在一起，看着像闪了两下。
   · 没有任何一处依赖具体字号或写死的像素高度：
     让位距离读 --nav-h 与脚本量出来的 --pm-subbar，
     导航收起走 -100%，面板关闭阈值按面板自身高度的百分比算。
   ============================================================ */

:root{
  /* 入场位移。苹果官网的位移量很小，靠的是时间长、缓动缓，不是靠飞得远。
     12~24px 是他们的常用区间，超过就开始像模板站。 */
  --pm-y:14px;
  /* 入场时长跟着 pro.css 的 --t-in 走，那边改节奏这边自动跟上 */
  --pm-in:var(--t-in,.72s);
  /* 微交互时长，跟 pro.css 的 --t-1 同一档 */
  --pm-t:var(--t-1,.15s);

  /* 按压缩放，全站唯一一处定义。任何地方要做「按下去」都读这两个数，
     不要各写各的。分两档是因为缩放的观感取决于绝对位移而不是比例：
     一个 40px 的按钮缩 3% 是 1.2px，一张 340px 的卡片缩 3% 是 10px ——
     后者会像被踹了一脚。大面必须缩得更少。 */
  --pm-press:.97;      /* 控件：按钮、胶囊、tab、缩略图、色环 */
  --pm-press-lg:.985;  /* 大面：商品卡、香型卡 */
}

/* ── 关键帧 ─────────────────────────────────
   动态内容一律用 animation 而不是 transition：
   animation 一定会跑到终点，元素不会因为丢了一次 transitionend 而卡在半透明。
─────────────────────────────────────────── */
@keyframes pm-enter{
  from{opacity:0;transform:translateY(var(--pm-y,14px))}
  to{opacity:1;transform:none}
}
/* 只写 to、不写 from 的兜底帧：起点取元素当时的实际状态，
   已经显现过的元素在兜底触发时不会先闪回透明再淡入一次。 */
@keyframes pm-show{ to{opacity:1;transform:none} }
@keyframes pm-fade{ to{opacity:1} }
/* 数字徽标变化时弹一下。幅度 1.22 看着大，但元素只有 18px 见方，
   实际位移不到 2px，在视野边缘刚好够被余光抓到。 */
@keyframes pm-pop{ 0%{transform:scale(1)} 38%{transform:scale(1.22)} 100%{transform:scale(1)} }
/* 锚点跳转的落点提示：一圈品牌色描边渐隐。
   动的是 box-shadow，纯绘制、不重排，而且整页只在点击锚点时发生一次。 */
@keyframes pm-target{
  0%,16%{box-shadow:0 0 0 2px var(--brand-b),0 1px 2px -1px rgba(20,28,22,.05)}
  100%{box-shadow:0 0 0 2px transparent,0 1px 2px -1px rgba(20,28,22,.05)}
}

/* ── 兜底：脚本一个都没跑起来时 ──────────────
   pro.css 里 .js .rv 是 opacity:0，等 JS 把 .in 加上才显现；
   而 html.js 是页面 <head> 里一行内联脚本直接写死的。
   也就是说只要外部脚本挂了（CDN 断、语法错、被拦），这些内容就永远读不到。
   给它一个 6 秒的保险：到点自己浮出来。
   6 秒是刻意放长的，正常滚动早把它们显现完了，保险不会跟正常入场抢。
   pro-motion.js 一跑起来就把这条摘掉。
─────────────────────────────────────────── */
.js .rv{animation:pm-show .5s var(--ease,ease) 6s forwards}
html.pm .rv{animation:none}

/* ── 滚动入场：分层浮起 ─────────────────────
   苹果的区块不是整块一起淡入，是标题先、正文后、按钮最后。
   pro.css 里 .sec-head / .stats，以及各页的 .hd / .calc / .faq，
   都是「一个 .rv 包着几个子元素」，脚本识别到就给容器加 pm-lay，
   改成子元素依次错开。容器自己不再淡入（否则跟子元素叠成两层雾），
   只负责当舞台，所以这里把它的过渡整条关掉。
─────────────────────────────────────────── */
html.pm .rv.pm-lay.in{opacity:1;transform:none;transition:none}
html.pm .rv.pm-lay.in>*{
  animation:pm-enter var(--pm-in,.72s) var(--ease,ease) both;
  animation-delay:var(--pm-d,0ms);
}

/* ── 动态内容入场 ───────────────────────────
   商城筛选后的结果、香型列表、工作台异步填进来的面板、
   用 hidden 属性切换的分栏（AI 页的两个大面板就是这么切的），
   全都是脚本改 DOM，pro.css 的 .rv 管不到。脚本给它们挂 pm-e。
   不加 html.pm 前缀：pm-e 只有脚本会加，本身就等于已经确认脚本活着；
   特异度压低一点，页面自己写过入场动画的地方仍然按页面的来。
─────────────────────────────────────────── */
.pm-e{
  animation:pm-enter .5s var(--ease,ease) both;
  animation-delay:var(--pm-d,0ms);
}

/* ── 图片载入淡入 ───────────────────────────
   商城卡片是懒加载的，图到了直接「啪」地出现很硬。
   只做 opacity，不碰 transform：卡片 hover 时的图片放大是另一条规则，
   两边抢同一个 transform 会打架。淡完脚本把 class 摘干净，不留残留声明。
─────────────────────────────────────────── */
img.pm-img{opacity:0;animation:pm-fade .4s var(--ease,ease) 5s forwards}
img.pm-img.pm-in{opacity:1;transition:opacity .5s var(--ease,ease);animation:none}

/* ── 导航：滚动后才「贴」上来 ───────────────
   苹果官网在页面最顶端时导航几乎没有存在感，一滚起来才浮出毛玻璃底和底边线。
   只改 background / border-color / box-shadow，都是纯绘制；
   不改 --nav-h：导航是 sticky 且在文档流里，改高度会让整页跟着重排。
   毛玻璃一直开着不做开关，backdrop-filter 反复开关比一直开更贵。
   页面最顶端时底色本来就是白或 --g2，透明和不透明看起来一样，
   真正被看见的变化是那条底边线的渐显 —— 这才是苹果这个动作的重点。
─────────────────────────────────────────── */
html.pm .nav{
  background-color:rgba(255,255,255,0);
  border-bottom-color:transparent;
  transition:background-color .45s var(--ease,ease),
             border-color .45s var(--ease,ease),
             box-shadow .45s var(--ease,ease),
             transform .34s var(--ease-o,ease);
}
html.pm .nav.pm-stuck{
  background-color:rgba(255,255,255,.72);
  border-bottom-color:var(--line2);
  box-shadow:0 8px 24px -20px rgba(20,28,22,.34);
}
/* 窄屏菜单展开时导航必须是实底，否则展开面板顶上会留一条透明缝 */
html.pm .nav.pm-open{
  background-color:rgba(255,255,255,.86);
  border-bottom-color:var(--line2);
}
/* 单独一条：老浏览器不认 :has 会把整条规则丢掉，
   跟上面写在一个选择器列表里会连 pm-open 一起报废。 */
html.pm .nav:has(.nav-panel.on){
  background-color:rgba(255,255,255,.86);
  border-bottom-color:var(--line2);
}

/* ── 导航退让（手机版 Apple Store 的做法） ───
   往下看的时候导航让开，把整屏交给内容；手一往回拉它就回来。
   桌面端不做 —— apple.com 的导航是一直在的，只有 App 里才退让，
   这是两种场景，不该混。

   用 translateY(-100%) 而不是改高度：导航是 sticky 且在文档流里，
   动高度整页都要重排。-100% 相对元素自身，字体工程师把 --nav-h
   改成别的值也不用回来调这里。

   媒体查询是第二道保险：脚本只在窄屏加这个 class，
   万一 resize 竞态留下一个过期的 class，桌面端也不会把导航弄丢。
─────────────────────────────────────────── */
@media (max-width:900px){
  html.pm.pm-navhide .nav{ transform:translateY(-100%) }
  /* 导航让开之后，吸在它下面的筛选条要跟上来补位，
     否则中间空出一条 50px 的缝，能看见内容从缝里穿过去。
     只有「此刻真的吸住了」的条才移动 —— 还在文档流里的不能动。 */
  html.pm.pm-navhide .pm-sticky.pm-stuck{
    transform:translateY(calc(-1 * var(--nav-h)));
  }
}

/* ── 吸顶子栏：同一个道理 ───────────────────
   商城和香型库的筛选条、定制页的类目条都吸在导航下方，
   现在无论有没有滚动都挂着一条底边线，页面在顶端时那条线是多余的。
   pm-sticky 只会加在脚本确认过 position:sticky 的元素上
   （AI 页有个同名的 .bar 是度量条，不是吸顶栏，靠这一步排除掉）。
─────────────────────────────────────────── */
html.pm .pm-sticky{
  background-color:transparent;
  border-bottom-color:transparent;
  transition:background-color .4s var(--ease,ease),
             border-color .4s var(--ease,ease),
             box-shadow .4s var(--ease,ease),
             transform .34s var(--ease-o,ease);
}
html.pm .bar.pm-sticky.pm-stuck{
  background-color:rgba(251,251,253,.88);
  border-bottom-color:var(--line2);
  box-shadow:0 8px 22px -22px rgba(20,28,22,.3);
}
html.pm .cats.pm-sticky.pm-stuck{
  background-color:rgba(255,255,255,.86);
  border-bottom-color:var(--line2);
  box-shadow:0 8px 22px -22px rgba(20,28,22,.3);
}

/* ── 按压物理 ───────────────────────────────
   pro.css 已经给 .btn 和 .pill 做好了按压，这里不碰它们。
   问题在别处：各页自己写的按钮（视图切换、类目 tab、缩略图、色环、
   数量步进、弹层关闭、删除、模式切换）一个个都有自己的时长 ——
   .25s / .28s / .3s / .4s 全站散着五六档，而且大多完全没有按下反馈。
   这里把它们统一到 --t-1 + --ease-o，跟 designer 给 .btn 定的那一档对齐。

   缩放用 scale 独立属性而不是 transform：这些元素里有几个
   （缩略图、色环）本身就用 transform 做 hover 放大，不能抢。
─────────────────────────────────────────── */
html.pm button:not(.btn):not(.pill):not([data-pm-press="off"]){
  /* transform 也列进来：个别页面件用它做 hover 放大，
     换成我这份列表后不能把人家原有的过渡弄丢 */
  transition-property:background-color,border-color,color,box-shadow,opacity,transform,scale;
  transition-duration:var(--pm-t,.15s);
  transition-timing-function:var(--ease-o,ease);
  /* 安卓默认的灰色点击块会和缩放叠在一起，看着像闪了两下 */
  -webkit-tap-highlight-color:transparent;
}
html.pm button:not(.btn):not(.pill):not(.p-open):not([data-pm-press="off"]):not(:disabled):not([aria-disabled="true"]):active{
  scale:var(--pm-press,.97);
}
/* 系统点击高亮只在「我们自己给了反馈」的元素上去掉。
   普通正文链接不动它 —— 拿掉了又不补，触屏上就什么反馈都没有了。 */
html.pm .btn,html.pm .pill,
html.pm .side a,html.pm .nav-panel a,html.pm .nav-links a,html.pm .foot a,
html.pm .todo,html.pm .lk,html.pm summary,html.pm label.check{
  -webkit-tap-highlight-color:transparent;
}

/* pro.css 里 .btn:active 写的是 scale(.975)，跟本库的 .97 差 0.005，
   肉眼看不出，但「全站按压是一个数」这件事要在代码里成立才守得住。
   统一读 --pm-press，designer 那边也可以直接引这个变量。 */
html.pm .btn:active{ transform:scale(var(--pm-press,.97)) }

/* 按钮里的箭头收短到同一档，让它和按压属于同一个反应速度 */
html.pm .btn .dot{
  transition:transform var(--t-2,.24s) var(--ease-o,ease),
             background var(--pm-t,.15s) var(--ease-o,ease);
}

/* ── 卡片抬起 ───────────────────────────────
   只在真有鼠标的设备上做。触屏没有 hover，:hover 会在点击后粘住不放。

   用 translate 而不是 transform，是因为这些卡片同时也是 .rv：
   入场占着 transform，两边共用一个属性就只能共用一条时长，
   要么入场变快、要么 hover 变钝。translate 是独立属性，各走各的时长，
   入场位移和抬起位移还会自动叠加，不会互相清零。
─────────────────────────────────────────── */
@media (hover:hover) and (pointer:fine){
  @supports (translate:0 0){
    /* 特异度要压得过 pro.css 的 .js .rv，否则 .rv 上那条 transition
       会把整张列表替换掉，translate 就没有过渡了。
       所以入场那两条（opacity .52s / transform --t-in）必须原样抄回来，
       抄的是 designer 现在的拆分：透明度比位移早收尾。 */
    html.pm .card,
    html.pm .cell,
    html.pm .flow-card{
      transition:opacity .52s var(--ease-o,ease),
                 transform var(--t-in,.72s) var(--ease,ease),
                 translate var(--t-2,.24s) var(--ease-o,ease),
                 box-shadow var(--t-2,.24s) var(--ease-o,ease);
    }
    html.pm .card:hover,
    html.pm .cell:hover,
    html.pm .flow-card:hover{ translate:0 -3px;box-shadow:var(--sh-3) }
  }
}

/* ── 数字徽标：数值变了弹一下 ───────────────
   下单清单的件数挂在导航和浮动入口上，加货时那个数字在视野边缘，
   不动一下根本不知道加进去了。只有数值真的变了才弹，重绘同一个值不弹。
─────────────────────────────────────────── */
html.pm .pm-pop{ animation:pm-pop .42s var(--ease-o,ease) }

/* ── 折叠问答展开 ───────────────────────────
   details 的高度变化没法只靠合成属性做，这里不碰高度，
   只给展开出来的答案一个淡入上浮。位移压到 6px：
   它是在已有内容中间撑开的，位移大了会把下面的内容推得很吵。
─────────────────────────────────────────── */
html.pm details[open]>:not(summary){
  animation:pm-enter .42s var(--ease,ease) both;
  --pm-y:6px;
}

/* ── 锚点落位 ───────────────────────────────
   导航是吸顶的，点页内锚点会把目标顶到导航底下。
   scroll-margin-top 不参与渲染、不引起重排，只影响滚动停在哪。
   这是「滚动体验」的一部分，所以放在动效层，而不是去动 pro.css 的排版。
─────────────────────────────────────────── */
/* 让位距离 = 导航高 + 吸顶筛选条高 + 一点余量。
   两个高度都不写死：--nav-h 来自 pro.css，--pm-subbar 由脚本在运行时
   量出来（没有吸顶条的页面就是 0）。字体工程师改字号导致筛选条变高时，
   这里会自动跟上，不需要有人记得回来改一个魔法数字。 */
html.pm section[id],
html.pm .panel[id],
html.pm [id].sec-head{
  scroll-margin-top:calc(var(--nav-h) + var(--pm-subbar,0px) + 20px);
}

/* 跳过去之后给一圈提示，否则长页面上根本看不出停在了哪一块 */
html.pm .panel:target{ animation:pm-target 1.8s var(--ease,ease) 1 }

/* ── 滚动链隔离（移动端手感） ────────────────
   抽屉、弹层、限高面板里滚到底之后继续划，会把底下的整页带着滚，
   这是移动端最常被投诉的一种「滑飞了」。按轴分开写：
   横向滚动条只挡横向，否则手指落在上面时连整页的上下滚都会被吞掉。
─────────────────────────────────────────── */
html.pm .modal-bd,
html.pm .drawer-bd,
html.pm .cmp-tw,
html.pm .fpanel,
html.pm .slist{ overscroll-behavior-y:contain }
html.pm .tw,
html.pm .strip,
html.pm .cmp-thumbs,
html.pm .cats-in,
html.pm .side{ overscroll-behavior-x:contain }

/* 遮罩和大图层底下不该有东西能滑动。这两个元素内部没有可滚动的子节点，
   所以可以直接禁掉手势；弹窗和抽屉不能这么写 —— touch-action 会沿着
   祖先链取并集，写在外层会把里面那块该滚的内容一起锁死。
   注意这只挡住「手指落在遮罩上」的情况，真正的背景滚动锁定要在
   各页自己的开关函数里做，不适合由一个共用库替页面决定。 */
html.pm .scrim,
html.pm .lb{ touch-action:none }

/* ── 看不见的动画不该继续跑 ──────────────────
   骨架屏和小助手都是无限循环。滚出视口之后它们还在逐帧推进，
   对低端安卓就是白白烧电。脚本在元素离开视口时挂 pm-pause。
   用 animation-play-state 而不是 animation:none —— 回到视口时
   从原来的相位继续，不会「跳一下重新开始」。 */
html.pm .pm-pause,
html.pm .pm-pause *{ animation-play-state:paused!important }

/* 低端机 / 省流量：骨架屏的扫光是全站唯一每帧重绘的动效
   （动的是 background-position，没法交给合成器），
   一屏二十几张骨架卡同时扫是实打实的负担。
   只去掉扫光，保留灰块 —— 「正在加载」这件事还得说清楚。
   小助手的呼吸和眨眼走的是 transform / opacity，能上合成层，予以保留。 */
html.pm.pm-lite .sk{ animation:none;background-image:none }

/* ============================================================
   页面件补充
   下面这些 class 属于单个页面自己的 <style>，不是 pro.css 的通用件。
   写在这里是因为它们是平台上被摸得最多的几块，缺了反馈最明显。
   每一条都用 html.pm 前缀，压得过页面规则，也便于日后整条搬走。
   ============================================================ */

/* 商城：整块商品图是「看详情」的入口，上面铺的是一层透明按钮，
   按上去缩放的是那层看不见的按钮，指头下面什么都不动。
   把反馈转嫁到图片本身。:has 不支持时这条整体失效，回到现状，不会坏。 */
@supports selector(:has(*)){
  html.pm .p-media:has(.p-open:active) img{ transform:scale(.985) }
}

/* 注：那层透明按钮 .p-open 撑满整张图，按下时缩 3% 会让边缘的手指
   掉出热区、:active 闪断，所以它被排除在通用按压之外（见上面那条
   选择器里的 :not(.p-open)），反馈由上面这条图片缩放代劳。 */

/* 商城 / 香型库的卡片：按下时整张轻微内缩，所有设备都做。
   这两张卡本来就用 transform 做桌面端的 hover 抬起，所以按压走 scale
   独立属性，两者叠加而不是互相清零；过渡写在常态上而不是 :active 里，
   否则松手时会硬弹回去。原页面那两条时长（.4s）原样抄回来，不改人家的手感。

   .card / .cell / .flow-card 不做按压：它们是首页的展示格子，
   本身点不了。给点不了的东西做按下反馈，是在骗手指。 */
html.pm .p-card,
html.pm .s-card{
  transition:box-shadow .4s var(--ease,ease),transform .4s var(--ease,ease),
             scale var(--pm-t,.15s) var(--ease-o,ease);
}
html.pm .p-card:active,
html.pm .s-card:active{ scale:var(--pm-press-lg,.985) }

/* 工作台：建议行右侧的小箭头跟着走一点 */
html.pm .todo::after{ transition:transform var(--t-2,.24s) var(--ease-o,ease) }
html.pm .todo:hover::after{ transform:translateY(-54%) translateX(3px) }

/* ── 底部面板：升起与下拉关闭 ───────────────
   静态样式归设计师，这里只出两个状态 class，由脚本挂：
   pm-drag  手指正按着，过渡必须完全关掉，否则每一帧都在追一个
            .4s 的过渡，面板会黏在手指后面半拍；
   pm-snap  松手回弹或关闭，接管一条统一的 .34s，
            不去看各页原本是 .38s 还是 .42s。
   两个 class 同时挂在面板和它的遮罩上，遮罩的透明度跟着拖动走。

   把手区域要 touch-action:none，否则浏览器会先把这一下判成「滚页面」，
   pointermove 直接变成 pointercancel，拖不动。
   标题栏那几个只在窄屏当把手，所以限定在窄屏内 —— 桌面端不该因为
   一个从没出现过的底部面板，把标题栏的文字选中和双指缩放也一起禁掉。
─────────────────────────────────────────── */
html.pm .pm-drag{ transition:none!important }
html.pm .pm-snap{
  transition:transform .34s var(--ease,ease),opacity .34s var(--ease,ease)!important;
}
html.pm .sheet-handle,
html.pm [data-pm-handle]{
  touch-action:none;
  -webkit-user-select:none;user-select:none;
  -webkit-tap-highlight-color:transparent;
  cursor:grab;
}
html.pm .sheet-handle:active,
html.pm [data-pm-handle]:active{ cursor:grabbing }
@media (max-width:900px){
  html.pm .sheet-hd,
  html.pm .drawer-hd,
  html.pm .modal-hd{
    touch-action:none;
    -webkit-user-select:none;user-select:none;
  }
}

/* ── 全部关闭：尊重系统的「减弱动态效果」 ─────
   此时不是「动得慢一点」，是一步到位，内容立即可见。
   pro.css 里已有一条全局 transition-duration 压制，
   这里再把 animation 和会藏内容的状态显式清掉，双保险。
─────────────────────────────────────────── */
@media (prefers-reduced-motion:reduce){
  html.pm .rv,
  html.pm .rv.pm-lay.in>*,
  html.pm .pm-e,
  html.pm .pm-pop,
  html.pm .panel:target,
  html.pm details[open]>:not(summary){
    opacity:1!important;transform:none!important;
    animation:none!important;transition:none!important;
  }
  html.pm img.pm-img{opacity:1!important;animation:none!important}
  html.pm .nav,html.pm .pm-sticky{transition:none!important}
  /* 导航退让整个关掉：减弱动态的人要的是「东西待在原地」 */
  html.pm.pm-navhide .nav,
  html.pm.pm-navhide .pm-sticky.pm-stuck{transform:none!important}
  /* 面板的拖拽手势仍然可用（那是操作不是装饰），只是回弹不做动画 */
  html.pm .pm-snap{transition:none!important}
  html.pm .nav{background-color:rgba(255,255,255,.72);border-bottom-color:var(--line2)}
  html.pm .card:hover,
  html.pm .cell:hover,
  html.pm .flow-card:hover{ translate:none }
  html.pm button:active,
  html.pm .p-card:active,
  html.pm .s-card:active{ scale:none }
  @supports selector(:has(*)){
    html.pm .p-media:has(.p-open:active) img{ transform:none }
  }
}

/* 打印：不留任何动效残留状态 */
@media print{
  html.pm .rv,html.pm .pm-e,html.pm img.pm-img{
    opacity:1!important;transform:none!important;animation:none!important;
  }
  html.pm .panel:target{animation:none!important}
}
