# OVERDRIVE Motion API

## 共同约定

`createMotion({ gsap, scrollTrigger?, root?, reducedMotion? })` 创建上下文。`root` 可传 Document 或 HTMLElement；浏览器默认当前 document。`reducedMotion` 默认 `'system'`。

上下文及每个效果均支持 `pause()`、`resume()`、`destroy()`。单个实例的 pause 不会被上下文 resume 意外取消。`destroy` 可重复调用；销毁之后，有限操作返回 cancelled。

`motion.setReducedMotion(true | false | 'system')` 可随时调整。有限时序直接提交终态并兑现回调；空间背景保留静态帧。切回 false 不重播已经完成的操作。页面隐藏会暂停动态播放。

所有时长以秒计。创建时传入 **元素引用**；没有全局 selector、路由或站点 ID 约定。容器需要实际宽高。库生成的元素以 `.odm-*` 命名。

有限方法返回 `Promise<{ status: 'completed' | 'cancelled' | 'replaced' }>`。最近调用优先。输入错误同步抛出 TypeError / RangeError；内容回调错误拒绝 Promise。所有内容回调均为同步；不要把 fetch 等异步工作放在幕布中点。

## 模块

下表 `motion` 为所有工厂的必填选项。

| 模块 | 签名与默认选项 | 控制方法 |
|---|---|---|
| curtain | `curtain({motion, container, color:'#c4a3ff', slats:9, timing:{}})` | `play({title, onCovered, onRevealed})` |
| opening | `opening({motion, container, title:'ENTER / PLAY', targets:[], color:'#c6ff6d', timing:{speed:1}})` | `play()` |
| textReveal | `textReveal(element,{motion,duration:.85,stagger:.025,blur:14,y:105,rotation:95})` | `play()` |
| liquidField | `liquidField(canvas,{motion,palettes?,renderer:'auto',pixelRatio:1.25,duration:.7})` | `setPalette(index)` |
| particleField | `particleField(canvas,{motion,count:320,text:':D',colors?,pixelRatio:1.25,duration:.8})` | `setProgress(0…1)`、`burst()` |
| pixelMorph | `pixelMorph(container,{motion,count:100,duration:.8,color:'#caff86'})` | `setProgress(0…1)` |
| pointerTrail | `pointerTrail(container,{motion,colors?,label:'PLAY'})` | 生命周期方法 |
| magnetic | `magnetic(element,{motion,strength:.16,duration:.4})` | 生命周期方法 |
| tilt | `tilt(element,{motion,strength:26,duration:.6,layers:[]})` | 生命周期方法 |
| imageShutters | `imageShutters(img,{motion,slices:8,duration:1.2})` | `play()` |
| maskMorph | `maskMorph(element,{motion,shapes:defaultShapes,duration:.7})` | `set(index)` |
| radialReveal | `radialReveal(element,{motion,duration:.9,origin:[82,14]})` | `set(boolean)` |
| presence | `presence(element,{motion,duration:.28,blur:7})` | `enter()`、`exit()` |
| swap | `swap(element,{motion,duration:.46})` | `play({update, direction:1})` |
| accordion | `accordion(element,{motion,duration:.28})` | `setOpen(boolean)` |
| stackFan | `stackFan(backElements,{motion,duration:.8,spread:45})` | `setExpanded(boolean)` |
| pixelBurst | `pixelBurst(container,{motion,count:18,duration:.7,colors?})` | `play({x?,y?})` |
| activeIndicator | `activeIndicator(indicator,{motion,container:parent,duration:.35})` | `moveTo(element)` |
| scrollScene | `scrollScene({motion,trigger,target,from:{},to:{},pin:false,start:'top 90%',end:'bottom 10%',scrub:.7,onProgress?,scroller?})` | 生命周期方法 |

### curtain 的时序

快门合拢 → 完整纯色幕布 → onCovered → 标题进入/停留/退场 → 揭幕 → onRevealed。

可覆盖的 timing 默认值：`cover:.55, coverStagger:.024, titleIn:.42, titleStagger:.045, hold:.18, titleOut:.3, outStagger:.025, reveal:.78, slatOut:.58, slatStagger:.02`。错峰已计入相邻阶段顺序，文字不会提前暴露底下内容。`title` 是纯文本，支持换行，不执行 HTML。

容器为 document.body 时遮罩固定到视口，其他容器内绝对定位。内容更新与 URL 跳转由 onCovered 的调用方决定。

### 文字与组件

textReveal 按 Intl.Segmenter 的字素拆分，保留嵌套标记及原节点；销毁会恢复原来的文本节点，不重建外部绑定的 em / a / button。现代浏览器支持完整 emoji 字素。较旧、无 Intl.Segmenter 的环境按码点降级，复杂 emoji 可能分开。

presence 只改变透明度、位移、缩放、模糊。它不会自动设置 hidden 或 inert。accordion 同样不接管触发按钮的 aria-expanded；在宿主中设置这些状态。swap 的 update 只在最新操作的切换点执行。direction 只接受 1 或 -1。

imageShutters 接收已挂在父容器内的 img，可在图片尚未加载时调用 play；加载错误会拒绝。复制 img 当前的 object-fit / object-position，保持切片一致。请给图片指定明确尺寸，避免布局在动画中跳动。

### 背景、空间与指针

liquidField 默认三组配色（黄 / 赤 / 紫），每组为 `[base, shade, light]` 三个六位十六进制颜色。setPalette 支持索引之间的小数。`renderer:'css'` 强制静态降级；WebGL 丢失也会显示配色背景。上下文恢复后重新创建着色器。

Canvas 的 pixelRatio 默认上限 1.25，可设 0.1–3；以实际设备 DPR 和上限的较小值绘制。particleField count 1–2000，pixelMorph count 1–500；少量设备建议 160 和 100。particleField text 1–32 字符，过长文字会缩入点阵范围。点阵使用系统等宽字体，无字体下载依赖。

tilt layers 每项是 `{ element, depth }`，默认 depth 20，范围 -200…200；元素由调用方提供。magnetic strength 范围 0…2，tilt strength 0…90。指针效果仅精细指针且支持 hover 时生效。

### 形状与滚动

maskMorph 需要至少两个 `polygon(...)`，每个多边形顶点数相同。set 的 index 是有效整数。radialReveal origin 是容器百分比 `[x,y]`，范围 0…100。

scrollScene 需要创建上下文时传入 ScrollTrigger。from / to 应只包含可动画 CSS 属性，不接受业务回调；用 onProgress 获取进度。pin 默认为 false；小屏建议普通滚动。指向自定义滚动容器时传 scroller。组件卸载时 destroy，恢复样式并移除固定占位层。

## CSS 预设

可选 src/styles.css 提供 `.odm-glow`、`.odm-grid`、`.odm-aura` 与 `.odm-marquee` / `.odm-marquee-track`。CSS 循环遵循系统减少动态偏好；宿主在外层设置 `data-odm-paused` 可以暂停 CSS 循环。JS 上下文只管理自己创建的 JS 效果。

## 所有权与边界

每个实例恢复自己接管的 CSS 属性。一个元素的同一属性只交给一个存活实例；叠加多个变换效果时，用不同的嵌套元素。与动画无关的颜色、内容、事件监听器由宿主拥有。

每个文档/iframe 单独创建上下文。不要将 effect 的元素从所属文档移动到另一个文档后继续使用旧实例。实例内部不会读取个人网站数据，不生成海报、不管理真实行程，也不修改浏览器导航。
