/* ══════════════════════════════════════════════════════════
   577 动效层 —— 精密仪表感
   ──────────────────────────────────────────────────────────
   取向:这是「仪器通电」的感觉,不是「网页很花」。因此:
     · 动画只表达「数据在流动 / 系统在运转」,不做装饰性运动
     · 幅度极小(位移 ≤ 16px),时长偏短,曲线偏硬
     · 只动 transform / opacity / clip-path —— 这三个走合成器,不触发重排
     · 一切动效都必须能被 prefers-reduced-motion 关掉

   独立成一层是刻意的:出问题时整个文件摘掉,页面回到静态也完全可用。
   ══════════════════════════════════════════════════════════ */

:root {
  --m-fast: 220ms;
  --m-base: 520ms;
  --m-slow: 900ms;
  /* 偏硬的缓出:起步快、收尾稳,像机械件到位而不是弹性材料 */
  --m-ease: cubic-bezier(.16, .84, .28, 1);
}

/* ── 入场:进入视口才播,只播一次 ───────────────────────
   不用「页面加载就全播」—— 那样首屏之外的动画白白播完,
   用户滚下去看到的是已经结束的静止画面。

   ⚠️ 注意这里用 .js-motion 门控:隐藏只在「JS 确实接管了」之后才生效。
   直接写 [data-reveal]{opacity:0} 是个陷阱 —— 脚本被拦截、报错、
   或 IntersectionObserver 不投递(后台标签页就会这样)时,
   整页内容会永远停在透明状态,等于白屏。内容可见性不能依赖动效。 */
.js-motion [data-reveal] {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity var(--m-base) var(--m-ease),
              transform var(--m-base) var(--m-ease);
  will-change: transform, opacity;
}
.js-motion [data-reveal].in { opacity: 1; transform: none; }
/* 播完就摘掉 will-change —— 留着会一直占用合成层内存 */
.js-motion [data-reveal].done { will-change: auto; }

/* 侧向入场,给并排的内容用 */
.js-motion [data-reveal="left"]  { transform: translateX(-16px); }
.js-motion [data-reveal="right"] { transform: translateX(16px); }
.js-motion [data-reveal="scale"] { transform: scale(.97); }
.js-motion [data-reveal].in { transform: none; }

/* ── 网格底纹:极缓慢地推移,像示波器的时基 ─────────────── */
@keyframes m-grid-drift {
  from { background-position: 0 0, 0 0; }
  to   { background-position: 56px 0, 0 56px; }
}

/* ── 扫描线:一条细光带周期性掠过首屏 ───────────────────
   这是整套视觉的签名元素。透明度压得很低,余光可见即可,
   太亮就变成装饰条了。 */
@keyframes m-sweep {
  0%   { transform: translateY(0);     opacity: 0; }
  8%   { opacity: .5; }
  92%  { opacity: .5; }
  100% { transform: translateY(var(--sweep-dist, 420px)); opacity: 0; }
}

.scan-line {
  position: absolute; left: 0; right: 0; top: 0; height: 1px;
  pointer-events: none;
  background: linear-gradient(90deg,
    transparent, var(--accent-line) 18%, var(--accent) 50%, var(--accent-line) 82%, transparent);
  animation: m-sweep 6.5s var(--m-ease) infinite;
  opacity: 0;
}

/* ── 数字:滚动到位时不能抖 ─────────────────────────────
   等宽 + tabular-nums 保证每一位宽度恒定,否则滚动过程中
   整行文字会左右抽搐。 */
[data-count] {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

/* ── 实时脉冲:表示「此刻仍在采集」 ─────────────────────── */
@keyframes m-pulse-ring {
  0%   { transform: scale(1);   opacity: .55; }
  70%  { transform: scale(2.4); opacity: 0; }
  100% { transform: scale(2.4); opacity: 0; }
}
.live-dot { position: relative; display: inline-flex; flex: none;
  width: 7px; height: 7px; border-radius: 50%; background: var(--up); }
.live-dot::after {
  content: ''; position: absolute; inset: 0; border-radius: 50%;
  background: var(--up); animation: m-pulse-ring 2s ease-out infinite;
}

/* ── 卡片:hover 时四角出现定位角标 ─────────────────────
   比整块变色更像「取景框对焦」,也更克制。 */
.feature, .plan, .card-hover {
  position: relative;
}
.feature::after, .plan::after {
  content: ''; position: absolute; inset: 8px;
  pointer-events: none; opacity: 0;
  transition: opacity var(--m-fast) var(--m-ease);
  /* 只画四个角,不画完整边框 */
  background:
    linear-gradient(var(--accent), var(--accent)) left top / 10px 1px no-repeat,
    linear-gradient(var(--accent), var(--accent)) left top / 1px 10px no-repeat,
    linear-gradient(var(--accent), var(--accent)) right top / 10px 1px no-repeat,
    linear-gradient(var(--accent), var(--accent)) right top / 1px 10px no-repeat,
    linear-gradient(var(--accent), var(--accent)) left bottom / 10px 1px no-repeat,
    linear-gradient(var(--accent), var(--accent)) left bottom / 1px 10px no-repeat,
    linear-gradient(var(--accent), var(--accent)) right bottom / 10px 1px no-repeat,
    linear-gradient(var(--accent), var(--accent)) right bottom / 1px 10px no-repeat;
}
.feature:hover::after, .plan:hover::after,
.feature:focus-visible::after { opacity: .65; }

/* 图标在 hover 时轻微上浮,幅度小到几乎只是「有反应」 */
.feature .ico { transition: transform var(--m-fast) var(--m-ease); }
.feature:hover .ico { transform: translateY(-2px); }

/* ── 数据条:数字下方一条极细的活动指示 ─────────────────── */
@keyframes m-bar-load {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}
.stats-strip .s { position: relative; overflow: hidden; }
.stats-strip .s::after {
  content: ''; position: absolute; left: 0; bottom: 0; height: 2px; width: 100%;
  background: var(--accent); transform-origin: left center; transform: scaleX(0);
}
.stats-strip.in .s::after {
  animation: m-bar-load var(--m-slow) var(--m-ease) forwards;
}
.stats-strip.in .s:nth-child(2)::after { animation-delay: .08s; }
.stats-strip.in .s:nth-child(3)::after { animation-delay: .16s; }
.stats-strip.in .s:nth-child(4)::after { animation-delay: .24s; }

/* ── 标题:分行揭开(clip-path 走合成器,比 height 动画便宜) ── */
@keyframes m-line-reveal {
  from { clip-path: inset(0 0 100% 0); transform: translateY(8px); }
  to   { clip-path: inset(0 0 -6% 0);  transform: none; }
}
.reveal-lines > * {
  display: block;
  animation: m-line-reveal var(--m-base) var(--m-ease) both;
}
.reveal-lines > *:nth-child(2) { animation-delay: .1s; }
.reveal-lines > *:nth-child(3) { animation-delay: .2s; }

/* ── 按钮:hover 时一道光沿边掠过 ─────────────────────── */
.btn { position: relative; overflow: hidden; }
.btn::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(105deg, transparent 40%, rgba(255,255,255,.28) 50%, transparent 60%);
  transform: translateX(-100%);
  transition: transform 620ms var(--m-ease);
  pointer-events: none;
}
.btn:hover::after { transform: translateX(100%); }
.btn.ghost::after { background: linear-gradient(105deg, transparent 40%, rgba(31,111,235,.12) 50%, transparent 60%); }

/* ── 表格行:hover 时左侧出现定位条 ───────────────────────
   ⚠️ 定位条必须画在行内**第一个单元格**上,不能画在 <tr> 上。
   <tr> 是 display:table-row,它的任何非单元格子节点(包括 ::before 生成的框)
   都会被 CSS 的表格修正规则包进一个**匿名 table-cell** ——
   于是每个表体行凭空多出一列,整行右移一格,而表头没有这一条规则、不会移,
   结果就是表头与表体错开一整列。加 position:absolute 也挡不住,
   匿名单元格在盒生成阶段就已经产生了。 */
.cmp-table tbody tr > :first-child,
.ord-table tbody tr > :first-child { position: relative; }
.cmp-table tbody tr > :first-child::before,
.ord-table tbody tr > :first-child::before {
  content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 2px;
  background: var(--accent); transform: scaleY(0); transform-origin: center;
  transition: transform var(--m-fast) var(--m-ease);
}
.cmp-table tbody tr:hover > :first-child::before,
.ord-table tbody tr:hover > :first-child::before { transform: scaleY(1); }

/* ── 排行榜条:进入视口时从左展开 ───────────────────────── */
.rank .bar > i {
  transform-origin: left center;
  transition: transform var(--m-slow) var(--m-ease), background var(--m-fast);
}

/* ══════════════════════════════════════════════════════════
   降级:用户在系统里关掉了动效,就一律不要动。
   这不是「减弱」,是彻底关闭 —— 前庭功能障碍的用户会因为
   视差与缩放产生真实的眩晕。
   ══════════════════════════════════════════════════════════ */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
  .js-motion [data-reveal] { opacity: 1 !important; transform: none !important; }
  .scan-line, .live-dot::after { display: none; }
  .stats-strip .s::after { transform: scaleX(1); }
  .btn::after { display: none; }
}
