@getnarro/atlas 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +143 -0
  3. package/dist/check/index.d.ts +82 -0
  4. package/dist/check/index.js +150 -0
  5. package/dist/check/index.js.map +1 -0
  6. package/dist/chunk-5SRKWTOU.js +33 -0
  7. package/dist/chunk-5SRKWTOU.js.map +1 -0
  8. package/dist/chunk-BDF2X5LC.js +28 -0
  9. package/dist/chunk-BDF2X5LC.js.map +1 -0
  10. package/dist/chunk-RHTCDWZD.js +142 -0
  11. package/dist/chunk-RHTCDWZD.js.map +1 -0
  12. package/dist/chunk-RWYOYDBD.js +75 -0
  13. package/dist/chunk-RWYOYDBD.js.map +1 -0
  14. package/dist/chunk-SD4Y4EBG.js +69 -0
  15. package/dist/chunk-SD4Y4EBG.js.map +1 -0
  16. package/dist/chunk-UTCNNNPT.js +89 -0
  17. package/dist/chunk-UTCNNNPT.js.map +1 -0
  18. package/dist/embed/narro-atlas.js +12 -0
  19. package/dist/image/index.d.ts +132 -0
  20. package/dist/image/index.js +117 -0
  21. package/dist/image/index.js.map +1 -0
  22. package/dist/index.d.ts +33 -0
  23. package/dist/index.js +12 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/lod-DBuZ0MG3.d.ts +86 -0
  26. package/dist/map/index.d.ts +222 -0
  27. package/dist/map/index.js +184 -0
  28. package/dist/map/index.js.map +1 -0
  29. package/dist/path-BIxJH8g2.d.ts +101 -0
  30. package/dist/provider-CjL3i-0A.d.ts +54 -0
  31. package/dist/react/index.d.ts +183 -0
  32. package/dist/react/index.js +291 -0
  33. package/dist/react/index.js.map +1 -0
  34. package/dist/replay/index.d.ts +162 -0
  35. package/dist/replay/index.js +138 -0
  36. package/dist/replay/index.js.map +1 -0
  37. package/dist/tag-BP51cBza.d.ts +24 -0
  38. package/dist/timeline-B_dNydQA.d.ts +194 -0
  39. package/dist/video/index.d.ts +101 -0
  40. package/dist/video/index.js +91 -0
  41. package/dist/video/index.js.map +1 -0
  42. package/dist/view-BzztOpV6.d.ts +83 -0
  43. package/dist/web/embed.d.ts +59 -0
  44. package/dist/web/embed.js +192 -0
  45. package/dist/web/embed.js.map +1 -0
  46. package/dist/web/index.d.ts +89 -0
  47. package/dist/web/index.js +4 -0
  48. package/dist/web/index.js.map +1 -0
  49. package/package.json +119 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/react/useCamera.ts","../../src/react/Atlas.tsx"],"names":["useMemo","useRef","useState","useEffect","target"],"mappings":";;;;;;;AAiBO,SAAS,uBAAA,GAAmC;AACjD,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAS,MAAM;AAC3C,IAAA,IAAI,OAAO,MAAA,KAAW,WAAA,IAAe,CAAC,MAAA,CAAO,YAAY,OAAO,KAAA;AAChE,IAAA,OAAO,MAAA,CAAO,UAAA,CAAW,kCAAkC,CAAA,CAAE,OAAA;AAAA,EAC/D,CAAC,CAAA;AAED,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,IAAI,OAAO,MAAA,KAAW,WAAA,IAAe,CAAC,OAAO,UAAA,EAAY;AACzD,IAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,UAAA,CAAW,kCAAkC,CAAA;AAClE,IAAA,MAAM,QAAA,GAAW,CAAC,KAAA,KAA+B,UAAA,CAAW,MAAM,OAAO,CAAA;AACzE,IAAA,KAAA,CAAM,gBAAA,CAAiB,UAAU,QAAQ,CAAA;AACzC,IAAA,OAAO,MAAM,KAAA,CAAM,mBAAA,CAAoB,QAAA,EAAU,QAAQ,CAAA;AAAA,EAC3D,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,OAAO,OAAA;AACT;AA6BO,SAAS,SAAA,CAAU;AAAA,EACxB,MAAA;AAAA,EACA,QAAA;AAAA,EACA,GAAA;AAAA,EACA,GAAA,GAAM,KAAA;AAAA,EACN;AACF,CAAA,EAAkC;AAChC,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAI,SAAe,MAAM,CAAA;AAC7C,EAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAI,SAAS,KAAK,CAAA;AAG1C,EAAA,MAAM,OAAA,GAAU,OAAO,IAAI,CAAA;AAC3B,EAAA,OAAA,CAAQ,OAAA,GAAU,IAAA;AAElB,EAAA,MAAM,WAAA,GAAc,OAAO,QAAQ,CAAA;AACnC,EAAA,WAAA,CAAY,OAAA,GAAU,QAAA;AAOtB,EAAA,MAAM,EAAE,EAAA,EAAI,EAAA,EAAI,CAAA,EAAE,GAAI,MAAA;AAEtB,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,MAAM,EAAA,GAAK,EAAE,EAAA,EAAI,EAAA,EAAI,CAAA,EAAE;AACvB,IAAA,MAAM,OAAO,OAAA,CAAQ,OAAA;AACrB,IAAA,IAAI,IAAA,CAAK,EAAA,KAAO,EAAA,CAAG,EAAA,IAAM,IAAA,CAAK,EAAA,KAAO,EAAA,CAAG,EAAA,IAAM,IAAA,CAAK,CAAA,KAAM,EAAA,CAAG,CAAA,EAAG;AAC7D,MAAA;AAAA,IACF;AAEA,IAAA,IAAI,GAAA,EAAK;AACP,MAAA,OAAA,CAAQ,EAAE,CAAA;AACV,MAAA,SAAA,CAAU,KAAK,CAAA;AACf,MAAA,WAAA,CAAY,OAAA,IAAU;AACtB,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,IAAA,GAAe,MAAA,CAAO,IAAA,EAAM,EAAA,EAAI,GAAG,CAAA;AACzC,IAAA,MAAM,OAAA,GAAU,YAAY,IAAA,CAAK,iBAAA;AACjC,IAAA,IAAI,WAAW,CAAA,EAAG;AAChB,MAAA,OAAA,CAAQ,EAAE,CAAA;AACV,MAAA,WAAA,CAAY,OAAA,IAAU;AACtB,MAAA;AAAA,IACF;AAEA,IAAA,IAAI,GAAA,GAAM,CAAA;AACV,IAAA,IAAI,KAAA,GAAuB,IAAA;AAC3B,IAAA,SAAA,CAAU,IAAI,CAAA;AAEd,IAAA,MAAM,IAAA,GAAO,CAAC,GAAA,KAAgB;AAC5B,MAAA,IAAI,KAAA,KAAU,MAAM,KAAA,GAAQ,GAAA;AAC5B,MAAA,MAAM,IAAI,IAAA,CAAK,GAAA,CAAI,IAAI,GAAA,GAAM,KAAA,KAAU,UAAU,GAAA,CAAK,CAAA;AACtD,MAAA,OAAA,CAAQ,IAAA,CAAK,EAAA,CAAG,CAAC,CAAC,CAAA;AAClB,MAAA,IAAI,IAAI,CAAA,EAAG;AACT,QAAA,GAAA,GAAM,sBAAsB,IAAI,CAAA;AAAA,MAClC,CAAA,MAAO;AACL,QAAA,SAAA,CAAU,KAAK,CAAA;AACf,QAAA,WAAA,CAAY,OAAA,IAAU;AAAA,MACxB;AAAA,IACF,CAAA;AAEA,IAAA,GAAA,GAAM,sBAAsB,IAAI,CAAA;AAChC,IAAA,OAAO,MAAM;AACX,MAAA,oBAAA,CAAqB,GAAG,CAAA;AACxB,MAAA,SAAA,CAAU,KAAK,CAAA;AAAA,IACjB,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,EAAA,EAAI,EAAA,EAAI,GAAG,GAAA,EAAK,QAAA,EAAU,GAAG,CAAC,CAAA;AAElC,EAAA,OAAO,OAAA,CAAQ,OAAO,EAAE,IAAA,EAAM,QAAO,CAAA,EAAI,CAAC,IAAA,EAAM,MAAM,CAAC,CAAA;AACzD;AAkBO,SAAS,iBAAiB,MAAA,EAAuC;AACtE,EAAA,OAAO,SAAS,WAAA,GAAc,MAAA;AAChC;AAGO,SAAS,UAAA,CAAW,OAAe,MAAA,EAAwB;AAChE,EAAA,IAAI,MAAA,IAAU,GAAG,OAAO,CAAA;AACxB,EAAA,OAAO,IAAA,CAAK,IAAI,IAAA,CAAK,GAAA,CAAI,OAAO,CAAC,CAAA,EAAG,SAAS,CAAC,CAAA;AAChD;AC7IA,IAAM,gBAAA,GAA6B,EAAE,KAAA,EAAO,IAAA,EAAM,QAAQ,IAAA,EAAK;AAiB/D,IAAM,YAAA,GAAe,cAAwC,IAAI,CAAA;AAQ1D,SAAS,QAAA,GAA8B;AAC5C,EAAA,MAAM,KAAA,GAAQ,WAAW,YAAY,CAAA;AACrC,EAAA,IAAI,CAAC,KAAA,EAAO,MAAM,IAAI,MAAM,yCAAyC,CAAA;AACrE,EAAA,OAAO,KAAA;AACT;AA0DO,SAAS,KAAA,CAAM;AAAA,EACpB,IAAA;AAAA,EACA,QAAA;AAAA,EACA,QAAQ,EAAC;AAAA,EACT,QAAA;AAAA,EACA,QAAA,GAAW,gBAAA;AAAA,EACX,eAAA,GAAkB,CAAA;AAAA,EAClB,QAAA,GAAW,IAAA;AAAA,EACX,MAAA,GAAS,IAAA;AAAA,EACT,QAAA;AAAA,EACA,SAAA;AAAA,EACA;AACF,CAAA,EAAmC;AACjC,EAAA,MAAM,KAAA,GAAQA,OAAAA,CAAQ,MAAM,QAAA,IAAY,aAAA,CAAc,EAAE,KAAA,EAAO,CAAA,EAAG,CAAC,QAAA,EAAU,KAAK,CAAC,CAAA;AAEnF,EAAA,MAAM,EAAE,UAAS,GAAIA,OAAAA;AAAA,IACnB,MAAM,YAAY,IAAA,EAAM,CAAC,QAAQ,KAAA,CAAM,OAAA,CAAQ,GAAA,EAAK,QAAQ,CAAC,CAAA;AAAA,IAC7D,CAAC,IAAA,EAAM,KAAA,EAAO,QAAQ;AAAA,GACxB;AAEA,EAAA,MAAM,UAAA,GAAaA,QAAQ,MAAM;AAC/B,IAAA,IAAI,OAAO,oBAAoB,QAAA,EAAU;AACvC,MAAA,MAAM,QAAQ,QAAA,CAAS,SAAA,CAAU,CAAC,CAAA,KAAM,CAAA,CAAE,OAAO,eAAe,CAAA;AAChE,MAAA,OAAO,KAAA,KAAU,KAAK,CAAA,GAAI,KAAA;AAAA,IAC5B;AACA,IAAA,OAAO,UAAA,CAAW,eAAA,EAAiB,QAAA,CAAS,MAAM,CAAA;AAAA,EACpD,CAAA,EAAG,CAAC,eAAA,EAAiB,QAAQ,CAAC,CAAA;AAc9B,EAAA,MAAM,OAAA,GAAUC,OAAuB,IAAI,CAAA;AAC3C,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAIC,SAA0B,IAAI,CAAA;AAE5D,EAAAC,UAAU,MAAM;AACd,IAAA,MAAM,UAAU,OAAA,CAAQ,OAAA;AACxB,IAAA,IAAI,CAAC,OAAA,EAAS;AAEd,IAAA,MAAM,UAAU,MAAM;AACpB,MAAA,MAAM,QAAQ,OAAA,CAAQ,WAAA;AACtB,MAAA,MAAM,SAAS,OAAA,CAAQ,YAAA;AACvB,MAAA,IAAI,KAAA,IAAS,CAAA,IAAK,MAAA,IAAU,CAAA,EAAG;AAC/B,MAAA,UAAA;AAAA,QAAW,CAAC,QAAA,KACV,QAAA,IAAY,QAAA,CAAS,KAAA,KAAU,KAAA,IAAS,QAAA,CAAS,MAAA,KAAW,MAAA,GACxD,QAAA,GACA,EAAE,KAAA,EAAO,MAAA;AAAO,OACtB;AAAA,IACF,CAAA;AAEA,IAAA,OAAA,EAAQ;AACR,IAAA,IAAI,OAAO,mBAAmB,WAAA,EAAa;AAC3C,IAAA,MAAM,QAAA,GAAW,IAAI,cAAA,CAAe,OAAO,CAAA;AAC3C,IAAA,QAAA,CAAS,QAAQ,OAAO,CAAA;AACxB,IAAA,OAAO,MAAM,SAAS,UAAA,EAAW;AAAA,EACnC,CAAA,EAAG,EAAE,CAAA;AAIL,EAAA,MAAM,UAAU,OAAA,IAAW,QAAA;AAE3B,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAID,SAAS,UAAU,CAAA;AAC7C,EAAA,MAAM,SAAA,GAAY,UAAA,CAAW,KAAA,EAAO,QAAA,CAAS,MAAM,CAAA;AACnD,EAAA,MAAM,OAAA,GAAU,SAAS,SAAS,CAAA;AAClC,EAAA,MAAM,UAAU,uBAAA,EAAwB;AAExC,EAAA,MAAM,MAAA,GAAe,OAAA,EAAS,EAAA,IAAM,EAAE,EAAA,EAAI,GAAG,EAAA,EAAI,CAAA,EAAG,CAAA,EAAG,QAAA,CAAS,KAAA,EAAM;AAEtE,EAAA,MAAM,EAAE,IAAA,EAAM,MAAA,EAAO,GAAI,SAAA,CAAU;AAAA,IACjC,MAAA;AAAA,IACA,UAAU,OAAA,EAAS,QAAA;AAAA,IACnB,GAAA,EAAK,OAAA,GAAU,MAAA,CAAO,IAAA,EAAM,OAAO,CAAA,GAAI,MAAA;AAAA,IACvC,GAAA,EAAK,OAAA;AAAA,IACL,UAAU,MAAM;AACd,MAAA,IAAI,OAAA,EAAS,QAAA,GAAW,OAAA,CAAQ,EAAA,EAAI,SAAS,CAAA;AAAA,IAC/C;AAAA,GACD,CAAA;AAID,EAAAC,UAAU,MAAM;AACd,IAAA,IAAI,CAAC,QAAA,IAAY,CAAC,MAAA,IAAU,QAAA,CAAS,WAAW,CAAA,EAAG;AAEnD,IAAA,MAAM,SAAA,GAAY,CAAC,KAAA,KAAyB;AAC1C,MAAA,IAAI,KAAA,CAAM,OAAA,IAAW,KAAA,CAAM,OAAA,EAAS;AACpC,MAAA,MAAMC,UAAS,KAAA,CAAM,MAAA;AACrB,MAAA,IACEA,OAAAA,KACCA,QAAO,OAAA,KAAY,OAAA,IAAWA,QAAO,OAAA,KAAY,UAAA,IAAcA,QAAO,iBAAA,CAAA,EACvE;AACA,QAAA;AAAA,MACF;AAEA,MAAA,MAAM,OAAA,GAAU,MAAM,GAAA,KAAQ,YAAA,IAAgB,MAAM,GAAA,KAAQ,GAAA,IAAO,MAAM,GAAA,KAAQ,OAAA;AACjF,MAAA,MAAM,IAAA,GAAO,MAAM,GAAA,KAAQ,WAAA;AAE3B,MAAA,IAAI,OAAA,IAAW,SAAA,GAAY,QAAA,CAAS,MAAA,GAAS,CAAA,EAAG;AAC9C,QAAA,KAAA,CAAM,cAAA,EAAe;AACrB,QAAA,KAAA,CAAM,eAAA,EAAgB;AACtB,QAAA,QAAA,CAAS,YAAY,CAAC,CAAA;AAAA,MACxB,CAAA,MAAA,IAAW,IAAA,IAAQ,SAAA,GAAY,CAAA,EAAG;AAChC,QAAA,KAAA,CAAM,cAAA,EAAe;AACrB,QAAA,KAAA,CAAM,eAAA,EAAgB;AACtB,QAAA,QAAA,CAAS,YAAY,CAAC,CAAA;AAAA,MACxB,CAAA,MAAA,IAAW,KAAA,CAAM,GAAA,KAAQ,MAAA,EAAQ;AAC/B,QAAA,KAAA,CAAM,cAAA,EAAe;AACrB,QAAA,KAAA,CAAM,eAAA,EAAgB;AACtB,QAAA,QAAA,CAAS,CAAC,CAAA;AAAA,MACZ,CAAA,MAAA,IAAW,KAAA,CAAM,GAAA,KAAQ,KAAA,EAAO;AAC9B,QAAA,KAAA,CAAM,cAAA,EAAe;AACrB,QAAA,KAAA,CAAM,eAAA,EAAgB;AACtB,QAAA,QAAA,CAAS,QAAA,CAAS,SAAS,CAAC,CAAA;AAAA,MAC9B;AAAA,IACF,CAAA;AAEA,IAAA,MAAA,CAAO,iBAAiB,SAAA,EAAW,SAAA,EAAW,EAAE,OAAA,EAAS,MAAM,CAAA;AAC/D,IAAA,OAAO,MAAM,OAAO,mBAAA,CAAoB,SAAA,EAAW,WAAW,EAAE,OAAA,EAAS,MAAM,CAAA;AAAA,EACjF,GAAG,CAAC,QAAA,EAAU,QAAQ,SAAA,EAAW,QAAA,CAAS,MAAM,CAAC,CAAA;AAGjD,EAAA,MAAM,IAAA,GAAOJ,QAAQ,MAAM;AACzB,IAAA,MAAM,GAAA,uBAAU,GAAA,EAAY;AAC5B,IAAA,KAAA,MAAW,MAAA,IAAU,CAAC,EAAA,EAAI,CAAA,EAAG,CAAC,CAAA,EAAG;AAC/B,MAAA,MAAM,CAAA,GAAI,QAAA,CAAS,SAAA,GAAY,MAAM,CAAA;AACrC,MAAA,IAAI,CAAA,EAAG,GAAA,EAAK,GAAA,CAAI,GAAA,CAAI,EAAE,GAAA,CAAI,UAAA,CAAW,GAAG,CAAA,GAAI,EAAE,GAAA,CAAI,KAAA,CAAM,CAAC,CAAA,GAAI,EAAE,GAAG,CAAA;AAAA,IACpE;AACA,IAAA,OAAO,GAAA;AAAA,EACT,CAAA,EAAG,CAAC,QAAA,EAAU,SAAS,CAAC,CAAA;AAExB,EAAA,MAAM,OAAA,GAAUA,OAAAA;AAAA,IACd,MAAM,IAAI,GAAA,CAAI,YAAA,CAAa,KAAA,EAAO,MAAM,QAAA,EAAU,EAAE,IAAA,EAAM,EAAE,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,CAAE,EAAE,CAAC,CAAA;AAAA,IAC5E,CAAC,KAAA,EAAO,IAAA,EAAM,QAAA,EAAU,IAAI;AAAA,GAC9B;AAEA,EAAA,MAAM,OAAA,GAAU,KAAA,CAAM,KAAA,GAAQ,IAAA,EAAM,OAAO,CAAA,IAAK,KAAA;AAChD,EAAA,MAAM,YAAY,OAAA,GAAU,MAAA,GAAY,eAAe,eAAA,CAAgB,IAAA,EAAM,OAAO,CAAC,CAAA;AAErF,EAAA,MAAM,OAAA,GAA6BA,OAAAA;AAAA,IACjC,OAAO;AAAA,MACL,IAAA;AAAA,MACA,MAAA;AAAA,MACA,KAAA,EAAO,SAAA;AAAA,MACP,UAAA,EAAY,SAAS,EAAA,IAAM,IAAA;AAAA,MAC3B,OAAO,QAAA,CAAS,MAAA;AAAA,MAChB,IAAA,EAAM,CAAC,SAAA,KAA+B;AACpC,QAAA,MAAM,IAAA,GACJ,OAAO,SAAA,KAAc,QAAA,GAAW,QAAA,CAAS,SAAA,CAAU,CAAC,CAAA,KAAM,CAAA,CAAE,EAAA,KAAO,SAAS,CAAA,GAAI,SAAA;AAClF,QAAA,IAAI,QAAQ,CAAA,EAAG,QAAA,CAAS,WAAW,IAAA,EAAM,QAAA,CAAS,MAAM,CAAC,CAAA;AAAA,MAC3D,CAAA;AAAA,MACA,IAAA,EAAM,MAAM,QAAA,CAAS,CAAC,CAAA,KAAM,WAAW,CAAA,GAAI,CAAA,EAAG,QAAA,CAAS,MAAM,CAAC,CAAA;AAAA,MAC9D,QAAA,EAAU,MAAM,QAAA,CAAS,CAAC,CAAA,KAAM,WAAW,CAAA,GAAI,CAAA,EAAG,QAAA,CAAS,MAAM,CAAC;AAAA,KACpE,CAAA;AAAA,IACA,CAAC,IAAA,EAAM,MAAA,EAAQ,SAAA,EAAW,SAAS,QAAQ;AAAA,GAC7C;AAEA,EAAA,uBACE,GAAA,CAAC,YAAA,CAAa,QAAA,EAAb,EAAsB,OAAO,OAAA,EAC5B,QAAA,kBAAA,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,GAAA,EAAK,OAAA;AAAA,MACL,SAAA;AAAA,MACA,KAAA,EAAO;AAAA,QACL,QAAA,EAAU,UAAA;AAAA,QACV,KAAA,EAAO,MAAA;AAAA,QACP,MAAA,EAAQ,MAAA;AAAA,QACR,QAAA,EAAU,QAAA;AAAA,QACV,GAAG;AAAA,OACL;AAAA,MAEA,QAAA,kBAAA,GAAA;AAAA,QAAC,KAAA;AAAA,QAAA;AAAA,UACC,mBAAA,EAAkB,EAAA;AAAA,UAClB,KAAA,EAAO;AAAA,YACL,QAAA,EAAU,UAAA;AAAA,YACV,KAAA,EAAO,CAAA;AAAA,YACP,SAAA;AAAA,YACA,eAAA,EAAiB,KAAA;AAAA;AAAA;AAAA,YAGjB,UAAA,EAAY,iBAAiB,MAAM;AAAA,WACrC;AAAA,UAEA,QAAA,kBAAA,GAAA,CAAC,YAAA,EAAA,EAAa,OAAA,EAAkB,KAAA,EAC7B,QAAA,EACH;AAAA;AAAA;AACF;AAAA,GACF,EACF,CAAA;AAEJ;AASA,IAAM,cAAA,GAAiB,cAA8B,IAAI,CAAA;AAEzD,SAAS,YAAA,CAAa;AAAA,EACpB,OAAA;AAAA,EACA,KAAA;AAAA,EACA;AACF,CAAA,EAIuB;AACrB,EAAA,MAAM,QAAA,GAAWA,OAAAA,CAAQ,MAAM,IAAI,IAAI,KAAA,CAAM,GAAA,CAAI,CAAC,CAAA,KAAM,EAAE,EAAE,CAAC,CAAA,EAAG,CAAC,KAAK,CAAC,CAAA;AACvE,EAAA,MAAM,KAAA,GAAQA,OAAAA,CAAQ,OAAO,EAAE,OAAA,EAAS,UAAS,CAAA,EAAI,CAAC,OAAA,EAAS,QAAQ,CAAC,CAAA;AACxE,EAAA,uBAAO,GAAA,CAAC,cAAA,CAAe,QAAA,EAAf,EAAwB,OAAe,QAAA,EAAS,CAAA;AAC1D;AASO,SAAS,aAAa,EAAA,EAAiC;AAC5D,EAAA,MAAM,OAAA,GAAU,WAAW,cAAc,CAAA;AACzC,EAAA,IAAI,CAAC,OAAA,IAAW,CAAC,EAAA,EAAI,OAAO,IAAA;AAC5B,EAAA,IAAI,CAAC,OAAA,CAAQ,QAAA,CAAS,GAAA,CAAI,EAAE,GAAG,OAAO,IAAA;AACtC,EAAA,OAAO,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,EAAE,CAAA;AAC/B;AA0BO,SAAS,SAAA,CAAU;AAAA,EACxB,EAAA;AAAA,EACA,CAAA;AAAA,EACA,CAAA;AAAA,EACA,KAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA;AAAA,EACA,SAAA;AAAA,EACA;AACF,CAAA,EAA8C;AAC5C,EAAA,MAAM,OAAA,GAAU,aAAa,EAAE,CAAA;AAC/B,EAAA,IAAI,CAAC,SAAS,OAAO,IAAA;AAErB,EAAA,uBACE,GAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,eAAA,EAAe,EAAA;AAAA,MACf,SAAA;AAAA,MACA,KAAA,EAAO;AAAA,QACL,QAAA,EAAU,UAAA;AAAA,QACV,IAAA,EAAM,CAAA;AAAA,QACN,GAAA,EAAK,CAAA;AAAA,QACL,KAAA;AAAA,QACA,MAAA;AAAA,QACA,OAAA,EAAS,cAAA;AAAA,QACT,GAAG;AAAA,OACL;AAAA,MAEC;AAAA;AAAA,GACH;AAEJ","file":"index.js","sourcesContent":["/**\n * The camera as a hook: a target waypoint goes in, a view comes out every frame.\n *\n * Driven by `requestAnimationFrame` rather than by a CSS transition or a spring library,\n * for three reasons. The flight is not a cubic bézier, so a CSS transition cannot express\n * it. The view is needed in JavaScript anyway, because culling and level-of-detail are\n * decided from it. And the same `flight` has to serve the video host, where there is no\n * rAF at all — so the interpolation lives in a pure function and only the clock differs.\n *\n * @packageDocumentation\n */\n\nimport { useEffect, useMemo, useRef, useState } from \"react\";\nimport { type Flight, flight } from \"../camera/flight\";\nimport type { View } from \"../camera/view\";\n\n/** Does this viewer want less motion? Re-read live, because they can change it mid-deck. */\nexport function usePrefersReducedMotion(): boolean {\n const [reduced, setReduced] = useState(() => {\n if (typeof window === \"undefined\" || !window.matchMedia) return false;\n return window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n });\n\n useEffect(() => {\n if (typeof window === \"undefined\" || !window.matchMedia) return;\n const query = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n const onChange = (event: MediaQueryListEvent) => setReduced(event.matches);\n query.addEventListener(\"change\", onChange);\n return () => query.removeEventListener(\"change\", onChange);\n }, []);\n\n return reduced;\n}\n\nexport interface CameraState {\n /** Where the camera is right now. */\n view: View;\n /** Is a flight in progress? The `will-change` switch keys off this. */\n flying: boolean;\n}\n\nexport interface UseCameraOptions {\n /** Where the camera should end up. */\n target: View;\n /** How long to take, in seconds. Omit and the flight's own suggestion is used. */\n duration?: number;\n /** van Wijk's ρ. */\n arc?: number;\n /** Cut instead of flying — what a reduced-motion viewer gets. */\n cut?: boolean;\n /** Called once the camera settles. */\n onArrive?: () => void;\n}\n\n/**\n * Fly the camera to `target` whenever it changes.\n *\n * A target that changes mid-flight is honoured immediately: the new flight starts from\n * wherever the camera actually is, not from the waypoint it was heading for. A presenter\n * who presses `→` twice quickly gets one continuous move, not a jump.\n */\nexport function useCamera({\n target,\n duration,\n arc,\n cut = false,\n onArrive,\n}: UseCameraOptions): CameraState {\n const [view, setView] = useState<View>(target);\n const [flying, setFlying] = useState(false);\n\n // The camera's live position, read by the next flight so an interruption is seamless.\n const viewRef = useRef(view);\n viewRef.current = view;\n\n const onArriveRef = useRef(onArrive);\n onArriveRef.current = onArrive;\n\n // Depend on the target's three numbers, never on the object.\n //\n // A parent that rebuilds an equal `{ cx, cy, w }` each render — which `Atlas` does,\n // because the waypoint is resolved per render — would otherwise restart the flight on\n // every render and the camera would never arrive.\n const { cx, cy, w } = target;\n\n useEffect(() => {\n const to = { cx, cy, w };\n const from = viewRef.current;\n if (from.cx === to.cx && from.cy === to.cy && from.w === to.w) {\n return;\n }\n\n if (cut) {\n setView(to);\n setFlying(false);\n onArriveRef.current?.();\n return;\n }\n\n const path: Flight = flight(from, to, arc);\n const seconds = duration ?? path.suggestedDuration;\n if (seconds <= 0) {\n setView(to);\n onArriveRef.current?.();\n return;\n }\n\n let raf = 0;\n let start: number | null = null;\n setFlying(true);\n\n const step = (now: number) => {\n if (start === null) start = now;\n const t = Math.min(1, (now - start) / (seconds * 1000));\n setView(path.at(t));\n if (t < 1) {\n raf = requestAnimationFrame(step);\n } else {\n setFlying(false);\n onArriveRef.current?.();\n }\n };\n\n raf = requestAnimationFrame(step);\n return () => {\n cancelAnimationFrame(raf);\n setFlying(false);\n };\n }, [cx, cy, w, cut, duration, arc]);\n\n return useMemo(() => ({ view, flying }), [view, flying]);\n}\n\n/**\n * The `will-change` value for a camera root, and the reason it is not a constant.\n *\n * Chrome re-rasterises content when its transform scale changes — *unless* it carries\n * `will-change: transform`, which is a promise to the compositor to apply the transform\n * fast and skip the re-raster. That makes the property a straight trade:\n *\n * - always on → cheap frames, and permanently blurry after a zoom, because the texture\n * is still rasterised at whatever scale the layer was created at. This is the blur\n * reported against impress.js and against WebKit 3D transforms for a decade.\n * - never set → sharp, and a re-raster of the whole subtree on every frame of a zoom.\n *\n * So switch it: on while flying, off on arrival. The blur then exists only during the\n * move, when nobody is reading, and every resting frame — every frame anyone stops on,\n * screenshots, or exports — is sharp.\n */\nexport function cameraWillChange(flying: boolean): \"transform\" | \"auto\" {\n return flying ? \"transform\" : \"auto\";\n}\n\n/** Step to a waypoint index, clamped, without wrapping. */\nexport function clampIndex(index: number, length: number): number {\n if (length <= 0) return 0;\n return Math.min(Math.max(index, 0), length - 1);\n}\n","/**\n * The deck host: one plane, one camera, one composited transform.\n *\n * @packageDocumentation\n */\n\nimport type React from \"react\";\nimport { createContext, useContext, useEffect, useMemo, useRef, useState } from \"react\";\nimport { type AtlasPath, arcFor, resolvePath } from \"../camera/path\";\nimport { transformToCss, type View, type Viewport, viewToTransform } from \"../camera/view\";\nimport { type PlacedNode, visibleNodes } from \"../scene/lod\";\nimport { planeProvider } from \"../scene/plane\";\nimport type { SceneProvider } from \"../scene/provider\";\nimport { cameraWillChange, clampIndex, useCamera, usePrefersReducedMotion } from \"./useCamera\";\n\n/** The deck's own canvas units. A waypoint's `w` is measured against the same grid. */\nconst DEFAULT_VIEWPORT: Viewport = { width: 1920, height: 1080 };\n\nexport interface AtlasContextValue {\n /** Where the camera is this frame. */\n view: View;\n /** Is a flight in progress? */\n flying: boolean;\n /** Index of the waypoint the camera is at or heading for. */\n index: number;\n /** Its id, when there is one. */\n waypointId: string | null;\n total: number;\n goTo: (indexOrId: number | string) => void;\n next: () => void;\n previous: () => void;\n}\n\nconst AtlasContext = createContext<AtlasContextValue | null>(null);\n\n/**\n * Read the camera from inside a scene.\n *\n * A checklist beside the canvas ticking as the camera arrives is this hook plus the\n * waypoint ids, and nothing else.\n */\nexport function useAtlas(): AtlasContextValue {\n const value = useContext(AtlasContext);\n if (!value) throw new Error(\"useAtlas must be used inside an <Atlas>\");\n return value;\n}\n\nexport interface AtlasProps {\n /** The camera path. */\n path: AtlasPath;\n /** The scene. Defaults to a plane built from `nodes`. */\n provider?: SceneProvider;\n /** Nodes on the default plane. Ignored when `provider` is given. */\n nodes?: readonly PlacedNode[];\n /** What to render on the plane. Node ids come from `data-atlas-id`. */\n children?: React.ReactNode;\n /**\n * The coordinate grid the world is measured in.\n *\n * This is the *world*, not the surface it is painted on: it fixes what `at: 0, 0, 4000`\n * means, and it is what a reference resolves against. The actual pixel size of the\n * canvas is measured, so a deck rendered at any size frames the same content.\n *\n * @defaultValue 1920x1080\n */\n viewport?: Viewport;\n /** Which waypoint to start at. @defaultValue 0 */\n initialWaypoint?: number | string;\n /**\n * Take the arrow keys.\n *\n * Uses the same contract as `Slide`'s sub-slides: capture the key, and swallow it only\n * while there is another waypoint to move to, so the deck advances normally at either\n * end. Without that, the deck and the canvas both act on one press.\n *\n * @defaultValue true\n */\n keyboard?: boolean;\n /** Only listen while this is true — a deck passes its slide's `isActive`. @defaultValue true */\n active?: boolean;\n /** Called on arrival at each waypoint. */\n onArrive?: (id: string, index: number) => void;\n className?: string;\n style?: React.CSSProperties;\n}\n\n/**\n * A zooming canvas.\n *\n * ```tsx\n * <Atlas path={{ waypoints: [\n * { id: \"whole\", at: { cx: 0, cy: 0, w: 4000 } },\n * { id: \"ingest\", at: \"#ingest\" },\n * ] }} nodes={nodes}>\n * <div data-atlas-id=\"ingest\">…</div>\n * </Atlas>\n * ```\n *\n * @behaviour Fills its container and clips to it, so it wants a slide of its own. Its\n * children are placed in world units on a plane the camera moves over rather than laid out\n * by the slide — `AtlasNode` is what places them — and a node the camera cannot see, or\n * that its level-of-detail band excludes, is unmounted rather than merely hidden.\n */\nexport function Atlas({\n path,\n provider,\n nodes = [],\n children,\n viewport = DEFAULT_VIEWPORT,\n initialWaypoint = 0,\n keyboard = true,\n active = true,\n onArrive,\n className,\n style,\n}: AtlasProps): React.ReactElement {\n const scene = useMemo(() => provider ?? planeProvider({ nodes }), [provider, nodes]);\n\n const { resolved } = useMemo(\n () => resolvePath(path, (ref) => scene.resolve(ref, viewport)),\n [path, scene, viewport],\n );\n\n const startIndex = useMemo(() => {\n if (typeof initialWaypoint === \"string\") {\n const found = resolved.findIndex((w) => w.id === initialWaypoint);\n return found === -1 ? 0 : found;\n }\n return clampIndex(initialWaypoint, resolved.length);\n }, [initialWaypoint, resolved]);\n\n /**\n * The surface the camera is actually painted on, in layout pixels.\n *\n * Measured rather than assumed. `viewport` is the world grid — it says what a\n * waypoint's numbers mean — and the two are rarely the same: a layout's padding alone\n * makes the canvas smaller than the deck's 1920x1080. Scaling by the world grid instead\n * of by the real surface renders the whole canvas at the wrong zoom and clips whatever\n * falls outside, which is invisible until somebody looks at the slide.\n *\n * `offsetWidth`, not `getBoundingClientRect`: the deck scales itself to the window with\n * a transform, and a rect would count that scale twice.\n */\n const rootRef = useRef<HTMLDivElement>(null);\n const [painted, setPainted] = useState<Viewport | null>(null);\n\n useEffect(() => {\n const element = rootRef.current;\n if (!element) return;\n\n const measure = () => {\n const width = element.offsetWidth;\n const height = element.offsetHeight;\n if (width <= 0 || height <= 0) return;\n setPainted((previous) =>\n previous && previous.width === width && previous.height === height\n ? previous\n : { width, height },\n );\n };\n\n measure();\n if (typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(measure);\n observer.observe(element);\n return () => observer.disconnect();\n }, []);\n\n // Before the first measurement — a server render, or a test with no layout — the world\n // grid is the best guess available, and it is right whenever the two do match.\n const surface = painted ?? viewport;\n\n const [index, setIndex] = useState(startIndex);\n const safeIndex = clampIndex(index, resolved.length);\n const current = resolved[safeIndex];\n const reduced = usePrefersReducedMotion();\n\n const target: View = current?.at ?? { cx: 0, cy: 0, w: viewport.width };\n\n const { view, flying } = useCamera({\n target,\n duration: current?.duration,\n arc: current ? arcFor(path, current) : undefined,\n cut: reduced,\n onArrive: () => {\n if (current) onArrive?.(current.id, safeIndex);\n },\n });\n\n // Waypoint navigation. Capture, and stop the event only while this canvas has\n // somewhere left to go — at either end the deck gets the key and moves on.\n useEffect(() => {\n if (!keyboard || !active || resolved.length === 0) return;\n\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.ctrlKey || event.metaKey) return;\n const target = event.target as HTMLElement | null;\n if (\n target &&\n (target.tagName === \"INPUT\" || target.tagName === \"TEXTAREA\" || target.isContentEditable)\n ) {\n return;\n }\n\n const forward = event.key === \"ArrowRight\" || event.key === \" \" || event.key === \"Enter\";\n const back = event.key === \"ArrowLeft\";\n\n if (forward && safeIndex < resolved.length - 1) {\n event.preventDefault();\n event.stopPropagation();\n setIndex(safeIndex + 1);\n } else if (back && safeIndex > 0) {\n event.preventDefault();\n event.stopPropagation();\n setIndex(safeIndex - 1);\n } else if (event.key === \"Home\") {\n event.preventDefault();\n event.stopPropagation();\n setIndex(0);\n } else if (event.key === \"End\") {\n event.preventDefault();\n event.stopPropagation();\n setIndex(resolved.length - 1);\n }\n };\n\n window.addEventListener(\"keydown\", onKeyDown, { capture: true });\n return () => window.removeEventListener(\"keydown\", onKeyDown, { capture: true });\n }, [keyboard, active, safeIndex, resolved.length]);\n\n // Keep the waypoints either side mounted, so arriving never costs a layout.\n const keep = useMemo(() => {\n const ids = new Set<string>();\n for (const offset of [-1, 0, 1]) {\n const w = resolved[safeIndex + offset];\n if (w?.ref) ids.add(w.ref.startsWith(\"#\") ? w.ref.slice(1) : w.ref);\n }\n return ids;\n }, [resolved, safeIndex]);\n\n const mounted = useMemo(\n () => new Set(visibleNodes(nodes, view, viewport, { keep }).map((n) => n.id)),\n [nodes, view, viewport, keep],\n );\n\n const handled = scene.apply?.(view, surface) ?? false;\n const transform = handled ? undefined : transformToCss(viewToTransform(view, surface));\n\n const context: AtlasContextValue = useMemo(\n () => ({\n view,\n flying,\n index: safeIndex,\n waypointId: current?.id ?? null,\n total: resolved.length,\n goTo: (indexOrId: number | string) => {\n const next =\n typeof indexOrId === \"string\" ? resolved.findIndex((w) => w.id === indexOrId) : indexOrId;\n if (next >= 0) setIndex(clampIndex(next, resolved.length));\n },\n next: () => setIndex((i) => clampIndex(i + 1, resolved.length)),\n previous: () => setIndex((i) => clampIndex(i - 1, resolved.length)),\n }),\n [view, flying, safeIndex, current, resolved],\n );\n\n return (\n <AtlasContext.Provider value={context}>\n <div\n ref={rootRef}\n className={className}\n style={{\n position: \"relative\",\n width: \"100%\",\n height: \"100%\",\n overflow: \"hidden\",\n ...style,\n }}\n >\n <div\n data-atlas-camera=\"\"\n style={{\n position: \"absolute\",\n inset: 0,\n transform,\n transformOrigin: \"0 0\",\n // On while flying, off on arrival — see `cameraWillChange`. This one line is\n // the difference between text that is sharp at rest and text that never is.\n willChange: cameraWillChange(flying),\n }}\n >\n <MountedNodes mounted={mounted} nodes={nodes}>\n {children}\n </MountedNodes>\n </div>\n </div>\n </AtlasContext.Provider>\n );\n}\n\ninterface Culling {\n /** Ids the camera can currently see. */\n mounted: ReadonlySet<string>;\n /** Ids the scene described. Anything else never takes part in culling. */\n declared: ReadonlySet<string>;\n}\n\nconst MountedContext = createContext<Culling | null>(null);\n\nfunction MountedNodes({\n mounted,\n nodes,\n children,\n}: {\n mounted: ReadonlySet<string>;\n nodes: readonly PlacedNode[];\n children: React.ReactNode;\n}): React.ReactElement {\n const declared = useMemo(() => new Set(nodes.map((n) => n.id)), [nodes]);\n const value = useMemo(() => ({ mounted, declared }), [mounted, declared]);\n return <MountedContext.Provider value={value}>{children}</MountedContext.Provider>;\n}\n\n/**\n * Is this node currently mounted?\n *\n * A node the scene never described — a backdrop, a caption, a progress rail — is always\n * mounted. Culling is opt-in per node, so declaring `nodes` can never make content the\n * author did not describe disappear.\n */\nexport function useIsMounted(id: string | undefined): boolean {\n const culling = useContext(MountedContext);\n if (!culling || !id) return true;\n if (!culling.declared.has(id)) return true;\n return culling.mounted.has(id);\n}\n\nexport interface AtlasNodeProps {\n /** Must match a `PlacedNode.id`, or the node is never culled. */\n id: string;\n /** Where it sits, in world units. */\n x: number;\n y: number;\n width: number;\n height: number;\n children?: React.ReactNode;\n className?: string;\n style?: React.CSSProperties;\n}\n\n/**\n * A node on the plane.\n *\n * `contain: layout paint` is not decoration: it stops a node's own layout from escaping\n * into the camera root, which is what keeps a frame at one style recalculation and zero\n * layout however many nodes are mounted.\n *\n * @behaviour Positions itself absolutely at `x`/`y` in world units, so it only works\n * inside an `Atlas` and ignores whatever layout surrounds it. It renders nothing at all\n * while the camera cannot see it, or while its `lod` band excludes the current framing.\n */\nexport function AtlasNode({\n id,\n x,\n y,\n width,\n height,\n children,\n className,\n style,\n}: AtlasNodeProps): React.ReactElement | null {\n const mounted = useIsMounted(id);\n if (!mounted) return null;\n\n return (\n <div\n data-atlas-id={id}\n className={className}\n style={{\n position: \"absolute\",\n left: x,\n top: y,\n width,\n height,\n contain: \"layout paint\",\n ...style,\n }}\n >\n {children}\n </div>\n );\n}\n"]}
@@ -0,0 +1,162 @@
1
+ import { R as Rect } from '../view-BzztOpV6.js';
2
+ import { S as SceneProvider } from '../provider-CjL3i-0A.js';
3
+
4
+ /**
5
+ * A camera over a recording of a real app.
6
+ *
7
+ * This is the case no other product in the field can serve. `@getnarro/replay` replays a
8
+ * recorded app as **real DOM**, so zooming into a control magnifies live text and vector
9
+ * borders — it stays sharp, and stays selectable, at any zoom. Prezi, Genially, Miro and
10
+ * every video tool can only zoom a bitmap here, and a bitmap falls apart at 3x.
11
+ *
12
+ * Two facts about a cast make this fit together rather than merely work.
13
+ *
14
+ * **A cast has chapters**, so a waypoint can carry both a moment and a framing: *go to
15
+ * this point in the recording, and frame this control*.
16
+ *
17
+ * **A cast is not responsive.** Its README calls that a limitation — "layout was computed
18
+ * once, at the recording viewport… the player scales; it does not reflow" — and here it
19
+ * is exactly what makes selector waypoints sound. The recording viewport *is* the world
20
+ * coordinate system, fixed for the life of the cast, so a rectangle measured around a DOM
21
+ * node is stable rather than a function of the window somebody happens to be watching in.
22
+ *
23
+ * @packageDocumentation
24
+ */
25
+
26
+ /** The parts of a `NarroCast` this provider needs. Structural, so `@getnarro/replay` stays optional. */
27
+ interface CastLike {
28
+ viewport: {
29
+ width: number;
30
+ height: number;
31
+ };
32
+ chapters?: {
33
+ name: string;
34
+ atMs: number;
35
+ }[];
36
+ events?: unknown[];
37
+ }
38
+ interface ReplayProviderOptions {
39
+ cast: CastLike;
40
+ /**
41
+ * Measure a selector in the live replay, in recording-viewport coordinates.
42
+ *
43
+ * Supplied by the host, because only it has the replay's document. Return null when the
44
+ * selector matches nothing right now — the recording may not have reached the moment
45
+ * where that element exists, which is a legitimate state and not an error.
46
+ *
47
+ * Omit it entirely and the provider resolves nothing, which is what a build-time or
48
+ * server-side render wants: {@link findInCast} does the checking there instead.
49
+ */
50
+ measure?: (selector: string) => Rect | null;
51
+ /**
52
+ * Is the replay mounted and settled on the moment being framed?
53
+ *
54
+ * The video host holds the frame on this. A recording that has not reached its chapter
55
+ * yet renders the wrong screen, and the renderer would encode it without complaint.
56
+ *
57
+ * @defaultValue always ready
58
+ */
59
+ isReady?: () => boolean;
60
+ /** Padding around a measured element, as a fraction of its size. @defaultValue 0.25 */
61
+ padding?: number;
62
+ }
63
+ /**
64
+ * A scene backed by a recorded app.
65
+ *
66
+ * The world is the recording's own viewport, so a waypoint of
67
+ * `{ cx: 640, cy: 360, w: 1280 }` is "the whole app" for a 1280x720 recording, and
68
+ * `at: "#publish"` is whatever rectangle that button occupies.
69
+ */
70
+ declare function replayProvider(options: ReplayProviderOptions): SceneProvider;
71
+ /**
72
+ * A `measure` that queries a document — the replay's iframe, in practice.
73
+ *
74
+ * Rects come back relative to the document rather than to the window, so a replay that
75
+ * has been scrolled still measures in the coordinate system the waypoints are written in.
76
+ */
77
+ declare function measureIn(doc: Document | null | undefined): (selector: string) => Rect | null;
78
+ /** When a waypoint's chapter starts, in milliseconds. Null when the cast has no such chapter. */
79
+ declare function chapterTime(cast: CastLike, chapter: string): number | null;
80
+
81
+ /**
82
+ * Reading a `.narrocast` without a browser.
83
+ *
84
+ * A waypoint over a recording names a **selector** — `at: "#publish-button"` — rather
85
+ * than coordinates, because coordinates over a recording rot the instant somebody
86
+ * re-records and the button moves four pixels. That is the whole reason the replay
87
+ * provider is worth having, and it comes with one obligation: a selector that matches
88
+ * nothing has to be caught at build time, not discovered as a camera flying to an empty
89
+ * corner of a video that is already encoded.
90
+ *
91
+ * So this walks the cast's rrweb full snapshot and answers *does anything match*. It is
92
+ * pure, needs no DOM, and therefore runs inside `narro check`.
93
+ *
94
+ * **It answers existence, not position.** The snapshot carries the DOM as markup, with no
95
+ * layout — rects only exist once something has actually laid the recording out. So this
96
+ * is the build-time half; {@link replayProvider} does the measuring at runtime. Saying so
97
+ * is the point: a checker that guessed at geometry would report failures it cannot
98
+ * establish, which is worse than reporting nothing.
99
+ *
100
+ * @packageDocumentation
101
+ */
102
+ /** An element as the snapshot recorded it. */
103
+ interface CastElement {
104
+ tagName: string;
105
+ attributes: Record<string, string>;
106
+ }
107
+ /** Every element in the cast's first full snapshot. */
108
+ declare function castElements(cast: {
109
+ events?: unknown[];
110
+ }): CastElement[];
111
+ /** One piece of a compound selector. */
112
+ type Clause = {
113
+ kind: "tag";
114
+ value: string;
115
+ } | {
116
+ kind: "id";
117
+ value: string;
118
+ } | {
119
+ kind: "class";
120
+ value: string;
121
+ } | {
122
+ kind: "attr";
123
+ name: string;
124
+ value?: string;
125
+ };
126
+ /**
127
+ * Parse a **simple** selector — one element, no combinators.
128
+ *
129
+ * Null for anything with a descendant, child, sibling or pseudo part. That is a refusal
130
+ * to guess, not a gap: a checker that reported "no match" for a selector it could not
131
+ * parse would fail builds over its own limitations, and an author cannot tell the two
132
+ * apart from the error message.
133
+ */
134
+ declare function parseSimpleSelector(selector: string): Clause[] | null;
135
+ /** What checking a selector against a cast can tell you. */
136
+ type SelectorVerdict =
137
+ /** At least one element matches. */
138
+ {
139
+ status: "found";
140
+ count: number;
141
+ }
142
+ /** Parsed fine, matched nothing. This is the one worth failing a build over. */
143
+ | {
144
+ status: "missing";
145
+ }
146
+ /** Too complex to verify offline. Reported as nothing, never as a failure. */
147
+ | {
148
+ status: "unverifiable";
149
+ reason: string;
150
+ };
151
+ /** Does anything in the cast match this selector? */
152
+ declare function findInCast(cast: {
153
+ events?: unknown[];
154
+ }, selector: string): SelectorVerdict;
155
+ /** The chapter names a cast defines. */
156
+ declare function castChapters(cast: {
157
+ chapters?: {
158
+ name: string;
159
+ }[];
160
+ }): string[];
161
+
162
+ export { type CastElement, type CastLike, type ReplayProviderOptions, type SelectorVerdict, castChapters, castElements, chapterTime, findInCast, measureIn, parseSimpleSelector, replayProvider };
@@ -0,0 +1,138 @@
1
+ import { rectToView } from '../chunk-RWYOYDBD.js';
2
+
3
+ // src/replay/provider.ts
4
+ function replayProvider(options) {
5
+ const { cast, measure, isReady, padding = 0.25 } = options;
6
+ return {
7
+ name: "replay",
8
+ bounds() {
9
+ return { x: 0, y: 0, width: cast.viewport.width, height: cast.viewport.height };
10
+ },
11
+ resolve(ref, viewport) {
12
+ if (!measure) return null;
13
+ const rect = measure(ref);
14
+ return rect ? rectToView(rect, viewport, padding) : null;
15
+ },
16
+ ready() {
17
+ return isReady ? isReady() : true;
18
+ }
19
+ };
20
+ }
21
+ function measureIn(doc) {
22
+ return (selector) => {
23
+ if (!doc) return null;
24
+ let element = null;
25
+ try {
26
+ element = doc.querySelector(selector);
27
+ } catch {
28
+ return null;
29
+ }
30
+ if (!element) return null;
31
+ const rect = element.getBoundingClientRect();
32
+ const view = doc.defaultView;
33
+ const scrollX = view?.scrollX ?? 0;
34
+ const scrollY = view?.scrollY ?? 0;
35
+ return {
36
+ x: rect.left + scrollX,
37
+ y: rect.top + scrollY,
38
+ width: rect.width,
39
+ height: rect.height
40
+ };
41
+ };
42
+ }
43
+ function chapterTime(cast, chapter) {
44
+ return cast.chapters?.find((c) => c.name === chapter)?.atMs ?? null;
45
+ }
46
+
47
+ // src/replay/snapshot.ts
48
+ var FULL_SNAPSHOT = 2;
49
+ var ELEMENT_NODE = 2;
50
+ function isRecord(value) {
51
+ return typeof value === "object" && value !== null;
52
+ }
53
+ function castElements(cast) {
54
+ const elements = [];
55
+ for (const event of cast.events ?? []) {
56
+ if (!isRecord(event) || event.type !== FULL_SNAPSHOT) continue;
57
+ const data = event.data;
58
+ if (!isRecord(data)) continue;
59
+ walk(data.node, elements);
60
+ }
61
+ return elements;
62
+ }
63
+ function walk(node, out) {
64
+ if (!node || typeof node !== "object") return;
65
+ if (node.type === ELEMENT_NODE && typeof node.tagName === "string") {
66
+ const attributes = {};
67
+ for (const [key, value] of Object.entries(node.attributes ?? {})) {
68
+ if (typeof value === "string") attributes[key] = value;
69
+ else if (typeof value === "number" || typeof value === "boolean") {
70
+ attributes[key] = String(value);
71
+ }
72
+ }
73
+ out.push({ tagName: node.tagName.toLowerCase(), attributes });
74
+ }
75
+ for (const child of node.childNodes ?? []) walk(child, out);
76
+ }
77
+ function parseSimpleSelector(selector) {
78
+ const trimmed = selector.trim();
79
+ if (trimmed.length === 0) return null;
80
+ if (/[\s>+~,:()]/.test(trimmed)) return null;
81
+ const clauses = [];
82
+ const pattern = /(^[a-zA-Z][\w-]*)|#([\w-]+)|\.([\w-]+)|\[([\w-]+)(?:([~|^$*]?=)"?([^\]"]*)"?)?\]/g;
83
+ let consumed = 0;
84
+ let match = pattern.exec(trimmed);
85
+ while (match !== null) {
86
+ if (match.index !== consumed) return null;
87
+ consumed = match.index + match[0].length;
88
+ if (match[1]) clauses.push({ kind: "tag", value: match[1].toLowerCase() });
89
+ else if (match[2]) clauses.push({ kind: "id", value: match[2] });
90
+ else if (match[3]) clauses.push({ kind: "class", value: match[3] });
91
+ else if (match[4]) {
92
+ if (match[5] && match[5] !== "=") return null;
93
+ clauses.push({ kind: "attr", name: match[4], value: match[6] });
94
+ }
95
+ match = pattern.exec(trimmed);
96
+ }
97
+ if (consumed !== trimmed.length || clauses.length === 0) return null;
98
+ return clauses;
99
+ }
100
+ function matches(element, clauses) {
101
+ return clauses.every((clause) => {
102
+ switch (clause.kind) {
103
+ case "tag":
104
+ return element.tagName === clause.value;
105
+ case "id":
106
+ return element.attributes.id === clause.value;
107
+ case "class":
108
+ return (element.attributes.class ?? "").split(/\s+/).includes(clause.value);
109
+ case "attr":
110
+ if (!(clause.name in element.attributes)) return false;
111
+ return clause.value === void 0 || element.attributes[clause.name] === clause.value;
112
+ default:
113
+ return false;
114
+ }
115
+ });
116
+ }
117
+ function findInCast(cast, selector) {
118
+ const clauses = parseSimpleSelector(selector);
119
+ if (!clauses) {
120
+ return {
121
+ status: "unverifiable",
122
+ reason: "only a simple selector (tag, #id, .class, [attr]) can be checked offline"
123
+ };
124
+ }
125
+ const elements = castElements(cast);
126
+ if (elements.length === 0) {
127
+ return { status: "unverifiable", reason: "the cast carries no full snapshot to check against" };
128
+ }
129
+ const count = elements.filter((element) => matches(element, clauses)).length;
130
+ return count > 0 ? { status: "found", count } : { status: "missing" };
131
+ }
132
+ function castChapters(cast) {
133
+ return (cast.chapters ?? []).map((chapter) => chapter.name);
134
+ }
135
+
136
+ export { castChapters, castElements, chapterTime, findInCast, measureIn, parseSimpleSelector, replayProvider };
137
+ //# sourceMappingURL=index.js.map
138
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/replay/provider.ts","../../src/replay/snapshot.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * A camera over a recording of a real app.\n *\n * This is the case no other product in the field can serve. `@getnarro/replay` replays a\n * recorded app as **real DOM**, so zooming into a control magnifies live text and vector\n * borders — it stays sharp, and stays selectable, at any zoom. Prezi, Genially, Miro and\n * every video tool can only zoom a bitmap here, and a bitmap falls apart at 3x.\n *\n * Two facts about a cast make this fit together rather than merely work.\n *\n * **A cast has chapters**, so a waypoint can carry both a moment and a framing: *go to\n * this point in the recording, and frame this control*.\n *\n * **A cast is not responsive.** Its README calls that a limitation — \"layout was computed\n * once, at the recording viewport… the player scales; it does not reflow\" — and here it\n * is exactly what makes selector waypoints sound. The recording viewport *is* the world\n * coordinate system, fixed for the life of the cast, so a rectangle measured around a DOM\n * node is stable rather than a function of the window somebody happens to be watching in.\n *\n * @packageDocumentation\n */\n\nimport { type Rect, rectToView, type View, type Viewport } from \"../camera/view\";\nimport type { SceneProvider } from \"../scene/provider\";\n\n/** The parts of a `NarroCast` this provider needs. Structural, so `@getnarro/replay` stays optional. */\nexport interface CastLike {\n viewport: { width: number; height: number };\n chapters?: { name: string; atMs: number }[];\n events?: unknown[];\n}\n\nexport interface ReplayProviderOptions {\n cast: CastLike;\n /**\n * Measure a selector in the live replay, in recording-viewport coordinates.\n *\n * Supplied by the host, because only it has the replay's document. Return null when the\n * selector matches nothing right now — the recording may not have reached the moment\n * where that element exists, which is a legitimate state and not an error.\n *\n * Omit it entirely and the provider resolves nothing, which is what a build-time or\n * server-side render wants: {@link findInCast} does the checking there instead.\n */\n measure?: (selector: string) => Rect | null;\n /**\n * Is the replay mounted and settled on the moment being framed?\n *\n * The video host holds the frame on this. A recording that has not reached its chapter\n * yet renders the wrong screen, and the renderer would encode it without complaint.\n *\n * @defaultValue always ready\n */\n isReady?: () => boolean;\n /** Padding around a measured element, as a fraction of its size. @defaultValue 0.25 */\n padding?: number;\n}\n\n/**\n * A scene backed by a recorded app.\n *\n * The world is the recording's own viewport, so a waypoint of\n * `{ cx: 640, cy: 360, w: 1280 }` is \"the whole app\" for a 1280x720 recording, and\n * `at: \"#publish\"` is whatever rectangle that button occupies.\n */\nexport function replayProvider(options: ReplayProviderOptions): SceneProvider {\n const { cast, measure, isReady, padding = 0.25 } = options;\n\n return {\n name: \"replay\",\n\n bounds(): Rect {\n return { x: 0, y: 0, width: cast.viewport.width, height: cast.viewport.height };\n },\n\n resolve(ref: string, viewport: Viewport): View | null {\n if (!measure) return null;\n const rect = measure(ref);\n return rect ? rectToView(rect, viewport, padding) : null;\n },\n\n ready(): boolean {\n return isReady ? isReady() : true;\n },\n };\n}\n\n/**\n * A `measure` that queries a document — the replay's iframe, in practice.\n *\n * Rects come back relative to the document rather than to the window, so a replay that\n * has been scrolled still measures in the coordinate system the waypoints are written in.\n */\nexport function measureIn(doc: Document | null | undefined): (selector: string) => Rect | null {\n return (selector: string) => {\n if (!doc) return null;\n let element: Element | null = null;\n try {\n element = doc.querySelector(selector);\n } catch {\n // An invalid selector is an authoring mistake the checker reports by name; at\n // runtime it must not take the deck down.\n return null;\n }\n if (!element) return null;\n\n const rect = element.getBoundingClientRect();\n const view = doc.defaultView;\n const scrollX = view?.scrollX ?? 0;\n const scrollY = view?.scrollY ?? 0;\n\n return {\n x: rect.left + scrollX,\n y: rect.top + scrollY,\n width: rect.width,\n height: rect.height,\n };\n };\n}\n\n/** When a waypoint's chapter starts, in milliseconds. Null when the cast has no such chapter. */\nexport function chapterTime(cast: CastLike, chapter: string): number | null {\n return cast.chapters?.find((c) => c.name === chapter)?.atMs ?? null;\n}\n","/**\n * Reading a `.narrocast` without a browser.\n *\n * A waypoint over a recording names a **selector** — `at: \"#publish-button\"` — rather\n * than coordinates, because coordinates over a recording rot the instant somebody\n * re-records and the button moves four pixels. That is the whole reason the replay\n * provider is worth having, and it comes with one obligation: a selector that matches\n * nothing has to be caught at build time, not discovered as a camera flying to an empty\n * corner of a video that is already encoded.\n *\n * So this walks the cast's rrweb full snapshot and answers *does anything match*. It is\n * pure, needs no DOM, and therefore runs inside `narro check`.\n *\n * **It answers existence, not position.** The snapshot carries the DOM as markup, with no\n * layout — rects only exist once something has actually laid the recording out. So this\n * is the build-time half; {@link replayProvider} does the measuring at runtime. Saying so\n * is the point: a checker that guessed at geometry would report failures it cannot\n * establish, which is worse than reporting nothing.\n *\n * @packageDocumentation\n */\n\n/** rrweb's `EventType.FullSnapshot`. */\nconst FULL_SNAPSHOT = 2;\n\n/** rrweb's `NodeType.Element`. */\nconst ELEMENT_NODE = 2;\n\n/** An element as the snapshot recorded it. */\nexport interface CastElement {\n tagName: string;\n attributes: Record<string, string>;\n}\n\ninterface SerializedNode {\n type?: number;\n tagName?: string;\n attributes?: Record<string, unknown>;\n childNodes?: SerializedNode[];\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\n/** Every element in the cast's first full snapshot. */\nexport function castElements(cast: { events?: unknown[] }): CastElement[] {\n const elements: CastElement[] = [];\n\n for (const event of cast.events ?? []) {\n if (!isRecord(event) || event.type !== FULL_SNAPSHOT) continue;\n const data = event.data;\n if (!isRecord(data)) continue;\n walk(data.node as SerializedNode | undefined, elements);\n }\n\n return elements;\n}\n\nfunction walk(node: SerializedNode | undefined, out: CastElement[]): void {\n if (!node || typeof node !== \"object\") return;\n\n if (node.type === ELEMENT_NODE && typeof node.tagName === \"string\") {\n const attributes: Record<string, string> = {};\n for (const [key, value] of Object.entries(node.attributes ?? {})) {\n if (typeof value === \"string\") attributes[key] = value;\n else if (typeof value === \"number\" || typeof value === \"boolean\") {\n attributes[key] = String(value);\n }\n }\n out.push({ tagName: node.tagName.toLowerCase(), attributes });\n }\n\n for (const child of node.childNodes ?? []) walk(child, out);\n}\n\n/** One piece of a compound selector. */\ntype Clause =\n | { kind: \"tag\"; value: string }\n | { kind: \"id\"; value: string }\n | { kind: \"class\"; value: string }\n | { kind: \"attr\"; name: string; value?: string };\n\n/**\n * Parse a **simple** selector — one element, no combinators.\n *\n * Null for anything with a descendant, child, sibling or pseudo part. That is a refusal\n * to guess, not a gap: a checker that reported \"no match\" for a selector it could not\n * parse would fail builds over its own limitations, and an author cannot tell the two\n * apart from the error message.\n */\nexport function parseSimpleSelector(selector: string): Clause[] | null {\n const trimmed = selector.trim();\n if (trimmed.length === 0) return null;\n if (/[\\s>+~,:()]/.test(trimmed)) return null;\n\n const clauses: Clause[] = [];\n const pattern =\n /(^[a-zA-Z][\\w-]*)|#([\\w-]+)|\\.([\\w-]+)|\\[([\\w-]+)(?:([~|^$*]?=)\"?([^\\]\"]*)\"?)?\\]/g;\n\n let consumed = 0;\n let match: RegExpExecArray | null = pattern.exec(trimmed);\n while (match !== null) {\n if (match.index !== consumed) return null;\n consumed = match.index + match[0].length;\n\n if (match[1]) clauses.push({ kind: \"tag\", value: match[1].toLowerCase() });\n else if (match[2]) clauses.push({ kind: \"id\", value: match[2] });\n else if (match[3]) clauses.push({ kind: \"class\", value: match[3] });\n else if (match[4]) {\n // Only exact and bare attribute matches; `~=`, `^=` and friends are \"too complex\".\n if (match[5] && match[5] !== \"=\") return null;\n clauses.push({ kind: \"attr\", name: match[4], value: match[6] });\n }\n match = pattern.exec(trimmed);\n }\n\n if (consumed !== trimmed.length || clauses.length === 0) return null;\n return clauses;\n}\n\nfunction matches(element: CastElement, clauses: Clause[]): boolean {\n return clauses.every((clause) => {\n switch (clause.kind) {\n case \"tag\":\n return element.tagName === clause.value;\n case \"id\":\n return element.attributes.id === clause.value;\n case \"class\":\n return (element.attributes.class ?? \"\").split(/\\s+/).includes(clause.value);\n case \"attr\":\n if (!(clause.name in element.attributes)) return false;\n return clause.value === undefined || element.attributes[clause.name] === clause.value;\n default:\n return false;\n }\n });\n}\n\n/** What checking a selector against a cast can tell you. */\nexport type SelectorVerdict =\n /** At least one element matches. */\n | { status: \"found\"; count: number }\n /** Parsed fine, matched nothing. This is the one worth failing a build over. */\n | { status: \"missing\" }\n /** Too complex to verify offline. Reported as nothing, never as a failure. */\n | { status: \"unverifiable\"; reason: string };\n\n/** Does anything in the cast match this selector? */\nexport function findInCast(cast: { events?: unknown[] }, selector: string): SelectorVerdict {\n const clauses = parseSimpleSelector(selector);\n if (!clauses) {\n return {\n status: \"unverifiable\",\n reason: \"only a simple selector (tag, #id, .class, [attr]) can be checked offline\",\n };\n }\n\n const elements = castElements(cast);\n if (elements.length === 0) {\n return { status: \"unverifiable\", reason: \"the cast carries no full snapshot to check against\" };\n }\n\n const count = elements.filter((element) => matches(element, clauses)).length;\n return count > 0 ? { status: \"found\", count } : { status: \"missing\" };\n}\n\n/** The chapter names a cast defines. */\nexport function castChapters(cast: { chapters?: { name: string }[] }): string[] {\n return (cast.chapters ?? []).map((chapter) => chapter.name);\n}\n"]}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * The element's name and modes, split out from the element itself.
3
+ *
4
+ * `class NarroAtlasElement extends HTMLElement` is evaluated the moment its module is
5
+ * imported, and `HTMLElement` does not exist in Node — so a module that re-exports the
6
+ * class cannot be imported on a server at all. That would make
7
+ * `import { scrollProgress } from "@getnarro/atlas/web"` throw during SSR, over
8
+ * arithmetic that has nothing to do with the DOM.
9
+ *
10
+ * So the constants live here, `@getnarro/atlas/web` stays importable anywhere, and the
11
+ * element is reached through `@getnarro/atlas/embed`, which is a browser entry by
12
+ * construction.
13
+ *
14
+ * @packageDocumentation
15
+ */
16
+ declare const TAG = "narro-atlas";
17
+ /** How the page drives the camera. */
18
+ type AtlasMode =
19
+ /** The camera follows the page's scroll. The default, and what a page usually wants. */
20
+ "scroll"
21
+ /** Click, or arrow keys while focused, to step between waypoints. */
22
+ | "steps";
23
+
24
+ export { type AtlasMode as A, TAG as T };
@@ -0,0 +1,194 @@
1
+ import { V as View } from './view-BzztOpV6.js';
2
+ import { R as ResolvedWaypoint, A as AtlasPath } from './path-BIxJH8g2.js';
3
+
4
+ /**
5
+ * Smooth and efficient zooming and panning — van Wijk & Nuij, InfoVis 2003.
6
+ *
7
+ * This is the whole reason this package exists, so it is worth saying what it fixes.
8
+ *
9
+ * Tween `cx`, `cy` and `w` as three independent curves — which is what every
10
+ * presentation tool in this field does, `TransformSlide` included — and consider what a
11
+ * viewer's eye actually tracks. Apparent velocity is world velocity divided by the
12
+ * current view width. Fly 5000 world units while zooming from width 4000 to width 200,
13
+ * and the camera starts at about one screen per second and ends at *fifty*, because the
14
+ * denominator collapsed while the numerator did not. The picture accelerates by more
15
+ * than an order of magnitude while the numbers say it is slowing down. The eye tries to
16
+ * track features that keep getting faster, the vestibular system gets no matching
17
+ * signal, and that mismatch is the mechanical cause of the motion-sickness complaint
18
+ * that tops every review of every product in this category. No easing curve fixes it;
19
+ * easing only moves the peak around.
20
+ *
21
+ * Van Wijk and Nuij solve it properly: treat `(cx, cy, log w)` as a hyperbolic space and
22
+ * take the geodesic between the two views. Two things fall out, and a presentation tool
23
+ * wants both.
24
+ *
25
+ * - **Apparent velocity is constant** for the whole flight, by construction rather than
26
+ * by tuning. The lurch is gone.
27
+ * - **The path knows how long it should take.** {@link Flight.length} is the distance
28
+ * through that space, so a far, deep flight *asks* for more time than a small nudge.
29
+ * The author does not have to guess, and {@link Flight.suggestedDuration} is what
30
+ * `narro check` compares an author's override against.
31
+ *
32
+ * The characteristic "pull back, travel, drop in" arc that people actually like about
33
+ * Prezi is emergent here, not authored. `rho` is how pronounced it is.
34
+ *
35
+ * It is closed-form in `t`, which matters more here than anywhere else: the video
36
+ * renderer advances a frame counter and calls a render function, so frame 37 must not
37
+ * depend on frames 0-36 having been drawn. A spring would have to be integrated. This
38
+ * does not.
39
+ *
40
+ * @packageDocumentation
41
+ */
42
+
43
+ /**
44
+ * How much the camera pulls back on the way — van Wijk's ρ.
45
+ *
46
+ * √2 is d3's default and the value this is tested against. The paper's own user study
47
+ * lands on ≈1.42 for "fastest without discomfort", which is the same number to within
48
+ * the width of the preference. Higher arcs out further and reads as more dramatic;
49
+ * lower flies flatter and reads as more direct. Below about 1 the geometry degenerates.
50
+ */
51
+ declare const DEFAULT_RHO: number;
52
+ /** A camera move from one view to another, as a pure function of `t` in `[0, 1]`. */
53
+ interface Flight {
54
+ readonly from: View;
55
+ readonly to: View;
56
+ /** The ρ this flight was built with. */
57
+ readonly rho: number;
58
+ /**
59
+ * The length of the path through (x, y, log w) space.
60
+ *
61
+ * This is the number that makes a duration checkable: it is "how far the picture
62
+ * travels", counted in a unit where zooming and panning are commensurate. Zero for a
63
+ * flight that goes nowhere.
64
+ */
65
+ readonly length: number;
66
+ /**
67
+ * How long this flight wants to take, in seconds, at a comfortable apparent speed.
68
+ *
69
+ * Derived from {@link length}, so it scales with the move rather than being a constant
70
+ * somebody picked. Use it as the default and let authors override it — then check the
71
+ * override against it.
72
+ */
73
+ readonly suggestedDuration: number;
74
+ /** The view at `t`, clamped to `[0, 1]`. `at(0)` is `from` and `at(1)` is `to`. */
75
+ at(t: number): View;
76
+ }
77
+ /** The shortest flight worth animating, in seconds. */
78
+ declare const MIN_FLIGHT_SECONDS = 0.2;
79
+ /** The longest a single flight should ever be asked to take, in seconds. */
80
+ declare const MAX_FLIGHT_SECONDS = 4;
81
+ /**
82
+ * Build the camera move between two views.
83
+ *
84
+ * @param from - where the camera starts
85
+ * @param to - where it ends
86
+ * @param rho - how far it pulls back on the way; see {@link DEFAULT_RHO}
87
+ */
88
+ declare function flight(from: View, to: View, rho?: number): Flight;
89
+ /**
90
+ * The perceived speed of a flight at `t`, in the units the flight is smooth in.
91
+ *
92
+ * Panning and zooming are different sensations, and the whole trick of the algorithm is
93
+ * that ρ is the exchange rate between them. Writing `a = ρs + r₀`, the path's pan
94
+ * component works out to `sech(a)/ρ` and its zoom component to `ρ·tanh(a)`, so
95
+ *
96
+ * ```text
97
+ * (ρ · pan)² + (zoom / ρ)² = sech²(a) + tanh²(a) = 1
98
+ * ```
99
+ *
100
+ * — identically, everywhere along the path. That identity *is* the guarantee: a viewer
101
+ * experiences one steady rate of change, however the flight trades travelling for
102
+ * zooming along the way. Measured here numerically, which makes it a property a test can
103
+ * assert rather than a claim in a comment.
104
+ *
105
+ * Returns speed per unit of `t`, so it is `length` for a well-formed flight.
106
+ */
107
+ declare function perceivedSpeedAt(f: Flight, t: number, h?: number): number;
108
+ /**
109
+ * How much bigger one view is than the other, as a factor ≥ 1.
110
+ *
111
+ * The number the motion-sickness rule is written in: a jump of more than about 8× in one
112
+ * move is the thing audiences report as disorienting, whatever interpolation gets it there.
113
+ */
114
+ declare function zoomRatio(a: View, b: View): number;
115
+
116
+ /**
117
+ * A path, plus time.
118
+ *
119
+ * The deck does not need this — a presenter is the clock, and the host only ever
120
+ * interpolates one flight at a time. Video and autoplay do: they need the whole path
121
+ * laid out on a timeline so the camera can be a pure function of a moment, with no
122
+ * memory of the moments before it.
123
+ *
124
+ * That purity is the requirement the video renderer imposes and the reason the camera is
125
+ * built from a closed-form flight rather than a spring. `at(12.5)` must give the same
126
+ * view whether or not second 12.4 was ever drawn, because the renderer advances a frame
127
+ * counter and may render frames out of order, in parallel, or not at all.
128
+ *
129
+ * @packageDocumentation
130
+ */
131
+
132
+ /** How a timeline gets from one waypoint to the next. */
133
+ type MotionStyle =
134
+ /** Fly, as authored. */
135
+ "fly"
136
+ /**
137
+ * Cut instantly, keeping the holds.
138
+ *
139
+ * What `prefers-reduced-motion: reduce` gets. A zooming canvas is the worst offender
140
+ * in this whole category for motion sensitivity, and honouring the preference by
141
+ * *cutting* — rather than by hiding the content, or by ignoring it — keeps every
142
+ * waypoint reachable and loses nothing but the swoop.
143
+ */
144
+ | "cut";
145
+ /** One stretch of the timeline: either resting at a waypoint, or travelling between two. */
146
+ type Segment = {
147
+ kind: "hold";
148
+ /** Seconds from the start of the path. */
149
+ start: number;
150
+ end: number;
151
+ waypoint: ResolvedWaypoint;
152
+ } | {
153
+ kind: "flight";
154
+ start: number;
155
+ end: number;
156
+ from: ResolvedWaypoint;
157
+ to: ResolvedWaypoint;
158
+ flight: Flight;
159
+ };
160
+ interface Timeline {
161
+ readonly segments: readonly Segment[];
162
+ /** Total length in seconds. */
163
+ readonly duration: number;
164
+ /** The view at a moment. Pure: no state, no history, safe to call out of order. */
165
+ at(seconds: number): View;
166
+ /** Which segment a moment falls in. */
167
+ segmentAt(seconds: number): Segment | null;
168
+ /** The waypoint a moment belongs to — the one being held, or the one being flown to. */
169
+ waypointAt(seconds: number): ResolvedWaypoint | null;
170
+ /** When the camera arrives at a waypoint, in seconds. */
171
+ arrivalOf(id: string): number | null;
172
+ }
173
+ interface TimelineOptions {
174
+ /** @defaultValue `"fly"` */
175
+ style?: MotionStyle;
176
+ /**
177
+ * A hold given to every waypoint that does not ask for one, in seconds.
178
+ *
179
+ * A video with no holds is a camera that never stops, which is unwatchable; the deck
180
+ * host passes 0 because a presenter provides the pauses.
181
+ *
182
+ * @defaultValue the path's `hold`, else 0
183
+ */
184
+ defaultHold?: number;
185
+ }
186
+ /**
187
+ * Lay a resolved path out on a timeline.
188
+ *
189
+ * Empty paths and single-waypoint paths are legal and produce a timeline that holds
190
+ * still, which is what a host wants while a scene is still loading.
191
+ */
192
+ declare function timeline(path: AtlasPath, waypoints: readonly ResolvedWaypoint[], options?: TimelineOptions): Timeline;
193
+
194
+ export { DEFAULT_RHO as D, type Flight as F, MAX_FLIGHT_SECONDS as M, type Segment as S, type Timeline as T, MIN_FLIGHT_SECONDS as a, type MotionStyle as b, type TimelineOptions as c, flight as f, perceivedSpeedAt as p, timeline as t, zoomRatio as z };