hero x -60→80 · orbs ∓40 / ∓24 · 700–1100ms

Counter-motion

手前の要素が右へ動くとき、背景の ambient レイヤーを逆方向にわずかにずらして奥行きを出す。before / after の比較デモ。

hero x -60→80 · orbs ∓40 / ∓24 · 700–1100ms

Without counter-motion

Hero card

→ 右へ移動

With counter-motion ✓

Hero card

→ 右へ移動

/**
 * Counter-motion — hero と ambient を逆方向に動かし視差効果を出す比較デモ。
 * 移植元: motion-playground app/_sections/CounterMotion.tsx(dark 変換 M-B-2)
 */
import { motion } from 'motion/react';
import { easings } from '../../../lib/motion-tokens/playground';
import { useLabReplay } from './use-lab-replay';

function Scene({
  withCounter,
  playKey,
}: {
  withCounter: boolean;
  playKey: number;
}) {
  return (
    <div className="relative h-56 overflow-hidden rounded-lg bg-[var(--bg-surface)]">
      {/* Ambient layer */}
      <motion.div
        key={`bg-${playKey}`}
        initial={{ x: 0, opacity: 0 }}
        animate={{ x: withCounter ? -40 : 40, opacity: 1 }}
        transition={{ duration: 0.9, ease: easings.gentle }}
        className="pointer-events-none absolute -top-12 -right-20 h-72 w-72 rounded-full bg-indigo-400/20 blur-3xl"
      />
      <motion.div
        key={`bg2-${playKey}`}
        initial={{ x: 0, opacity: 0 }}
        animate={{ x: withCounter ? -24 : 24, opacity: 1 }}
        transition={{ duration: 1.1, ease: easings.gentle }}
        className="pointer-events-none absolute -bottom-12 -left-12 h-56 w-56 rounded-full bg-amber-400/20 blur-3xl"
      />

      {/* Hero card */}
      <motion.div
        key={`hero-${playKey}`}
        initial={{ x: -60, opacity: 0 }}
        animate={{ x: 80, opacity: 1 }}
        transition={{ duration: 0.7, ease: easings.md3Emphasized }}
        className="absolute top-1/2 left-0 -translate-y-1/2 rounded-lg bg-[var(--bg-elevated)] px-5 py-3 ring-1 ring-[var(--border-strong)]"
        style={{ boxShadow: '0 8px 24px rgba(0, 0, 0, 0.35)' }}
      >
        <p className="text-sm font-medium text-[var(--text-primary)]">
          Hero card
        </p>
        <p className="text-xs text-[var(--text-muted)]">→ 右へ移動</p>
      </motion.div>
    </div>
  );
}

export default function CounterMotion() {
  const { rootRef, playKey } = useLabReplay();

  return (
    <div ref={rootRef} data-lab-island className="w-full p-4 lg:p-6">
      <div className="grid gap-6 sm:grid-cols-2">
        <div>
          <p className="mb-3 text-xs font-medium uppercase tracking-wide text-[var(--text-muted)]">
            Without counter-motion
          </p>
          <Scene withCounter={false} playKey={playKey} />
        </div>
        <div>
          <p className="mb-3 text-xs font-medium uppercase tracking-wide text-[var(--text-muted)]">
            With counter-motion ✓
          </p>
          <Scene withCounter={true} playKey={playKey} />
        </div>
      </div>
    </div>
  );
}