/* ============================================================================
   motion.css —— 全站动效语言（2026-10-23）

   设计基调：**卡面金光 + 轻微上浮**。SVE 是卡牌店，动效要"像把卡从桌上拿起来"，
   而不是网页常见的弹跳/霓虹。三条纪律：

     ① **只动 transform / opacity / filter** —— 这三样能走合成层，不掉帧；
        width/height/top/left 一律不做动画（会触发重排，列表一卡就抖）。
     ② **短**：按下 120ms、悬停 200ms、弹窗 320ms、进场 520ms。
        超过 400ms 的动画在连续操作里会"跟不上手"，用户会觉得卡。
     ③ **尊重系统设置**：`prefers-reduced-motion: reduce` 时全部退化为瞬切；
        另有 `html.mv-off` 手动关闭（localStorage 记）。

   类名前缀 `mv-`，只在本文件定义；JS 侧是 `SVE.Motion`（store.js 内）。
   ========================================================================= */

:root {
  --mv-1: 120ms;                                  /* 按下 / 勾选 */
  --mv-2: 200ms;                                  /* 悬停 / 淡入 */
  --mv-3: 320ms;                                  /* 弹窗 */
  --mv-4: 520ms;                                  /* 区块进场 */
  --mv-ease: cubic-bezier(.22, 1, .36, 1);        /* 快出慢停（主流手感） */
  --mv-ease-io: cubic-bezier(.65, 0, .35, 1);
  --mv-spring: cubic-bezier(.34, 1.4, .64, 1);    /* 轻微回弹，只用在"选中" */
  --mv-rise: 10px;                                /* 进场位移 */
}

/* ── ① 按下反馈：所有按钮 / 可点卡片 ─────────────────────────────
   为什么用 `:active` 而不是 JS：零延迟，按下那一帧就有反应。
   缩放 0.97 是"凹下去"的临界值；再大就会像坏掉的玩具。 */
.btn:active,
.mv-press:active,
button.pcard:active,
a.pcard:active,
.dk-tile:active,
.setcard:active {
  transform: scale(.97);
  transition-duration: var(--mv-1);
}

/* ── ② 悬停金光：卡面类元素（卡组卡 / 系列卡 / 商品卡 / 卡表格）────
   一道很淡的金色斜光从左上扫到右下（背景位移，不碰布局）。
   不用 `filter: brightness()`：深色卡面上提亮会把卡图洗白。 */
.mv-sheen {
  position: relative;
  overflow: hidden;
  isolation: isolate;
}
.mv-sheen::after {
  content: '';
  position: absolute;
  inset: -1px;
  z-index: 2;
  pointer-events: none;
  background: linear-gradient(115deg,
    transparent 32%,
    rgba(240, 200, 74, .16) 46%,
    rgba(255, 255, 255, .05) 52%,
    transparent 62%);
  background-size: 260% 260%;
  background-position: 120% 0;
  opacity: 0;
  transition: opacity var(--mv-2) var(--mv-ease);
}
.mv-sheen:hover::after,
.mv-sheen:focus-visible::after {
  opacity: 1;
  animation: mv-sweep 900ms var(--mv-ease) forwards;
}
@keyframes mv-sweep {
  from { background-position: 120% 0; }
  to   { background-position: -40% 0; }
}

/* ── ③ 页面本体进入：一次很轻的淡入，让"换页"有节奏 ────────────── */
body { animation: mv-page-in 260ms var(--mv-ease) both; }
@keyframes mv-page-in { from { opacity: .01; } to { opacity: 1; } }

/* ── ③ 进场：区块/卡片依次浮起 ───────────────────────────────────
   JS 给元素加 `mv-in`，stagger 由 `--mv-i`（第几个）控制，最多 8 档后归零，
   否则长列表第 40 个要等 2 秒才出现。 */
.mv-reveal {
  opacity: 0;
  transform: translate3d(0, var(--mv-rise), 0);
  transition:
    opacity var(--mv-4) var(--mv-ease),
    transform var(--mv-4) var(--mv-ease);
  transition-delay: calc(min(var(--mv-i, 0), 8) * 45ms);
  will-change: opacity, transform;
}
.mv-reveal.mv-in {
  opacity: 1;
  transform: none;
  will-change: auto;          /* 进场完就释放合成层，长列表不占显存 */
}
/* 卡片进场完，自己扫一道金光再收住 —— 不用鼠标也能看出"这是一张卡" */
/* 🔴 2026-10-25 店主：「把卡片一进来会闪一下的特效给删了。这个不需要，看久了眼睛会疼」。
   原来卡面进入视口时会扫一道 780ms 的高光（`.mv-sheen.mv-in::after`）——
   一屏 6 张卡同时闪，翻页/筛选每次重绘都闪一轮，确实是"看久了眼睛疼"。
   现在**只保留 hover / focus 的扫光**（那是用户主动指向某一张，有明确因果），
   进场不再扫。 */
/* .mv-sheen.mv-in::after { animation: mv-sweep 780ms var(--mv-ease) 120ms 1 both; opacity: 1; } */

/* ── ④ 弹窗：像"从桌上抬起一张卡" ───────────────────────────────
   遮罩淡入 + 面板轻微上浮放大。已有的 `.mask/.modal` 不重写，只借用它们的类名，
   用 `:where()` 降低权重，页面自己的样式永远能覆盖。 */
:where(.mask) { animation: mv-fade var(--mv-2) var(--mv-ease) both; }
:where(.mask) :where(.modal, [role="dialog"]) {
  animation: mv-lift var(--mv-3) var(--mv-ease) both;
}
@keyframes mv-fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes mv-lift {
  from { opacity: 0; transform: translate3d(0, 14px, 0) scale(.985); }
  to   { opacity: 1; transform: none; }
}

/* ── ⑤ 数字滚动：价格 / 合计变化时 ──────────────────────────────
   JS 逐帧改 textContent；这里只给一点"数字在动"的视觉重量。 */
.mv-count { font-variant-numeric: tabular-nums; }
.mv-count.up   { animation: mv-num-up var(--mv-3) var(--mv-ease); }
.mv-count.down { animation: mv-num-down var(--mv-3) var(--mv-ease); }
@keyframes mv-num-up   { from { color: #FF8B96; } to { color: inherit; } }
@keyframes mv-num-down { from { color: #5FD18D; } to { color: inherit; } }

/* ── ⑥ 成功勾选：加购 / 收藏 / 复制成功 ─────────────────────────
   SVG 描边动画（stroke-dashoffset），比弹一个小图标更"完成"。 */
.mv-check {
  display: inline-flex; align-items: center; gap: 6px;
}
.mv-check svg { width: 16px; height: 16px; flex: none; }
.mv-check svg path {
  fill: none; stroke: currentColor; stroke-width: 2.4;
  stroke-linecap: round; stroke-linejoin: round;
  stroke-dasharray: 22; stroke-dashoffset: 22;
}
.mv-check.on svg path { animation: mv-draw 380ms var(--mv-ease) forwards; }
@keyframes mv-draw { to { stroke-dashoffset: 0; } }

/* ── ⑦ 骨架：加载中的呼吸，不再用转圈 ─────────────────────────── */
.mv-skel {
  background: linear-gradient(90deg, var(--ink-3) 25%, var(--ink-4) 37%, var(--ink-3) 63%);
  background-size: 400% 100%;
  animation: mv-shimmer 1.4s linear infinite;
  border-radius: 8px;
}
@keyframes mv-shimmer { from { background-position: 100% 0; } to { background-position: -100% 0; } }

/* ── ⑧ 滚动条与焦点：细微但统一 ───────────────────────────────── */
html:not(.mv-off) { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
  html:not(.mv-force) { scroll-behavior: auto; }
}
:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
  transition: outline-offset var(--mv-1) var(--mv-ease);
}

/* ── ⑨ 关闭开关 / 系统偏好 ─────────────────────────────────────
   🔴 2026-10-24 事故：上一版这里是 `html.mv-off * { transition-duration:1ms !important }`
   —— 用 `*` 通配等于**把全站的动画一起杀了**：首页轮播是 JS 定时 + CSS transition，
   被压成 1ms 以后就"看不见在动"，店主直接说「轮播动画没了」。
   现在只作用于**本文件自己加的类**（`.mv-*`），站内原有动画（轮播 / 进度条 / 加载）
   一律不碰 —— 那些是内容的一部分，关不关由各自模块决定。 */
html.mv-off .mv-reveal,
html.mv-off .mv-reveal.mv-in {
  opacity: 1;
  transform: none;
  transition: none;
}
html.mv-off .mv-sheen::after,
html.mv-off .mv-sheen.mv-in::after { animation: none; opacity: 0; }
html.mv-off .mv-check svg path { animation: none; stroke-dashoffset: 0; }
html.mv-off .mv-skel { animation: none; }
html.mv-off body { animation: none; }
html.mv-off .btn:active,
html.mv-off .mv-press:active,
html.mv-off a.pcard:active,
html.mv-off .dk-tile:active,
html.mv-off .setcard:active { transform: none; }

/* ── ⑩ 降级档：`html.mv-soft`（系统要"减少动态效果"且本站没有强制开）
   🔴 2026-10-25 重写。上一版这里是 `@media (prefers-reduced-motion: reduce)`
   —— **媒体查询不看类名**，于是店主那台"Windows 动画效果关着"的 PC 上，
   即便 `html.mv-force` 用 JS 加上了也没用：整站进场、扫光、页面淡入全被这条压掉，
   表现就是「PC 端看起来没有特效」。
   现在改成 class 驱动：JS 在 `Motion.sync()` 里决定加 `mv-force` 还是 `mv-soft`。
   `mv-soft` 只去掉**位移/缩放/扫光**，保留淡入与颜色过渡 —— 不是归零。 */
html.mv-soft .mv-reveal {
  opacity: 0;
  transform: none;
  transition: opacity 220ms linear;
  transition-delay: 0ms;
}
html.mv-soft .mv-reveal.mv-in { opacity: 1; }
html.mv-soft .mv-sheen::after,
html.mv-soft .mv-sheen.mv-in::after { animation: none; opacity: 0; }
html.mv-soft .mv-spot::before { opacity: 0; }
html.mv-soft body { animation: none; }
html.mv-soft :where(.mask) :where(.modal, [role="dialog"]) { animation: mv-fade var(--mv-2) var(--mv-ease) both; }

/* 无 JS 时的兜底：真浏览器偏好 reduce 且脚本没跑起来（极少见），仍给淡入不给位移 */
@media (prefers-reduced-motion: reduce) {
  html:not(.mv-force):not(.mv-soft) .mv-reveal {
    transform: none; transition: opacity 200ms linear; transition-delay: 0ms;
  }
}

/* ============================================================================
   ⑪ PC 交互层（2026-10-25 店主：「全面重做 PC 端的交互动效并且默认为启动」）

   只在**真有鼠标**的设备上开：`(hover: hover) and (pointer: fine)`。
   触屏上 hover 会"粘住"（点一下就一直是悬停态），所以整层用媒体查询包住 ——
   这也顺带保证手机端的点击手感没被改动。
   ========================================================================== */
@media (hover: hover) and (pointer: fine) {

  /* ① 指针跟随高光：鼠标在哪，卡面哪里亮一束金光
     ——由 JS 把相对坐标写进 `--mx/--my`（rAF 节流），CSS 只读变量，不碰布局。
     `soft-light` 混合只提亮暗部，不会把卡图洗白，也不会盖住文字。 */
  .mv-spot { position: relative; isolation: isolate; }
  .mv-spot::before {
    content: '';
    position: absolute; inset: 0; z-index: 3;
    border-radius: inherit;
    pointer-events: none;
    background: radial-gradient(
      200px 200px at var(--mx, 50%) var(--my, 0%),
      rgba(255, 224, 0, .28), rgba(255, 224, 0, .06) 45%, transparent 72%);
    mix-blend-mode: soft-light;
    opacity: 0;
    transition: opacity var(--mv-2) var(--mv-ease);
  }
  .mv-spot:hover::before,
  .mv-spot:focus-visible::before { opacity: 1; }

  /* ② 卡片抬起：卡面类元素统一"从桌上拿起一张卡"的手感
     （页面自己的 hover 只改边框色，这里补上位移与阴影的节奏） */
  .pcard, .edk, .setcard, .dk-tile, .shp-card, .pk-card, .tool, .mynav {
    transition:
      transform var(--mv-2) var(--mv-ease),
      box-shadow var(--mv-2) var(--mv-ease),
      border-color var(--mv-2) var(--mv-ease),
      background-color var(--mv-2) var(--mv-ease);
  }
  .pcard:hover, .edk:hover, .setcard:hover, .dk-tile:hover,
  .shp-card:hover, .pk-card:hover, .tool:hover, .mynav:hover {
    transform: translate3d(0, -4px, 0);
  }

  /* ③ 卡图轻微推近：像把卡凑近看
     `.pcard-thumb` 已 overflow:hidden，缩放不会溢出圆角 */
  .pcard-thumb img,
  .pk-thumb img,
  .shp-thumb img,
  .edk .top > img {
    transition: transform 460ms var(--mv-ease), filter 300ms var(--mv-ease);
  }
  .pcard:hover .pcard-thumb img,
  .pk-card:hover .pk-thumb img,
  .shp-card:hover .shp-thumb img,
  .edk:hover .top > img { transform: scale(1.045); }

  /* ④ 按钮：悬停提亮 + 轻微上抬（`:active` 的按下反馈在 ① 段已有） */
  .btn:hover:not(:disabled),
  .nav-btn:hover,
  .member-btn:hover {
    transform: translate3d(0, -1px, 0);
    transition: transform var(--mv-1) var(--mv-ease), background-color var(--mv-1) var(--mv-ease), color var(--mv-1) var(--mv-ease);
  }
  .btn:active:not(:disabled),
  .nav-btn:active,
  .member-btn:active { transform: translate3d(0, 0, 0) scale(.98); }

  /* ⑤ 顶栏导航：金色下划线从中间展开 */
  .nav-btn::after {
    content: '';
    position: absolute; left: 50%; right: 50%; bottom: 6px; height: 2px;
    background: var(--gold); border-radius: 2px;
    opacity: 0;
    transition: left var(--mv-2) var(--mv-ease), right var(--mv-2) var(--mv-ease), opacity var(--mv-1) linear;
  }
  .nav-btn { position: relative; }
  .nav-btn:hover::after { left: 22%; right: 22%; opacity: .9; }
  .nav-btn.on::after { left: 22%; right: 22%; opacity: 1; }
}

/* ⑥ 关闭时把这些一律还原（`html.mv-off` 是唯一的总闸） */
html.mv-off .mv-spot::before { display: none; }
html.mv-off .pcard:hover, html.mv-off .edk:hover, html.mv-off .setcard:hover,
html.mv-off .dk-tile:hover, html.mv-off .shp-card:hover, html.mv-off .pk-card:hover,
html.mv-off .tool:hover, html.mv-off .mynav:hover { transform: none; }
html.mv-off .pcard:hover .pcard-thumb img,
html.mv-off .pk-card:hover .pk-thumb img,
html.mv-off .shp-card:hover .shp-thumb img,
html.mv-off .edk:hover .top > img { transform: none; }

/* ============================================================================
   ⑫ 首页整屏翻页：让「一屏一页」有节奏（2026-10-25 店主：
      「全面实施动效的全面覆盖，包括不限于滑动时动画、首页翻页动画」）

   整屏滑动本身只是 scroll-snap 的位移 —— 内容跟着页面整体平移，切屏像翻 PDF。
   这里让**每屏的内容自己再入场一次**：轮播先落、六格依次跟、公告卡错峰，
   间隔 55ms。和列表进场（`.mv-reveal`）用同一套缓动，全站动效才是一个语言。

   触发靠 `home-fullscreen.js` 给进入的屏加 `.is-live`（go() 里会先摘再加，
   所以来回切都能重播）。只动 transform/opacity。
   ========================================================================== */
html.fs-home .fs-pane.is-live #heroCarousel,
html.fs-home .fs-pane.is-live .tool,
html.fs-home .fs-pane.is-live .pk-card {
  animation: mv-fs-rise 560ms var(--mv-ease) both;
}
html.fs-home .fs-pane.is-live #heroCarousel { animation-duration: 620ms; }

/* 六格：第一格 110ms 起，每格 +55ms（六格走完 ≈ 385ms，仍在 560ms 内收住） */
html.fs-home .fs-pane.is-live .tool:nth-child(1) { animation-delay: 110ms; }
html.fs-home .fs-pane.is-live .tool:nth-child(2) { animation-delay: 165ms; }
html.fs-home .fs-pane.is-live .tool:nth-child(3) { animation-delay: 220ms; }
html.fs-home .fs-pane.is-live .tool:nth-child(4) { animation-delay: 275ms; }
html.fs-home .fs-pane.is-live .tool:nth-child(5) { animation-delay: 330ms; }
html.fs-home .fs-pane.is-live .tool:nth-child(6) { animation-delay: 385ms; }

/* 公告屏：竖排大字从左侧轻轻推入，卡片从下浮起 */
html.fs-home .fs-pane.is-live .pk-side { animation: mv-fs-slide 640ms var(--mv-ease) both; }
html.fs-home .fs-pane.is-live .notice-sub { animation: mv-fs-fade 520ms var(--mv-ease) 60ms both; }
html.fs-home .fs-pane.is-live .pk-card:nth-child(1) { animation-delay: 120ms; }
html.fs-home .fs-pane.is-live .pk-card:nth-child(2) { animation-delay: 175ms; }
html.fs-home .fs-pane.is-live .pk-card:nth-child(3) { animation-delay: 230ms; }
html.fs-home .fs-pane.is-live .pk-card:nth-child(4) { animation-delay: 285ms; }
html.fs-home .fs-pane.is-live .pk-card:nth-child(5) { animation-delay: 340ms; }
html.fs-home .fs-pane.is-live .pk-card:nth-child(n+6) { animation-delay: 340ms; }

@keyframes mv-fs-rise {
  from { opacity: 0; transform: translate3d(0, 22px, 0) scale(.985); }
  to   { opacity: 1; transform: none; }
}
@keyframes mv-fs-slide {
  from { opacity: 0; transform: translate3d(-16px, 0, 0); }
  to   { opacity: 1; transform: none; }
}
@keyframes mv-fs-fade { from { opacity: 0; } to { opacity: 1; } }

/* 降级：mv-soft（系统要求减少动态）只留淡入，不做位移；mv-off 全停 */
html.mv-soft.fs-home .fs-pane.is-live #heroCarousel,
html.mv-soft.fs-home .fs-pane.is-live .tool,
html.mv-soft.fs-home .fs-pane.is-live .pk-card,
html.mv-soft.fs-home .fs-pane.is-live .pk-side {
  animation: mv-fs-fade 240ms linear both;
}
html.mv-off .fs-pane.is-live #heroCarousel,
html.mv-off .fs-pane.is-live .tool,
html.mv-off .fs-pane.is-live .pk-card,
html.mv-off .fs-pane.is-live .pk-side,
html.mv-off .fs-pane.is-live .notice-sub { animation: none; }

/* ============================================================================
   ⑬ 主题切换：不要"啪一下"换脸
   theme.js 在切换时给 <html> 加 300ms 的 `.mv-theming`，这里只让**大面积的底**过渡，
   不给全站元素加 transition（那会让滚动/悬停全部变迟钝）。
   ========================================================================== */
html.mv-theming body,
html.mv-theming #site-header,
html.mv-theming .topbar,
html.mv-theming #site-footer.footer-drawer,
html.mv-theming .fd-mask {
  transition: background-color 280ms linear, border-color 280ms linear, color 280ms linear;
}

/* ============================================================================
   ⑭ 页脚抽屉打开：内容跟着一起"升上来"，而不是一块死板
   抽屉本体是 transform 过渡；内容再淡入一档，避免"整块布被拖上来"的感觉。
   ========================================================================== */
#site-footer.footer-drawer.is-open .fd-scroll > .footer {
  animation: mv-fs-fade 360ms var(--mv-ease) 80ms both;
}
html.mv-soft #site-footer.footer-drawer.is-open .fd-scroll > .footer,
html.mv-off #site-footer.footer-drawer.is-open .fd-scroll > .footer { animation: none; }

/* ============================================================================
   ⑮ 换页转场（2026-10-26 店主：「增加跳转页面动画，我打开任何其他页面的时候都要有
      动画播放。而且目前只有首页才有滑动动画（鼠标滑轮上下滑动的动画）」）
   ────────────────────────────────────────────────────────────────────────────
   进场是**纯 CSS**（`body` 淡入，HTML 一解析完就播，不等脚本）；退场是 JS 加的
   `html.pf-out`（store.js 的 SVE.PageFX，点内链先淡出 170ms 再跳）。

   🔴 两段都只用 `opacity`，**绝对不给 body / main 加 transform**：
      任何 transform 都会让 `position: fixed` 的后代（筛选弹窗 .filters、筛选球
      .fab-filters、专属窗口 .psheet）改以它为包含块 —— 换页那 300ms 里它们会歪，
      "为了动效把浮层弄错位"是最典型的反效果。
   🔴 260 → 320ms 是量出来的：260ms 在 60Hz 上只有 15 帧，肉眼基本看不出
      "这是一次转场"，所以店主说"打开页面没有动画"。320ms 才够看清。
   ========================================================================== */
body { animation: mv-page-in 320ms var(--mv-ease) both; }
html.pf-out body { animation: mv-page-out 170ms var(--mv-ease-io) both; }
@keyframes mv-page-out { from { opacity: 1; } to { opacity: 0; } }
/* 关动效（含系统"减少动态效果"降级档）时两段都不播 —— 与 ⑨/⑩ 段同一条口径 */
html.pf-out.mv-off body,
html.pf-out.mv-soft body { animation: none; }
