6 presets · 160–950ms · bounce / gentle / accelerate
Emotion to Motion
喜び・穏やかさ・緊急・悲しみ・驚き・気品の 6 感情を duration・easing・移動・回転・scale の組み合わせで表現する。同じ「丸が登場する」動きでもパラメータだけで感情が変わる。
※ カードをクリックすると再生される
// notes
- duration と easing の組み合わせだけで「喜び」と「悲しみ」のような対照的な感情を表現できる。速く弾む = 喜び、遅く重力に従う = 悲しみ。
- overshoot(bounce-settle)は playful な質感を生むが、使いすぎると軽薄に見える。
- travel の x / y 方向は感情の「出どころ」を暗示する。urgency は横から割り込ませると緊迫感が出る。
- scaleFrom の初期値が「驚き」と「穏やかさ」の差を生む。極端に小さい初期 scale は pop-in の驚き、1 に近い初期 scale は落ち着き。
/**
* Emotion → Motion — 感情プリセットを duration / easing / travel path に翻訳する。
* クリックで個別再生(stage の Replay ではなく操作駆動なので useLabReplay は使わない)。
* 移植元: motion-playground app/_sections/EmotionToMotion.tsx(dark 変換 M-B-2)
*/
import { motion } from 'motion/react';
import { useState } from 'react';
import { emotions, type EmotionKey } from '../../../lib/motion-tokens/playground';
const order: EmotionKey[] = [
'joy',
'calm',
'urgency',
'sadness',
'surprise',
'elegance',
];
function EmotionTile({ emotionKey }: { emotionKey: EmotionKey }) {
const e = emotions[emotionKey];
const [playKey, setPlayKey] = useState(0);
return (
<button
type="button"
onClick={() => setPlayKey((k) => k + 1)}
className={`group relative h-44 overflow-hidden rounded-lg bg-gradient-to-br ${e.accent} text-left transition-shadow hover:shadow-md focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--border-strong)]`}
>
<div className="absolute inset-0 flex items-center justify-center">
<motion.div
key={playKey}
initial={{
x: e.travel.x,
y: e.travel.y,
rotate: e.rotate,
scale: e.scaleFrom,
opacity: 0,
}}
animate={{
x: 0,
y: 0,
rotate: 0,
scale: 1,
opacity: 1,
}}
transition={{ duration: e.duration, ease: e.ease }}
className="h-12 w-12 rounded-full bg-[var(--bg-surface)]/90 shadow-sm ring-1 ring-[var(--border-subtle)]"
/>
</div>
<div className="absolute inset-x-0 bottom-0 flex items-end justify-between p-4">
<div>
<p className="text-sm font-semibold text-[var(--text-primary)]">
{e.label}
</p>
<p className="text-xs text-[var(--text-secondary)]">{e.jp}</p>
</div>
<span className="font-mono text-[11px] text-[var(--text-muted)]">
{Math.round(e.duration * 1000)}ms
</span>
</div>
</button>
);
}
export default function EmotionToMotion() {
return (
<div data-lab-island className="w-full p-4 lg:p-6">
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
{order.map((key) => (
<EmotionTile key={key} emotionKey={key} />
))}
</div>
<p className="mt-4 text-xs text-[var(--text-muted)]">
※ カードをクリックすると再生される
</p>
</div>
);
} Map 6 emotions to motion parameters (motion/react), each a circle entrance:
- joy: 320ms, cubic-bezier(0.175, 0.885, 0.32, 1.275), travel y -40, rotate -8deg, scale from 0.85
- calm: 850ms, cubic-bezier(0.4, 0, 0.2, 1), travel y +16, scale from 0.98
- urgency: 160ms, cubic-bezier(0.05, 0.7, 0.1, 1), travel x +24, scale from 1.05
- sadness: 950ms, cubic-bezier(0.3, 0, 1, 1), travel y +28, rotate 4deg
- surprise: 220ms, cubic-bezier(0.05, 0.7, 0.1, 1), no travel, scale from 0.4 (pop-in)
- elegance: 550ms, cubic-bezier(0.4, 0, 0.2, 1), travel x -18 / y -10, rotate -2deg, scale from 0.96
- re-trigger each tile independently on click via a local playKey