# twelve-principles API 早見表

正はリポジトリの `src/index.ts` と `src/react/index.ts`（docs の API リファレンス）。ここは要点だけ。

## モデル

- `Pose`: `x y z`(px) `scale scaleX scaleY` `rotate rotateX rotateY skewX skewY`(deg) `opacity` `blur`(px) `elevation`(影の高さ)。静止状態 `REST` からの**絶対値**。
- `PoseFrame = Pose & { offset?: 0..1; easing?: string | (t) => number }` — easing はそのフレームから次までの区間に効く。
- `MotionSpec = { frames, duration(ms), delay?, easing?(全体), iterations?, direction?, origin?, perspective? }`。
- `compile(spec)` → WAAPI 引数。JS イージング（spring 等）は 60fps 相当で焼き込み。

## 再生（DOM）

| API | 要点 |
| --- | --- |
| `play(el, spec, { reducedMotion?, persist? })` | `Animation` を返す。同一要素の前のモーションは置換。静止で終わればインラインスタイルを消す |
| `animateTo(el, pose, { transition })` | 現在姿勢から割り込み遷移。`transition` は `{ duration, easing }` か `spring(...)` |
| `setLayer(el, name, pose \| null, opts)` | 名前付きレイヤー合成（移動・回転・高さは加算、拡大・不透明度は乗算） |
| `currentPose(el)` / `stop(el)` / `playAll(items)` | 再生中の姿勢 / その場で停止 / まとめて再生 |
| `pressable` `hoverable` `tiltable` | ビヘイビア。解除関数を返す |
| `stage(el, opts)` → `{ release() }` | 主役を前へ、周囲を暗く・ぼかし・後退 |

## 原則関数（MotionSpec → MotionSpec など）

| # | API | 既定値など |
| --- | --- | --- |
| 1 | `deform(amount, axis)` `squash` `stretch` `squashStretch({ intensity=0.05, axis="y", duration=300, origin })` | 体積保存 scaleX×scaleY=1 |
| 2 | `anticipate(spec, { amount=0.15, share=0.3, fit="compress" })` `windUp` | fit `"extend"` で本動作の尺を保つ |
| 3 | `stagingPoses({ dim=0.5, blur=2, recede=0.02, lift=12 })` | |
| 4 | `straightAhead((t, ms) => pose, { duration, fps=60 })` `poseToPose(frames, { duration=300 })` | poseToPose の区間既定は inOut |
| 5 | `followThrough(spec, { bounce=0.3 })` `overlap(spec, n, { drag=0.1, each=30, cap=50, total=300, from })` `spring({ duration=400, bounce=0.2 } \| { stiffness, damping, mass })` | spring は `.duration` に静定時間 |
| 6 | `easings.{linear,inOut,out,in,anticipate,overshoot}` `cubicBezier` | |
| 7 | `arc(from, to, { bend=0.2, orient, samples=24, duration, easing=inOut })` | bend 正で上に膨らむ |
| 8 | `secondaryAction("wiggle"\|"pulse"\|"float"\|"sway", opts)` `accompany(primary, kind, { attenuation=0.6, lag=0.15 })` | |
| 9 | `durations{instant 100, fast 150, base 200, slow 300, deliberate 500}` `duration(token, tempo)` `travelDuration(px)` `staggerDelays(n, opts)` | |
| 10 | `exaggerate(poseOrSpec, factor, { rest, keys })` | opacity は対象外 |
| 11 | `lift(level=4)` `tiltToward(point, max=8)` `solid(spec, perspective=800)` `shadowForElevation(e)` | |
| 12 | `personalities` `definePersonality(overrides, base)` `resolvePersonality` | 下表 |

## Personality

| name | tempo | exaggeration | bounce | anticipation | squash | guardrails |
| --- | --- | --- | --- | --- | --- | --- |
| natural | 1 | 1 | 0.2 | 0.15 | 0.04 | true |
| snappy | 0.8 | 0.9 | 0.1 | 0.08 | 0.03 | true |
| calm | 1.25 | 0.7 | 0 | 0 | 0.02 | true |
| playful | 1 | 1.4 | 0.45 | 0.25 | 0.08 | true |
| bouncy | 0.95 | 1.7 | 0.55 | 0.3 | 0.12 | false |
| cartoon | 1.15 | 2.4 | 0.65 | 0.45 | 0.22 | false |

`definePersonality` の範囲: tempo 0.1–4, exaggeration 0–4, bounce 0–0.95, anticipation 0–1, squash 0–0.5, guardrails boolean。

## レシピ（`RecipeOptions = { personality, duration, distance }`）

- UI: `enter(kind="rise")` `exit(kind="fade")`（kind: fade rise drop slideLeft slideRight zoom pop）`jump({ height=16 })` `shake({ distance=6 })` `pressDepth(p)` `settleSpring(p)`
- Expressive: `rubberBand` `jello` `tada` `heartbeat` `swing` `wobble({ distance=25 })` `flip` `bounceIn` `fallIn({ height=80 })`、名前引き `expressive[name](opts)`

## React（`twelve-principles/react`）

- `MotionProvider({ personality, reducedMotion })` `useMotionConfig()`
- `usePress(opts|false)` `useHover` `useTilt` → callback ref
- `useEnter(kind|spec|(p)=>spec)` `usePresence(show, { enter, exit, initial })` → `{ present, ref }`
- `useMotion()` → `{ ref, play(specOrBuilder), animateTo(pose), stop() }`
- `useStage(active, opts)` `useCascade(input, { trigger, each, cap, drag, from })`
- `<Motion as press hover tilt enter>` `<Presence show enter exit initial as>` `mergeRefs(...)`
