export type ElementType = 'code' | 'text' | 'arrow' | 'image' | 'shape' | 'counter' | 'chart' | 'icon' | 'svg' | 'motionPath' | 'video' | 'progress' | 'container' | 'draw' | 'sticky'; /** * Per-element keyframe — animates state at a specific time WITHIN a slide's * display window. Layered on top of slide-to-slide morphing: morphing handles * between-slide transitions, keyframes handle within-slide animation. */ export interface Keyframe { id: string; time: number; easing?: 'linear' | 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out' | 'spring'; position?: Position; size?: Size; rotation?: number; opacity?: number; skewX?: number; skewY?: number; tiltX?: number; tiltY?: number; borderRadius?: number; fontSize?: number; fillColor?: string; strokeColor?: string; strokeWidth?: number; backgroundColor?: string; color?: string; blur?: number; brightness?: number; contrast?: number; saturate?: number; grayscale?: number; path?: string; } export type ShapeType = 'rectangle' | 'circle' | 'triangle' | 'ellipse' | 'star' | 'hexagon'; export interface Position { x: number; y: number; } export interface Size { width: number; height: number; } export type TiltOrigin = string; export type AnimatableProperty = 'position' | 'rotation' | 'tilt' | 'skew' | 'size' | 'opacity' | 'borderRadius' | 'color' | 'perspective' | 'blur'; export interface PropertySequence { property: AnimatableProperty; order: number; delay: number; duration: number; } export type EntranceAnimation = 'none' | 'fade' | 'slide-up' | 'slide-down' | 'slide-left' | 'slide-right' | 'pop' | 'scale-in' | 'rotate-in' | 'blur-in' | 'rise' | 'flip-x' | 'flip-y' | 'bounce-in'; export type ExitAnimation = 'none' | 'fade' | 'slide-up' | 'slide-down' | 'slide-left' | 'slide-right' | 'pop' | 'scale-out' | 'rotate-out' | 'blur-out' | 'sink' | 'flip-x' | 'flip-y'; export type EmphasisAnimation = 'none' | 'pulse' | 'shake' | 'flash' | 'wobble' | 'glow-flash' | 'bob-once' | 'tada'; export interface ElementAnimationConfig { order: number; delay: number; duration: number; easing: 'linear' | 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out' | 'spring'; propertySequences?: PropertySequence[]; entrance?: EntranceAnimation; exit?: ExitAnimation; emphasis?: EmphasisAnimation; entranceDuration?: number; exitDuration?: number; emphasisDuration?: number; emphasisDelay?: number; } export type FloatingDirection = 'vertical' | 'horizontal' | 'both'; export type IdlePreset = 'float' | 'breathe' | 'pulse' | 'wiggle' | 'sway' | 'drift' | 'bob' | 'tilt'; export interface FloatingAnimationConfig { enabled: boolean; direction: FloatingDirection; amplitude: number; speed: number; amplitudeRandomness: number; speedRandomness: number; preset?: IdlePreset; } export interface PathPoint { x: number; y: number; handleIn?: Position; handleOut?: Position; } export interface MotionPathConfig { motionPathId: string; startPercent: number; endPercent: number; autoRotate: boolean; orientationOffset: number; loop: boolean; laps?: number; } export interface BaseElement { id: string; type: ElementType; name?: string; locked?: boolean; groupId?: string; /** Auto-layout container parent. When set, position/size is computed * by the container's flex layout pass. */ containerId?: string; /** Per-element keyframes that play DURING this slide. Sorted by time. */ keyframes?: Keyframe[]; position: Position; size: Size; rotation: number; skewX: number; skewY: number; tiltX: number; tiltY: number; tiltOrigin: TiltOrigin; zIndex: number; visible: boolean; perspective?: number; backfaceVisibility?: 'visible' | 'hidden'; animationConfig?: ElementAnimationConfig; floatingAnimation?: FloatingAnimationConfig; decorations?: DecorationsConfig; depth?: number; motionPathId?: string; motionPathConfig?: MotionPathConfig; /** Solid color for the shape-morph transition only (render-time override). */ morphColor?: string; blur?: number; brightness?: number; contrast?: number; saturate?: number; grayscale?: number; } export type CodeAnimationMode = 'instant' | 'typewriter' | 'highlight-changes'; export interface CodeAnimationConfig { mode: CodeAnimationMode; highlightColor: string; typewriterSpeed: number; highlightDuration: number; } export interface CodeElement extends BaseElement { type: 'code'; code: string; language: string; theme: string; filename: string; showLineNumbers: boolean; highlightedLines: number[]; blurredLines: number[]; fontSize: number; fontWeight: number; padding: number; borderRadius: number; showHeader: boolean; headerStyle: 'macos' | 'windows' | 'none'; animation: CodeAnimationConfig; transparentBackground?: boolean; bgColor?: string; headerRadius?: number; tabRadius?: number; } export type TextAnimationMode = 'instant' | 'typewriter' | 'fade-words' | 'fade-letters' | 'handwriting' | 'bounce-in' | 'scramble-in' | 'slot-machine' | 'drop' | 'glitch' | 'marquee' | 'blur-in' | 'stretch' | 'slide-words' | 'wave' | 'typewriter-erase'; export interface TextAnimationConfig { mode: TextAnimationMode; typewriterSpeed: number; duration?: number; stagger?: number; loop?: boolean; } export interface TextElement extends BaseElement { type: 'text'; content: string; fontSize: number; fontWeight: number; fontFamily: string; fontStyle?: 'normal' | 'italic'; textDecoration?: 'none' | 'underline' | 'line-through'; color: string; backgroundColor: string; padding: number; borderRadius: number; textAlign: 'left' | 'center' | 'right'; opacity: number; animation?: TextAnimationConfig; textStroke?: { enabled: boolean; width: number; color: string; }; textShadow?: { enabled: boolean; offsetX: number; offsetY: number; blur: number; color: string; }; hollow?: boolean; backgroundImage?: string; backgroundPositionX?: number; backgroundPositionY?: number; backgroundScale?: number; /** Optional CSS gradient as the text fill, rendered via * `background-clip: text`. Same shape as `CanvasBackground.gradient`. */ gradient?: { type: 'linear' | 'radial' | 'conic'; angle?: number; colors: string[]; stops?: GradientStop[]; radialShape?: 'circle' | 'ellipse'; radialPosition?: string; }; } export type ArrowAnimationMode = 'none' | 'grow' | 'draw' | 'undraw' | 'draw-undraw' | 'flow'; export interface ArrowAnimationConfig { mode: ArrowAnimationMode; duration: number; loop?: boolean; direction?: 'forward' | 'reverse'; } export type SvgPathAnimationMode = 'none' | 'draw' | 'undraw' | 'draw-undraw' | 'flow'; export interface SvgPathAnimationConfig { mode: SvgPathAnimationMode; duration: number; loop?: boolean; direction?: 'forward' | 'reverse'; } export interface FlowMarkerConfig { enabled: boolean; count: number; size: number; color: string; speedMs: number; direction: 'forward' | 'reverse' | 'bidirectional'; pattern: 'loop' | 'once' | 'bounce'; shape: 'dot' | 'square' | 'pulse'; easing: 'linear' | 'ease-in-out'; } export interface ArrowElement extends BaseElement { type: 'arrow'; startPoint: Position; endPoint: Position; controlPoints: Position[]; color: string; strokeWidth: number; headSize: number; style: 'solid' | 'dashed' | 'dotted'; showHead: boolean; opacity: number; animation?: ArrowAnimationConfig; flowMarkers?: FlowMarkerConfig; } export interface ImageElement extends BaseElement { type: 'image'; src: string; objectFit: 'cover' | 'contain' | 'fill'; borderRadius: number; opacity: number; blur: number; backgroundColor?: string; clipMask?: { enabled: boolean; shapeType: ShapeType; borderRadius?: number; }; } export interface CameraViewport { x: number; y: number; width: number; height: number; rotation?: number; } export interface DecorationsConfig { glow?: { enabled: boolean; color: string; intensity?: number; speedMs?: number; }; shimmer?: { enabled: boolean; color?: string; angle?: number; speedMs?: number; widthPct?: number; randomness?: number; }; gradientShift?: { enabled: boolean; colors?: string[]; speedMs?: number; angle?: number; direction?: 'forward' | 'reverse' | 'snake' | 'chase'; borderWidth?: number; }; rgbSplit?: { enabled: boolean; offset?: number; speedMs?: number; }; } export interface VideoElement extends BaseElement { type: 'video'; src: string; posterImage?: string; startTime?: number; endTime?: number; volume: number; muted: boolean; autoplay: boolean; loop: boolean; playbackRate: number; objectFit: 'cover' | 'contain' | 'fill'; borderRadius: number; opacity: number; showControls?: boolean; } export type StrokeStyle = 'solid' | 'dashed' | 'dotted'; export interface ShapeElement extends BaseElement { type: 'shape'; shapeType: ShapeType; fillColor: string; fillOpacity?: number; strokeColor: string; strokeOpacity?: number; strokeWidth: number; strokeStyle?: StrokeStyle; strokeDashGap?: number; opacity: number; borderRadius: number; boxShadow?: { enabled: boolean; offsetX: number; offsetY: number; blur: number; spread: number; color: string; }; } export type CounterType = 'number' | 'letter' | 'percentage' | 'currency'; export interface CounterElement extends BaseElement { type: 'counter'; counterType: CounterType; startValue: number; endValue: number; prefix: string; suffix: string; decimals: number; duration: number; fontSize: number; fontWeight: number; fontFamily: string; color: string; backgroundColor: string; padding: number; borderRadius: number; textAlign: 'left' | 'center' | 'right'; opacity: number; } export type ChartType = 'bar' | 'line' | 'pie' | 'donut' | 'area'; export interface ChartDataPoint { label: string; value: number; color?: string; } export interface ChartSeries { id: string; name: string; data: ChartDataPoint[]; color?: string; } export type ChartBarLayout = 'grouped' | 'stacked'; export interface ChartValueFormat { prefix?: string; suffix?: string; decimals?: number; abbreviate?: boolean; } export interface ChartYAxis { min?: number; max?: number; showLabels?: boolean; } export interface ChartElement extends BaseElement { type: 'chart'; chartType: ChartType; data: ChartDataPoint[]; series?: ChartSeries[]; title: string; showLabels: boolean; showValues: boolean; showGrid: boolean; showLegend: boolean; animationDuration: number; colors: string[]; useKitPalette?: boolean; backgroundColor: string; textColor: string; fontSize: number; padding: number; borderRadius: number; barLayout?: ChartBarLayout; revealStagger?: number; valueFormat?: ChartValueFormat; yAxis?: ChartYAxis; } export interface IconElement extends BaseElement { type: 'icon'; iconName: string; iconLibrary: 'lucide' | 'tabler'; svgContent: string; color: string; strokeWidth: number; fillMode: 'stroke' | 'fill' | 'both'; animation?: SvgPathAnimationConfig; } export interface SvgElement extends BaseElement { type: 'svg'; svgContent: string; color?: string; opacity: number; preserveAspectRatio: string; viewBox?: string; animation?: SvgPathAnimationConfig; } export type DrawBrush = 'pen' | 'marker' | 'highlighter'; export interface DrawElement extends BaseElement { type: 'draw'; points: number[][]; color: string; opacity: number; brush: DrawBrush; strokeWidth: number; thinning?: number; smoothing?: number; streamline?: number; taperStart?: boolean; taperEnd?: boolean; animation?: ArrowAnimationConfig; } export interface StickyElement extends BaseElement { type: 'sticky'; text: string; bgColor: string; textColor: string; fontSize: number; fontFamily: string; fontWeight: number; textAlign: 'left' | 'center' | 'right'; padding: number; borderRadius: number; opacity: number; shadow?: boolean; } export interface MotionPathElement extends BaseElement { type: 'motionPath'; points: PathPoint[]; closed: boolean; pathColor: string; pathWidth: number; showInPresentation: boolean; } export interface ProgressElement extends BaseElement { type: 'progress'; shape: 'bar-horizontal' | 'bar-vertical' | 'ring'; value: number; max: number; fillColor: string; fillGradient?: { type: 'linear' | 'radial' | 'conic'; angle?: number; colors: string[]; stops?: GradientStop[]; radialShape?: 'circle' | 'ellipse'; radialPosition?: string; }; showTrack: boolean; trackColor: string; roundness: number; thickness: number; direction: 'ltr' | 'rtl' | 'btt' | 'ttb' | 'cw' | 'ccw'; indeterminate?: boolean; stripeAngle?: number; stripeWidth?: number; stripeSpeed?: number; label?: { position: 'inside' | 'top' | 'bottom' | 'left' | 'right' | 'above' | 'below' | 'center' | 'none'; format: 'percent' | 'value' | 'value-of-max' | 'custom'; customText?: string; fontSize?: number; fontWeight?: number; color?: string; }; animationDuration?: number; } /** * Auto-layout container. Holds a list of child element ids and arranges them * via flex-style rules. Children remain independently selectable; layout * pass writes computed position+size onto each child. */ export interface ContainerElement extends BaseElement { type: 'container'; childIds: string[]; direction: 'row' | 'column'; gap: number; padding: number | { top: number; right: number; bottom: number; left: number; }; align: 'start' | 'center' | 'end' | 'stretch'; justify: 'start' | 'center' | 'end' | 'space-between' | 'space-around' | 'space-evenly'; fillColor?: string; borderColor?: string; borderWidth?: number; borderRadius?: number; } export type CanvasElement = CodeElement | TextElement | ArrowElement | ImageElement | VideoElement | ShapeElement | CounterElement | ChartElement | IconElement | SvgElement | MotionPathElement | ProgressElement | ContainerElement | DrawElement | StickyElement; export type ParticleShape = 'circle' | 'square' | 'star' | 'triangle'; export interface ParticlesConfig { enabled: boolean; count: number; color: string; opacity: number; minSize: number; maxSize: number; speed: number; shape: ParticleShape; connectDistance: number; direction: 'up' | 'down' | 'left' | 'right' | 'random'; } export type ConfettiMode = 'burst' | 'continuous' | 'fireworks' | 'snow'; export interface ConfettiConfig { enabled: boolean; mode: ConfettiMode; particleCount: number; spread: number; colors: string[]; gravity: number; drift: number; startVelocity: number; scalar: number; } export interface GradientStop { color: string; position: number; } export interface CanvasBackground { type: 'solid' | 'gradient' | 'image' | 'transparent'; color?: string; gradient?: { type: 'linear' | 'radial' | 'conic'; angle?: number; colors: string[]; stops?: GradientStop[]; radialShape?: 'circle' | 'ellipse'; radialPosition?: string; }; image?: string; particles?: ParticlesConfig; confetti?: ConfettiConfig; } export type TransitionType = 'none' | 'fade' | 'slide-left' | 'slide-right' | 'slide-up' | 'slide-down' | 'zoom-in' | 'zoom-out' | 'flip' | 'flip-x' | 'flip-y'; export interface TransitionConfig { type: TransitionType; duration: number; easing: 'linear' | 'ease' | 'ease-in' | 'ease-out' | 'ease-in-out'; } export interface SlideCanvas { width: number; height: number; background: CanvasBackground; elements: CanvasElement[]; } export interface Slide { id: string; name: string; canvas: SlideCanvas; transition: TransitionConfig; duration: number; camera?: CameraViewport; /** Speaker notes — never rendered onto the audience canvas. Kept on the * type so JSON imports preserve them across edit cycles. */ notes?: string; /** Per-slide voice-over track. Played when the slide is shown if the * project has `settings.narrationEnabled` true. `src` is a base64 data * URL; `duration` is the clip length in ms. */ narration?: { src: string; duration: number; }; } export interface ProjectSettings { defaultCanvasWidth: number; defaultCanvasHeight: number; defaultTransition: TransitionConfig; defaultSlideDuration: number; /** How the presentation behaves when looping from last slide back to first. * 'reset' = snap all elements back to slide 1 initial state. * 'transition' = smoothly morph from last slide to first. * Default: 'reset' */ loopMode?: 'reset' | 'transition'; /** When true, per-slide narration is played. Consumers should also * suppress autoplay when this is on so the viewer's first interaction * unlocks the browser audio context. Default: false. */ narrationEnabled?: boolean; } /** Animot project JSON format */ export interface AnimotProject { /** Schema version for forward compatibility. Default: 1 */ schemaVersion?: number; id: string; name: string; slides: Slide[]; createdAt: number; updatedAt: number; settings: ProjectSettings; } /** Props for the AnimotPresenter component */ export interface AnimotPresenterProps { /** URL to JSON file (fetched automatically) */ src?: string; /** Inline JSON object */ data?: AnimotProject; /** Start playing automatically (default: false) */ autoplay?: boolean; /** Loop back to start when reaching end (default: false) */ loop?: boolean; /** Show bottom playback bar (default: true) */ controls?: boolean; /** Show left/right carousel arrows (default: false) */ arrows?: boolean; /** Show progress bar (default: true) */ progress?: boolean; /** Enable keyboard navigation (default: true) */ keyboard?: boolean; /** Override all slide durations (ms) */ duration?: number; /** Initial slide index (default: 0) */ startSlide?: number; /** Force-disable narration playback regardless of the project's * `settings.narrationEnabled`. Useful for preview surfaces (gallery * cards, thumbnails) where audio would be intrusive. Default: false. */ muteNarration?: boolean; /** CSS class for outer container */ class?: string; /** Fired when slide changes */ onslidechange?: (index: number, total: number) => void; /** Fired when last slide is reached (non-loop mode) */ oncomplete?: () => void; }