{"version":3,"sources":["../../src/lottie/useLoupeLottie.ts","../../src/lottie/LoupeLottie.tsx"],"names":["useRef","useTimeline","useEffect","useMotionValueEvent","jsx"],"mappings":";;;;;;;;AA2GO,SAAS,eACd,IAAA,EACsB;AACtB,EAAA,MAAM,OAAA,GAAUA,aAA6B,IAAI,CAAA;AACjD,EAAA,MAAM,cAAA,GAAiBA,YAAA,CAAe,IAAA,CAAK,WAAA,IAAe,CAAC,CAAA;AAC3D,EAAA,MAAM,EAAE,IAAA,EAAK,GAAIC,iBAAA,EAAY;AAK7B,EAAAC,eAAA,CAAU,MAAM;AACd,IAAA,MAAM,YAAY,IAAA,CAAK,SAAA;AACvB,IAAA,IAAI,CAAC,SAAA,EAAW;AAChB,IAAA,IAAI,SAAA,GAAY,KAAA;AAChB,IAAA,IAAI,IAAA,GAA6B,IAAA;AAEjC,IAAA,CAAC,YAAY;AACX,MAAA,MAAM,EAAE,OAAA,EAAS,MAAA,EAAO,GAAI,MAAM,OAAO,YAAY,CAAA;AACrD,MAAA,IAAI,SAAA,EAAW;AACf,MAAA,IAAA,GAAO,OAAO,aAAA,CAAc;AAAA,QAC1B,SAAA;AAAA,QACA,QAAA,EAAU,KAAK,QAAA,IAAY,KAAA;AAAA,QAC3B,IAAA,EAAM,IAAA;AAAA,QACN,QAAA,EAAU,KAAA;AAAA,QACV,GAAI,IAAA,CAAK,IAAA,KAAS,MAAA,GACd,EAAE,aAAA,EAAe,IAAA,CAAK,IAAA,EAAK,GAC3B,EAAE,IAAA,EAAM,IAAA,CAAK,GAAA,EAAI;AAAA,QACrB,gBAAA,EAAkB,IAAA,CAAK,gBAAA,IAAoB;AAAC,OAC7C,CAAA;AACD,MAAA,OAAA,CAAQ,OAAA,GAAU,IAAA;AAIlB,MAAA,MAAM,UAAU,MAAM;AACpB,QAAA,IAAI,IAAA,IAAQ,CAAC,IAAA,CAAK,WAAA,EAAa;AAC7B,UAAA,cAAA,CAAe,UAAU,IAAA,CAAK,WAAA;AAAA,QAChC;AAIA,QAAA,IAAI,IAAA,EAAM;AACR,UAAA,MAAM,KAAA,GAAQ,YAAA;AAAA,YACZ,KAAK,GAAA,EAAI;AAAA,YACT,KAAK,GAAA,IAAO,EAAA;AAAA,YACZ,cAAA,CAAe,WAAW,IAAA,CAAK,WAAA;AAAA,YAC/B,KAAK,IAAA,KAAS;AAAA,WAChB;AACA,UAAA,IAAA,CAAK,WAAA,CAAY,OAAO,IAAI,CAAA;AAAA,QAC9B;AAAA,MACF,CAAA;AACA,MAAA,IAAA,CAAK,gBAAA,CAAiB,aAAa,OAAO,CAAA;AAAA,IAC5C,CAAA,GAAG;AAEH,IAAA,OAAO,MAAM;AACX,MAAA,SAAA,GAAY,IAAA;AACZ,MAAA,OAAA,CAAQ,SAAS,OAAA,EAAQ;AACzB,MAAA,OAAA,CAAQ,OAAA,GAAU,IAAA;AAAA,IACpB,CAAA;AAAA,EAEF,CAAA,EAAG,CAAC,IAAA,CAAK,SAAA,EAAW,KAAK,GAAA,EAAK,IAAA,CAAK,IAAI,CAAC,CAAA;AAGxC,EAAAC,gCAAA,CAAoB,IAAA,EAAM,QAAA,EAAU,CAAC,EAAA,KAAO;AAC1C,IAAA,MAAM,OAAO,OAAA,CAAQ,OAAA;AACrB,IAAA,IAAI,CAAC,IAAA,EAAM;AACX,IAAA,MAAM,KAAA,GAAQ,cAAA,CAAe,OAAA,IAAW,IAAA,CAAK,WAAA;AAC7C,IAAA,IAAI,CAAC,KAAA,EAAO;AACZ,IAAA,MAAM,KAAA,GAAQ,aAAa,EAAA,EAAI,IAAA,CAAK,OAAO,EAAA,EAAI,KAAA,EAAO,IAAA,CAAK,IAAA,KAAS,KAAK,CAAA;AACzE,IAAA,IAAA,CAAK,WAAA,CAAY,OAAO,IAAI,CAAA;AAAA,EAC9B,CAAC,CAAA;AAED,EAAA,OAAO,EAAE,IAAA,EAAM,OAAA,CAAQ,OAAA,EAAQ;AACjC;AAQA,SAAS,YAAA,CACP,EAAA,EACA,GAAA,EACA,KAAA,EACA,IAAA,EACQ;AACR,EAAA,MAAM,QAAA,GAAY,KAAK,GAAA,GAAQ,GAAA;AAC/B,EAAA,IAAI,CAAC,IAAA,EAAM;AACT,IAAA,IAAI,QAAA,GAAW,GAAG,OAAO,CAAA;AACzB,IAAA,IAAI,QAAA,GAAW,KAAA,GAAQ,CAAA,EAAG,OAAO,KAAA,GAAQ,CAAA;AACzC,IAAA,OAAO,QAAA;AAAA,EACT;AACA,EAAA,MAAM,OAAA,GAAA,CAAY,QAAA,GAAW,KAAA,GAAS,KAAA,IAAS,KAAA;AAC/C,EAAA,OAAO,OAAA;AACT;ACrKO,SAAS,YAAY,KAAA,EAAyB;AACnD,EAAA,MAAM,EAAE,QAAQ,MAAA,EAAQ,MAAA,GAAS,QAAQ,KAAA,EAAO,SAAA,EAAW,GAAG,IAAA,EAAK,GAAI,KAAA;AACvE,EAAA,MAAM,OAAA,GAAUH,aAA8B,IAAI,CAAA;AAElD,EAAA,cAAA,CAAe;AAAA,IACb,GAAG,IAAA;AAAA,IACH,WAAW,OAAA,CAAQ;AAAA,GACpB,CAAA;AAED,EAAA,uBACEI,cAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,GAAA,EAAK,OAAA;AAAA,MACL,SAAA;AAAA,MACA,KAAA,EAAO;AAAA,QACL,KAAA;AAAA,QACA,MAAA;AAAA,QACA,GAAG;AAAA;AACL;AAAA,GACF;AAEJ","file":"index.cjs","sourcesContent":["import { useEffect, useRef } from 'react';\nimport { useMotionValueEvent } from 'framer-motion';\nimport type { AnimationItem } from 'lottie-web';\n// Self-import the main entry so this subpath shares the same\n// TimelineContext instance as `<TimelineProvider>`. If we import\n// from a relative path here, tsup bundles a SECOND copy of the\n// context into the lottie chunk and `useTimeline` can't see the\n// provider consumers mounted — which produces a white screen\n// (\"useTimeline must be used inside <TimelineProvider>\"). The\n// main package is externalized in tsup.config so the host resolves\n// this import to the already-loaded main bundle at runtime.\nimport { useTimeline } from '@arinze-clinton/loupe';\n\n/**\n * Options for {@link useLoupeLottie}.\n */\nexport type UseLoupeLottieOptions = {\n  /**\n   * URL or `path:` of the Lottie JSON. Passed through to lottie-web's\n   * `loadAnimation({ path })` / `loadAnimation({ animationData })`.\n   * Either `src` or `data` must be provided.\n   */\n  src?: string;\n  /**\n   * Inlined Lottie JSON (parsed object). Takes precedence over `src`\n   * when both are set.\n   */\n  data?: unknown;\n  /**\n   * The DOM node lottie-web mounts into. Typically a `<div>` ref's\n   * current value. If null, the hook is a no-op until a real\n   * element is passed on a later render.\n   */\n  container: HTMLElement | null;\n  /**\n   * Lottie renderer. Canvas gives you an `HTMLCanvasElement` you\n   * can composite against (the conic-mask case); SVG gives\n   * per-path DOM you can annotate. Default: `'svg'`.\n   */\n  renderer?: 'svg' | 'canvas' | 'html';\n  /**\n   * The native frame rate of the Lottie. Needed to translate the\n   * TimelineProvider's `time` (ms) into a lottie frame number.\n   * Default: `24`. If wrong, scrubbing will feel sped up or\n   * slowed down relative to playback.\n   */\n  fps?: number;\n  /**\n   * Total frame count of the Lottie animation. If omitted, the\n   * hook reads it from the loaded animation (`anim.totalFrames`)\n   * after `DOMLoaded`. Setting it explicitly avoids a one-frame\n   * flash of frame 0 before sync kicks in.\n   */\n  totalFrames?: number;\n  /**\n   * Whether to wrap the frame pointer at the loop boundary. Default\n   * `true` — Loupe loops phases on its own, but this guards against\n   * out-of-range time values (e.g. during dev-panel scrubbing past\n   * the end). Set to `false` if you want the lottie to clamp at\n   * the last frame instead of wrapping.\n   */\n  loop?: boolean;\n  /**\n   * Additional renderer settings forwarded to lottie-web\n   * (`loadAnimation({ rendererSettings })`). Useful for canvas\n   * `clearCanvas`, `preserveAspectRatio`, etc.\n   */\n  rendererSettings?: Record<string, unknown>;\n};\n\nexport type UseLoupeLottieResult = {\n  /** The underlying lottie-web `AnimationItem`, once loaded.\n   *  Null until DOMLoaded fires. */\n  anim: AnimationItem | null;\n};\n\n/**\n * Mount a lottie-web animation whose frame pointer is driven by the\n * nearest `TimelineProvider`'s `time` MotionValue.\n *\n * When Loupe plays, the hook seeks the Lottie frame-by-frame. When\n * Loupe is paused, the Lottie freezes on the current frame. Scrubbing\n * the Loupe panel scrubs the Lottie like a native Lottie previewer.\n *\n * The lottie-web animation is loaded with `autoplay: false` — its\n * internal clock is never used. All motion comes from Loupe's time,\n * so every consumer (video thumbnails, still renders via Remotion,\n * etc.) stays deterministic.\n *\n * @example\n * ```tsx\n * function Mark() {\n *   const hostRef = useRef<HTMLDivElement | null>(null);\n *   useLoupeLottie({\n *     src: '/brand/blend.json',\n *     container: hostRef.current,\n *     fps: 24,\n *     totalFrames: 121,\n *   });\n *   return <div ref={hostRef} style={{ width: 220, height: 220 }} />;\n * }\n * ```\n *\n * @remarks\n * `lottie-web` is an optional peer dependency. Install it in your\n * app separately: `npm i lottie-web`.\n */\nexport function useLoupeLottie(\n  opts: UseLoupeLottieOptions,\n): UseLoupeLottieResult {\n  const animRef = useRef<AnimationItem | null>(null);\n  const totalFramesRef = useRef<number>(opts.totalFrames ?? 0);\n  const { time } = useTimeline();\n\n  // Load lottie-web dynamically so the runtime package stays\n  // lottie-free at import time. Consumers that don't touch the\n  // /lottie subpath never pay for lottie-web.\n  useEffect(() => {\n    const container = opts.container;\n    if (!container) return;\n    let cancelled = false;\n    let anim: AnimationItem | null = null;\n\n    (async () => {\n      const { default: lottie } = await import('lottie-web');\n      if (cancelled) return;\n      anim = lottie.loadAnimation({\n        container,\n        renderer: opts.renderer ?? 'svg',\n        loop: true,\n        autoplay: false,\n        ...(opts.data !== undefined\n          ? { animationData: opts.data }\n          : { path: opts.src }),\n        rendererSettings: opts.rendererSettings ?? {},\n      });\n      animRef.current = anim;\n\n      // If totalFrames wasn't given, read it once the JSON loads so\n      // the ms→frame math is accurate from the first real seek.\n      const onReady = () => {\n        if (anim && !opts.totalFrames) {\n          totalFramesRef.current = anim.totalFrames;\n        }\n        // Snap to whatever the current time says right now so we\n        // don't briefly flash frame 0 before the first subscriber\n        // tick fires.\n        if (anim) {\n          const frame = computeFrame(\n            time.get(),\n            opts.fps ?? 24,\n            totalFramesRef.current || anim.totalFrames,\n            opts.loop !== false,\n          );\n          anim.goToAndStop(frame, true);\n        }\n      };\n      anim.addEventListener('DOMLoaded', onReady);\n    })();\n\n    return () => {\n      cancelled = true;\n      animRef.current?.destroy();\n      animRef.current = null;\n    };\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [opts.container, opts.src, opts.data]);\n\n  // Drive the frame pointer from Loupe's time MotionValue.\n  useMotionValueEvent(time, 'change', (ms) => {\n    const anim = animRef.current;\n    if (!anim) return;\n    const total = totalFramesRef.current || anim.totalFrames;\n    if (!total) return;\n    const frame = computeFrame(ms, opts.fps ?? 24, total, opts.loop !== false);\n    anim.goToAndStop(frame, true);\n  });\n\n  return { anim: animRef.current };\n}\n\n/**\n * ms → lottie frame, respecting wrap vs. clamp.\n * Pulled out so the initial-seek path and the change handler share\n * the same math (and don't drift if we change one and forget the\n * other).\n */\nfunction computeFrame(\n  ms: number,\n  fps: number,\n  total: number,\n  loop: boolean,\n): number {\n  const rawFrame = (ms / 1000) * fps;\n  if (!loop) {\n    if (rawFrame < 0) return 0;\n    if (rawFrame > total - 1) return total - 1;\n    return rawFrame;\n  }\n  const wrapped = ((rawFrame % total) + total) % total;\n  return wrapped;\n}\n","import { useRef, type CSSProperties } from 'react';\nimport {\n  useLoupeLottie,\n  type UseLoupeLottieOptions,\n} from './useLoupeLottie';\n\nexport type LoupeLottieProps = Omit<UseLoupeLottieOptions, 'container'> & {\n  /** Width of the lottie host element. Defaults to `'100%'`. */\n  width?: number | string;\n  /** Height of the lottie host element. Defaults to `'100%'`. */\n  height?: number | string;\n  /** Extra styles on the host element. */\n  style?: CSSProperties;\n  /** Class name for the host element. */\n  className?: string;\n};\n\n/**\n * Drop-in Lottie renderer whose frame pointer is driven by the\n * nearest `TimelineProvider`'s `time`. Scrubbing Loupe scrubs the\n * clip frame-by-frame, no further wiring required.\n *\n * Prefer `useLoupeLottie()` directly if you need to composite the\n * lottie canvas yourself (masks, blend modes, capture streams).\n *\n * @example\n * ```tsx\n * <LoupeLottie\n *   src=\"/brand/blend.json\"\n *   fps={24}\n *   totalFrames={121}\n *   width={220}\n *   height={220}\n * />\n * ```\n */\nexport function LoupeLottie(props: LoupeLottieProps) {\n  const { width = '100%', height = '100%', style, className, ...rest } = props;\n  const hostRef = useRef<HTMLDivElement | null>(null);\n\n  useLoupeLottie({\n    ...rest,\n    container: hostRef.current,\n  });\n\n  return (\n    <div\n      ref={hostRef}\n      className={className}\n      style={{\n        width,\n        height,\n        ...style,\n      }}\n    />\n  );\n}\n"]}