playful 250ms · premium 550ms · energetic 180ms
Motion Personality
同じ「下から登場」でも duration と overshoot の違いだけでブランドの温度感が変わることを 4 アーキタイプで比較する。
Playful250ms
fun · whimsical · bouncy
Hello
ease: 0.17, 0.89, 0.32, 1.27
Premium550ms
elegant · minimal · luxury
Hello
ease: 0.40, 0.00, 0.20, 1.00
Corporate300ms
clean · professional · dashboard
Hello
ease: 0.20, 0.00, 0.00, 1.00
Energetic180ms
dynamic · bold · exciting
Hello
ease: 0.05, 0.70, 0.10, 1.00
/**
* Motion Personality — 4つの archetype(Playful / Premium / Corporate / Energetic)を
* 同じカードで対比する。
* 移植元: motion-playground app/_sections/PersonalityArchetypes.tsx(dark 変換 M-B-2)
*/
import { motion } from 'motion/react';
import {
personalities,
type PersonalityKey,
} from '../../../lib/motion-tokens/playground';
import { useLabReplay } from './use-lab-replay';
const order: PersonalityKey[] = [
'playful',
'premium',
'corporate',
'energetic',
];
function PersonalityCard({
personalityKey,
playKey,
}: {
personalityKey: PersonalityKey;
playKey: number;
}) {
const p = personalities[personalityKey];
const overshootScale = 1 + p.overshoot;
return (
<div className="rounded-xl border border-[var(--border-subtle)] bg-[var(--bg-surface)] p-5">
<div className="mb-4 flex items-center justify-between">
<span
className={`inline-flex items-center rounded-md px-2 py-0.5 text-xs font-medium ring-1 ${p.accent}`}
>
{p.label}
</span>
<span className="font-mono text-xs tabular-nums text-[var(--text-muted)]">
{Math.round(p.duration * 1000)}ms
</span>
</div>
<p className="mb-4 text-xs text-[var(--text-muted)]">{p.description}</p>
<div className="relative h-32 overflow-hidden rounded-md bg-[var(--bg-elevated)]">
<motion.div
key={`${personalityKey}-${playKey}`}
initial={{ y: 40, opacity: 0, scale: 0.92 }}
animate={{
y: [40, 0, 0],
opacity: [0, 1, 1],
scale: [0.92, overshootScale, 1],
}}
transition={{
duration: p.duration,
ease: p.ease,
times: p.overshoot > 0 ? [0, 0.7, 1] : [0, 1, 1],
}}
className="absolute inset-x-4 bottom-4 rounded-md bg-[var(--text-primary)] px-4 py-3 text-[var(--text-inverted)]"
>
<p className="text-sm font-medium">Hello</p>
<p className="mt-0.5 text-[11px] text-[rgba(10,10,11,0.6)]">
ease: {p.ease.map((n) => n.toFixed(2)).join(', ')}
</p>
</motion.div>
</div>
</div>
);
}
export default function PersonalityArchetypes() {
const { rootRef, playKey } = useLabReplay();
return (
<div
ref={rootRef}
data-lab-island
className="grid w-full gap-4 p-4 sm:grid-cols-2 lg:grid-cols-4 lg:p-6"
>
{order.map((key) => (
<PersonalityCard key={key} personalityKey={key} playKey={playKey} />
))}
</div>
);
} @keyframes personality-enter {
0% { transform: translateY(40px) scale(0.92); opacity: 0; }
70% { transform: translateY(0) scale(var(--overshoot, 1)); opacity: 1; }
100% { transform: translateY(0) scale(1); opacity: 1; }
}
.personality-chip { animation: personality-enter var(--duration) var(--ease) forwards; }
.personality-chip.playful { --duration: 250ms; --ease: cubic-bezier(0.175, 0.885, 0.32, 1.275); --overshoot: 1.15; }
.personality-chip.premium { --duration: 550ms; --ease: cubic-bezier(0.4, 0, 0.2, 1); }
.personality-chip.corporate { --duration: 300ms; --ease: cubic-bezier(0.2, 0, 0, 1); --overshoot: 1.03; }
.personality-chip.energetic { --duration: 180ms; --ease: cubic-bezier(0.05, 0.7, 0.1, 1); --overshoot: 1.2; } Implement 4 brand-personality entrances with motion/react:
- shared initial: { y: 40, opacity: 0, scale: 0.92 }
- playful: 250ms, cubic-bezier(0.175, 0.885, 0.32, 1.275), scale overshoot 1.15
- premium: 550ms, cubic-bezier(0.4, 0, 0.2, 1), no overshoot
- corporate: 300ms, cubic-bezier(0.2, 0, 0, 1), overshoot 1.03
- energetic: 180ms, cubic-bezier(0.05, 0.7, 0.1, 1), overshoot 1.2
- when overshoot > 0, use keyframe times [0, 0.7, 1] so scale peaks before settling
- replay by re-keying all 4 chips to restart simultaneously