80 · 200 · 350 · 600 · 1200ms · md3 standard

Duration Lab

80ms から 1200ms まで duration を並べ、要素の種類ごとの体感差を比較する。tooltip は 80–120ms、page transition は 400–600ms が目安。

80 · 200 · 350 · 600 · 1200ms · md3 standard

80ms

tooltip

micro feedback

200ms

icon transition

clear state change

350ms

card enter

spatial awareness

600ms

page transition

context switch

1200ms

dramatic reveal

theatrical

/**
 * Duration Lab — 80ms から 1200ms まで、体感の違いを比較する。
 * 移植元: motion-playground app/_sections/DurationLab.tsx(dark 変換 M-B-2)
 */
import { motion } from 'motion/react';
import { easings } from '../../../lib/motion-tokens/playground';
import { useLabReplay } from './use-lab-replay';
import { useTrackWidth } from './use-track-width';

const durations = [
  { ms: 80, label: 'tooltip', note: 'micro feedback' },
  { ms: 200, label: 'icon transition', note: 'clear state change' },
  { ms: 350, label: 'card enter', note: 'spatial awareness' },
  { ms: 600, label: 'page transition', note: 'context switch' },
  { ms: 1200, label: 'dramatic reveal', note: 'theatrical' },
];

function Lane({
  ms,
  label,
  note,
  playKey,
}: {
  ms: number;
  label: string;
  note: string;
  playKey: number;
}) {
  const [trackRef, travel] = useTrackWidth(28, 12);
  return (
    <div className="flex items-center gap-4 border-b border-[var(--border-subtle)] py-3 last:border-none">
      <div className="w-40 shrink-0">
        <p className="font-mono text-sm font-medium text-[var(--text-primary)]">
          {ms}ms
        </p>
        <p className="mt-0.5 text-xs text-[var(--text-muted)]">{label}</p>
        <p className="text-[11px] text-[var(--text-muted)]">{note}</p>
      </div>
      <div
        ref={trackRef}
        className="relative h-10 flex-1 overflow-hidden rounded-md bg-[var(--bg-elevated)]"
      >
        <motion.div
          key={playKey}
          initial={{ x: 0 }}
          animate={{ x: travel }}
          transition={{ duration: ms / 1000, ease: easings.md3Standard }}
          className="absolute top-1.5 left-1.5 h-7 w-7 rounded-md bg-[var(--text-primary)]"
        />
      </div>
    </div>
  );
}

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

  return (
    <div ref={rootRef} data-lab-island className="w-full p-4 lg:p-6">
      <div className="rounded-lg border border-[var(--border-subtle)] bg-[var(--bg-surface)] px-4 py-2">
        {durations.map((d) => (
          <Lane
            key={d.ms}
            ms={d.ms}
            label={d.label}
            note={d.note}
            playKey={playKey}
          />
        ))}
      </div>
    </div>
  );
}