/** * Motion.page SDK - Type Definitions */ export type { TimelineAction } from '../utils/executeTimelineAction'; /** * Easing function type */ export type EasingFunction = (t: number) => number; /** * Text split types for character/word/line animations */ export type SplitType = 'chars' | 'words' | 'lines' | 'chars,words' | 'words,lines' | 'chars,words,lines'; /** * Bleed padding applied to split-text mask wrappers. * Numbers are interpreted as em values so the clip area scales with the font. */ export type MaskPaddingValue = number | string; export type MaskPadding = MaskPaddingValue | { inline?: MaskPaddingValue; block?: MaskPaddingValue; }; /** * Configuration for repeat behavior */ export interface RepeatConfig { times: number; delay?: number; yoyo?: boolean; } /** * Configuration for a Timeline instance */ export interface TimelineConfig { /** * Number of times to repeat the entire timeline. * Use a number for simple repeats, or RepeatConfig for advanced options. * Use -1 for infinite repeat. * @example * new Timeline('loop', { repeat: -1 }) * new Timeline('bounce', { repeat: { times: 3, delay: 0.2, yoyo: true } }) */ repeat?: number | RepeatConfig; /** Called when the timeline starts playing (first frame with time > 0) */ onStart?: () => void; /** Called every frame with the current progress (0–1) and time (seconds) */ onUpdate?: (progress: number, time: number) => void; /** Called when the timeline completes all repeats */ onComplete?: () => void; /** Called at the end of each repeat cycle */ onRepeat?: (repeatCount: number) => void; } /** * Stagger configuration for multiple elements */ export interface StaggerVars { each?: number; amount?: number; from?: 'start' | 'center' | 'edges' | 'random' | 'end' | number; grid?: 'auto' | [number, number]; axis?: 'x' | 'y'; ease?: string; } /** * Animatable properties */ export interface AnimationVars { x?: number | string; y?: number | string; z?: number | string; rotate?: number | string; rotateX?: number | string; rotateY?: number | string; rotateZ?: number | string; scale?: number | string; scaleX?: number | string; scaleY?: number | string; scaleZ?: number; skewX?: number | string; skewY?: number | string; perspective?: number | string; opacity?: number; backgroundColor?: string; color?: string; width?: number | string; height?: number | string; top?: number | string; left?: number | string; right?: number | string; bottom?: number | string; margin?: number | string; marginTop?: number | string; marginRight?: number | string; marginBottom?: number | string; marginLeft?: number | string; padding?: number | string; paddingTop?: number | string; paddingRight?: number | string; paddingBottom?: number | string; paddingLeft?: number | string; borderRadius?: number | string; borderWidth?: number | string; fontSize?: number | string; lineHeight?: number | string; letterSpacing?: number | string; zIndex?: number; filter?: string; transformOrigin?: string; backgroundPosition?: string; borderColor?: string; borderTopColor?: string; borderRightColor?: string; borderBottomColor?: string; borderLeftColor?: string; outlineColor?: string; textDecorationColor?: string; caretColor?: string; drawSVG?: string | { start?: number; end?: number; }; fill?: string; stroke?: string; path?: PathConfig; [key: string]: string | number | boolean | undefined | null | object; } /** * Path configuration for motion along SVG path */ export interface PathConfig { /** SVG path - CSS selector, Element, or raw path data (starting with M/m) */ target: string | Element; /** Element to align to (CSS selector or Element) */ align?: string | Element; /** Origin point on element [x%, y%], default [50, 50] */ alignAt?: [number, number]; /** Start position on path 0-1 (default: 0) */ start?: number; /** End position on path 0-1 (default: 1) */ end?: number; /** Auto-rotate element along path tangent */ rotate?: boolean; } /** * Preset character sets available for TextFlapper cycling animations. */ export type CharsetPreset = 'alphanumeric' | 'alpha' | 'numeric' | 'hex' | 'binary' | 'katakana' | 'symbols' | 'blocks'; /** * Configuration for TextFlapper split-flap display animations. * Passed as the `flap` property on an AnimationConfig to enable character cycling. * * **Per-cycle composition** — When `flap` is present on an AnimationConfig, all * `from`/`to` properties automatically animate per character-swap cycle instead of * once over the full duration. Each cycle follows a V-curve: rests at `to`, dips to * `from` at the midpoint (when the character swaps), then returns to `to`. */ export interface FlapConfig { /** * Visual style for each character cycle. * - `'flip'` — 3D CSS rotateX transition (default) * - `'fade'` — CSS opacity fade out/in * - `'slide'` — CSS translateY slide out/in * - `'blur'` — CSS filter blur out/in * - `'scale'` — CSS scale pop/shrink * - `'board'` — mechanical split-flap (departure board) * - `'none'` — No built-in visual effect; characters cycle with instant swap. * Use with `from`/`to` to create fully custom flap animations — all * properties animate per-cycle (V-curve: to→from→to on each swap). */ type?: 'flip' | 'fade' | 'slide' | 'blur' | 'scale' | 'board' | 'none'; /** * Character set used for intermediate (scrambled) frames. * Use a CharsetPreset name or any custom string of characters. * @default 'alphanumeric' */ charset?: CharsetPreset | string; /** * Number of random cycles each character performs before settling. * - `number` : exact cycle count for every character * - `[min, max]` : random count in range, chosen independently per character * @default [2, 5] */ cycles?: number | [min: number, max: number]; /** * Perspective depth (px) for the 3D flip/board animation. * Only used when `type` is 'flip' or 'board'. * @default 400 */ perspective?: number; /** * When true, board-type flaps get a realistic departure-board look: * dark gradient background, rounded corners, and a visible split line. * Only applies when `type` is 'board'. * @default true */ styledBoard?: boolean; /** * Strategy for preventing layout shift while characters cycle: * * - `false` (default) — no pinning. Text width flexes with each glyph. * Fine for same-width charsets (Latin → alphanumeric). * - `true` or `'cells'` — pin each character cell to the widest glyph * across the target chars AND the charset pool. Every cell is the * same width regardless of which random glyph renders at the moment. * Good for monospace aesthetics (departure boards, counters). Adds * visible horizontal space around narrow Latin letters when paired * with wide charsets (katakana, CJK). * - `'container'` — pin the parent element's outer width to whatever * the text would measure at the widest possible flap state, but * DON'T pin individual cells. Characters flow naturally (no extra * spacing between Latin letters), while the surrounding layout * (rows of links, inline columns, etc.) stays rock-steady because * the element's outer box never changes size. Best choice for * inline hover flappers on navigation links where cell-pinning * would space the letters out unnaturally. * * Automatically enabled (as `'cells'`) for `board` + `styledBoard: true`. * @default false */ stableWidth?: boolean | 'cells' | 'container'; /** * When true and `type` is 'board', whitespace characters (spaces, tabs, * newlines) are rendered as empty board cells — the cell frame, background, * perspective, and split line are built as usual, but the cell face is blank * (non-breaking space) and no flap animation runs. * * Rationale: real split-flap departure boards have fixed cells regardless of * which positions contain characters. Enabling this lets you animate text of * varying lengths without the board width or cell count shifting between * lines. * * Has no effect when `type` is anything other than 'board'. Default `false` * preserves existing behavior (whitespace cells are skipped entirely). * @default false */ preserveWhitespaceCells?: boolean; } /** * Configuration for Fit animations. * Morphs one element's geometry to match another's — captures source and target * element bounding rects at play time and animates the visual delta. */ export interface FitConfig { /** CSS selector for the target element to morph toward. Measured at animation play time via getBoundingClientRect() */ target: string; /** Convert elements to absolute positioning during animation to prevent layout flow disruption. Default: false. Animates CSS left/top/width/height instead of transforms */ absolute?: boolean; /** Include scale (scaleX/scaleY transform) changes in the animation. Default: true. Distorts content (text, borders). Use `resize` for undistorted dimension changes. */ scale?: boolean; /** Animate actual width/height CSS properties instead of scaleX/scaleY transforms. Default: false. No content distortion but triggers layout reflow. Mutually exclusive with `scale`. */ resize?: boolean; } /** * Animation configuration for Motion(name, target, config) */ export interface AnimationConfig { from?: AnimationVars; to?: AnimationVars; /** * Immediately set CSS properties on target(s) without animation (duration: 0). * Equivalent to GSAP's gsap.set() — applied before from/to tweens. * @example * Motion('init', '.box', { set: { opacity: 0, visibility: 'visible' }, to: { opacity: 1 }, duration: 1 }) */ set?: AnimationVars; duration?: number; delay?: number; ease?: string; stagger?: number | StaggerVars; /** * Number of times to repeat the animation. * Use a number for simple repeats, or RepeatConfig for advanced options. * Use -1 for infinite repeat. * @example * repeat: 3 * repeat: { times: -1, delay: 0.2, yoyo: true } */ repeat?: number | RepeatConfig; split?: SplitType; /** * When true (used with `split`), each split element is wrapped in a parent * with `overflow: hidden` so animated content is clipped to its natural bounds. * Creates a "reveal" effect when animating `y: '100%'` — text slides up from * behind an invisible edge. * @example * Motion('reveal', 'h1', { split: 'lines', mask: true, from: { y: '100%' }, to: { y: 0 }, stagger: 0.1 }) */ mask?: boolean; /** * Extra clip-area padding for `mask: true` split text. Numbers are treated as * `em`; strings are used as CSS lengths. Pass a single value for both axes or * separate `inline` / `block` values for cursive and display fonts. * @example * maskPadding: 0.2 * maskPadding: { inline: 0.25, block: '3px' } */ maskPadding?: MaskPadding; /** * Split-flap display animation configuration. * Animates pre-split character elements through random character cycling before * settling on the target character. Requires the target elements to already be * split into individual character spans (e.g. via TextSplitter with 'chars'). * * Per-character animations are created by the engine; flap preset values * (rotateX, opacity, etc.) merge into from/to at low priority — user values always win. * @example * Motion('scramble', chars, { flap: { type: 'flip', charset: 'alphanumeric', cycles: [2, 5] } }) */ flap?: FlapConfig; /** * Fit animation configuration. * Morphs the source element's geometry toward the target element's geometry. * When set, `from` and `to` are ignored. * @example * Motion('reorder', '.container', { fit: { target: '.item' }, duration: 0.5 }) */ fit?: FitConfig; /** Axis binding for onMouseMove trigger (x or y) */ axis?: 'x' | 'y'; /** * Called when the animation starts (first frame with progress > 0). * @example * Motion('fade', '#box', { to: { opacity: 0 }, onStart: () => console.log('started') }) */ onStart?: () => void; /** * Called every frame with the current progress value (0–1). * @param progress - Current playback progress from 0 (start) to 1 (end) * @example * Motion('fade', '#box', { to: { opacity: 0 }, onUpdate: (p) => console.log(p) }) */ onUpdate?: (progress: number) => void; /** * Called when the animation completes (after the final repeat, forward direction). * @example * Motion('fade', '#box', { to: { opacity: 0 }, onComplete: () => console.log('done') }) */ onComplete?: () => void; /** * Called at the end of each repeat cycle. * @param repeatCount - Number of repeats completed so far * @example * Motion('pulse', '#box', { to: { scale: 1.2 }, repeat: { times: 3 }, onRepeat: (n) => console.log('repeat', n) }) */ onRepeat?: (repeatCount: number) => void; /** * Called when the animation completes in the reverse direction (after the final yoyo repeat). * @example * Motion('pulse', '#box', { to: { scale: 1.2 }, repeat: { times: 1, yoyo: true }, onReverseComplete: () => console.log('reversed') }) */ onReverseComplete?: () => void; } /** * Animation entry for Motion(name, [entries]) */ export interface AnimationEntry extends AnimationConfig { target: TargetInput; position?: number | string; } /** * Plain object target for non-DOM animation */ export type ObjectTarget = Record; /** * Valid animation targets */ export type AnimationTarget = Element | ObjectTarget; /** * Input types accepted by Motion() */ export type TargetInput = string | string[] | Element | NodeList | Element[] | ObjectTarget | ObjectTarget[]; export interface MarkerConfig { startColor?: string; endColor?: string; fontSize?: string; fontWeight?: string; indent?: number; } export interface HoverConfig { target?: string | Element; each?: boolean; /** * What happens on mouseenter: * - `'play'` (default) — `.play()` the timeline. Resumes from current * position; silently no-ops when already at the end. Best for * reversible hover animations paired with `onLeave: 'reverse'`. * - `'restart'` — `.restart()` the timeline ONLY when the previous run * has finished (or hasn't started yet). If the timeline is still * mid-play, the current run is allowed to finish uninterrupted — * jittery cursors at the element edge won't stutter the animation * and leaving + re-entering mid-flap won't reset it. Best for * one-shot effects (text flappers, shake, decode reveal). Pair with * `onLeave: 'none'` so the animation finishes naturally on leave. */ onEnter?: 'play' | 'restart'; onLeave?: 'reverse' | 'pause' | 'stop' | 'restart' | 'none'; leaveDelay?: number; } export interface ClickConfig { target?: string | Element; each?: boolean; secondTarget?: string | Element; toggle?: 'reverse' | 'restart' | 'play'; preventDefault?: boolean; } export interface ScrollConfig { target?: string | Element; /** * Optional element the `end` position is measured against, when it differs * from `target`. Mirrors GSAP ScrollTrigger's `endTrigger`: `start` is resolved * relative to `target`, `end` relative to `endTarget`. When omitted, `end` is * resolved against `target` (GSAP's default). Used by v2→v3 migrations that * carried a distinct end element (e.g. a seed pinned from its own bottom until a * taller sibling's bottom). Ignored when `end` is a relative `+=`/`-=` offset, * which is always measured from the start position. */ endTarget?: string | Element; start?: string; end?: string; scrub?: boolean | number; markers?: boolean | MarkerConfig; scroller?: string | Element; pin?: boolean | string; pinSpacing?: boolean | 'margin' | 'padding'; each?: boolean; /** Toggle actions for non-scrub mode: "onEnter onLeave onEnterBack onLeaveBack" */ toggleActions?: string; /** * Snap scroll progress to fixed points. * - `number`: snap to evenly-spaced increments (e.g., 0.25 → 0, 0.25, 0.5, 0.75, 1) * - `number[]`: snap to specific progress values (e.g., [0, 0.33, 0.66, 1]) * - `function`: custom snap function receiving raw progress, returning snapped progress * * @example Horizontal scroll with 4 sections * snap: 1 / (document.querySelectorAll('.section').length - 1) */ snap?: number | number[] | ((progress: number) => number); } export interface MouseMoveConfig { type?: 'distance' | 'axis'; target?: string | Element; each?: boolean; smooth?: number; startProgress?: number; leaveProgress?: number; } export interface PageExitConfig { /** Link selection mode: 'all' (default), 'include', or 'exclude' */ mode?: 'all' | 'include' | 'exclude'; /** CSS selector(s) for links — required for 'include'/'exclude' modes */ selectors?: string; /** Href patterns to skip (never intercept) */ skipHref?: ('anchor' | 'javascript' | 'mailto')[]; } export type GestureInputType = 'pointer' | 'touch' | 'wheel' | 'scroll'; export type GestureEvent = 'Up' | 'Down' | 'Left' | 'Right' | 'UpComplete' | 'DownComplete' | 'LeftComplete' | 'RightComplete' | 'Change' | 'ChangeX' | 'ChangeY' | 'ToggleX' | 'ToggleY' | 'Press' | 'Release' | 'PressInit' | 'Drag' | 'DragEnd' | 'Stop' | 'Hover' | 'HoverEnd'; export type GestureAction = 'play' | 'pause' | 'reverse' | 'restart' | 'toggle' | 'reset' | 'complete' | 'kill' | 'playReverse' | 'progressUp' | 'progressDown' | 'playNext' | 'playPrevious'; export interface GestureConfig { target?: string | Element; types: GestureInputType[]; events: Partial>; tolerance?: number; dragMinimum?: number; wheelSpeed?: number; scrollSpeed?: number; preventDefault?: boolean; lockAxis?: boolean; each?: boolean; stopDelay?: number; animationStep?: number | Partial>; smooth?: number; } /** * Extended GestureConfig with internal fields used by GestureTrigger. * @internal - Not part of the public API. Used by the builder when setting up each-mode animations. */ export interface GestureConfigInternal extends GestureConfig { /** @internal */ _siblings?: unknown[]; /** @internal */ _index?: number; } /** * CSS properties as object (like GSAP/React style) * duration and ease are extracted for transitions */ export interface CursorStateVars { /** CSS selectors that trigger this state (hover only) */ targets?: string[]; /** Transition duration in seconds. Default: 0.15 */ duration?: number; /** Easing function. Default: 'power3.inOut' */ ease?: string; /** Whether state is enabled. Default: true */ enabled?: boolean; /** Additional CSS properties to apply (e.g., width, height, backgroundColor) */ [key: string]: string | number | string[] | boolean | undefined; } /** * Squeeze effect configuration */ export interface CursorSqueezeConfig { /** Minimum scale when moving fast. Default: 0.55 */ min?: number; /** Maximum scale when stationary. Default: 1.2 */ max?: number; /** Velocity sensitivity multiplier. Default: 150 */ multiplier?: number; } /** * Main cursor configuration */ export interface CursorConfig { /** Cursor type. Default: 'basic' */ type?: 'basic' | 'text' | 'media'; /** Smoothing factor (0 = most delay/smoothing, 1 = instant). Default: 0.25 */ smooth?: number; /** Enable velocity-based squeeze effect */ squeeze?: boolean | CursorSqueezeConfig; /** Hide native browser cursor. Default: false */ hideNative?: boolean; /** Default state configuration (required) */ default: CursorStateVars; /** Hover state configuration */ hover?: CursorStateVars; /** Click/pressed state configuration */ click?: CursorStateVars; /** Text element styles (for type: 'text') */ text?: Record; /** Media element styles (for type: 'media') */ media?: Record; }