framefields 2.12.0 → 2.13.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.
- package/dist/audio/index.d.mts +6 -5
- package/dist/audio/index.mjs +13 -13
- package/dist/{audio-afTBdB3U.d.mts → audio-CHV33Od9.d.mts} +3 -4
- package/dist/audio-CHV33Od9.d.mts.map +1 -0
- package/dist/{audio-DHLoNcxD.mjs → audio-DHzLQ_RG.mjs} +4 -4
- package/dist/{audio-DHLoNcxD.mjs.map → audio-DHzLQ_RG.mjs.map} +1 -1
- package/dist/{audio-KHBadECb.mjs → audio-DS9Jg2ls.mjs} +1 -1
- package/dist/{audio-KHBadECb.mjs.map → audio-DS9Jg2ls.mjs.map} +1 -1
- package/dist/{check-result-Culfu7Nx.mjs → check-result-DZWx73V8.mjs} +1 -1
- package/dist/{check-result-Culfu7Nx.mjs.map → check-result-DZWx73V8.mjs.map} +1 -1
- package/dist/cli/check-worker.mjs +51 -51
- package/dist/cli/check-worker.mjs.map +1 -1
- package/dist/cli/local-worker.d.mts +28 -0
- package/dist/cli/local-worker.d.mts.map +1 -0
- package/dist/cli/local-worker.mjs +83 -0
- package/dist/cli/local-worker.mjs.map +1 -0
- package/dist/cli.mjs +229 -54
- package/dist/cli.mjs.map +1 -1
- package/dist/cloud/index.d.mts +2 -0
- package/dist/cloud/index.d.mts.map +1 -1
- package/dist/cloud/index.mjs +1 -1
- package/dist/{cloud-COid1Ipa.mjs → cloud-Cj1TIme8.mjs} +306 -7
- package/dist/cloud-Cj1TIme8.mjs.map +1 -0
- package/dist/components/index.d.mts +15 -14
- package/dist/components/index.mjs +47 -47
- package/dist/{dist-CySGkG54.mjs → dist-BSKAbyea.mjs} +2 -2
- package/dist/{dist-CySGkG54.mjs.map → dist-BSKAbyea.mjs.map} +1 -1
- package/dist/{dist-BwtxciN4.mjs → dist-B_rSWeOz.mjs} +1 -1
- package/dist/{dist-Bf2_efYP.mjs → dist-C8XSqfTx.mjs} +1 -1
- package/dist/{dist-Bf2_efYP.mjs.map → dist-C8XSqfTx.mjs.map} +1 -1
- package/dist/{dist-BSqeChht.mjs → dist-CUNvNDS3.mjs} +129 -82
- package/dist/dist-CUNvNDS3.mjs.map +1 -0
- package/dist/dist-CiTgmfUw.mjs +502 -0
- package/dist/dist-CiTgmfUw.mjs.map +1 -0
- package/dist/{dist-B-OPY7Cs.mjs → dist-D3BeAZ7X.mjs} +44 -10
- package/dist/dist-D3BeAZ7X.mjs.map +1 -0
- package/dist/{dist-BIE8fvlv.mjs → dist-DgZpN6IK.mjs} +1 -1
- package/dist/{dist-BIE8fvlv.mjs.map → dist-DgZpN6IK.mjs.map} +1 -1
- package/dist/{dist-2_lT3Dwc.mjs → dist-Kbf-JTry.mjs} +2 -2
- package/dist/{dist-2_lT3Dwc.mjs.map → dist-Kbf-JTry.mjs.map} +1 -1
- package/dist/effects/index.d.mts +6 -6
- package/dist/effects/index.mjs +36 -36
- package/dist/{effects-C8CL9cOq.mjs → effects-BiDgCK1L.mjs} +3 -3
- package/dist/{effects-C8CL9cOq.mjs.map → effects-BiDgCK1L.mjs.map} +1 -1
- package/dist/fonts/index.d.mts +1 -1
- package/dist/fonts/index.mjs +3 -3
- package/dist/{fonts-BQrDjnpV.mjs → fonts-DeDPWdvJ.mjs} +2 -2
- package/dist/{fonts-BQrDjnpV.mjs.map → fonts-DeDPWdvJ.mjs.map} +1 -1
- package/dist/{gpu-DHa-3Dou.mjs → gpu-CWjgql4C.mjs} +2 -2
- package/dist/{gpu-DHa-3Dou.mjs.map → gpu-CWjgql4C.mjs.map} +1 -1
- package/dist/index-BFTtXosj.d.mts +63 -0
- package/dist/index-BFTtXosj.d.mts.map +1 -0
- package/dist/{index-BW0Q1ygn.d.mts → index-C5sgRLlG.d.mts} +1 -1
- package/dist/{index-BW0Q1ygn.d.mts.map → index-C5sgRLlG.d.mts.map} +1 -1
- package/dist/{index-CdCrae-I.d.mts → index-CnZhBlob.d.mts} +2 -2
- package/dist/index-CnZhBlob.d.mts.map +1 -0
- package/dist/{index-CTv9ZieD.d.mts → index-D68eGFNE.d.mts} +3 -30
- package/dist/index-D68eGFNE.d.mts.map +1 -0
- package/dist/{index-WwuuX8ZJ.d.mts → index-DBh3WphX.d.mts} +2 -2
- package/dist/{index-WwuuX8ZJ.d.mts.map → index-DBh3WphX.d.mts.map} +1 -1
- package/dist/{index-gOVtTm-P.d.mts → index-DQ1U3HC3.d.mts} +14 -6
- package/dist/index-DQ1U3HC3.d.mts.map +1 -0
- package/dist/{index-CAuNuvIg.d.mts → index-Dibc_xpC.d.mts} +5 -5
- package/dist/{index-CAuNuvIg.d.mts.map → index-Dibc_xpC.d.mts.map} +1 -1
- package/dist/{index-BNClElng.d.mts → index-E2IXX0Cz.d.mts} +282 -284
- package/dist/index-E2IXX0Cz.d.mts.map +1 -0
- package/dist/{index-Fliv2Z-w.d.mts → index-X2u96TEo.d.mts} +131 -5
- package/dist/index-X2u96TEo.d.mts.map +1 -0
- package/dist/{index-Drk6HC2k.d.mts → index-YB9j9KUx.d.mts} +3 -3
- package/dist/{index-Drk6HC2k.d.mts.map → index-YB9j9KUx.d.mts.map} +1 -1
- package/dist/{index-COYNQ_mf.d.mts → index-a6JrbAK0.d.mts} +1 -1
- package/dist/{index-COYNQ_mf.d.mts.map → index-a6JrbAK0.d.mts.map} +1 -1
- package/dist/{index-DQa7mhzq.d.mts → index-rtCXNxGO.d.mts} +2 -2
- package/dist/{index-DQa7mhzq.d.mts.map → index-rtCXNxGO.d.mts.map} +1 -1
- package/dist/index.d.mts +16 -15
- package/dist/index.mjs +58 -58
- package/dist/preview/index.d.mts +5 -4
- package/dist/preview/index.mjs +12 -12
- package/dist/{preview-BUtt481A.mjs → preview-PDz7o2xg.mjs} +323 -35
- package/dist/preview-PDz7o2xg.mjs.map +1 -0
- package/dist/preview-engine/audio.mjs +1 -1
- package/dist/preview-engine/chunk-4P3MMJWD.mjs +1 -0
- package/dist/preview-engine/{chunk-4JNIEQS2.mjs → chunk-77WWW75O.mjs} +1 -1
- package/dist/preview-engine/{chunk-UKZVUYB6.mjs → chunk-7M5LE6EM.mjs} +1 -1
- package/dist/preview-engine/{chunk-KBCZKIIQ.mjs → chunk-CYM75INB.mjs} +1 -1
- package/dist/preview-engine/chunk-FRJPXWAG.mjs +1 -0
- package/dist/preview-engine/{chunk-FJTURGJV.mjs → chunk-IEEV5RB3.mjs} +1 -1
- package/dist/preview-engine/chunk-LZNHGH56.mjs +5 -0
- package/dist/preview-engine/{chunk-RGKS6YIA.mjs → chunk-N2VISDTG.mjs} +1 -1
- package/dist/preview-engine/{chunk-O75BFM4S.mjs → chunk-NVXFEDJS.mjs} +1 -1
- package/dist/preview-engine/{chunk-VUO4K52B.mjs → chunk-PD2HKW2S.mjs} +1 -1
- package/dist/preview-engine/{chunk-HTAHYSCW.mjs → chunk-R4H52Q7S.mjs} +1 -1
- package/dist/preview-engine/{chunk-6O7UDVNL.mjs → chunk-RYSV44B4.mjs} +1 -1
- package/dist/preview-engine/{chunk-JAUMCOOJ.mjs → chunk-SMHEMYCV.mjs} +1 -1
- package/dist/preview-engine/{chunk-TKXOSEIJ.mjs → chunk-SV3ZNZQU.mjs} +1 -1
- package/dist/preview-engine/{chunk-32WC4FRR.mjs → chunk-UE7Y65C3.mjs} +3 -3
- package/dist/preview-engine/chunk-UFPA36D2.mjs +2227 -0
- package/dist/preview-engine/{chunk-M4QVCQ3O.mjs → chunk-VMXJZ4KQ.mjs} +1 -1
- package/dist/preview-engine/{chunk-EYKN2VET.mjs → chunk-XYCR3STG.mjs} +1 -1
- package/dist/preview-engine/components.mjs +1 -1
- package/dist/preview-engine/effects.mjs +1 -1
- package/dist/preview-engine/framefields.mjs +1 -1
- package/dist/preview-engine/player.mjs +5 -5
- package/dist/preview-engine/{renderers-L6GFGHS6.mjs → renderers-AFTUFMQQ.mjs} +1 -1
- package/dist/preview-engine/renderers-DU7KPFVV.mjs +1 -1
- package/dist/preview-engine/{renderers-PREFONJG.mjs → renderers-EPSF5CSQ.mjs} +1 -1
- package/dist/preview-engine/{renderers-R22CDFO4.mjs → renderers-OJWWIVNW.mjs} +1 -1
- package/dist/preview-engine/{renderers-TS32FDV7.mjs → renderers-RHMRWX46.mjs} +1 -1
- package/dist/preview-engine/{renderers-PSBTV2DO.mjs → renderers-TTXTV4PC.mjs} +1 -1
- package/dist/preview-engine/renderers-VEHYARKY.mjs +1 -0
- package/dist/preview-engine/renderers-VRXAG3FR.mjs +1 -0
- package/dist/preview-engine/renderers-ZHSOWEMQ.mjs +1 -0
- package/dist/preview-engine/renderers-ZMDRNYSS.mjs +1 -0
- package/dist/preview-engine/signals.mjs +1 -1
- package/dist/preview-engine/{src-GPF7RIBU.mjs → src-YXNX4MLK.mjs} +1 -1
- package/dist/{program-Cvv_nb9T.mjs → program-Bn6ko_I4.mjs} +4 -4
- package/dist/{program-Cvv_nb9T.mjs.map → program-Bn6ko_I4.mjs.map} +1 -1
- package/dist/renderer/index.d.mts +4 -3
- package/dist/renderer/index.mjs +3 -3
- package/dist/{renderer-CZClDMgF.mjs → renderer--Q0pM9Vh.mjs} +5 -5
- package/dist/{renderer-CZClDMgF.mjs.map → renderer--Q0pM9Vh.mjs.map} +1 -1
- package/dist/renderer-1aSF5BmY.mjs +5 -0
- package/dist/{renderer-tRi3KNoG.mjs → renderer-45ifiDkn.mjs} +5 -5
- package/dist/{renderer-tRi3KNoG.mjs.map → renderer-45ifiDkn.mjs.map} +1 -1
- package/dist/{renderer-BTd6tFcS.mjs → renderer-549d94qG.mjs} +5 -5
- package/dist/{renderer-BTd6tFcS.mjs.map → renderer-549d94qG.mjs.map} +1 -1
- package/dist/{renderer-CMCmljb0.mjs → renderer-B-LUGB9h.mjs} +3 -3
- package/dist/{renderer-CMCmljb0.mjs.map → renderer-B-LUGB9h.mjs.map} +1 -1
- package/dist/renderer-B2e4Gr9u.mjs +5 -0
- package/dist/{renderer-sMFJtt_H.mjs → renderer-B5b-ehRb.mjs} +3 -3
- package/dist/{renderer-sMFJtt_H.mjs.map → renderer-B5b-ehRb.mjs.map} +1 -1
- package/dist/renderer-B6fz4j50.mjs +5 -0
- package/dist/{renderer-BJEexai1.mjs → renderer-BCTUqJG7.mjs} +5 -5
- package/dist/{renderer-BJEexai1.mjs.map → renderer-BCTUqJG7.mjs.map} +1 -1
- package/dist/renderer-BEftIYw-.mjs +5 -0
- package/dist/{renderer-smP1F57W.mjs → renderer-BHPgkoEL.mjs} +4 -4
- package/dist/{renderer-smP1F57W.mjs.map → renderer-BHPgkoEL.mjs.map} +1 -1
- package/dist/renderer-BHTnO-7e.mjs +5 -0
- package/dist/{renderer-LrAldCWN.mjs → renderer-BIieU9pY.mjs} +5 -5
- package/dist/{renderer-LrAldCWN.mjs.map → renderer-BIieU9pY.mjs.map} +1 -1
- package/dist/{renderer-snjhPaP-.mjs → renderer-BKi6jDi_.mjs} +4 -4
- package/dist/{renderer-snjhPaP-.mjs.map → renderer-BKi6jDi_.mjs.map} +1 -1
- package/dist/renderer-BMlnQqTO.mjs +5 -0
- package/dist/{renderer-C2kFIZ4r.mjs → renderer-BMpmmYOq.mjs} +4 -4
- package/dist/{renderer-C2kFIZ4r.mjs.map → renderer-BMpmmYOq.mjs.map} +1 -1
- package/dist/{renderer-_KIBvES8.mjs → renderer-BO_wuUKA.mjs} +5 -5
- package/dist/{renderer-_KIBvES8.mjs.map → renderer-BO_wuUKA.mjs.map} +1 -1
- package/dist/renderer-BOzcFezY.mjs +5 -0
- package/dist/{renderer-DLaUT6JZ.mjs → renderer-BPOh9x5X.mjs} +4 -4
- package/dist/{renderer-DLaUT6JZ.mjs.map → renderer-BPOh9x5X.mjs.map} +1 -1
- package/dist/{renderer-CNpq7rL-.mjs → renderer-BPiWsrC2.mjs} +3 -3
- package/dist/{renderer-CNpq7rL-.mjs.map → renderer-BPiWsrC2.mjs.map} +1 -1
- package/dist/{renderer-t1sXDiqm.mjs → renderer-BUj7jE6t.mjs} +3 -3
- package/dist/{renderer-t1sXDiqm.mjs.map → renderer-BUj7jE6t.mjs.map} +1 -1
- package/dist/renderer-BVbjJIKJ.mjs +5 -0
- package/dist/renderer-BXsHKf4k.mjs +5 -0
- package/dist/{renderer-rKw5_EjG.mjs → renderer-BZgJj8Cl.mjs} +4 -4
- package/dist/{renderer-rKw5_EjG.mjs.map → renderer-BZgJj8Cl.mjs.map} +1 -1
- package/dist/{renderer-5fGj_tqN.mjs → renderer-BgjPbgbn.mjs} +6 -6
- package/dist/{renderer-5fGj_tqN.mjs.map → renderer-BgjPbgbn.mjs.map} +1 -1
- package/dist/{renderer-D3QPNBEj.mjs → renderer-BjgFpwVK.mjs} +5 -5
- package/dist/{renderer-D3QPNBEj.mjs.map → renderer-BjgFpwVK.mjs.map} +1 -1
- package/dist/renderer-Bks49NVl.mjs +5 -0
- package/dist/{renderer-HhJPFidU.mjs → renderer-BlZgyBSo.mjs} +5 -5
- package/dist/{renderer-HhJPFidU.mjs.map → renderer-BlZgyBSo.mjs.map} +1 -1
- package/dist/{renderer-DO_zXwMJ.mjs → renderer-BqUAyzCZ.mjs} +5 -5
- package/dist/{renderer-DO_zXwMJ.mjs.map → renderer-BqUAyzCZ.mjs.map} +1 -1
- package/dist/{renderer-O4y02xkD.mjs → renderer-Br8km655.mjs} +4 -4
- package/dist/{renderer-O4y02xkD.mjs.map → renderer-Br8km655.mjs.map} +1 -1
- package/dist/{renderer-DEoerd-j.mjs → renderer-BsMZ5Iaq.mjs} +4 -4
- package/dist/{renderer-DEoerd-j.mjs.map → renderer-BsMZ5Iaq.mjs.map} +1 -1
- package/dist/{renderer-BBjkX0Vt.mjs → renderer-BtgqTg2K.mjs} +3 -3
- package/dist/{renderer-BOg8i8eF.mjs → renderer-BuvKGwk3.mjs} +1 -1
- package/dist/{renderer-BOg8i8eF.mjs.map → renderer-BuvKGwk3.mjs.map} +1 -1
- package/dist/{renderer-DsPLMzy1.mjs → renderer-C-3qhk5x.mjs} +2 -2
- package/dist/{renderer-DsPLMzy1.mjs.map → renderer-C-3qhk5x.mjs.map} +1 -1
- package/dist/{renderer-BwV-o_1b.mjs → renderer-C-GTxnfv.mjs} +4 -4
- package/dist/{renderer-BwV-o_1b.mjs.map → renderer-C-GTxnfv.mjs.map} +1 -1
- package/dist/renderer-C-t7qFKr.mjs +5 -0
- package/dist/{renderer-Bxiiar50.mjs → renderer-C1ctHdXl.mjs} +4 -4
- package/dist/{renderer-Bxiiar50.mjs.map → renderer-C1ctHdXl.mjs.map} +1 -1
- package/dist/renderer-C24n-6CX.mjs +6 -0
- package/dist/{renderer-xNOKSN3V.mjs → renderer-C7m7Yxgc.mjs} +5 -5
- package/dist/{renderer-xNOKSN3V.mjs.map → renderer-C7m7Yxgc.mjs.map} +1 -1
- package/dist/{renderer-4uZGSmUD.mjs → renderer-C9dqx3U7.mjs} +5 -5
- package/dist/{renderer-4uZGSmUD.mjs.map → renderer-C9dqx3U7.mjs.map} +1 -1
- package/dist/renderer-CCReX-7M.mjs +5 -0
- package/dist/{renderer-CtpEzVBn.mjs → renderer-CE4535aW.mjs} +3 -3
- package/dist/{renderer-CtpEzVBn.mjs.map → renderer-CE4535aW.mjs.map} +1 -1
- package/dist/renderer-CIxFQrAm.mjs +5 -0
- package/dist/{renderer-6g1ri445.mjs → renderer-CL-vyXgX.mjs} +5 -5
- package/dist/{renderer-6g1ri445.mjs.map → renderer-CL-vyXgX.mjs.map} +1 -1
- package/dist/renderer-CLHBMIqJ.mjs +5 -0
- package/dist/renderer-CMJKi-Ye.mjs +5 -0
- package/dist/{renderer-Bf7Z7WT5.mjs → renderer-CMnlgAGs.mjs} +4 -4
- package/dist/{renderer-Bf7Z7WT5.mjs.map → renderer-CMnlgAGs.mjs.map} +1 -1
- package/dist/{renderer-uA0UCxjA.mjs → renderer-CQFMyC_c.mjs} +4 -4
- package/dist/{renderer-uA0UCxjA.mjs.map → renderer-CQFMyC_c.mjs.map} +1 -1
- package/dist/{renderer-DKFaVaRJ.mjs → renderer-CYwVNbUh.mjs} +4 -4
- package/dist/{renderer-DKFaVaRJ.mjs.map → renderer-CYwVNbUh.mjs.map} +1 -1
- package/dist/{renderer-DZIxduEt.mjs → renderer-CZ5mwBW1.mjs} +3 -3
- package/dist/{renderer-DZIxduEt.mjs.map → renderer-CZ5mwBW1.mjs.map} +1 -1
- package/dist/{renderer-BSsKlcHk.mjs → renderer-C_1eqfI0.mjs} +5 -5
- package/dist/{renderer-BSsKlcHk.mjs.map → renderer-C_1eqfI0.mjs.map} +1 -1
- package/dist/renderer-C_H3mHZa.mjs +5 -0
- package/dist/{renderer-CGpi8CDT.mjs → renderer-CciiDHuU.mjs} +4 -4
- package/dist/{renderer-CGpi8CDT.mjs.map → renderer-CciiDHuU.mjs.map} +1 -1
- package/dist/renderer-CfkTdR5B.mjs +5 -0
- package/dist/{renderer-DGXqspB9.mjs → renderer-CgK7Q1G8.mjs} +3 -3
- package/dist/{renderer-B09I95bo.mjs → renderer-CnL8Cmmr.mjs} +3 -3
- package/dist/renderer-CnhkXJ0R.mjs +5 -0
- package/dist/{renderer-BwJ0nXXu.mjs → renderer-Cp_HOmp7.mjs} +5 -5
- package/dist/{renderer-BwJ0nXXu.mjs.map → renderer-Cp_HOmp7.mjs.map} +1 -1
- package/dist/renderer-CqpyVsq_.mjs +5 -0
- package/dist/renderer-CtWgIpTE.mjs +5 -0
- package/dist/{renderer-C3CGYjrC.mjs → renderer-Cu3iKMSJ.mjs} +5 -5
- package/dist/{renderer-C3CGYjrC.mjs.map → renderer-Cu3iKMSJ.mjs.map} +1 -1
- package/dist/{renderer-BzTj_CeK.mjs → renderer-CuPAQjDK.mjs} +4 -4
- package/dist/{renderer-BzTj_CeK.mjs.map → renderer-CuPAQjDK.mjs.map} +1 -1
- package/dist/{renderer-FvvywNvv.mjs → renderer-CwlY8Tb-.mjs} +3 -3
- package/dist/{renderer-FvvywNvv.mjs.map → renderer-CwlY8Tb-.mjs.map} +1 -1
- package/dist/{renderer-ByFPmHBC.mjs → renderer-CxSFvKd1.mjs} +4 -4
- package/dist/{renderer-ByFPmHBC.mjs.map → renderer-CxSFvKd1.mjs.map} +1 -1
- package/dist/{renderer-DL3o_lG1.mjs → renderer-CyUiXLRa.mjs} +5 -5
- package/dist/{renderer-DL3o_lG1.mjs.map → renderer-CyUiXLRa.mjs.map} +1 -1
- package/dist/renderer-CyuKr5ca.mjs +5 -0
- package/dist/renderer-CzgcZXKH.mjs +3 -0
- package/dist/{renderer-DCMoBppL.mjs → renderer-D-GWBoVT.mjs} +3 -3
- package/dist/{renderer-DCMoBppL.mjs.map → renderer-D-GWBoVT.mjs.map} +1 -1
- package/dist/{renderer-IAffbHXx.mjs → renderer-D-fzEZfX.mjs} +4 -4
- package/dist/{renderer-IAffbHXx.mjs.map → renderer-D-fzEZfX.mjs.map} +1 -1
- package/dist/renderer-D5yp74NL.mjs +3 -0
- package/dist/{renderer-BC_Nse6h.mjs → renderer-D8gN7aqk.mjs} +4 -4
- package/dist/{renderer-BC_Nse6h.mjs.map → renderer-D8gN7aqk.mjs.map} +1 -1
- package/dist/renderer-D9ON_1Hf.mjs +5 -0
- package/dist/renderer-DAnTgh4o.mjs +5 -0
- package/dist/{renderer-Dak_wVh9.mjs → renderer-DGOMfyh8.mjs} +2 -2
- package/dist/{renderer-Dak_wVh9.mjs.map → renderer-DGOMfyh8.mjs.map} +1 -1
- package/dist/{renderer-CInMMs5x.mjs → renderer-DGb_wgTW.mjs} +6 -6
- package/dist/{renderer-CInMMs5x.mjs.map → renderer-DGb_wgTW.mjs.map} +1 -1
- package/dist/{renderer-DO1SZ5xY.mjs → renderer-DJPZs_zV.mjs} +5 -5
- package/dist/{renderer-DO1SZ5xY.mjs.map → renderer-DJPZs_zV.mjs.map} +1 -1
- package/dist/renderer-DKJOon6y.d.mts +1 -0
- package/dist/{renderer-DQJHjU0L.mjs → renderer-DOZPZTxe.mjs} +5 -5
- package/dist/{renderer-DQJHjU0L.mjs.map → renderer-DOZPZTxe.mjs.map} +1 -1
- package/dist/{renderer-DaY04IyU.mjs → renderer-DRal7Goa.mjs} +5 -5
- package/dist/{renderer-DaY04IyU.mjs.map → renderer-DRal7Goa.mjs.map} +1 -1
- package/dist/renderer-DWgpivA3.mjs +5 -0
- package/dist/{renderer-qVJdxbXB.mjs → renderer-DXFJv_e6.mjs} +4 -4
- package/dist/{renderer-qVJdxbXB.mjs.map → renderer-DXFJv_e6.mjs.map} +1 -1
- package/dist/renderer-Dh_qNNwR.mjs +5 -0
- package/dist/renderer-DkCpXPMP.mjs +5 -0
- package/dist/{renderer-PnnkUWm-.mjs → renderer-Dp5ChuOs.mjs} +4 -4
- package/dist/{renderer-PnnkUWm-.mjs.map → renderer-Dp5ChuOs.mjs.map} +1 -1
- package/dist/{renderer-C_gR2H3R.mjs → renderer-DpZballM.mjs} +3 -3
- package/dist/{renderer-C_gR2H3R.mjs.map → renderer-DpZballM.mjs.map} +1 -1
- package/dist/renderer-Dre-6cDR.mjs +5 -0
- package/dist/renderer-Ds5z4_HX.mjs +5 -0
- package/dist/{renderer-b0H3oDae.mjs → renderer-DwGlDNOV.mjs} +3 -3
- package/dist/{renderer-b0H3oDae.mjs.map → renderer-DwGlDNOV.mjs.map} +1 -1
- package/dist/{renderer-oBgCEqaM.mjs → renderer-DxB_OfLs.mjs} +4 -4
- package/dist/{renderer-oBgCEqaM.mjs.map → renderer-DxB_OfLs.mjs.map} +1 -1
- package/dist/{renderer-BvRM3OZ6.mjs → renderer-Dz6P-rPI.mjs} +3 -3
- package/dist/{renderer-BvRM3OZ6.mjs.map → renderer-Dz6P-rPI.mjs.map} +1 -1
- package/dist/{renderer-DrL3J9ci.mjs → renderer-FPiLBwj5.mjs} +4 -4
- package/dist/{renderer-DrL3J9ci.mjs.map → renderer-FPiLBwj5.mjs.map} +1 -1
- package/dist/renderer-HGSCOMyP.mjs +5 -0
- package/dist/renderer-KMY8W-cJ.mjs +5 -0
- package/dist/{renderer-BXGuCnCJ.mjs → renderer-KgsVk9c6.mjs} +4 -4
- package/dist/{renderer-BXGuCnCJ.mjs.map → renderer-KgsVk9c6.mjs.map} +1 -1
- package/dist/{renderer-CLTRTtQ8.mjs → renderer-LXhEPCmy.mjs} +5 -5
- package/dist/{renderer-CLTRTtQ8.mjs.map → renderer-LXhEPCmy.mjs.map} +1 -1
- package/dist/{renderer-CN-mCIoI.mjs → renderer-NBMmvqWl.mjs} +5 -5
- package/dist/{renderer-CN-mCIoI.mjs.map → renderer-NBMmvqWl.mjs.map} +1 -1
- package/dist/{renderer-HXw4cJZx.mjs → renderer-Nnu32rqg.mjs} +3 -3
- package/dist/{renderer-HXw4cJZx.mjs.map → renderer-Nnu32rqg.mjs.map} +1 -1
- package/dist/renderer-R21YvXz3.mjs +5 -0
- package/dist/{renderer-Dj54y3h3.mjs → renderer-RAq3LnNy.mjs} +3 -3
- package/dist/{renderer-Dj54y3h3.mjs.map → renderer-RAq3LnNy.mjs.map} +1 -1
- package/dist/renderer-RtzCz37W.mjs +5 -0
- package/dist/{renderer-CiHVnhUk.mjs → renderer-RzkU_O1i.mjs} +5 -5
- package/dist/{renderer-CiHVnhUk.mjs.map → renderer-RzkU_O1i.mjs.map} +1 -1
- package/dist/{renderer-D3YbHjRG.mjs → renderer-S9tVGneU.mjs} +95 -80
- package/dist/renderer-S9tVGneU.mjs.map +1 -0
- package/dist/{renderer-DP9ScNM6.mjs → renderer-SJyp9-62.mjs} +3 -3
- package/dist/{renderer-DP9ScNM6.mjs.map → renderer-SJyp9-62.mjs.map} +1 -1
- package/dist/{renderer-TiaHrU5z.mjs → renderer-_54hiCq0.mjs} +5 -5
- package/dist/{renderer-TiaHrU5z.mjs.map → renderer-_54hiCq0.mjs.map} +1 -1
- package/dist/{renderer-CcmxR6Fy.d.mts → renderer-aRk849b3.d.mts} +2 -2
- package/dist/{renderer-CcmxR6Fy.d.mts.map → renderer-aRk849b3.d.mts.map} +1 -1
- package/dist/{renderer-Dy1xQOTu.mjs → renderer-dGEMVaZ3.mjs} +4 -4
- package/dist/{renderer-Dy1xQOTu.mjs.map → renderer-dGEMVaZ3.mjs.map} +1 -1
- package/dist/{renderer-Dp9FQKVP.mjs → renderer-dRXP2dJU.mjs} +5 -5
- package/dist/{renderer-Dp9FQKVP.mjs.map → renderer-dRXP2dJU.mjs.map} +1 -1
- package/dist/{renderer-DwH5e3hx.mjs → renderer-g9SstkTz.mjs} +3 -3
- package/dist/renderer-iMrqAjix.mjs +5 -0
- package/dist/{renderer-DX60Kr20.mjs → renderer-opVuiAQR.mjs} +3 -3
- package/dist/{renderer-DX60Kr20.mjs.map → renderer-opVuiAQR.mjs.map} +1 -1
- package/dist/{renderer-DrK6pyfz.mjs → renderer-puQhVfDS.mjs} +5 -5
- package/dist/{renderer-DrK6pyfz.mjs.map → renderer-puQhVfDS.mjs.map} +1 -1
- package/dist/renderer-sMed1u1O.mjs +5 -0
- package/dist/renderer-tsAuo46l.mjs +5 -0
- package/dist/{renderer-DaLy1dFA.mjs → renderer-vll8LX0I.mjs} +9 -9
- package/dist/{renderer-DaLy1dFA.mjs.map → renderer-vll8LX0I.mjs.map} +1 -1
- package/dist/{renderers-CP8CFWDs-DWRjjeUg.mjs → renderers-CP8CFWDs-BRQyvcut.mjs} +4 -4
- package/dist/{renderers-CP8CFWDs-DWRjjeUg.mjs.map → renderers-CP8CFWDs-BRQyvcut.mjs.map} +1 -1
- package/dist/sam/index.d.mts +3 -3
- package/dist/sam/index.mjs +1 -1
- package/dist/signals/index.d.mts +7 -7
- package/dist/signals/index.mjs +8 -8
- package/dist/{signals-BfJ1aqpT.mjs → signals-tiho_9fp.mjs} +4 -4
- package/dist/{signals-BfJ1aqpT.mjs.map → signals-tiho_9fp.mjs.map} +1 -1
- package/dist/{src-BU6vm9sP-DFJ3phW0.mjs → src-BU6vm9sP-DdTHmCr4.mjs} +3 -3
- package/dist/{src-BU6vm9sP-DFJ3phW0.mjs.map → src-BU6vm9sP-DdTHmCr4.mjs.map} +1 -1
- package/dist/{src-D7M8VTzv.mjs → src-BzFGotRc.mjs} +11 -11
- package/dist/{src-D7M8VTzv.mjs.map → src-BzFGotRc.mjs.map} +1 -1
- package/dist/storage/index.d.mts +206 -0
- package/dist/storage/index.d.mts.map +1 -0
- package/dist/storage/index.mjs +3 -0
- package/package.json +67 -59
- package/dist/audio-afTBdB3U.d.mts.map +0 -1
- package/dist/cloud-COid1Ipa.mjs.map +0 -1
- package/dist/dist-B-OPY7Cs.mjs.map +0 -1
- package/dist/dist-BSqeChht.mjs.map +0 -1
- package/dist/index-BNClElng.d.mts.map +0 -1
- package/dist/index-CTv9ZieD.d.mts.map +0 -1
- package/dist/index-CdCrae-I.d.mts.map +0 -1
- package/dist/index-Fliv2Z-w.d.mts.map +0 -1
- package/dist/index-gOVtTm-P.d.mts.map +0 -1
- package/dist/preview-BUtt481A.mjs.map +0 -1
- package/dist/preview-engine/chunk-5SLBE2FX.mjs +0 -1
- package/dist/preview-engine/chunk-74J4DJQA.mjs +0 -1
- package/dist/preview-engine/chunk-KIPXBOYG.mjs +0 -1974
- package/dist/preview-engine/chunk-P4Y3PMJF.mjs +0 -1
- package/dist/preview-engine/chunk-SE2C623Z.mjs +0 -5
- package/dist/preview-engine/renderers-TEKHQHCL.mjs +0 -1
- package/dist/preview-engine/renderers-U3RLYFCN.mjs +0 -1
- package/dist/preview-engine/renderers-V5HM6PZ2.mjs +0 -1
- package/dist/preview-engine/renderers-ZGX3A3QZ.mjs +0 -1
- package/dist/renderer--RJepuOy.mjs +0 -5
- package/dist/renderer-B1U6YRRV.d.mts +0 -117
- package/dist/renderer-B1U6YRRV.d.mts.map +0 -1
- package/dist/renderer-B4NFnaty.mjs +0 -5
- package/dist/renderer-B6g4ts0e.mjs +0 -5
- package/dist/renderer-BGoAr-tl.mjs +0 -5
- package/dist/renderer-BSOdBgdE.mjs +0 -5
- package/dist/renderer-BU6O0fpM.mjs +0 -5
- package/dist/renderer-BZ97iidG.mjs +0 -5
- package/dist/renderer-B_Klh1VT.mjs +0 -5
- package/dist/renderer-BaWQM1y-.mjs +0 -5
- package/dist/renderer-Bc0jvAj0.mjs +0 -5
- package/dist/renderer-Bd-NuU3M.mjs +0 -3
- package/dist/renderer-Bm077kZ6.mjs +0 -5
- package/dist/renderer-BtzE_e_Y.mjs +0 -5
- package/dist/renderer-C225eCAo.mjs +0 -5
- package/dist/renderer-CC4fhNup.mjs +0 -3
- package/dist/renderer-CLXdR4vz.mjs +0 -5
- package/dist/renderer-CszM8B3C.mjs +0 -5
- package/dist/renderer-Ctp8ZYfk.mjs +0 -5
- package/dist/renderer-CxQdSp8I.mjs +0 -5
- package/dist/renderer-D3YbHjRG.mjs.map +0 -1
- package/dist/renderer-D4GMRtDd.mjs +0 -6
- package/dist/renderer-DEsGIvTZ.mjs +0 -5
- package/dist/renderer-DIaUQ0EP.mjs +0 -5
- package/dist/renderer-DLjqcNzQ.mjs +0 -5
- package/dist/renderer-DMuQZa51.mjs +0 -5
- package/dist/renderer-DOgi2aF2.mjs +0 -5
- package/dist/renderer-DSh7cvi4.mjs +0 -5
- package/dist/renderer-DV1LVBBP.mjs +0 -5
- package/dist/renderer-Dhrj2bJz.mjs +0 -5
- package/dist/renderer-DoFUu1R4.mjs +0 -5
- package/dist/renderer-DsydX3cx.mjs +0 -5
- package/dist/renderer-DxVSoeYi.mjs +0 -5
- package/dist/renderer-Nc9bHb0n.mjs +0 -5
- package/dist/renderer-TFaSD2Db.mjs +0 -5
- package/dist/renderer-Z61ctppe.mjs +0 -5
- package/dist/renderer-awXf3sNS.mjs +0 -5
- package/dist/renderer-cHT28oYI.mjs +0 -5
- package/dist/renderer-g7Y3bFot.mjs +0 -5
- package/dist/renderer-qVv0OzVc.mjs +0 -5
- /package/dist/{chunk-W71TSjRM.mjs → chunk-DOL2i2sR.mjs} +0 -0
- /package/dist/{renderer-B6rZUBwE.mjs → renderer-Co2YqWX0.mjs} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview-PDz7o2xg.mjs","names":["url","resolve"],"sources":["../src/preview/bundle.ts","../src/preview/notes.ts","../src/preview/page.ts","../src/preview/peaks.ts","../src/preview/index.ts"],"sourcesContent":["/**\n * The preview page runs the project's composition code and renders it with\n * WebGPU. Two bundles make that work:\n *\n * - The engine (`buildEngine`): framefields and its browser player, built once\n * for the browser. Published packages ship it in `dist/preview-engine`;\n * running from source, it is built on first use.\n * - The project (`bundleProject`): only the user's own files, built on each\n * preview. Its `framefields` imports point at the engine's files, so the\n * composition and the player share one engine (signals, clock, caches).\n *\n * Node APIs that compositions use at build time are shimmed: `path`, `url`,\n * `os`, `import.meta.url` (each file keeps its own `file://` URL), and `fs`\n * reads, which fetch the file from the preview server. Absolute file paths\n * double as URLs there, so assets referenced by path load unchanged.\n * Node-only packages become stubs that throw when used.\n */\nimport fs from \"node:fs\";\nimport { createRequire, isBuiltin } from \"node:module\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport { fileURLToPath, pathToFileURL } from \"node:url\";\nimport type { BuildOptions, PluginBuild } from \"esbuild\";\n\nconst HERE = path.dirname(fileURLToPath(import.meta.url));\n// Spelled in two parts so this file survives its own rewrite.\nconst META_URL = [\"import\", \"meta\", \"url\"].join(\".\");\nconst require = createRequire(import.meta.url);\n\n/** URL prefix the server serves the engine's files under. */\nexport const ENGINE_URL = \"/@framefields/engine\";\n\n/** Public entry points a composition may import, and their sources. */\nconst ENTRIES: Record<string, string> = {\n\tframefields: \"index.ts\",\n\t\"framefields/effects\": \"effects/index.ts\",\n\t\"framefields/audio\": \"audio/index.ts\",\n\t\"framefields/signals\": \"signals/index.ts\",\n\t\"framefields/fonts\": \"fonts/index.ts\",\n\t// Installed components import their toolkit from here (`framefields add`).\n\t\"framefields/components\": \"components/index.ts\",\n};\n\n/** Node built-ins with a browser version (shim file names); others are stubbed. */\nconst SHIMS: Record<string, string> = {\n\tpath: \"path\",\n\t\"path/posix\": \"path\",\n\turl: \"url\",\n\tos: \"os\",\n\tfs: \"fs\",\n\t\"fs/promises\": \"fs-promises\",\n\tmodule: \"module\",\n};\n\n/** Packages that only work in Node; the browser never needs them to draw. */\nconst NODE_ONLY = new Set([\n\t\"sharp\",\n\t\"skia-canvas\",\n\t\"node-av\",\n\t\"onnxruntime-node\",\n\t\"@resvg/resvg-js\",\n\t\"@napi-rs/webcodecs\",\n\t\"@mediabunny/server\",\n\t\"@mediabunny/mp3-encoder\",\n\t\"webgpu\",\n\t\"pino\",\n\t\"pino-pretty\",\n\t\"dotenv\",\n\t\"esbuild\",\n\t\"@framefields/renderer\",\n\t\"framefields/renderer\",\n\t\"framefields/preview\",\n]);\n\n// Reported as an ES module so named imports (`import { homedir } from \"os\"`)\n// read through the proxy instead of being copied off it.\nconst STUB = `\nconst stub = () => new Proxy(function () {}, {\n\tget: (_t, k) => (k === \"__esModule\" ? true : k === Symbol.toPrimitive ? () => \"\" : stub()),\n\tapply: () => stub(),\n\tconstruct: () => stub(),\n});\nmodule.exports = stub();\n`;\n\n/**\n * Builds framefields for the browser into `outDir`: one module per public entry,\n * the player and the shims, with shared code split into chunks (node renderers\n * load only when a composition uses them).\n */\nexport async function buildEngine(outDir: string): Promise<void> {\n\tconst { build } = await import(\"esbuild\");\n\tconst src = path.resolve(HERE, \"..\");\n\tconst shims = path.join(HERE, \"shims\");\n\tconst entryPoints: Record<string, string> = {\n\t\tplayer: path.join(HERE, \"player.ts\"),\n\t};\n\tfor (const [spec, file] of Object.entries(ENTRIES)) {\n\t\tentryPoints[engineName(spec)] = path.join(src, file);\n\t}\n\tfor (const name of new Set(Object.values(SHIMS))) {\n\t\tentryPoints[`shims/${name}`] = path.join(shims, `${name}.ts`);\n\t}\n\n\tawait fs.promises.rm(outDir, { recursive: true, force: true });\n\tawait build({\n\t\t...BROWSER,\n\t\tentryPoints,\n\t\toutdir: outDir,\n\t\toutExtension: { \".js\": \".mjs\" },\n\t\tsplitting: true,\n\t\tminify: true,\n\t\tconditions: [\"development\", \"browser\", \"import\"],\n\t\tdefine: { \"process.env.NODE_ENV\": '\"production\"', global: \"globalThis\" },\n\t\tinject: [path.join(shims, \"process.ts\")],\n\t\tplugins: [\n\t\t\t{\n\t\t\t\tname: \"framefields-engine\",\n\t\t\t\tsetup(b) {\n\t\t\t\t\tb.onResolve({ filter: /^framefields-preview:renderers$/ }, () => ({\n\t\t\t\t\t\tpath: builtinRenderersModule(),\n\t\t\t\t\t}));\n\t\t\t\t\tresolveNode(b, (name) => ({ path: path.join(shims, `${name}.ts`) }));\n\t\t\t\t},\n\t\t\t},\n\t\t],\n\t});\n}\n\nexport interface ProjectBundle {\n\tcode: string;\n\t/** The project's source files, to watch for changes. */\n\tinputs: string[];\n}\n\n/** Bundles the module that builds the composition, and what it imports. */\nexport async function bundleProject(options: {\n\t/** Module that exports the composition factory. */\n\tentry: string;\n\t/** Name of the export; a function returning a Composition (or one itself). */\n\texportName: string;\n\t/** Where cached vision models are, and where onnxruntime-web loads from. */\n\tmodelsDir: string;\n\tortBase: string;\n}): Promise<ProjectBundle> {\n\tconst { build } = await import(\"esbuild\");\n\tconst entry = path.resolve(options.entry);\n\tconst fromEngine = (file: string) => ({\n\t\tpath: `${ENGINE_URL}/${file}.mjs`,\n\t\texternal: true,\n\t});\n\tconst settings = { modelsDir: options.modelsDir, ortBase: options.ortBase };\n\n\tconst result = await build({\n\t\t...BROWSER,\n\t\tentryPoints: [\"framefields-preview:entry\"],\n\t\twrite: false,\n\t\tmetafile: true,\n\t\tsourcemap: \"inline\",\n\t\tplugins: [\n\t\t\t{\n\t\t\t\tname: \"framefields-project\",\n\t\t\t\tsetup(b) {\n\t\t\t\t\tb.onResolve({ filter: /^framefields-preview:entry$/ }, () => ({\n\t\t\t\t\t\tpath: \"entry\",\n\t\t\t\t\t\tnamespace: \"framefields-preview\",\n\t\t\t\t\t}));\n\t\t\t\t\tb.onLoad({ filter: /.*/, namespace: \"framefields-preview\" }, () => ({\n\t\t\t\t\t\tcontents: [\n\t\t\t\t\t\t\t`import { startAudioMixer, startPlayer } from ${JSON.stringify(`${ENGINE_URL}/player.mjs`)};`,\n\t\t\t\t\t\t\t`import * as mod from ${JSON.stringify(entry)};`,\n\t\t\t\t\t\t\t// The page loads it as the player; a worker it starts, as the soundtrack's mixer.\n\t\t\t\t\t\t\t`if (typeof document === \"undefined\") startAudioMixer(mod, ${JSON.stringify(options.exportName)});`,\n\t\t\t\t\t\t\t`else startPlayer(mod, ${JSON.stringify(options.exportName)}, ${JSON.stringify(settings)});`,\n\t\t\t\t\t\t].join(\"\\n\"),\n\t\t\t\t\t\tresolveDir: path.dirname(entry),\n\t\t\t\t\t\tloader: \"js\",\n\t\t\t\t\t}));\n\t\t\t\t\tb.onResolve({ filter: /^\\/@framefields\\/engine\\// }, (args) => ({\n\t\t\t\t\t\tpath: args.path,\n\t\t\t\t\t\texternal: true,\n\t\t\t\t\t}));\n\t\t\t\t\tb.onResolve({ filter: /^framefields(\\/|$)/ }, (args) =>\n\t\t\t\t\t\tENTRIES[args.path] ? fromEngine(engineName(args.path)) : undefined,\n\t\t\t\t\t);\n\t\t\t\t\tresolveNode(b, (name) => fromEngine(`shims/${name}`));\n\t\t\t\t\trewriteMetaUrl(b);\n\t\t\t\t},\n\t\t\t},\n\t\t],\n\t});\n\n\tconst inputs = Object.keys(result.metafile?.inputs ?? {})\n\t\t.filter((p) => !p.includes(\":\") && !p.includes(\"node_modules\"))\n\t\t.map((p) => path.resolve(p));\n\treturn { code: result.outputFiles[0].text, inputs };\n}\n\nlet engineBuild: Promise<string> | undefined;\n\n/**\n * The prebuilt engine shipped beside a compiled bundle, if there is one. The\n * bundle lands either at the package's dist root or beside dist/preview,\n * depending on how the bundler splits it, so both are checked.\n */\nexport function prebuiltEngineDir(here: string): string | undefined {\n\treturn [here, path.join(here, \"..\")]\n\t\t.map((base) => path.join(base, \"preview-engine\"))\n\t\t.find((dir) => fs.existsSync(path.join(dir, \"player.mjs\")));\n}\n\n/**\n * The engine to serve: prebuilt in the package's dist, or, running from\n * source, built once per process (so engine edits show without a rebuild).\n */\nexport function engineDir(): Promise<string> {\n\t// From source HERE is src/preview, so this misses and the engine is built.\n\tconst prebuilt = prebuiltEngineDir(HERE);\n\tif (prebuilt) {\n\t\treturn Promise.resolve(prebuilt);\n\t}\n\tengineBuild ??= (async () => {\n\t\tconst dir = path.join(\n\t\t\tos.tmpdir(),\n\t\t\t`framefields-preview-engine-${process.pid}`,\n\t\t);\n\t\tawait buildEngine(dir);\n\t\treturn dir;\n\t})();\n\tengineBuild.catch(() => {\n\t\tengineBuild = undefined;\n\t});\n\treturn engineBuild;\n}\n\n/** Where vision models are cached, as `@framefields/vision` resolves it. */\nexport function visionModelsDir(): string {\n\treturn process.env.FRAMEFIELDS_MODELS_DIR\n\t\t? path.resolve(process.env.FRAMEFIELDS_MODELS_DIR)\n\t\t: path.join(os.homedir(), \".cache\", \"framefields\", \"models\");\n}\n\nconst ORT_WEB_VERSION = \"1.30.0\";\n\n/**\n * onnxruntime-web, for vision effects: the project's own install when it has\n * one (served from disk), otherwise a pinned CDN build. Loaded only when a\n * vision effect runs, so framefields doesn't ship its WebAssembly.\n */\nexport function onnxRuntimeWeb(fromDir: string): {\n\tbase: string;\n\tdir?: string;\n} {\n\tfor (const from of [fromDir, HERE]) {\n\t\ttry {\n\t\t\tconst main = createRequire(path.join(from, \"noop.js\")).resolve(\n\t\t\t\t\"onnxruntime-web\",\n\t\t\t);\n\t\t\tconst dir = path.dirname(main);\n\t\t\treturn { base: `/@fs${dir}/`, dir };\n\t\t} catch {}\n\t}\n\treturn {\n\t\tbase: `https://cdn.jsdelivr.net/npm/onnxruntime-web@${ORT_WEB_VERSION}/dist/`,\n\t};\n}\n\n// ── shared ──\n\nconst BROWSER: BuildOptions = {\n\tbundle: true,\n\tformat: \"esm\",\n\tplatform: \"browser\",\n\ttarget: \"es2022\",\n\tmainFields: [\"browser\", \"module\", \"main\"],\n\tlogLevel: \"silent\",\n};\n\nfunction engineName(spec: string): string {\n\treturn spec === \"framefields\"\n\t\t? \"framefields\"\n\t\t: spec.slice(\"framefields/\".length);\n}\n\n/** Node built-ins to shims, the rest of Node to stubs. */\nfunction resolveNode(\n\tb: PluginBuild,\n\tshim: (name: string) => { path: string; external?: boolean },\n): void {\n\tb.onResolve({ filter: /^(node:)?[a-z_/]+$/ }, (args) => {\n\t\tconst name = args.path.replace(/^node:/, \"\");\n\t\tif (SHIMS[name]) return shim(SHIMS[name]);\n\t\tif (args.path.startsWith(\"node:\") || isBuiltin(name))\n\t\t\treturn { path: name, namespace: \"framefields-stub\" };\n\t\treturn undefined;\n\t});\n\tb.onResolve({ filter: /.*/ }, (args) => {\n\t\tconst pkg = packageName(args.path);\n\t\tif (pkg && (NODE_ONLY.has(pkg) || NODE_ONLY.has(args.path)))\n\t\t\treturn { path: args.path, namespace: \"framefields-stub\" };\n\t\treturn undefined;\n\t});\n\tb.onLoad({ filter: /.*/, namespace: \"framefields-stub\" }, () => ({\n\t\tcontents: STUB,\n\t\tloader: \"js\",\n\t}));\n}\n\n/** Each project file keeps its own URL, as `fileURLToPath(import.meta.url)` expects. */\nfunction rewriteMetaUrl(b: PluginBuild): void {\n\tb.onLoad({ filter: /\\.[cm]?[jt]sx?$/ }, async (args) => {\n\t\tif (args.path.includes(`${path.sep}node_modules${path.sep}`))\n\t\t\treturn undefined;\n\t\tconst source = await fs.promises.readFile(args.path, \"utf8\");\n\t\tif (!source.includes(META_URL)) return undefined;\n\t\treturn {\n\t\t\tcontents: `const __framefieldsFileUrl = ${JSON.stringify(pathToFileURL(args.path).href)};\\n${source.replaceAll(META_URL, \"__framefieldsFileUrl\")}`,\n\t\t\tloader: loaderFor(args.path),\n\t\t};\n\t});\n}\n\n/** The generated list of node renderers (needed only to build the engine). */\nfunction builtinRenderersModule(): string {\n\tconst dist = require.resolve(\"@framefields/renderer\");\n\tconst file = path.join(\n\t\tpath.dirname(dist),\n\t\t\"..\",\n\t\t\"src\",\n\t\t\"generated\",\n\t\t\"node-renderers.ts\",\n\t);\n\tif (!fs.existsSync(file))\n\t\tthrow new Error(`[framefields] node renderer list not found at ${file}`);\n\treturn file;\n}\n\nfunction packageName(spec: string): string | undefined {\n\tif (spec.startsWith(\".\") || spec.startsWith(\"/\")) return undefined;\n\tconst parts = spec.split(\"/\");\n\treturn spec.startsWith(\"@\") ? parts.slice(0, 2).join(\"/\") : parts[0];\n}\n\nfunction loaderFor(file: string): \"ts\" | \"tsx\" | \"js\" | \"jsx\" {\n\tif (file.endsWith(\".tsx\")) return \"tsx\";\n\tif (file.endsWith(\".jsx\")) return \"jsx\";\n\treturn /\\.[cm]?ts$/.test(file) ? \"ts\" : \"js\";\n}\n","/**\n * Notes the user pins on the preview: a moment, a spot (or an area) on the\n * frame, and what should change there. They are kept as files so whoever\n * edits the film (a person, or an agent) can read them: `notes.json`, plus the\n * frame with the spot marked as `note-<id>.jpg`. The folder ignores itself in\n * git. `notes.json` is read on every change, so it can be edited by hand\n * (marking a note done, say) while the preview runs.\n */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\nexport interface PreviewNote {\n\tid: number;\n\tframe: number;\n\t/** Seconds into the film. */\n\ttime: number;\n\t/** The spot, as fractions of the frame's width and height (0..1). */\n\tx: number;\n\ty: number;\n\t/** Set when an area was marked: its size, from the spot at its top left. */\n\tw?: number;\n\th?: number;\n\ttext: string;\n\t/** The frame with the spot marked (absolute path), when it was captured. */\n\timage?: string;\n\tdone: boolean;\n\tcreated: string;\n}\n\nexport interface NewNote {\n\tframe: number;\n\ttime: number;\n\tx: number;\n\ty: number;\n\tw?: number;\n\th?: number;\n\ttext: string;\n\t/** JPEG as a base64 data URL. */\n\timage?: string;\n}\n\nexport class NoteStore {\n\treadonly file: string;\n\n\tconstructor(readonly dir: string) {\n\t\tthis.file = path.join(dir, \"notes.json\");\n\t}\n\n\tasync list(): Promise<PreviewNote[]> {\n\t\ttry {\n\t\t\tconst notes = JSON.parse(await fs.promises.readFile(this.file, \"utf8\"));\n\t\t\treturn Array.isArray(notes) ? notes : [];\n\t\t} catch {\n\t\t\treturn [];\n\t\t}\n\t}\n\n\tasync add(input: NewNote): Promise<PreviewNote> {\n\t\tconst notes = await this.list();\n\t\tconst id = notes.reduce((max, n) => Math.max(max, n.id), 0) + 1;\n\t\tconst note: PreviewNote = {\n\t\t\tid,\n\t\t\tframe: Math.max(0, Math.round(Number(input.frame) || 0)),\n\t\t\ttime: round(Number(input.time) || 0, 3),\n\t\t\tx: round(clamp01(input.x), 4),\n\t\t\ty: round(clamp01(input.y), 4),\n\t\t\t...(input.w !== undefined &&\n\t\t\t\tinput.h !== undefined && {\n\t\t\t\t\tw: round(clamp01(input.w), 4),\n\t\t\t\t\th: round(clamp01(input.h), 4),\n\t\t\t\t}),\n\t\t\ttext: String(input.text ?? \"\").trim(),\n\t\t\tdone: false,\n\t\t\tcreated: new Date().toISOString(),\n\t\t};\n\t\tawait this.ensureDir();\n\t\tconst jpeg = /^data:image\\/jpeg;base64,(.+)$/.exec(input.image ?? \"\");\n\t\tif (jpeg) {\n\t\t\tnote.image = path.join(this.dir, `note-${id}.jpg`);\n\t\t\tawait fs.promises.writeFile(note.image, Buffer.from(jpeg[1], \"base64\"));\n\t\t}\n\t\tawait this.save([...notes, note]);\n\t\treturn note;\n\t}\n\n\tasync update(\n\t\tid: number,\n\t\tpatch: { done?: boolean; text?: string },\n\t): Promise<PreviewNote | undefined> {\n\t\tconst notes = await this.list();\n\t\tconst note = notes.find((n) => n.id === id);\n\t\tif (!note) return undefined;\n\t\tif (typeof patch.done === \"boolean\") note.done = patch.done;\n\t\tif (typeof patch.text === \"string\" && patch.text.trim())\n\t\t\tnote.text = patch.text.trim();\n\t\tawait this.save(notes);\n\t\treturn note;\n\t}\n\n\tasync remove(id: number): Promise<PreviewNote | undefined> {\n\t\tconst notes = await this.list();\n\t\tconst note = notes.find((n) => n.id === id);\n\t\tif (!note) return undefined;\n\t\tif (note.image) await fs.promises.rm(note.image, { force: true });\n\t\tawait this.save(notes.filter((n) => n !== note));\n\t\treturn note;\n\t}\n\n\tprivate async save(notes: PreviewNote[]): Promise<void> {\n\t\tawait this.ensureDir();\n\t\tconst tmp = `${this.file}.${process.pid}.tmp`;\n\t\tawait fs.promises.writeFile(tmp, `${JSON.stringify(notes, null, \"\\t\")}\\n`);\n\t\tawait fs.promises.rename(tmp, this.file);\n\t}\n\n\tprivate async ensureDir(): Promise<void> {\n\t\tawait fs.promises.mkdir(this.dir, { recursive: true });\n\t\t// Notes are working material, not part of the project.\n\t\tconst ignore = path.join(path.dirname(this.dir), \".gitignore\");\n\t\tif (!fs.existsSync(ignore)) await fs.promises.writeFile(ignore, \"*\\n\");\n\t}\n}\n\n/** One line for a terminal (or an agent reading one). */\nexport function describeNote(note: PreviewNote, fps: number): string {\n\tconst pct = (v: number) => `${Math.round(v * 100)}%`;\n\tconst where =\n\t\tnote.w !== undefined && note.h !== undefined\n\t\t\t? `area ${pct(note.x)},${pct(note.y)} to ${pct(note.x + note.w)},${pct(note.y + note.h)}`\n\t\t\t: `spot ${pct(note.x)},${pct(note.y)}`;\n\treturn `note ${note.id} at ${timecode(note.frame, fps)} (frame ${note.frame}), ${where}: ${note.text}`;\n}\n\nfunction timecode(frame: number, fps: number): string {\n\tconst s = frame / fps;\n\tconst m = Math.floor(s / 60);\n\treturn `${m}:${(s - m * 60).toFixed(2).padStart(5, \"0\")}`;\n}\n\nfunction clamp01(v: unknown): number {\n\tconst n = Number(v);\n\treturn Number.isFinite(n) ? Math.min(1, Math.max(0, n)) : 0;\n}\n\nfunction round(v: number, digits: number): number {\n\treturn Number(v.toFixed(digits));\n}\n","/**\n * Single-file preview UI served by `startPreview`. No framework, no build step.\n * The page script avoids template literals so it can live inside this one.\n */\nexport const previewPage = /* html */ `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>framefields preview</title>\n<link rel=\"icon\" href=\"data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 512 512%22%3E%3Cg fill=%22none%22 stroke=%22%233B82F6%22 stroke-linecap=%22round%22%3E%3Cpath d=%22M184 56H86q-30 0-30 30v98M328 56h98q30 0 30 30v98M184 456H86q-30 0-30-30v-98M328 456h98q30 0 30-30v-98%22 stroke-width=%2234%22/%3E%3Cpath d=%22M176 182v148m0-148c0 74 60 74 100 74%22 stroke-width=%2228%22/%3E%3C/g%3E%3Cg fill=%22%233B82F6%22%3E%3Ccircle cx=%22176%22 cy=%22172%22 r=%2232%22/%3E%3Ccircle cx=%22176%22 cy=%22340%22 r=%2232%22/%3E%3Cpath d=%22M284 200l78 56-78 56z%22 stroke=%22%233B82F6%22 stroke-width=%2234%22 stroke-linejoin=%22round%22/%3E%3C/g%3E%3C/svg%3E\">\n<link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n<link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n<link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap\" rel=\"stylesheet\">\n<style>\n :root {\n --bg: #0e0f11; --surface: #16171a; --surface-2: #1d1e22; --surface-3: #26282d;\n --line: #2a2c31; --line-2: #3a3c43;\n --ink: #ededf0; --ink-2: #a6a8b0; --ink-3: #74777f;\n --stage: #060607;\n --primary-bg: #ededf0; --primary-ink: #0e0f11;\n --brand: #3b82f6; --focus: #5eaefc; --ok: #4fbf7a; --bad: #f27d8d;\n --wave: #4b4e57;\n --radius: 10px;\n --font: \"Inter\", ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", sans-serif;\n color-scheme: dark;\n }\n @media (prefers-color-scheme: light) {\n :root {\n --bg: #f5f5f6; --surface: #ffffff; --surface-2: #f0f0f2; --surface-3: #e6e6e9;\n --line: #e2e2e6; --line-2: #cfcfd5;\n --ink: #15161a; --ink-2: #53555d; --ink-3: #85878f;\n --stage: #0b0b0c;\n --primary-bg: #15161a; --primary-ink: #ffffff;\n --brand: #2563eb; --focus: #1c74cc; --ok: #23874a; --bad: #cf4057;\n --wave: #c2c3ca;\n color-scheme: light;\n }\n }\n * { box-sizing: border-box; }\n html, body { height: 100%; margin: 0; overflow: hidden; background: var(--bg); color: var(--ink); }\n body {\n display: flex; flex-direction: column; height: 100dvh;\n font: 13.5px/1.45 var(--font); -webkit-font-smoothing: antialiased;\n font-feature-settings: \"cv11\", \"ss03\";\n }\n button, input { font: inherit; color: inherit; }\n button { cursor: pointer; }\n button:focus-visible, input:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }\n .tnum { font-variant-numeric: tabular-nums; }\n .grow { flex: 1; }\n .spinner {\n width: 12px; height: 12px; border-radius: 50%; flex: none;\n border: 1.5px solid var(--line-2); border-top-color: var(--ink); animation: spin .8s linear infinite;\n }\n @keyframes spin { to { transform: rotate(360deg); } }\n .dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex: none; }\n\n /* ── Top bar ── */\n .top { flex: none; display: flex; align-items: center; gap: 10px; padding: 0 20px; height: 50px; border-bottom: 1px solid var(--line); min-width: 0; }\n .logo { width: 22px; height: 22px; flex: none; color: var(--brand); }\n .sep { width: 1px; height: 18px; background: var(--line-2); flex: none; margin: 0 2px; }\n .top h1 { font-size: 14px; font-weight: 600; margin: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n .spec { color: var(--ink-3); font-size: 12px; white-space: nowrap; }\n @media (max-width: 640px) { .spec { display: none; } }\n .chip {\n display: inline-flex; align-items: center; gap: 8px; height: 26px; padding: 0 10px; white-space: nowrap;\n border: 1px solid var(--line); border-radius: 999px; color: var(--ink-2); font-size: 12px; background: var(--surface);\n }\n .chip[data-state=\"ready\"] .dot { color: var(--ok); }\n .chip[data-state=\"none\"] .dot, .chip[data-state=\"offline\"] .dot { color: var(--ink-3); }\n .chip[data-state=\"error\"] { color: var(--bad); }\n\n /* ── Player: one screen, no page scroll; the picture takes the space left ── */\n .player {\n flex: 1; min-height: 0; display: flex; flex-direction: column; gap: 10px;\n width: 100%; max-width: 1600px; margin: 0 auto; padding: 16px 20px 20px;\n }\n @media (max-width: 640px) { .player { padding: 12px; } }\n .stage-wrap { flex: 1 1 0; min-height: 96px; container-type: size; display: grid; place-items: center; }\n .stage {\n position: relative; background: var(--stage); border-radius: var(--radius); overflow: hidden;\n box-shadow: 0 0 0 1px var(--line); user-select: none; -webkit-user-select: none;\n aspect-ratio: var(--ar, 16 / 9); width: min(100cqw, calc(100cqh * var(--ar-n, 1.7778)));\n }\n .stage canvas { display: block; width: 100%; height: 100%; }\n .stage-msg {\n position: absolute; inset: 0; display: none; place-items: center; padding: 24px; text-align: center;\n color: #fff; background: rgb(0 0 0 / .72); font-size: 13px; line-height: 1.5; white-space: pre-wrap; overflow: auto;\n }\n .stage-msg.on { display: grid; }\n /* Buffering: a centred spinning arc over the picture, like YouTube's. It\n fades in after a beat so brief waits don't flash it. */\n .buffering {\n position: absolute; left: 50%; top: 50%; width: clamp(36px, 9cqmin, 64px); aspect-ratio: 1;\n transform: translate(-50%, -50%); pointer-events: none; opacity: 0; visibility: hidden;\n transition: opacity .15s, visibility 0s .15s; filter: drop-shadow(0 0 3px rgb(0 0 0 / .45));\n }\n .buffering.on { opacity: 1; visibility: visible; transition: opacity .2s .25s, visibility 0s .25s; }\n .buffering svg { width: 100%; height: 100%; animation: yt-rotate 1.4s linear infinite; }\n .buffering circle {\n fill: none; stroke: #fff; stroke-width: 3.6; stroke-linecap: round;\n stroke-dasharray: 1 150; stroke-dashoffset: 0; animation: yt-dash 1.4s ease-in-out infinite;\n }\n @keyframes yt-rotate { to { transform: rotate(360deg); } }\n @keyframes yt-dash {\n 0% { stroke-dasharray: 1 150; stroke-dashoffset: 0; }\n 50% { stroke-dasharray: 90 150; stroke-dashoffset: -35; }\n 100% { stroke-dasharray: 90 150; stroke-dashoffset: -124; }\n }\n @media (prefers-reduced-motion: reduce) { .buffering circle, .play-btn .spin circle { animation: none; stroke-dasharray: 70 150; } }\n\n /* ── Transport ── */\n .transport { flex: none; display: flex; align-items: center; gap: 4px; flex-wrap: wrap; row-gap: 6px; }\n .icon-btn {\n width: 32px; height: 32px; display: inline-grid; place-items: center; border-radius: 8px;\n border: 0; background: transparent; color: var(--ink-2); flex: none;\n }\n .icon-btn:hover:not(:disabled) { background: var(--surface-2); color: var(--ink); }\n .icon-btn:disabled { opacity: .35; cursor: default; }\n .icon-btn svg { width: 16px; height: 16px; }\n .play-btn {\n width: 36px; height: 36px; border-radius: 50%; border: 0; display: inline-grid; place-items: center; flex: none;\n background: var(--primary-bg); color: var(--primary-ink); margin: 0 2px;\n }\n .play-btn:hover:not(:disabled) { filter: brightness(.9); }\n .play-btn:disabled { opacity: .4; cursor: default; }\n .play-btn svg { width: 14px; height: 14px; }\n .play-btn .spin { display: none; width: 20px; height: 20px; animation: yt-rotate 1.4s linear infinite; }\n .play-btn .spin circle {\n fill: none; stroke: currentColor; stroke-width: 5; stroke-linecap: round;\n stroke-dasharray: 1 150; animation: yt-dash 1.4s ease-in-out infinite;\n }\n .play-btn.loading .spin { display: block; }\n .play-btn.loading svg:not(.spin) { display: none !important; }\n .timecode { display: flex; align-items: baseline; gap: 5px; margin-left: 8px; font-size: 13px; white-space: nowrap; }\n .timecode .dim { color: var(--ink-3); }\n .timecode .frame { color: var(--ink-3); font-size: 11.5px; margin-left: 6px; }\n .fps { margin-left: 10px; color: var(--ink-3); font-size: 11.5px; white-space: nowrap; }\n input[type=\"range\"].slider { -webkit-appearance: none; appearance: none; width: 72px; height: 4px; border-radius: 2px; background: var(--line-2); margin: 0 8px 0 0; }\n input[type=\"range\"].slider::-webkit-slider-thumb { -webkit-appearance: none; width: 12px; height: 12px; border-radius: 50%; background: var(--ink); border: 0; }\n input[type=\"range\"].slider::-moz-range-thumb { width: 12px; height: 12px; border-radius: 50%; background: var(--ink); border: 0; }\n input[type=\"range\"].slider:disabled { opacity: .35; }\n\n /* ── Timeline ── */\n .timeline {\n flex: none; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface);\n padding: 0 14px; user-select: none; -webkit-user-select: none; touch-action: none; cursor: pointer;\n }\n .tl-inner { position: relative; }\n .ruler { position: relative; height: 22px; border-bottom: 1px solid var(--line); }\n .tick { position: absolute; bottom: 0; width: 1px; height: 4px; background: var(--line-2); }\n .tick.major { height: 8px; background: var(--ink-3); }\n .tick span { position: absolute; bottom: 9px; left: 4px; font-size: 10.5px; line-height: 1; color: var(--ink-3); white-space: nowrap; font-variant-numeric: tabular-nums; }\n .wave-lane { position: relative; height: 48px; }\n @media (max-height: 600px) { .wave-lane { height: 30px; } }\n .wave-lane canvas { position: absolute; inset: 0; width: 100%; height: 100%; }\n .wave-state { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; gap: 8px; color: var(--ink-3); font-size: 12px; }\n .wave-state.shimmer {\n background: linear-gradient(90deg, transparent 0%, color-mix(in srgb, var(--ink) 6%, transparent) 50%, transparent 100%);\n background-size: 200% 100%; animation: shimmer 1.4s linear infinite;\n }\n @keyframes shimmer { from { background-position: 100% 0; } to { background-position: -100% 0; } }\n .playhead { position: absolute; top: 0; bottom: 0; width: 0; pointer-events: none; z-index: 3; }\n .playhead::before { content: \"\"; position: absolute; top: 0; bottom: 0; left: -0.5px; width: 1px; background: var(--ink); }\n .playhead::after { content: \"\"; position: absolute; top: 0; left: -5px; width: 10px; height: 11px; background: var(--ink); clip-path: polygon(0 0, 100% 0, 100% 55%, 50% 100%, 0 55%); }\n .hover-line { position: absolute; top: 0; bottom: 0; width: 1px; background: var(--line-2); display: none; pointer-events: none; }\n .hover-time {\n position: absolute; top: -28px; transform: translateX(-50%); display: none; pointer-events: none;\n font-size: 11px; line-height: 1; background: var(--surface-3); color: var(--ink); padding: 5px 7px; border-radius: 5px;\n white-space: nowrap; z-index: 4; font-variant-numeric: tabular-nums;\n }\n /* ── Notes: pinned to a spot (or an area) and a moment ── */\n .stage.can-note { cursor: crosshair; }\n .overlay { position: absolute; inset: 0; pointer-events: none; }\n .pin {\n position: absolute; width: 22px; height: 22px; margin: -11px 0 0 -11px; padding: 0; border-radius: 50%;\n display: grid; place-items: center; pointer-events: auto; cursor: pointer;\n background: var(--brand); color: #fff; font-size: 11px; font-weight: 600; line-height: 1;\n border: 2px solid #fff; box-shadow: 0 1px 5px rgb(0 0 0 / .55); transition: transform .12s;\n }\n .pin:hover, .pin.active { transform: scale(1.18); }\n .pin.done { background: #6b6e76; }\n .pin.draft { animation: pin-pulse 1.5s ease-out infinite; }\n .pin svg { width: 11px; height: 11px; }\n @keyframes pin-pulse {\n from { box-shadow: 0 0 0 0 rgb(59 130 246 / .6), 0 1px 5px rgb(0 0 0 / .55); }\n to { box-shadow: 0 0 0 14px rgb(59 130 246 / 0), 0 1px 5px rgb(0 0 0 / .55); }\n }\n .area {\n position: absolute; border: 1.5px solid var(--brand); border-radius: 3px;\n background: rgb(59 130 246 / .12); box-shadow: 0 0 0 1px rgb(0 0 0 / .35);\n }\n .area.done { border-color: #9a9da5; background: rgb(255 255 255 / .05); }\n .marquee { position: absolute; display: none; border: 1.5px dashed #fff; background: rgb(59 130 246 / .16); border-radius: 3px; pointer-events: none; }\n .stage-hint, .toast {\n position: absolute; left: 50%; transform: translateX(-50%); pointer-events: none; white-space: nowrap;\n padding: 6px 11px; border-radius: 999px; font-size: 12px; color: #fff; background: rgb(0 0 0 / .62);\n backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px); opacity: 0; transition: opacity .15s;\n }\n .stage-hint { top: 12px; }\n .stage.can-note.hint:hover .stage-hint { opacity: 1; }\n .toast { bottom: 14px; }\n .toast.on { opacity: 1; }\n\n .pop {\n position: fixed; z-index: 20; width: 280px; display: none; padding: 10px 10px 8px;\n background: var(--surface); border: 1px solid var(--line-2); border-radius: 12px;\n box-shadow: 0 12px 32px rgb(0 0 0 / .35);\n }\n .pop.on { display: block; }\n .pop-head { display: flex; align-items: center; gap: 8px; font-size: 11.5px; color: var(--ink-3); margin-bottom: 6px; }\n .badge {\n min-width: 18px; height: 18px; padding: 0 5px; border-radius: 9px; display: inline-grid; place-items: center; flex: none;\n background: var(--brand); color: #fff; font-size: 10.5px; font-weight: 600;\n }\n .badge.done { background: var(--ink-3); }\n .pop textarea {\n display: block; width: 100%; min-height: 54px; max-height: 180px; resize: none; padding: 2px 2px 0;\n border: 0; outline: none; background: transparent; color: var(--ink); font: inherit; line-height: 1.45;\n }\n .pop textarea::placeholder { color: var(--ink-3); }\n .pop-text { white-space: pre-wrap; overflow-wrap: anywhere; padding: 0 2px; max-height: 200px; overflow: auto; }\n .pop-foot { display: flex; align-items: center; gap: 6px; margin-top: 8px; font-size: 11.5px; color: var(--ink-3); }\n .pop-foot .err { color: var(--bad); }\n .btn {\n height: 26px; padding: 0 10px; border-radius: 7px; border: 0; font-size: 12px; font-weight: 500;\n display: inline-flex; align-items: center; gap: 6px; background: transparent; color: var(--ink-2); white-space: nowrap;\n }\n .btn:hover:not(:disabled) { background: var(--surface-2); color: var(--ink); }\n .btn.primary { background: var(--primary-bg); color: var(--primary-ink); }\n .btn.primary:hover:not(:disabled) { background: var(--primary-bg); filter: brightness(.9); }\n .btn.danger:hover:not(:disabled) { color: var(--bad); }\n .btn:disabled { opacity: .4; cursor: default; }\n .btn svg { width: 13px; height: 13px; }\n .x-btn { width: 22px; height: 22px; padding: 0; justify-content: center; margin-right: -4px; }\n\n .transport { position: relative; }\n .notes-btn { height: 32px; padding: 0 9px 0 8px; margin-right: 6px; font-size: 12.5px; }\n .notes-btn svg { width: 15px; height: 15px; }\n .notes-btn .badge { background: var(--surface-3); color: var(--ink-2); }\n .notes-btn .badge.open { background: var(--brand); color: #fff; }\n .notes-panel {\n position: absolute; right: 0; bottom: calc(100% + 8px); z-index: 15; width: min(360px, calc(100vw - 24px));\n max-height: min(440px, 62vh); display: none; flex-direction: column; overflow: hidden;\n background: var(--surface); border: 1px solid var(--line-2); border-radius: 12px; box-shadow: 0 12px 32px rgb(0 0 0 / .35);\n }\n .notes-panel.on { display: flex; }\n .np-head { flex: none; display: flex; align-items: center; gap: 8px; padding: 8px 8px 8px 14px; border-bottom: 1px solid var(--line); }\n .np-head h2 { margin: 0; font-size: 13px; font-weight: 600; }\n .np-list { overflow: auto; padding: 4px; }\n .np-empty { padding: 22px 18px; text-align: center; color: var(--ink-3); font-size: 12.5px; }\n .np-foot { flex: none; padding: 8px 14px; border-top: 1px solid var(--line); color: var(--ink-3); font-size: 11.5px; overflow-wrap: anywhere; }\n .note-row {\n display: grid; grid-template-columns: auto 1fr auto; gap: 10px; align-items: start; padding: 8px 8px 8px 10px;\n border-radius: 8px; cursor: pointer;\n }\n .note-row:hover { background: var(--surface-2); }\n .note-row .badge { margin-top: 1px; }\n .note-row .when { font-size: 11.5px; color: var(--ink-3); }\n .note-row .text { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; overflow-wrap: anywhere; }\n .note-row.done .text { color: var(--ink-3); text-decoration: line-through; }\n .check {\n width: 20px; height: 20px; border-radius: 50%; padding: 0; display: grid; place-items: center; margin-top: 1px;\n border: 1.5px solid var(--line-2); background: transparent; color: transparent;\n }\n .check:hover { border-color: var(--ok); color: var(--ok); }\n .check.on { background: var(--ok); border-color: var(--ok); color: #fff; }\n .check svg { width: 11px; height: 11px; }\n\n .note-marks { position: absolute; left: 0; right: 0; top: 22px; bottom: 0; pointer-events: none; z-index: 2; }\n .note-mark {\n position: absolute; top: 5px; width: 14px; height: 14px; margin-left: -7px; padding: 0; border: 0; border-radius: 50%;\n display: grid; place-items: center; pointer-events: auto; cursor: pointer;\n background: var(--brand); color: #fff; font-size: 8.5px; font-weight: 700; box-shadow: 0 0 0 2px var(--surface);\n }\n .note-mark::after { content: \"\"; position: absolute; top: 14px; left: 6.5px; width: 1px; height: 30px; background: var(--brand); opacity: .45; }\n .note-mark.done { background: var(--ink-3); }\n .note-mark.stacked::after { display: none; }\n .note-mark.done::after { background: var(--ink-3); }\n .note-mark:hover { transform: scale(1.2); }\n</style>\n</head>\n<body>\n<header class=\"top\">\n <svg class=\"logo\" viewBox=\"0 0 512 512\" aria-label=\"framefields\" role=\"img\"><g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\"><path d=\"M184 56H86q-30 0-30 30v98M328 56h98q30 0 30 30v98M184 456H86q-30 0-30-30v-98M328 456h98q30 0 30-30v-98\" stroke-width=\"34\" stroke-linejoin=\"round\"/><path d=\"M176 182v148m0-148c0 74 60 74 100 74\" stroke-width=\"28\"/></g><g fill=\"currentColor\"><circle cx=\"176\" cy=\"172\" r=\"32\"/><circle cx=\"176\" cy=\"340\" r=\"32\"/><path d=\"M284 200l78 56-78 56z\" stroke=\"currentColor\" stroke-width=\"34\" stroke-linejoin=\"round\"/></g></svg>\n <span class=\"sep\" aria-hidden=\"true\"></span>\n <h1 id=\"title\">framefields preview</h1>\n <span class=\"grow\"></span>\n <span class=\"spec tnum\" id=\"spec\"></span>\n <span class=\"chip\" id=\"chip\" data-state=\"mixing\" role=\"status\" aria-live=\"polite\"><span class=\"spinner\"></span><span>Mixing audio</span></span>\n</header>\n\n<main class=\"player\" aria-label=\"Player\">\n <div class=\"stage-wrap\">\n <div class=\"stage\" id=\"stage\">\n <canvas id=\"canvas\" aria-label=\"Rendered frame\"></canvas>\n <div class=\"stage-msg\" id=\"stageMsg\" role=\"alert\"></div>\n <div class=\"overlay\" id=\"pins\"></div>\n <div class=\"marquee\" id=\"marquee\"></div>\n <div class=\"stage-hint\" id=\"stageHint\">Click to pin a note · drag to mark an area</div>\n <div class=\"toast\" id=\"toast\" role=\"status\" aria-live=\"polite\"></div>\n <div class=\"buffering\" id=\"buffering\" role=\"status\" aria-label=\"Loading\"><svg viewBox=\"0 0 50 50\"><circle cx=\"25\" cy=\"25\" r=\"20\"/></svg></div>\n </div>\n </div>\n\n <div class=\"transport\">\n <button class=\"icon-btn\" id=\"prev\" title=\"Previous frame\" aria-label=\"Previous frame\">\n <svg viewBox=\"0 0 16 16\" fill=\"currentColor\"><path d=\"M3 3h1.6v10H3zM13 3.6v8.8a.6.6 0 0 1-.92.5L5.8 8.5a.6.6 0 0 1 0-1l6.28-4.4a.6.6 0 0 1 .92.5z\"/></svg>\n </button>\n <button class=\"play-btn\" id=\"play\" title=\"Play\" aria-label=\"Play\" disabled>\n <svg id=\"playIcon\" viewBox=\"0 0 16 16\" fill=\"currentColor\"><path d=\"M4.5 2.9v10.2a.7.7 0 0 0 1.06.6l8.1-5.1a.7.7 0 0 0 0-1.2l-8.1-5.1a.7.7 0 0 0-1.06.6z\"/></svg>\n <svg class=\"spin\" viewBox=\"0 0 50 50\" aria-hidden=\"true\"><circle cx=\"25\" cy=\"25\" r=\"18\"/></svg>\n <svg id=\"pauseIcon\" viewBox=\"0 0 16 16\" fill=\"currentColor\" style=\"display:none\"><rect x=\"3.5\" y=\"2.5\" width=\"3.2\" height=\"11\" rx=\".8\"/><rect x=\"9.3\" y=\"2.5\" width=\"3.2\" height=\"11\" rx=\".8\"/></svg>\n </button>\n <button class=\"icon-btn\" id=\"next\" title=\"Next frame\" aria-label=\"Next frame\">\n <svg viewBox=\"0 0 16 16\" fill=\"currentColor\"><path d=\"M11.4 3H13v10h-1.6zM3 3.6v8.8a.6.6 0 0 0 .92.5l6.28-4.4a.6.6 0 0 0 0-1L3.92 3.1A.6.6 0 0 0 3 3.6z\"/></svg>\n </button>\n <div class=\"timecode tnum\">\n <span id=\"tcNow\">0:00.00</span><span class=\"dim\">/</span><span class=\"dim\" id=\"tcDur\">0:00.00</span>\n <span class=\"frame\" id=\"tcFrame\">frame 0</span>\n </div>\n <span class=\"fps tnum\" id=\"fps\" role=\"status\" hidden></span>\n <span class=\"grow\"></span>\n <button class=\"btn notes-btn\" id=\"notesBtn\" title=\"Notes\" aria-haspopup=\"dialog\" aria-expanded=\"false\" hidden>\n <svg viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linejoin=\"round\"><path d=\"M3 2.75h10a1.25 1.25 0 0 1 1.25 1.25v6.5A1.25 1.25 0 0 1 13 11.75H7.5L4.5 14v-2.25H3A1.25 1.25 0 0 1 1.75 10.5V4A1.25 1.25 0 0 1 3 2.75z\"/></svg>\n Notes <span class=\"badge\" id=\"notesCount\">0</span>\n </button>\n <div class=\"notes-panel\" id=\"notesPanel\" role=\"dialog\" aria-label=\"Notes\">\n <div class=\"np-head\">\n <h2>Notes</h2><span class=\"grow\"></span>\n <button class=\"btn\" id=\"notesCopy\" title=\"Copy the open notes, to paste into a chat\">\n <svg viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"><rect x=\"5.25\" y=\"5.25\" width=\"8\" height=\"8\" rx=\"1.5\"/><path d=\"M10.75 3.5V3.25a1.5 1.5 0 0 0-1.5-1.5h-5.5a1.5 1.5 0 0 0-1.5 1.5v5.5a1.5 1.5 0 0 0 1.5 1.5H4\"/></svg>\n <span id=\"notesCopyLabel\">Copy</span>\n </button>\n <button class=\"btn x-btn\" id=\"notesClose\" title=\"Close\" aria-label=\"Close\"><svg viewBox=\"0 0 16 16\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\"><path d=\"M4 4l8 8M12 4l-8 8\"/></svg></button>\n </div>\n <div class=\"np-list\" id=\"notesList\"></div>\n <div class=\"np-foot\" id=\"notesFoot\"></div>\n </div>\n <button class=\"icon-btn\" id=\"mute\" title=\"Mute\" aria-label=\"Mute\" disabled>\n <svg id=\"volIcon\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M2.5 6h2.2L8 3.2v9.6L4.7 10H2.5z\" fill=\"currentColor\" stroke=\"none\"/><path d=\"M10.6 5.6a3.4 3.4 0 0 1 0 4.8M12.4 3.9a5.8 5.8 0 0 1 0 8.2\"/></svg>\n <svg id=\"muteIcon\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\" stroke-linecap=\"round\" style=\"display:none\"><path d=\"M2.5 6h2.2L8 3.2v9.6L4.7 10H2.5z\" fill=\"currentColor\" stroke=\"none\"/><path d=\"M10.5 6l3.5 4M14 6l-3.5 4\"/></svg>\n </button>\n <input class=\"slider\" type=\"range\" id=\"volume\" min=\"0\" max=\"1\" step=\"0.01\" value=\"0.9\" aria-label=\"Volume\" disabled>\n </div>\n\n <div class=\"timeline\" id=\"timeline\" aria-label=\"Timeline\">\n <div class=\"tl-inner\" id=\"tlInner\">\n <div class=\"ruler\" id=\"ruler\"></div>\n <div class=\"note-marks\" id=\"noteMarks\"></div>\n <div class=\"wave-lane\">\n <canvas id=\"wave\"></canvas>\n <div class=\"wave-state shimmer\" id=\"waveState\"><span class=\"spinner\"></span>Mixing audio</div>\n </div>\n <div class=\"hover-line\" id=\"hoverLine\"></div>\n <div class=\"hover-time\" id=\"hoverTime\"></div>\n <div class=\"playhead\" id=\"playhead\"></div>\n </div>\n </div>\n</main>\n\n<div class=\"pop\" id=\"pop\" role=\"dialog\" aria-label=\"Note\"></div>\n\n<script>\n(function () {\n var $ = function (id) { return document.getElementById(id); };\n var stage = $(\"stage\"), timeline = $(\"timeline\"), tlInner = $(\"tlInner\");\n\n var meta = null, session = null, frame = 0, shown = -1, playing = false, playToken = 0;\n var player = null;\n\n // ── Helpers ──\n function fmt(f) {\n var s = f / meta.fps, m = Math.floor(s / 60);\n return m + \":\" + (s - m * 60).toFixed(2).padStart(5, \"0\");\n }\n function last() { return meta.frameCount - 1; }\n function clampFrame(f) { return Math.max(0, Math.min(last(), Math.round(f))); }\n function pct(f) { return meta.frameCount > 1 ? (f / last()) * 100 : 0; }\n function el(tag, cls, text) {\n var e = document.createElement(tag);\n if (cls) e.className = cls;\n if (text != null) e.textContent = text;\n return e;\n }\n function sleep(ms) { return new Promise(function (r) { setTimeout(r, ms); }); }\n\n // ── Reporting: errors and the engine's warnings go to the preview's\n // terminal too, so whoever runs it sees them without opening this tab.\n // Once each; frame numbers aside, playback repeats the same one per frame ──\n var reportedKeys = new Set();\n function errText(x) {\n if (x && x.message) return x.stack && x.stack.indexOf(x.message) >= 0 ? x.stack.split(\"\\\\n\").slice(0, 4).join(\"\\\\n\") : x.message;\n return typeof x === \"string\" ? x : String(x);\n }\n function report(level, parts) {\n var text = parts.map(errText).join(\" \").trim(), key = level + text.replace(/\\\\d+/g, \"#\");\n if (!text || reportedKeys.has(key) || reportedKeys.size > 100) return;\n reportedKeys.add(key);\n fetch(\"/@framefields/log\", {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ level: level, message: text.slice(0, 4000) }),\n keepalive: true\n }).catch(function () {});\n }\n [\"error\", \"warn\"].forEach(function (name) {\n var original = console[name].bind(console);\n console[name] = function () {\n original.apply(null, arguments);\n var args = Array.prototype.slice.call(arguments);\n // Errors from anywhere; warnings only from the engine (fonts, renderers).\n if (name === \"error\" || (typeof args[0] === \"string\" && args[0].indexOf(\"[framefields]\") === 0)) {\n report(name === \"error\" ? \"error\" : \"warning\", args);\n }\n };\n });\n\n // ── Frames: drawn in this tab by the composition's own code ──\n function setTime(f) {\n frame = f;\n $(\"tcNow\").textContent = fmt(f);\n $(\"tcFrame\").textContent = \"frame \" + f;\n $(\"playhead\").style.left = pct(f) + \"%\";\n if (!playing && notesOn) renderPins();\n }\n function show(f) {\n setTime(f);\n if (!player) return Promise.resolve();\n $(\"buffering\").classList.add(\"on\");\n // Calls made while a frame draws collapse to the latest.\n return player.render(f).then(function (drawn) {\n shown = drawn;\n if (!stalled) $(\"buffering\").classList.remove(\"on\");\n });\n }\n function seek(f) {\n stop();\n var done = show(clampFrame(f));\n keepPosition();\n scheduleWarm();\n return done;\n }\n\n // ── Position in the address: #t=12.5 opens there, and a reload (the next\n // preview taking over) comes back to the same moment ──\n function startFrame() {\n var m = /[#&]t=([\\\\d.]+)/.exec(location.hash), n = /[#&]frame=(\\\\d+)/.exec(location.hash);\n if (m) return clampFrame(Number(m[1]) * meta.fps);\n if (n) return clampFrame(Number(n[1]));\n return 0;\n }\n function keepPosition() {\n var at = frame > 0 ? \"#t=\" + Number((frame / meta.fps).toFixed(3)) : location.pathname;\n try { history.replaceState(null, \"\", at); } catch (e) {}\n }\n\n // ── Analysing ahead: while paused, frames after the playhead are drawn\n // off-screen so vision results are cached before they play ──\n var warmTimer = null, warmToken = 0, warmed = new Set();\n function scheduleWarm() {\n clearTimeout(warmTimer);\n warmToken++;\n if (!player || !player.hasVision || playing) return;\n warmTimer = setTimeout(warmAhead, 1000);\n }\n async function warmAhead() {\n var token = ++warmToken;\n var end = Math.min(last(), frame + Math.round(meta.fps * 20));\n for (var f = frame + 1; f <= end && token === warmToken && !playing; f++) {\n if (warmed.has(f)) continue;\n if (!(await player.warm(f))) { scheduleWarm(); return; }\n warmed.add(f);\n await sleep(0);\n }\n }\n function showError(err) {\n report(\"error\", [err]);\n pageState = \"error\";\n $(\"buffering\").classList.remove(\"on\");\n $(\"stageMsg\").textContent = String((err && err.message) || err);\n $(\"stageMsg\").classList.add(\"on\");\n }\n\n // ── Audio ──\n var audio = { mode: \"browser\", state: \"mixing\", error: \"\", buffer: null, peaks: null, ctx: null, gain: null, src: null, muted: false, volume: 0.9 };\n var AUDIO_LABEL = { mixing: \"Mixing audio\", loading: \"Loading audio\", ready: \"Audio ready\", none: \"No audio\", error: \"Audio failed\" };\n var offline = false;\n\n function renderChip() {\n var chip = $(\"chip\"), state, label, busy;\n if (offline) { state = \"offline\"; label = \"Preview stopped\"; busy = false; }\n else { state = audio.state; label = AUDIO_LABEL[audio.state]; busy = state === \"mixing\" || state === \"loading\"; }\n chip.dataset.state = state;\n chip.title = offline ? \"The preview command isn't running. This tab reconnects when it starts again.\" : audio.error || \"\";\n chip.replaceChildren(el(\"span\", busy ? \"spinner\" : \"dot\"), el(\"span\", null, label));\n }\n function setAudioStatus(state, error) {\n audio.state = state;\n audio.error = error || \"\";\n var busy = state === \"mixing\" || state === \"loading\", ws = $(\"waveState\");\n ws.classList.toggle(\"shimmer\", busy);\n ws.style.display = state === \"ready\" ? \"none\" : \"flex\";\n ws.replaceChildren();\n if (busy) ws.append(el(\"span\", \"spinner\"));\n ws.append(document.createTextNode(state === \"none\" ? \"No audio in this composition\" : state === \"error\" ? \"Audio could not be mixed\" : AUDIO_LABEL[state]));\n var usable = state === \"ready\" && (!!audio.buffer || !!stream);\n $(\"mute\").disabled = !usable;\n $(\"volume\").disabled = !usable;\n renderChip();\n drawWave();\n }\n function ensureAudioContext() {\n if (audio.ctx) return;\n var Ctx = window.AudioContext || window.webkitAudioContext;\n audio.ctx = new Ctx();\n audio.gain = audio.ctx.createGain();\n audio.gain.connect(audio.ctx.destination);\n applyVolume();\n }\n // ── Soundtrack, streamed. The page mixes it with the export's mixer in\n // BLOCK_SEC blocks: the ones at the playhead first, as it plays, the rest\n // in the background (which draws the waveform and finds the film's loudest\n // sample for the master gain). Blocks play back to back on the\n // AudioContext clock. Sources stream straight from where they live, pinned\n // assets from their bucket. The mixing runs in a worker with its own GPU\n // device, so it never holds up drawing; if the worker can't, the page mixes,\n // and if that fails too, the server does. ──\n var BLOCK_SEC = 2, AHEAD_SEC = 4, KEEP_BLOCKS = 64, PEAK_BINS = 1200, SILENT = 0.001;\n var stream = null, playback = null;\n function filmSec() { return meta.frameCount / meta.fps; }\n // The project's bundle, loaded as a worker, runs the mixer instead of a player.\n function mixerWorker() {\n var worker;\n try { worker = new Worker(\"/@framefields/player.js\", { type: \"module\" }); } catch (e) { return null; }\n var calls = new Map(), next = 0, failed = null;\n var fail = function (err) {\n failed = failed || err;\n calls.forEach(function (c) { c.reject(failed); });\n calls.clear();\n };\n var ready = new Promise(function (resolve, reject) {\n worker.onmessage = function (e) {\n var d = e.data;\n if (\"ready\" in d) { if (d.ready) resolve(); else { reject(new Error(d.error)); fail(new Error(d.error)); } return; }\n var call = calls.get(d.id);\n if (!call) return;\n calls.delete(d.id);\n if (d.error) call.reject(new Error(d.error)); else call.resolve(d.mix);\n };\n worker.onerror = function (e) {\n var err = new Error(e.message || \"the mixing worker failed\");\n reject(err);\n fail(err);\n };\n });\n return {\n mix: function (range, chain) {\n return ready.then(function () {\n if (failed) throw failed;\n return new Promise(function (resolve, reject) {\n var id = ++next;\n calls.set(id, { resolve: resolve, reject: reject });\n worker.postMessage({ id: id, range: range, chain: chain });\n });\n });\n },\n stop: function () { worker.terminate(); fail(new Error(\"stopped\")); }\n };\n }\n function beginStream(p) {\n if (stream && stream.worker) stream.worker.stop();\n stopClock();\n audio.buffer = null;\n audio.peaks = new Array(PEAK_BINS).fill(0);\n stream = { player: p, worker: mixerWorker(), blocks: new Map(), order: [], pending: new Map(), queue: [], busy: false,\n seen: new Set(), peak: 0, gain: 1, fill: 0, filling: false, total: Math.max(1, Math.ceil(filmSec() / BLOCK_SEC)) };\n ensureAudioContext();\n setAudioStatus(\"mixing\");\n var s = stream;\n streamBlock(s, Math.floor(frame / meta.fps / BLOCK_SEC), true).then(function () {\n if (stream === s && audio.state === \"mixing\") setAudioStatus(\"ready\");\n }, function () {});\n }\n // Blocks mix on two chains, each continuing its own effect state from the\n // block before: \"fill\" runs through the film from its start, so its blocks\n // are exactly the export's; \"play\" starts at a seek with a lead-in and\n // carries on from there. An exact block is never replaced by a lead-in one.\n function streamBlock(s, i, urgent) {\n var have = s.blocks.get(i);\n if (have) return Promise.resolve(have);\n var waiting = s.pending.get(i);\n if (waiting) {\n if (urgent) promote(s, i);\n return waiting;\n }\n waiting = enqueue(s, i, i === s.fill ? \"fill\" : \"play\", urgent, true);\n pumpStream(s);\n return waiting;\n }\n function enqueue(s, i, chain, urgent, own) {\n if (chain === \"fill\") s.filling = true;\n var waiting = new Promise(function (resolve, reject) {\n var job = { i: i, chain: chain, own: own, resolve: resolve, reject: reject };\n if (urgent) s.queue.unshift(job); else s.queue.push(job);\n });\n if (own) s.pending.set(i, waiting);\n else waiting.catch(function () {});\n return waiting;\n }\n function promote(s, i) {\n var at = s.queue.findIndex(function (j) { return j.i === i; });\n if (at > 0) s.queue.unshift(s.queue.splice(at, 1)[0]);\n }\n function pumpStream(s) {\n if (s.busy || stream !== s) return;\n if (!s.queue.length) { fillStream(s); if (!s.queue.length) return; }\n s.busy = true;\n var job = s.queue.shift();\n var range = { startSec: job.i * BLOCK_SEC, endSec: Math.min(filmSec(), (job.i + 1) * BLOCK_SEC) };\n mixBlock(s, range, job.chain).then(function (mix) {\n if (stream === s) keepBlock(s, job.i, { left: mix.left, right: mix.right, sampleRate: mix.sampleRate, exact: mix.exact }, mix.peak);\n if (job.chain === \"fill\") { s.fill = job.i + 1; s.filling = false; }\n job.resolve(s.blocks.get(job.i));\n }, function (err) {\n if (job.chain === \"fill\") s.filling = false;\n job.reject(err);\n streamFailed(s, err);\n }).then(function () {\n if (job.own) s.pending.delete(job.i);\n s.busy = false;\n pumpStream(s);\n });\n }\n // In the worker; on the page itself once the worker has failed.\n function mixBlock(s, range, chain) {\n if (!s.worker) return s.player.mixAudio(range, chain);\n return s.worker.mix(range, chain).catch(function (err) {\n if (s.worker) {\n console.warn(\"The soundtrack's mixing worker failed, so the page mixes it:\", err);\n s.worker.stop();\n s.worker = null;\n }\n return s.player.mixAudio(range, chain);\n });\n }\n // Idle: the fill chain mixes its next block, every block in order, so\n // its effect state runs on unbroken from the film's start.\n function fillStream(s) {\n if (s.filling || s.fill >= s.total) return;\n enqueue(s, s.fill, \"fill\", false, !s.pending.has(s.fill));\n }\n function keepBlock(s, i, block, peak) {\n var had = s.blocks.get(i);\n if (!had || !had.exact || block.exact) {\n s.blocks.set(i, block);\n s.order.push(i);\n while (s.order.length > KEEP_BLOCKS) s.blocks.delete(s.order.shift());\n }\n if (s.seen.has(i)) return;\n s.seen.add(i);\n s.peak = Math.max(s.peak, peak);\n var dur = filmSec(), from = Math.floor((i * BLOCK_SEC / dur) * PEAK_BINS);\n var to = Math.min(PEAK_BINS, Math.ceil((Math.min(dur, (i + 1) * BLOCK_SEC) / dur) * PEAK_BINS));\n var perBin = (dur / PEAK_BINS) * block.sampleRate, origin = i * BLOCK_SEC * block.sampleRate;\n for (var b = from; b < to; b++) {\n var lo = Math.max(0, Math.floor(b * perBin - origin)), hi = Math.min(block.left.length, Math.floor((b + 1) * perBin - origin));\n var m = audio.peaks[b];\n for (var k = lo; k < hi; k++) m = Math.max(m, Math.abs(block.left[k]), Math.abs(block.right[k]));\n audio.peaks[b] = Math.min(1, m);\n }\n drawWave();\n if (s.seen.size < s.total) return;\n // The whole film is mixed: master as the export does, or say it's silent.\n s.gain = s.peak > 0.95 ? 0.92 / s.peak : 1;\n if (s.peak < SILENT) setAudioStatus(\"none\");\n }\n function streamFailed(s, err) {\n if (stream !== s) return;\n if (s.worker) s.worker.stop();\n stream = null;\n stopClock();\n console.warn(\"Mixing the soundtrack in the browser failed, so the preview server mixes it:\", err);\n audio.mode = \"server\";\n setAudioStatus(\"mixing\");\n fetch(\"/audio/mix\", { method: \"POST\" }).catch(function () {});\n }\n // The master stage on a block: the film's gain, then the export's soft limiter.\n function mastered(channel, gain) {\n var out = new Float32Array(channel.length);\n for (var k = 0; k < channel.length; k++) {\n var v = Math.tanh(channel[k] * gain);\n out[k] = v === v ? v : 0;\n }\n return out;\n }\n function startStream(f0) {\n var s = stream, ctx = audio.ctx;\n ctx.resume();\n var pb = { t0: f0 / meta.fps, startAt: ctx.currentTime + 0.08, next: Math.floor(f0 / meta.fps / BLOCK_SEC), sources: [], timer: 0 };\n playback = pb;\n function tick() {\n if (playback !== pb || stream !== s) return;\n var now = ctx.currentTime - pb.startAt + pb.t0;\n while (pb.next < s.total && pb.next * BLOCK_SEC < now + AHEAD_SEC) {\n var have = s.blocks.get(pb.next);\n if (!have) { streamBlock(s, pb.next, true).then(tick, function () {}); return; }\n scheduleBlock(pb, pb.next, have, s.gain);\n pb.next++;\n }\n }\n pb.timer = setInterval(tick, 250);\n tick();\n return function () { return Math.max(0, ctx.currentTime - pb.startAt); };\n }\n // Block i starts at film second i * BLOCK_SEC: on the clock, that is\n // startAt + (i * BLOCK_SEC - t0). A block that comes late starts part way\n // in, so sound and picture stay on the same clock.\n function scheduleBlock(pb, i, block, gain) {\n var ctx = audio.ctx, buf = ctx.createBuffer(2, block.left.length, block.sampleRate);\n buf.copyToChannel(mastered(block.left, gain), 0);\n buf.copyToChannel(mastered(block.right, gain), 1);\n var src = ctx.createBufferSource();\n src.buffer = buf;\n src.connect(audio.gain);\n var when = pb.startAt + (i * BLOCK_SEC - pb.t0), at = Math.max(when, ctx.currentTime + 0.02), offset = at - when;\n if (offset >= buf.duration) return;\n src.start(at, offset);\n pb.sources.push(src);\n src.onended = function () { pb.sources = pb.sources.filter(function (x) { return x !== src; }); };\n }\n function stopStream() {\n var pb = playback;\n playback = null;\n if (!pb) return;\n clearInterval(pb.timer);\n pb.sources.forEach(function (src) { try { src.stop(); } catch (e) {} src.disconnect(); });\n }\n function loadAudio(status) {\n if (status && status.state === \"browser\") {\n audio.mode = \"browser\";\n setAudioStatus(\"mixing\");\n if (player) beginStream(player);\n return;\n }\n stopClock();\n audio.buffer = null;\n audio.peaks = null;\n if (!status || status.state !== \"ready\") { setAudioStatus(status ? status.state : \"none\", status && status.error); return; }\n audio.peaks = status.peaks;\n setAudioStatus(\"loading\");\n ensureAudioContext();\n fetch(\"/audio\")\n .then(function (r) { if (!r.ok) throw new Error(\"HTTP \" + r.status); return r.arrayBuffer(); })\n .then(function (data) { return audio.ctx.decodeAudioData(data); })\n .then(function (buf) { audio.buffer = buf; setAudioStatus(\"ready\"); })\n .catch(function (err) { setAudioStatus(\"error\", String((err && err.message) || err)); });\n }\n function applyVolume() {\n if (audio.gain) audio.gain.gain.value = audio.muted ? 0 : audio.volume;\n $(\"volIcon\").style.display = audio.muted ? \"none\" : \"\";\n $(\"muteIcon\").style.display = audio.muted ? \"\" : \"none\";\n $(\"mute\").title = audio.muted ? \"Unmute\" : \"Mute\";\n $(\"mute\").setAttribute(\"aria-label\", audio.muted ? \"Unmute\" : \"Mute\");\n }\n // The clock for real-time playback: the soundtrack when there is one,\n // otherwise the wall clock. Audio as master keeps picture and sound in sync.\n function startClock(f0) {\n if (stream && audio.ctx && audio.state === \"ready\") return startStream(f0);\n if (audio.buffer && audio.ctx) {\n audio.ctx.resume();\n var src = audio.ctx.createBufferSource();\n src.buffer = audio.buffer;\n src.connect(audio.gain);\n var startAt = audio.ctx.currentTime + 0.06, offset = f0 / meta.fps;\n if (offset < audio.buffer.duration) src.start(startAt, offset);\n audio.src = src;\n return function () { return Math.max(0, audio.ctx.currentTime - startAt); };\n }\n var t0 = performance.now();\n return function () { return (performance.now() - t0) / 1000; };\n }\n function stopClock() {\n stopStream();\n if (!audio.src) return;\n try { audio.src.stop(); } catch (e) {}\n audio.src.disconnect();\n audio.src = null;\n }\n\n // ── Playback ──\n var stalled = false;\n // Waiting on frames mid-playback: the play button and the picture both spin.\n function setStalled(on) {\n stalled = on;\n $(\"play\").classList.toggle(\"loading\", on);\n $(\"buffering\").classList.toggle(\"on\", on);\n }\n function setPlayingUi(on) {\n $(\"playIcon\").style.display = on ? \"none\" : \"\";\n $(\"pauseIcon\").style.display = on ? \"\" : \"none\";\n $(\"play\").title = on ? \"Pause\" : \"Play\";\n $(\"play\").setAttribute(\"aria-label\", on ? \"Pause\" : \"Play\");\n }\n function play() {\n if (playing || !player) return;\n playing = true;\n clearTimeout(warmTimer);\n warmToken++;\n var token = ++playToken;\n var alive = function () { return playing && token === playToken; };\n setPlayingUi(true);\n if (openId != null) closePop();\n if (draft && !draft.text.trim()) cancelDraft();\n if (notesOn) renderPins();\n if (frame >= last()) frame = 0;\n playFrames(alive).finally(function () { if (token === playToken && playing) pause(); });\n }\n function stop() {\n playing = false;\n stopClock();\n setStalled(false);\n setPlayingUi(false);\n meter.stop();\n renderFps();\n }\n\n // ── Frame rate reached: distinct frames shown per second while playing,\n // over the last 2 s, stalls included. The film's own rate is in meta ──\n var meter = (function () {\n var started = null, lastFrame = null, samples = [], lastFps = null;\n function read() {\n if (started === null) return { state: \"paused\", fps: lastFps };\n var now = performance.now(), elapsed = Math.min(2000, now - started);\n samples = samples.filter(function (t) { return t > now - 2000; });\n if (elapsed < 1000) return { state: \"measuring\", fps: null };\n // Counting whole frames in the window can overshoot the film's rate.\n return { state: \"playing\", fps: Math.min(meta ? meta.fps : Infinity, (samples.length * 1000) / elapsed) };\n }\n return {\n read: read,\n start: function () { started = performance.now(); lastFrame = null; samples = []; },\n stop: function () {\n if (started === null) return;\n // A playback too short to measure leaves no rate, not an older one.\n var r = read();\n lastFps = r.state === \"playing\" ? r.fps : null;\n started = null;\n samples = [];\n },\n record: function (f) {\n if (started === null || f === lastFrame) return;\n lastFrame = f;\n samples.push(performance.now());\n }\n };\n })();\n function renderFps() {\n var r = meter.read(), out = $(\"fps\");\n out.hidden = r.state === \"paused\" && r.fps === null;\n if (out.hidden) return;\n out.textContent = r.state === \"measuring\" ? \"… fps\" : r.fps.toFixed(1) + \" fps\";\n out.title = (r.state === \"paused\" ? \"Frame rate the last playback reached\" : \"Frame rate playback reaches\") + (meta ? \"; the film runs at \" + meta.fps + \" fps\" : \"\");\n }\n setInterval(function () { if (playing) renderFps(); }, 500);\n // Pausing settles on exactly the frame the timecode shows.\n function pause() { stop(); keepPosition(); show(frame).then(scheduleWarm); }\n function togglePlay() { playing ? pause() : play(); }\n\n // Real time: nothing is rendered ahead. Each turn draws the frame due on\n // the clock straight to the screen, and frames that drawing missed are\n // skipped. Playback starts at once and the sound keeps its speed; a device\n // that can't keep up shows fewer frames, and the frame rate says how many.\n async function playFrames(alive) {\n meter.start();\n renderFps();\n // Sound starts with picture: wait for the block at the playhead.\n if (stream && audio.state !== \"none\") {\n setStalled(true);\n await streamBlock(stream, Math.floor(frame / meta.fps / BLOCK_SEC), true).catch(function () {});\n if (!alive()) return;\n setStalled(false);\n if (stream && audio.state === \"mixing\") setAudioStatus(\"ready\");\n }\n // A soundtrack still downloading would start late: wait for it.\n if (audio.state === \"loading\") {\n setStalled(true);\n while (alive() && audio.state === \"loading\") await sleep(50);\n // A newer playback may be waiting too: only this one's own stall ends here.\n if (!alive()) return;\n setStalled(false);\n }\n var f0 = frame, clock = startClock(f0), presented = -1;\n while (alive()) {\n var due = Math.min(last(), f0 + Math.floor(clock() * meta.fps));\n if (due === presented) { await sleep(4); continue; }\n setTime(due);\n // Each frame is drawn once; caching it would cost a texture and a copy per frame.\n var drawn = await player.render(due, { cache: false });\n if (!alive()) return;\n presented = shown = drawn;\n meter.record(drawn);\n if (drawn >= last()) return;\n }\n }\n\n // ── Timeline ──\n function frameAtX(clientX) {\n var r = tlInner.getBoundingClientRect();\n return clampFrame(Math.min(1, Math.max(0, (clientX - r.left) / r.width)) * last());\n }\n function renderRuler() {\n var ruler = $(\"ruler\"), width = tlInner.clientWidth || 800, dur = last() / meta.fps;\n ruler.replaceChildren();\n if (dur <= 0) return;\n var steps = [0.5, 1, 2, 5, 10, 15, 30, 60, 120, 300, 600];\n var major = steps.find(function (s) { return (s / dur) * width >= 80; }) || 1200, minor = major / 5;\n for (var i = 0; i * minor <= dur + 1e-6; i++) {\n var t = i * minor, isMajor = i % 5 === 0, x = (t / dur) * width;\n var tick = el(\"div\", \"tick\" + (isMajor ? \" major\" : \"\"));\n tick.style.left = (t / dur) * 100 + \"%\";\n if (isMajor && x < width - 34) {\n var m = Math.floor(t / 60), s = Math.round((t - m * 60) * 10) / 10;\n tick.append(el(\"span\", null, m + \":\" + (s < 10 ? \"0\" : \"\") + s));\n }\n ruler.append(tick);\n }\n }\n function drawWave() {\n var canvas = $(\"wave\"), dpr = window.devicePixelRatio || 1;\n var w = canvas.clientWidth, h = canvas.clientHeight;\n canvas.width = Math.round(w * dpr);\n canvas.height = Math.round(h * dpr);\n var ctx = canvas.getContext(\"2d\");\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n ctx.clearRect(0, 0, w, h);\n var peaks = audio.peaks;\n if (!peaks || !peaks.length || !meta) return;\n var videoSec = Math.max(1e-6, last() / meta.fps);\n var audioSec = audio.buffer ? audio.buffer.duration : videoSec;\n var spanPx = Math.min(1, audioSec / videoSec) * w, mid = h / 2;\n ctx.fillStyle = getComputedStyle(document.documentElement).getPropertyValue(\"--wave\").trim();\n for (var x = 0; x < spanPx; x += 3) {\n var v = Math.max(peaks[Math.floor((x / spanPx) * peaks.length)] || 0, 0.015);\n var bh = Math.max(1, Math.sqrt(v) * (h - 8));\n ctx.fillRect(x, mid - bh / 2, 2, bh);\n }\n }\n\n var dragging = false;\n timeline.addEventListener(\"pointerdown\", function (e) {\n if (!meta || e.button !== 0) return;\n timeline.setPointerCapture(e.pointerId);\n dragging = true;\n seek(frameAtX(e.clientX));\n });\n timeline.addEventListener(\"pointermove\", function (e) {\n if (!meta) return;\n var f = frameAtX(e.clientX), r = tlInner.getBoundingClientRect();\n var x = Math.min(r.width, Math.max(0, e.clientX - r.left));\n $(\"hoverLine\").style.display = \"block\";\n $(\"hoverLine\").style.left = x + \"px\";\n $(\"hoverTime\").style.display = \"block\";\n $(\"hoverTime\").style.left = x + \"px\";\n $(\"hoverTime\").textContent = fmt(f);\n if (dragging && f !== frame) seek(f);\n });\n timeline.addEventListener(\"pointerleave\", function () {\n $(\"hoverLine\").style.display = \"none\";\n $(\"hoverTime\").style.display = \"none\";\n });\n timeline.addEventListener(\"pointerup\", function () { dragging = false; });\n timeline.addEventListener(\"pointercancel\", function () { dragging = false; });\n\n // ── Notes: pause, click the picture (or drag an area) and say what should\n // change. Each is saved with its moment, its spot and a picture of the\n // frame with the spot marked, for whoever edits the film ──\n var notes = [], notesOn = false, draft = null, openId = null, saving = false, press = null;\n var pop = $(\"pop\"), pins = $(\"pins\"), NL = String.fromCharCode(10);\n var ICON_CHECK = '<svg viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M3.5 8.5l3 3 6-7\"/></svg>';\n var ICON_X = '<svg viewBox=\"0 0 16 16\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\"><path d=\"M4 4l8 8M12 4l-8 8\"/></svg>';\n\n function pct1(v) { return Math.round(v * 100) + \"%\"; }\n function whereText(n) {\n return n.w != null ? \"area \" + pct1(n.x) + \",\" + pct1(n.y) + \" to \" + pct1(n.x + n.w) + \",\" + pct1(n.y + n.h) : \"spot \" + pct1(n.x) + \",\" + pct1(n.y);\n }\n function noteById(id) { return notes.find(function (n) { return n.id === id; }); }\n function openCount() { return notes.filter(function (n) { return !n.done; }).length; }\n\n function renderNotes() {\n renderPins();\n renderMarks();\n renderList();\n var open = openCount(), count = $(\"notesCount\");\n count.textContent = String(open || notes.length);\n count.classList.toggle(\"open\", open > 0);\n $(\"notesBtn\").title = open + \" open of \" + notes.length;\n }\n function renderPins() {\n if (!meta) return;\n stage.classList.toggle(\"can-note\", notesOn && !!player && !playing);\n stage.classList.toggle(\"hint\", notes.length === 0 && !draft);\n pins.replaceChildren();\n if (playing) return;\n var here = notes.filter(function (n) { return n.frame === frame; });\n if (draft && draft.frame === frame) here.push(draft);\n here.forEach(function (n) {\n if (n.w != null) {\n var a = el(\"div\", \"area\" + (n.done ? \" done\" : \"\"));\n a.style.left = n.x * 100 + \"%\"; a.style.top = n.y * 100 + \"%\";\n a.style.width = n.w * 100 + \"%\"; a.style.height = n.h * 100 + \"%\";\n pins.append(a);\n }\n var pin = el(\"button\", \"pin\" + (n.done ? \" done\" : \"\") + (n === draft ? \" draft\" : \"\") + (n.id === openId ? \" active\" : \"\"));\n pin.style.left = n.x * 100 + \"%\"; pin.style.top = n.y * 100 + \"%\";\n if (n === draft) { pin.setAttribute(\"aria-label\", \"New note\"); }\n else {\n if (n.done) pin.innerHTML = ICON_CHECK; else pin.textContent = String(n.id);\n pin.title = n.text;\n pin.setAttribute(\"aria-label\", \"Note \" + n.id);\n pin.addEventListener(\"click\", function (e) { e.stopPropagation(); openCard(n.id); });\n }\n pins.append(pin);\n });\n }\n function renderMarks() {\n var marks = $(\"noteMarks\");\n marks.replaceChildren();\n if (!meta) return;\n // Notes close together stack, up to three high.\n var width = tlInner.clientWidth || 800, lanes = [];\n notes.slice().sort(function (a, b) { return a.frame - b.frame || a.id - b.id; }).forEach(function (n) {\n var x = (pct(n.frame) / 100) * width, lane = lanes.findIndex(function (end) { return x - end >= 16; });\n if (lane < 0) lane = lanes.length < 3 ? lanes.length : 2;\n lanes[lane] = x;\n var m = el(\"button\", \"note-mark\" + (n.done ? \" done\" : \"\") + (lane ? \" stacked\" : \"\"), String(n.id));\n m.style.left = pct(n.frame) + \"%\";\n m.style.top = 4 + lane * 15 + \"px\";\n m.title = n.id + \" · \" + fmt(n.frame) + \" · \" + n.text;\n m.addEventListener(\"pointerdown\", function (e) { e.stopPropagation(); });\n m.addEventListener(\"click\", function (e) { e.stopPropagation(); goToNote(n.id); });\n marks.append(m);\n });\n }\n function renderList() {\n var list = $(\"notesList\");\n list.replaceChildren();\n $(\"notesCopy\").disabled = openCount() === 0;\n if (!notes.length) {\n list.append(el(\"div\", \"np-empty\", \"Pause and click the picture to pin a note to that spot and moment. Drag to mark an area.\"));\n }\n notes.slice().sort(function (a, b) { return a.frame - b.frame || a.id - b.id; }).forEach(function (n) {\n var row = el(\"div\", \"note-row\" + (n.done ? \" done\" : \"\"));\n var body = el(\"div\");\n body.append(el(\"div\", \"when tnum\", fmt(n.frame) + \" · frame \" + n.frame), el(\"div\", \"text\", n.text));\n var check = el(\"button\", \"check\" + (n.done ? \" on\" : \"\"));\n check.innerHTML = ICON_CHECK;\n check.title = n.done ? \"Reopen\" : \"Mark done\";\n check.setAttribute(\"aria-label\", check.title);\n check.addEventListener(\"click\", function (e) { e.stopPropagation(); setDone(n.id, !n.done); });\n row.append(el(\"span\", \"badge\" + (n.done ? \" done\" : \"\"), String(n.id)), body, check);\n row.addEventListener(\"click\", function () { goToNote(n.id); });\n list.append(row);\n });\n var dir = meta && meta.notesDir ? meta.notesDir.split(\"/\").slice(-2).join(\"/\") : \"\";\n $(\"notesFoot\").textContent = \"Saved in \" + dir + \" with a picture of each spot. Ask your agent to go through them.\";\n }\n\n function notesRequest(body) {\n return fetch(\"/@framefields/notes\", { method: \"POST\", headers: { \"content-type\": \"application/json\" }, body: JSON.stringify(body) })\n .then(function (r) { if (!r.ok) throw new Error(\"HTTP \" + r.status); return r.json(); });\n }\n function upsert(note) {\n var i = notes.findIndex(function (n) { return n.id === note.id; });\n if (i >= 0) notes[i] = note; else notes.push(note);\n renderNotes();\n }\n function setDone(id, done) {\n notesRequest({ op: \"update\", id: id, done: done }).then(function (n) {\n upsert(n);\n if (openId === id) openCard(id);\n }, function () { toast(\"Couldn't save. Is the preview running?\"); });\n }\n function removeNote(id) {\n notesRequest({ op: \"remove\", id: id }).then(function () {\n notes = notes.filter(function (n) { return n.id !== id; });\n closePop();\n renderNotes();\n }, function () { toast(\"Couldn't delete. Is the preview running?\"); });\n }\n function goToNote(id) {\n var n = noteById(id);\n if (!n) return;\n $(\"notesPanel\").classList.remove(\"on\");\n $(\"notesBtn\").setAttribute(\"aria-expanded\", \"false\");\n seek(n.frame).then(function () { openCard(id); });\n }\n\n var toastTimer = null;\n function toast(text) {\n var t = $(\"toast\");\n t.textContent = text;\n t.classList.add(\"on\");\n clearTimeout(toastTimer);\n toastTimer = setTimeout(function () { t.classList.remove(\"on\"); }, 2200);\n }\n\n // ── Placing a note on the picture ──\n function fracAt(e) {\n var r = stage.getBoundingClientRect();\n return { x: Math.min(1, Math.max(0, (e.clientX - r.left) / r.width)), y: Math.min(1, Math.max(0, (e.clientY - r.top) / r.height)), r: r };\n }\n stage.addEventListener(\"pointerdown\", function (e) {\n if (e.button !== 0 || !player || e.target.closest(\".pin\")) return;\n if (playing) { pause(); return; }\n if (!notesOn) return;\n if (draft && pop.querySelector(\"textarea\") && pop.querySelector(\"textarea\").value.trim()) {\n pop.querySelector(\"textarea\").focus();\n return;\n }\n cancelDraft();\n var p = fracAt(e);\n press = { x: p.x, y: p.y, cx: e.clientX, cy: e.clientY, area: null };\n stage.setPointerCapture(e.pointerId);\n e.preventDefault();\n });\n stage.addEventListener(\"pointermove\", function (e) {\n if (!press) return;\n var p = fracAt(e);\n if (!press.area && Math.hypot(e.clientX - press.cx, e.clientY - press.cy) < 6) return;\n press.area = { x: Math.min(press.x, p.x), y: Math.min(press.y, p.y), w: Math.abs(p.x - press.x), h: Math.abs(p.y - press.y) };\n var m = $(\"marquee\");\n m.style.display = \"block\";\n m.style.left = press.area.x * 100 + \"%\"; m.style.top = press.area.y * 100 + \"%\";\n m.style.width = press.area.w * 100 + \"%\"; m.style.height = press.area.h * 100 + \"%\";\n });\n function endPress() {\n if (!press) return;\n var a = press.area;\n $(\"marquee\").style.display = \"none\";\n draft = a && a.w > 0.01 && a.h > 0.01\n ? { frame: frame, x: a.x, y: a.y, w: a.w, h: a.h, text: \"\" }\n : { frame: frame, x: press.x, y: press.y, text: \"\" };\n press = null;\n openComposer();\n }\n stage.addEventListener(\"pointerup\", endPress);\n stage.addEventListener(\"pointercancel\", function () { press = null; $(\"marquee\").style.display = \"none\"; });\n\n // ── The popover: writing a note, or reading one ──\n var popAnchor = null;\n function placePop() {\n if (!popAnchor || !pop.classList.contains(\"on\")) return;\n var r = stage.getBoundingClientRect(), n = popAnchor;\n var ax = r.left + (n.x + (n.w || 0)) * r.width, ay = r.top + (n.y + (n.h || 0)) * r.height;\n var w = pop.offsetWidth, h = pop.offsetHeight, gap = 16;\n var left = ax + gap + w <= innerWidth - 8 ? ax + gap : ax - gap - w - (n.w || 0) * r.width;\n var top = ay + gap + h <= innerHeight - 8 ? ay + gap / 2 : ay - h - gap / 2 - (n.h || 0) * r.height;\n pop.style.left = Math.max(8, Math.min(innerWidth - w - 8, left)) + \"px\";\n pop.style.top = Math.max(8, Math.min(innerHeight - h - 8, top)) + \"px\";\n }\n function showPop(anchor) {\n popAnchor = anchor;\n pop.classList.add(\"on\");\n placePop();\n }\n function closePop() {\n pop.classList.remove(\"on\");\n pop.replaceChildren();\n popAnchor = null;\n openId = null;\n renderPins();\n }\n function cancelDraft() {\n if (!draft) return;\n draft = null;\n closePop();\n }\n function openComposer() {\n openId = null;\n var head = el(\"div\", \"pop-head\");\n head.append(el(\"span\", \"badge\", \"+\"), el(\"span\", \"tnum\", fmt(draft.frame) + \" · \" + (draft.w != null ? \"area\" : \"spot\")));\n var ta = document.createElement(\"textarea\");\n ta.placeholder = \"What should change here?\";\n ta.rows = 2;\n ta.setAttribute(\"aria-label\", \"Note\");\n var foot = el(\"div\", \"pop-foot\"), status = el(\"span\", \"grow\", \"Enter to add\");\n var cancel = el(\"button\", \"btn\", \"Cancel\"), add = el(\"button\", \"btn primary\", \"Add note\");\n add.disabled = true;\n foot.append(status, cancel, add);\n pop.replaceChildren(head, ta, foot);\n ta.addEventListener(\"input\", function () {\n draft.text = ta.value;\n add.disabled = !ta.value.trim() || saving;\n ta.style.height = \"auto\";\n ta.style.height = Math.min(180, ta.scrollHeight) + \"px\";\n placePop();\n });\n ta.addEventListener(\"keydown\", function (e) {\n if (e.key === \"Enter\" && !e.shiftKey && !e.isComposing) { e.preventDefault(); saveDraft(ta, add, status); }\n if (e.key === \"Escape\") { e.preventDefault(); cancelDraft(); }\n });\n cancel.addEventListener(\"click\", cancelDraft);\n add.addEventListener(\"click\", function () { saveDraft(ta, add, status); });\n renderPins();\n showPop(draft);\n ta.focus();\n }\n async function saveDraft(ta, add, status) {\n var d = draft, text = ta.value.trim();\n if (!d || !text || saving) return;\n saving = true;\n ta.readOnly = true;\n add.disabled = true;\n status.className = \"grow\";\n status.textContent = \"Saving…\";\n try {\n var image = null;\n try { image = await capture(d); } catch (err) { report(\"warning\", [\"[framefields] no picture for the note:\", err]); }\n var note = await notesRequest({ op: \"add\", note: { frame: d.frame, time: d.frame / meta.fps, x: d.x, y: d.y, w: d.w, h: d.h, text: text, image: image } });\n if (draft === d) draft = null;\n closePop();\n upsert(note);\n toast(\"Note \" + note.id + \" saved\");\n } catch (err) {\n status.className = \"grow err\";\n status.textContent = \"Couldn't save. Is the preview running?\";\n ta.readOnly = false;\n add.disabled = false;\n } finally {\n saving = false;\n }\n }\n // The frame with the spot marked, small enough to read at a glance.\n async function capture(d) {\n var bmp = await player.snapshot(d.frame);\n var scale = Math.min(1, 1600 / bmp.width), w = Math.round(bmp.width * scale), h = Math.round(bmp.height * scale);\n var c = document.createElement(\"canvas\");\n c.width = w; c.height = h;\n var g = c.getContext(\"2d\");\n g.drawImage(bmp, 0, 0, w, h);\n bmp.close();\n var lw = Math.max(3, w / 400);\n function mark(draw) {\n // Dark, white, then blue: it reads on any picture.\n g.lineWidth = lw + 5; g.strokeStyle = \"rgba(0,0,0,0.5)\"; draw();\n g.lineWidth = lw + 2.5; g.strokeStyle = \"#fff\"; draw();\n g.lineWidth = lw; g.strokeStyle = \"#3b82f6\"; draw();\n }\n if (d.w != null) {\n mark(function () { g.strokeRect(d.x * w, d.y * h, d.w * w, d.h * h); });\n } else {\n var rad = Math.max(16, w / 40);\n mark(function () { g.beginPath(); g.arc(d.x * w, d.y * h, rad, 0, Math.PI * 2); g.stroke(); });\n g.fillStyle = \"#3b82f6\";\n g.beginPath(); g.arc(d.x * w, d.y * h, lw, 0, Math.PI * 2); g.fill();\n }\n return c.toDataURL(\"image/jpeg\", 0.88);\n }\n function openCard(id) {\n var n = noteById(id);\n if (!n) return;\n if (draft) cancelDraft();\n openId = id;\n var head = el(\"div\", \"pop-head\");\n var close = el(\"button\", \"btn x-btn\");\n close.innerHTML = ICON_X;\n close.title = \"Close\";\n close.setAttribute(\"aria-label\", \"Close\");\n close.addEventListener(\"click\", closePop);\n head.append(el(\"span\", \"badge\" + (n.done ? \" done\" : \"\"), String(n.id)), el(\"span\", \"tnum grow\", fmt(n.frame) + \" · frame \" + n.frame), close);\n var foot = el(\"div\", \"pop-foot\");\n var done = el(\"button\", \"btn\");\n done.innerHTML = ICON_CHECK;\n done.append(document.createTextNode(n.done ? \"Reopen\" : \"Mark done\"));\n done.addEventListener(\"click\", function () { setDone(n.id, !n.done); });\n var del = el(\"button\", \"btn danger\", \"Delete\");\n del.addEventListener(\"click\", function () { removeNote(n.id); });\n foot.append(done, el(\"span\", \"grow\"), del);\n pop.replaceChildren(head, el(\"div\", \"pop-text\", n.text), foot);\n renderPins();\n showPop(n);\n }\n\n document.addEventListener(\"pointerdown\", function (e) {\n if (pop.classList.contains(\"on\") && !pop.contains(e.target) && !stage.contains(e.target) && !e.target.closest(\".note-mark, .note-row\")) {\n if (openId != null) closePop();\n else if (draft && !draft.text.trim()) cancelDraft();\n }\n var panel = $(\"notesPanel\");\n if (panel.classList.contains(\"on\") && !panel.contains(e.target) && !$(\"notesBtn\").contains(e.target)) {\n panel.classList.remove(\"on\");\n $(\"notesBtn\").setAttribute(\"aria-expanded\", \"false\");\n }\n });\n document.addEventListener(\"keydown\", function (e) {\n if (e.key !== \"Escape\") return;\n if (openId != null) closePop();\n $(\"notesPanel\").classList.remove(\"on\");\n $(\"notesBtn\").setAttribute(\"aria-expanded\", \"false\");\n });\n window.addEventListener(\"resize\", placePop);\n new ResizeObserver(placePop).observe(stage);\n\n $(\"notesBtn\").addEventListener(\"click\", function () {\n var on = $(\"notesPanel\").classList.toggle(\"on\");\n $(\"notesBtn\").setAttribute(\"aria-expanded\", String(on));\n });\n $(\"notesClose\").addEventListener(\"click\", function () {\n $(\"notesPanel\").classList.remove(\"on\");\n $(\"notesBtn\").setAttribute(\"aria-expanded\", \"false\");\n });\n // The open notes as text, for a chat that can't read the notes folder.\n $(\"notesCopy\").addEventListener(\"click\", function () {\n var lines = [\"Notes on the preview of \" + JSON.stringify(meta.title) + \":\"];\n notes.filter(function (n) { return !n.done; }).sort(function (a, b) { return a.frame - b.frame; }).forEach(function (n) {\n lines.push(\"- Note \" + n.id + \" at \" + fmt(n.frame) + \" (frame \" + n.frame + \"), \" + whereText(n) + \": \" + n.text + (n.image ? \" [picture: \" + n.image + \"]\" : \"\"));\n });\n var text = lines.join(NL), label = $(\"notesCopyLabel\");\n var done = function () { label.textContent = \"Copied\"; setTimeout(function () { label.textContent = \"Copy\"; }, 1600); };\n (navigator.clipboard ? navigator.clipboard.writeText(text) : Promise.reject()).then(done, function () {\n var t = document.createElement(\"textarea\");\n t.value = text;\n document.body.append(t);\n t.select();\n try { document.execCommand(\"copy\"); done(); } catch (e) {}\n t.remove();\n });\n });\n\n function loadNotes() {\n if (!notesOn) return;\n fetch(\"/@framefields/notes\").then(function (r) { return r.json(); }).then(function (list) {\n notes = Array.isArray(list) ? list : [];\n renderNotes();\n }, function () {});\n }\n\n // ── Wiring ──\n $(\"play\").addEventListener(\"click\", togglePlay);\n $(\"prev\").addEventListener(\"click\", function () { seek(frame - 1); });\n $(\"next\").addEventListener(\"click\", function () { seek(frame + 1); });\n $(\"mute\").addEventListener(\"click\", function () { audio.muted = !audio.muted; applyVolume(); });\n $(\"volume\").addEventListener(\"input\", function (e) { audio.volume = Number(e.target.value); audio.muted = audio.volume === 0; applyVolume(); });\n\n // A hidden tab's timers are throttled, so playback would stutter: pause.\n document.addEventListener(\"visibilitychange\", function () {\n if (document.hidden && playing) pause();\n });\n\n // ── For scripts and agents driving the page: seek, play, and read the\n // state instead of clicking at coordinates ──\n var pageState = \"loading\";\n window.framefieldsPreview = {\n /** Shows the frame at a time in seconds; resolves once it is drawn. */\n seek: function (sec) { return ready().then(function () { return seek(Number(sec) * meta.fps); }); },\n /** Shows a frame by number; resolves once it is drawn. */\n seekFrame: function (f) { return ready().then(function () { return seek(f); }); },\n play: function () { return ready().then(play); },\n /** Notes pinned on the page, as saved in notes.json. */\n notes: function () { return notes.slice(); },\n pause: function () { if (playing) pause(); },\n /** Where the preview is: loading, ready or error, and what it shows. */\n state: function () {\n var rate = meter.read().fps;\n return {\n state: pageState,\n error: pageState === \"error\" ? $(\"stageMsg\").textContent : null,\n frame: frame,\n time: meta ? Number((frame / meta.fps).toFixed(3)) : 0,\n playing: playing,\n buffering: stalled,\n audio: audio.state,\n // Soundtrack blocks mixed in this page so far, of the film's.\n audioMixed: stream ? { blocks: stream.seen.size, of: stream.total, exact: Array.from(stream.blocks.values()).filter(function (b) { return b.exact; }).length } : null,\n // Where the soundtrack mixes: \"worker\" (off the page's thread), \"page\", or \"server\".\n audioMixer: stream ? (stream.worker ? \"worker\" : \"page\") : audio.mode === \"server\" ? \"server\" : null,\n connected: !offline,\n // Frames shown per second over the last 2 s of playback (the last\n // playback's while paused), and how long the last frame took to draw.\n playbackFps: rate == null ? null : Number(rate.toFixed(1)),\n renderMs: player ? Math.round(player.lastRenderMs) : null,\n meta: meta && { title: meta.title, width: meta.width, height: meta.height, fps: meta.fps, frameCount: meta.frameCount, duration: Number((last() / meta.fps).toFixed(3)) }\n };\n }\n };\n var readyWaiters = [];\n function ready() {\n if (player) return Promise.resolve();\n if (pageState === \"error\") return Promise.reject(new Error($(\"stageMsg\").textContent));\n return new Promise(function (resolve, reject) { readyWaiters.push([resolve, reject]); });\n }\n function settleReady(err) {\n readyWaiters.splice(0).forEach(function (w) { err ? w[1](err) : w[0](); });\n }\n\n function applyMeta(m) {\n meta = m;\n document.title = m.title + \" · preview\";\n $(\"title\").textContent = m.title;\n $(\"spec\").textContent = m.width + \"×\" + m.height + \" · \" + m.fps + \" fps · \" + fmt(last());\n $(\"tcDur\").textContent = fmt(last());\n stage.style.setProperty(\"--ar\", m.width + \" / \" + m.height);\n stage.style.setProperty(\"--ar-n\", String(m.width / m.height));\n renderRuler();\n }\n\n new ResizeObserver(function () {\n if (!meta) return;\n renderRuler();\n drawWave();\n renderMarks();\n }).observe(tlInner);\n\n // When the preview command ends the event stream drops and the browser keeps\n // retrying. The next preview of this project (same port) says hello with a\n // new session, and this tab reloads into it.\n var events = new EventSource(\"/events\");\n events.addEventListener(\"hello\", function (e) {\n var s = JSON.parse(e.data).session;\n if (session && s !== session) { location.reload(); return; }\n offline = false;\n renderChip();\n });\n events.addEventListener(\"error\", function () {\n if (!meta) return;\n if (playing) { stop(); keepPosition(); }\n offline = true;\n renderChip();\n });\n events.addEventListener(\"notes\", function (e) {\n notes = JSON.parse(e.data);\n if (openId != null && !noteById(openId)) closePop();\n renderNotes();\n });\n events.addEventListener(\"audio\", function (e) {\n if (meta) loadAudio(JSON.parse(e.data));\n });\n\n fetch(\"/meta\").then(function (r) { return r.json(); }).then(function (m) {\n session = m.session;\n applyMeta(m);\n notesOn = !!m.notesDir;\n $(\"notesBtn\").hidden = !notesOn;\n loadNotes();\n loadAudio(m.audio);\n setTime(startFrame());\n $(\"buffering\").classList.add(\"on\");\n whenPlayer();\n });\n\n // The composition's bundle (loaded below) announces its player.\n function whenPlayer() {\n if (!window.framefieldsPlayer) return;\n window.framefieldsPlayer.then(function (p) {\n player = p;\n if (audio.mode === \"browser\") beginStream(p);\n $(\"play\").disabled = false;\n show(frame).then(function () {\n pageState = \"ready\";\n renderPins();\n document.documentElement.dataset.preview = \"ready\";\n settleReady();\n scheduleWarm();\n });\n }, function (err) {\n showError(err);\n document.documentElement.dataset.preview = \"error\";\n settleReady(err instanceof Error ? err : new Error(String(err)));\n });\n }\n window.addEventListener(\"framefields:player\", function () { if (meta) whenPlayer(); });\n window.addEventListener(\"error\", function (e) {\n if (!player) showError(e.error || e.message);\n else report(\"error\", [e.error || e.message]);\n });\n window.addEventListener(\"unhandledrejection\", function (e) { report(\"error\", [e.reason]); });\n})();\n</script>\n<script type=\"module\" src=\"/@framefields/player.js\"></script>\n</body>\n</html>\n`;\n","/** Waveform peaks of a stereo mix: the loudest sample of either channel per bin, 0–1. */\nexport function computePeaks(\n\tleft: Float32Array,\n\tright: Float32Array,\n\tbins: number,\n): number[] {\n\tconst peaks = new Array<number>(bins).fill(0);\n\tif (left.length === 0) return peaks;\n\tconst per = left.length / bins;\n\tfor (let b = 0; b < bins; b++) {\n\t\tconst start = Math.floor(b * per);\n\t\tconst end = Math.min(left.length, Math.floor((b + 1) * per));\n\t\tlet max = 0;\n\t\tfor (let i = start; i < end; i++) {\n\t\t\tconst v = Math.max(Math.abs(left[i] ?? 0), Math.abs(right[i] ?? 0));\n\t\t\tif (v > max) max = v;\n\t\t}\n\t\tpeaks[b] = Math.min(1, max);\n\t}\n\treturn peaks;\n}\n\n/** Bins the page's waveform draws. */\nexport const PEAK_BINS = 1200;\n\n/** A mix counts as silent below this peak. */\nexport const SILENT_PEAK = 0.001;\n","import { spawn } from \"node:child_process\";\nimport { createHash, randomUUID } from \"node:crypto\";\nimport fs from \"node:fs\";\nimport http from \"node:http\";\nimport type { AddressInfo } from \"node:net\";\nimport path from \"node:path\";\nimport { fileURLToPath, pathToFileURL } from \"node:url\";\nimport type { VirtualMediaData } from \"@framefields/core\";\nimport { type AssetLocator, openAssetLocator } from \"@framefields/storage\";\nimport { inputStore } from \"@framefields/webgpu-renderers\";\nimport { encodeStereoWav } from \"../audio/index.js\";\nimport { HeadlessMediaRenderer } from \"../renderer/index.js\";\nimport {\n\tbundleProject,\n\tENGINE_URL,\n\tengineDir,\n\tonnxRuntimeWeb,\n\tvisionModelsDir,\n} from \"./bundle.js\";\nimport { describeNote, type NewNote, NoteStore } from \"./notes.js\";\nimport { previewPage } from \"./page.js\";\nimport { computePeaks, PEAK_BINS, SILENT_PEAK } from \"./peaks.js\";\n\n/** Where the composition comes from: the module that builds it. */\nexport interface PreviewSource {\n\t/** Path or file URL of the module, e.g. `new URL(\"./film.ts\", import.meta.url)`. */\n\tentry: string | URL;\n\t/**\n\t * The export to preview: a function returning a `Composition` (it may be\n\t * async), or a `Composition`. Defaults to `\"default\"`.\n\t */\n\texport?: string;\n}\n\n/** What the server needs from the composition: its size, timing and soundtrack. */\ninterface PreviewTarget {\n\ttoVirtualMedia?(): VirtualMediaData;\n\ttoVirtualMediaAsync?(): Promise<VirtualMediaData>;\n\tcomputeDuration?(): Promise<number>;\n\tprepareMediaTime?(): Promise<void>;\n\tfps?: number;\n\tdurationMs?: number;\n\twidth?: number;\n\theight?: number;\n}\n\nexport interface PreviewOptions {\n\t/**\n\t * Port to listen on. Defaults to a port derived from the working directory,\n\t * so every preview of a project lands on the same URL and an open tab picks\n\t * up the next one by itself. A preview already running there is replaced.\n\t */\n\tport?: number;\n\t/** Interface to bind. Defaults to \"127.0.0.1\" so the preview never leaves the machine. */\n\thost?: string;\n\t/**\n\t * Also open the page in the system's default browser. Off by default: the\n\t * caller shows the URL where it suits, e.g. an agent app's own browser\n\t * pane. Skipped when a tab from an earlier preview reconnects on its own.\n\t */\n\topen?: boolean;\n\t/** Title shown in the page header. */\n\ttitle?: string;\n\t/** Mix and play the composition's audio. Defaults to true. */\n\taudio?: boolean;\n\t/** Optional shared renderer instance, used to mix the soundtrack. */\n\trenderer?: HeadlessMediaRenderer;\n\t/**\n\t * Close the server this many milliseconds after the last open tab goes\n\t * away (closed, not reloaded). Defaults to 5000. `false` keeps it serving\n\t * until `close()`. Nothing closes before a tab has connected.\n\t */\n\tidleCloseMs?: number | false;\n\t/**\n\t * Files the page may load by absolute path (the composition's assets).\n\t * Defaults to the project that holds the entry: the nearest `.git`, else\n\t * the nearest `package.json`, else the entry's directory. Pass one path or\n\t * several to allow assets kept outside it.\n\t */\n\troot?: string | string[];\n\t/**\n\t * Where the preview reports what happens out of sight: the page opening,\n\t * errors in the page (a failed build, a frame that threw, no WebGPU), and\n\t * why the server stopped. Defaults to stderr, so whoever runs the preview\n\t * (an agent's background command, say) reads them in its output. `false`\n\t * keeps it quiet.\n\t */\n\tlog?: ((line: string) => void) | false;\n\t/**\n\t * Where notes pinned on the page are kept: `notes.json` and, per note, the\n\t * frame with its spot marked. Defaults to `.framefields/preview-notes` in the\n\t * working directory (`.framefields` ignores itself in git). `false` turns\n\t * notes off.\n\t */\n\tnotesDir?: string | false;\n}\n\n/** Why a preview stopped. */\nexport type PreviewCloseReason =\n\t/** `close()` was called. */\n\t| \"closed\"\n\t/** Its last tab closed (see `idleCloseMs`). */\n\t| \"idle\"\n\t/** A newer preview of the project took over its port. */\n\t| \"replaced\";\n\nexport interface PreviewSession {\n\turl: string;\n\tclose(): Promise<void>;\n\t/** Resolves with the reason when the server stops. */\n\tclosed: Promise<PreviewCloseReason>;\n}\n\ninterface PreviewMeta {\n\tsession: string;\n\t/** Where notes are kept; null when they are off. */\n\tnotesDir: string | null;\n\ttitle: string;\n\tfps: number;\n\tframeCount: number;\n\twidth: number;\n\theight: number;\n}\n\ntype AudioStatus =\n\t/** The page mixes it (the default); the server only when the page asks. */\n\t| { state: \"browser\" }\n\t| { state: \"mixing\" }\n\t| { state: \"ready\"; durationSec: number; peaks: number[] }\n\t| { state: \"none\" }\n\t| { state: \"error\"; error: string };\n\n/** Requests carrying this header come from another preview process, not a page. */\nconst TAKEOVER_HEADER = \"x-framefields-preview\";\nconst PLAYER_PATH = \"/@framefields/player.js\";\n\n/**\n * Serves a localhost player for a composition. The page loads the\n * composition's own code, bundled with the engine, and renders its frames\n * with WebGPU in the browser, like the editor's player; nothing is streamed.\n * The server provides the bundle, the project's files, and the soundtrack,\n * which it mixes with the export engine.\n *\n * The server runs until its tab closes or `close()`. Starting another preview\n * on the same port (the default for a project) stops this one, and open tabs\n * reload into it.\n */\nexport async function startPreview(\n\tsource: PreviewSource,\n\toptions: PreviewOptions = {},\n): Promise<PreviewSession> {\n\tconst entry =\n\t\tsource.entry instanceof URL || String(source.entry).startsWith(\"file:\")\n\t\t\t? fileURLToPath(source.entry)\n\t\t\t: path.resolve(String(source.entry));\n\tconst exportName = source.export ?? \"default\";\n\t// Vision nodes load their cached models, and onnxruntime-web's files when\n\t// the project has it installed.\n\tconst modelsDir = visionModelsDir();\n\tconst ort = onnxRuntimeWeb(path.dirname(entry));\n\tconst roots = [\n\t\t...(options.root === undefined\n\t\t\t? [repoRoot(path.dirname(entry))]\n\t\t\t: Array.isArray(options.root)\n\t\t\t\t? options.root\n\t\t\t\t: [options.root]),\n\t\tmodelsDir,\n\t\tort.dir,\n\t]\n\t\t.filter((r): r is string => !!r)\n\t\t.map((r) => path.resolve(r));\n\tconst sseClients = new Set<http.ServerResponse>();\n\tconst idleCloseMs = options.idleCloseMs ?? 5000;\n\tlet idleTimer: NodeJS.Timeout | undefined;\n\tconst title = options.title ?? \"framefields preview\";\n\tconst session = randomUUID();\n\tconst log =\n\t\toptions.log === false\n\t\t\t? () => {}\n\t\t\t: (options.log ?? ((line: string) => console.error(line)));\n\tconst say = (line: string) => log(`[framefields preview] ${line}`);\n\t// Each distinct page error is reported once; playback repeats them per frame.\n\tconst reported = new Set<string>();\n\tlet pageOpened = false;\n\n\tconst notes =\n\t\toptions.notesDir === false\n\t\t\t? undefined\n\t\t\t: new NoteStore(\n\t\t\t\t\tpath.resolve(\n\t\t\t\t\t\toptions.notesDir ??\n\t\t\t\t\t\t\tpath.join(process.cwd(), \".framefields\", \"preview-notes\"),\n\t\t\t\t\t),\n\t\t\t\t);\n\t// Changes run one at a time: each reads and rewrites notes.json.\n\tlet noteQueue: Promise<unknown> = Promise.resolve();\n\n\t// Assets framefields.json pins but that aren't on disk (an agent's\n\t// sandbox, a fresh clone) stream from their bucket: the page is\n\t// redirected there, and the soundtrack mix reads them over HTTP.\n\tconst assets = await openAssetLocator(path.dirname(entry)).catch(\n\t\t(err: unknown) => {\n\t\t\tsay(`pinned assets are off: ${(err as Error).message}`);\n\t\t\treturn undefined;\n\t\t},\n\t);\n\tif (assets) inputStore.setMissingMediaResolver((file) => assets.url(file));\n\tconst unreadable = new Set<string>();\n\n\tconst target = await loadTarget(entry, exportName);\n\tconst meta = await resolveMeta(target, title, session, notes?.dir ?? null);\n\t// The project's own code, bundled against the browser engine; the engine\n\t// itself is prebuilt (or built once, running from source).\n\tlet bundle: Promise<string> | undefined;\n\tconst playerBundle = () => {\n\t\tif (!bundle) {\n\t\t\tconst built = bundleProject({\n\t\t\t\tentry,\n\t\t\t\texportName,\n\t\t\t\tmodelsDir,\n\t\t\t\tortBase: ort.base,\n\t\t\t}).then((b) => b.code);\n\t\t\tbundle = built;\n\t\t\tbuilt.catch((err: unknown) => {\n\t\t\t\tsay(\n\t\t\t\t\t`could not build ${path.relative(process.cwd(), entry)}:\\n${errorText(err)}`,\n\t\t\t\t);\n\t\t\t\t// Let a later request try again, unless it started its own build.\n\t\t\t\tif (bundle === built) bundle = undefined;\n\t\t\t});\n\t\t}\n\t\treturn bundle;\n\t};\n\tconst engine = engineDir();\n\tengine.catch(() => {});\n\n\t// ── Audio: the page mixes it with the export's mixer, reading sources\n\t// itself (straight from their bucket, for pinned assets). The server mixes\n\t// only when the page can't, and sends the mix as a WAV. ──\n\tlet audio: AudioStatus = {\n\t\tstate: options.audio === false ? \"none\" : \"browser\",\n\t};\n\tlet audioWav: Buffer | undefined;\n\n\tfunction broadcast(event: string, data: unknown) {\n\t\tfor (const client of sseClients) {\n\t\t\tclient.write(`event: ${event}\\ndata: ${JSON.stringify(data)}\\n\\n`);\n\t\t}\n\t}\n\n\tfunction setAudio(next: AudioStatus) {\n\t\taudio = next;\n\t\tbroadcast(\"audio\", audio);\n\t}\n\n\tfunction startAudio() {\n\t\tif (audio.state !== \"mixing\") return;\n\t\tif (!target.toVirtualMediaAsync && !target.toVirtualMedia) {\n\t\t\tsetAudio({ state: \"none\" });\n\t\t\treturn;\n\t\t}\n\t\tconst renderer = options.renderer ?? new HeadlessMediaRenderer();\n\t\t(async () => {\n\t\t\tconst vm = target.toVirtualMediaAsync\n\t\t\t\t? await target.toVirtualMediaAsync()\n\t\t\t\t: (target.toVirtualMedia as () => VirtualMediaData)();\n\t\t\t// Live signal time is recorded frame by frame; finish it before mixing.\n\t\t\tawait target.prepareMediaTime?.();\n\t\t\treturn renderer.renderAudio(vm, { fps: meta.fps });\n\t\t})().then(\n\t\t\t({ channels, sampleRate }) => {\n\t\t\t\tconst left = channels[0] ?? new Float32Array(0);\n\t\t\t\tconst right = channels[1] ?? left;\n\t\t\t\tconst peaks = computePeaks(left, right, PEAK_BINS);\n\t\t\t\tif (left.length === 0 || Math.max(...peaks) < SILENT_PEAK) {\n\t\t\t\t\tsetAudio({ state: \"none\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\taudioWav = encodeStereoWav([left, right], { sampleRate });\n\t\t\t\tsetAudio({\n\t\t\t\t\tstate: \"ready\",\n\t\t\t\t\tdurationSec: left.length / sampleRate,\n\t\t\t\t\tpeaks: peaks.map((p) => Math.round(p * 1000) / 1000),\n\t\t\t\t});\n\t\t\t},\n\t\t\t(err: unknown) => {\n\t\t\t\tsetAudio({\n\t\t\t\t\tstate: \"error\",\n\t\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t\t});\n\t\t\t},\n\t\t);\n\t}\n\n\tlet resolveClosed!: (reason: PreviewCloseReason) => void;\n\tconst closed = new Promise<PreviewCloseReason>((r) => {\n\t\tresolveClosed = r;\n\t});\n\tlet closing: Promise<void> | undefined;\n\tfunction close(reason: PreviewCloseReason = \"closed\"): Promise<void> {\n\t\tclosing ??= new Promise<void>((resolve) => {\n\t\t\tclearTimeout(idleTimer);\n\t\t\tif (reason === \"idle\") say(\"stopped: its tab was closed\");\n\t\t\tif (reason === \"replaced\") say(\"stopped: a newer preview took over\");\n\t\t\tfor (const client of sseClients) client.end();\n\t\t\tsseClients.clear();\n\t\t\tif (assets) inputStore.setMissingMediaResolver();\n\t\t\tserver.close(() => {\n\t\t\t\tresolve();\n\t\t\t\tresolveClosed(reason);\n\t\t\t});\n\t\t\tserver.closeAllConnections();\n\t\t});\n\t\treturn closing;\n\t}\n\n\tconst server = http.createServer(async (req, res) => {\n\t\tconst url = new URL(req.url ?? \"/\", \"http://localhost\");\n\t\t// Cross-origin isolation: lets onnxruntime-web use SharedArrayBuffer and\n\t\t// threads for vision models (single-threaded otherwise). \"credentialless\"\n\t\t// keeps cross-origin assets such as the font stylesheet loading.\n\t\tres.setHeader(\"cross-origin-opener-policy\", \"same-origin\");\n\t\tres.setHeader(\"cross-origin-embedder-policy\", \"credentialless\");\n\t\ttry {\n\t\t\tif (req.method === \"GET\" && url.pathname === \"/\") {\n\t\t\t\tres.writeHead(200, {\n\t\t\t\t\t\"content-type\": \"text/html; charset=utf-8\",\n\t\t\t\t\t\"cache-control\": \"no-store\",\n\t\t\t\t});\n\t\t\t\tres.end(previewPage);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (req.method === \"GET\" && url.pathname === \"/meta\") {\n\t\t\t\tsendJson(res, 200, { ...meta, audio });\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (req.method === \"GET\" && url.pathname.startsWith(`${ENGINE_URL}/`)) {\n\t\t\t\tconst dir = await engine;\n\t\t\t\tconst file = path.resolve(\n\t\t\t\t\tdir,\n\t\t\t\t\t`.${url.pathname.slice(ENGINE_URL.length)}`,\n\t\t\t\t);\n\t\t\t\tif (isInside(dir, file) && (await isFile(file))) {\n\t\t\t\t\tawait sendFile(req, res, file);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tsendJson(res, 404, { error: \"not found\" });\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (req.method === \"GET\" && url.pathname === PLAYER_PATH) {\n\t\t\t\tlet code: string;\n\t\t\t\ttry {\n\t\t\t\t\tawait engine;\n\t\t\t\t\tcode = await playerBundle();\n\t\t\t\t} catch (err) {\n\t\t\t\t\t// Shown on the page: usually an error in the composition's code.\n\t\t\t\t\tconst message = err instanceof Error ? err.message : String(err);\n\t\t\t\t\tcode = `throw new Error(${JSON.stringify(`Could not build the preview:\\n${message}`)});`;\n\t\t\t\t}\n\t\t\t\tres.writeHead(200, {\n\t\t\t\t\t\"content-type\": \"text/javascript; charset=utf-8\",\n\t\t\t\t\t\"cache-control\": \"no-store\",\n\t\t\t\t});\n\t\t\t\tres.end(code);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// `fs.existsSync` / `statSync` in the page: a missing file is an\n\t\t\t// answer here, not a 404 the browser logs as an error.\n\t\t\tif (req.method === \"GET\" && url.pathname === \"/@framefields/stat\") {\n\t\t\t\tconst file = path.resolve(url.searchParams.get(\"path\") ?? \"\");\n\t\t\t\tlet size = -1;\n\t\t\t\tif (roots.some((r) => isInside(r, file))) {\n\t\t\t\t\tsize = await fs.promises.stat(file).then(\n\t\t\t\t\t\t(st) => (st.isFile() ? st.size : -1),\n\t\t\t\t\t\t() => assets?.entry(file)?.size ?? -1,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tsendJson(res, 200, { exists: size >= 0, size: Math.max(0, size) });\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (req.method === \"GET\" && url.pathname === \"/events\") {\n\t\t\t\tres.writeHead(200, {\n\t\t\t\t\t\"content-type\": \"text/event-stream\",\n\t\t\t\t\t\"cache-control\": \"no-store\",\n\t\t\t\t\tconnection: \"keep-alive\",\n\t\t\t\t});\n\t\t\t\t// Reconnect fast: a tab left open finds the next preview's\n\t\t\t\t// server within a second of it starting.\n\t\t\t\tres.write(\"retry: 1000\\n\");\n\t\t\t\tres.write(\n\t\t\t\t\t`event: hello\\ndata: ${JSON.stringify({ session: meta.session })}\\n\\n`,\n\t\t\t\t);\n\t\t\t\tsseClients.add(res);\n\t\t\t\tclearTimeout(idleTimer);\n\t\t\t\tif (!pageOpened) {\n\t\t\t\t\tpageOpened = true;\n\t\t\t\t\tsay(\n\t\t\t\t\t\tidleCloseMs === false\n\t\t\t\t\t\t\t? \"page opened\"\n\t\t\t\t\t\t\t: `page opened; the server stops ${Number((idleCloseMs / 1000).toFixed(1))} s after its last tab closes`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tonClient?.();\n\t\t\t\treq.on(\"close\", () => {\n\t\t\t\t\tsseClients.delete(res);\n\t\t\t\t\t// A reload reconnects within a second; a closed tab doesn't.\n\t\t\t\t\tif (sseClients.size === 0 && idleCloseMs !== false && !closing) {\n\t\t\t\t\t\tidleTimer = setTimeout(() => {\n\t\t\t\t\t\t\tif (sseClients.size === 0) void close(\"idle\");\n\t\t\t\t\t\t}, idleCloseMs);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// A newer preview of the same project wants this port. The custom\n\t\t\t// header keeps web pages from triggering it (it forces a CORS preflight).\n\t\t\tif (\n\t\t\t\treq.method === \"POST\" &&\n\t\t\t\turl.pathname === \"/takeover\" &&\n\t\t\t\treq.headers[TAKEOVER_HEADER] === \"1\"\n\t\t\t) {\n\t\t\t\tsendJson(res, 200, { ok: true });\n\t\t\t\tsetImmediate(() => void close(\"replaced\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Errors in the page, so they reach the terminal (and an agent)\n\t\t\t// without anyone looking at the browser. JSON only: other sites\n\t\t\t// can't send that without a preflight this server doesn't answer.\n\t\t\tif (\n\t\t\t\treq.method === \"POST\" &&\n\t\t\t\turl.pathname === \"/@framefields/log\" &&\n\t\t\t\treq.headers[\"content-type\"] === \"application/json\"\n\t\t\t) {\n\t\t\t\tconst body = await readBody(req, 16 * 1024);\n\t\t\t\tconst { message, level } = JSON.parse(body) as {\n\t\t\t\t\tmessage?: unknown;\n\t\t\t\t\tlevel?: unknown;\n\t\t\t\t};\n\t\t\t\tconst text = String(message ?? \"\").trim();\n\t\t\t\tif (text && !reported.has(text) && reported.size < 200) {\n\t\t\t\t\treported.add(text);\n\t\t\t\t\tsay(\n\t\t\t\t\t\t`${level === \"warning\" ? \"warning\" : \"error\"} in the page: ${text}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tres.writeHead(204).end();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (notes && url.pathname === \"/@framefields/notes\") {\n\t\t\t\tif (req.method === \"GET\") {\n\t\t\t\t\tsendJson(res, 200, await notes.list());\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (\n\t\t\t\t\treq.method === \"POST\" &&\n\t\t\t\t\treq.headers[\"content-type\"] === \"application/json\"\n\t\t\t\t) {\n\t\t\t\t\tconst body = JSON.parse(await readBody(req, 16 * 1024 * 1024)) as {\n\t\t\t\t\t\top?: string;\n\t\t\t\t\t\tid?: number;\n\t\t\t\t\t\tdone?: boolean;\n\t\t\t\t\t\ttext?: string;\n\t\t\t\t\t\tnote?: NewNote;\n\t\t\t\t\t};\n\t\t\t\t\tconst change = noteQueue.then(() =>\n\t\t\t\t\t\tchangeNote(notes, body, meta.fps, say),\n\t\t\t\t\t);\n\t\t\t\t\tnoteQueue = change.catch(() => {});\n\t\t\t\t\tconst note = await change;\n\t\t\t\t\tbroadcast(\"notes\", await notes.list());\n\t\t\t\t\tsendJson(res, note ? 200 : 404, note ?? { error: \"no such note\" });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// The page couldn't mix the soundtrack: mix it here instead.\n\t\t\tif (req.method === \"POST\" && url.pathname === \"/audio/mix\") {\n\t\t\t\tif (audio.state === \"browser\" || audio.state === \"error\") {\n\t\t\t\t\taudio = { state: \"mixing\" };\n\t\t\t\t\tstartAudio();\n\t\t\t\t}\n\t\t\t\tsendJson(res, 202, audio);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (req.method === \"GET\" && url.pathname === \"/audio\") {\n\t\t\t\tif (audio.state !== \"ready\" || !audioWav) {\n\t\t\t\t\tsendJson(res, 404, { error: `audio is ${audio.state}` });\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tres.writeHead(200, {\n\t\t\t\t\t\"content-type\": \"audio/wav\",\n\t\t\t\t\t\"content-length\": audioWav.length,\n\t\t\t\t\t\"cache-control\": \"no-store\",\n\t\t\t\t});\n\t\t\t\tres.end(audioWav);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Fonts referenced by family only resolve to assets/fonts/<Family>.ttf,\n\t\t\t// read from the project's folder, as the export engine does.\n\t\t\tconst fontMatch = /^\\/assets\\/fonts\\/([^/]+)$/.exec(url.pathname);\n\t\t\tif (fontMatch && (req.method === \"GET\" || req.method === \"HEAD\")) {\n\t\t\t\tconst file = path.join(\n\t\t\t\t\tprocess.cwd(),\n\t\t\t\t\t\"assets\",\n\t\t\t\t\t\"fonts\",\n\t\t\t\t\tdecodeURIComponent(fontMatch[1]),\n\t\t\t\t);\n\t\t\t\tif (await isFile(file)) {\n\t\t\t\t\tawait sendFile(req, res, file);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// The composition's files, by absolute path: what the program\n\t\t\t// references loads as-is.\n\t\t\tif (req.method === \"GET\" || req.method === \"HEAD\") {\n\t\t\t\t// The engine asks for local files under Vite's `/@fs/` prefix.\n\t\t\t\tconst file = path.resolve(\n\t\t\t\t\tdecodeURIComponent(url.pathname.replace(/^\\/@fs(?=\\/)/, \"\")),\n\t\t\t\t);\n\t\t\t\tif (roots.some((r) => isInside(r, file)) && (await isFile(file))) {\n\t\t\t\t\tawait sendFile(req, res, file);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (\n\t\t\t\t\tassets &&\n\t\t\t\t\t(await redirectToBucket(res, assets, file, unreadable, say))\n\t\t\t\t)\n\t\t\t\t\treturn;\n\t\t\t}\n\t\t\tsendJson(res, 404, { error: \"not found\" });\n\t\t} catch (err) {\n\t\t\tif (!res.headersSent) {\n\t\t\t\tsendJson(res, 500, {\n\t\t\t\t\terror: err instanceof Error ? err.message : String(err),\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tres.end();\n\t\t\t}\n\t\t}\n\t});\n\n\tlet onClient: (() => void) | undefined;\n\tconst host = options.host ?? \"127.0.0.1\";\n\tconst port = options.port ?? stablePort();\n\tconst listen = (p: number) =>\n\t\tnew Promise<void>((resolve, reject) => {\n\t\t\tconst fail = (err: Error) => {\n\t\t\t\tserver.off(\"listening\", ok);\n\t\t\t\treject(err);\n\t\t\t};\n\t\t\tconst ok = () => {\n\t\t\t\tserver.off(\"error\", fail);\n\t\t\t\tresolve();\n\t\t\t};\n\t\t\tserver.once(\"error\", fail);\n\t\t\tserver.once(\"listening\", ok);\n\t\t\tserver.listen(p, host);\n\t\t});\n\ttry {\n\t\tawait listen(port);\n\t} catch (err) {\n\t\tif ((err as NodeJS.ErrnoException).code !== \"EADDRINUSE\") throw err;\n\t\t// Most likely the previous preview of this project: ask it to step aside.\n\t\tif (await requestTakeover(host, port)) {\n\t\t\tawait listen(port).catch(() => listen(0));\n\t\t} else if (options.port === undefined) {\n\t\t\tawait listen(0);\n\t\t} else {\n\t\t\tthrow err;\n\t\t}\n\t}\n\tconst { port: actualPort } = server.address() as AddressInfo;\n\tconst url = `http://${host.includes(\":\") ? `[${host}]` : host}:${actualPort}/`;\n\n\t// Build the page's bundle while the browser opens.\n\tvoid playerBundle().catch(() => {});\n\tif (options.open === true) {\n\t\t// A tab from the last preview reconnects within ~1 s and reloads itself;\n\t\t// only open a new one when nobody comes back.\n\t\tconst reconnected = await new Promise<boolean>((resolve) => {\n\t\t\tconst timer = setTimeout(() => resolve(false), 2000);\n\t\t\tonClient = () => {\n\t\t\t\tclearTimeout(timer);\n\t\t\t\tresolve(true);\n\t\t\t};\n\t\t\tif (sseClients.size > 0) onClient();\n\t\t});\n\t\tonClient = undefined;\n\t\tif (!reconnected) openBrowser(url);\n\t}\n\n\treturn { url, closed, close: () => close() };\n}\n\nasync function loadTarget(\n\tentry: string,\n\texportName: string,\n): Promise<PreviewTarget> {\n\tconst mod = (await import(pathToFileURL(entry).href)) as Record<\n\t\tstring,\n\t\tunknown\n\t>;\n\tconst exported = mod[exportName];\n\tif (exported === undefined) {\n\t\tthrow new Error(\n\t\t\t`[framefields] ${entry} has no export named \"${exportName}\" to preview.`,\n\t\t);\n\t}\n\treturn (\n\t\ttypeof exported === \"function\"\n\t\t\t? await (exported as () => unknown)()\n\t\t\t: exported\n\t) as PreviewTarget;\n}\n\n/**\n * The project holding `dir`: the nearest `.git`, else the nearest\n * `package.json`, else `dir` itself. A repo wins over a nested package, so a\n * monorepo's shared assets stay reachable.\n */\nfunction repoRoot(dir: string): string {\n\tlet project: string | undefined;\n\tfor (let d = dir; ; d = path.dirname(d)) {\n\t\tif (fs.existsSync(path.join(d, \".git\"))) return d;\n\t\tif (!project && fs.existsSync(path.join(d, \"package.json\"))) project = d;\n\t\tif (path.dirname(d) === d) return project ?? dir;\n\t}\n}\n\n/** Applies one change from the page and says what it was. */\nasync function changeNote(\n\tnotes: NoteStore,\n\tbody: {\n\t\top?: string;\n\t\tid?: number;\n\t\tdone?: boolean;\n\t\ttext?: string;\n\t\tnote?: NewNote;\n\t},\n\tfps: number,\n\tsay: (line: string) => void,\n) {\n\tif (body.op === \"add\" && body.note && String(body.note.text).trim()) {\n\t\tconst note = await notes.add(body.note);\n\t\tconst files = [\n\t\t\tnote.image && `frame with the spot marked: ${note.image}`,\n\t\t\t`all notes: ${notes.file}`,\n\t\t];\n\t\tsay(`${describeNote(note, fps)}\\n ${files.filter(Boolean).join(\" · \")}`);\n\t\treturn note;\n\t}\n\tconst id = Number(body.id);\n\tif (body.op === \"update\") {\n\t\tconst note = await notes.update(id, body);\n\t\tif (note && typeof body.done === \"boolean\")\n\t\t\tsay(`note ${id} marked ${note.done ? \"done\" : \"open\"}`);\n\t\treturn note;\n\t}\n\tif (body.op === \"remove\") {\n\t\tconst note = await notes.remove(id);\n\t\tif (note) say(`note ${id} deleted`);\n\t\treturn note;\n\t}\n\treturn undefined;\n}\n\nfunction errorText(err: unknown): string {\n\treturn err instanceof Error ? err.message : String(err);\n}\n\nfunction readBody(req: http.IncomingMessage, limit: number): Promise<string> {\n\treturn new Promise((resolve, reject) => {\n\t\tlet body = \"\";\n\t\treq.setEncoding(\"utf8\");\n\t\treq.on(\"data\", (chunk: string) => {\n\t\t\tbody += chunk;\n\t\t\tif (body.length > limit) {\n\t\t\t\treject(new Error(\"request body too large\"));\n\t\t\t\treq.destroy();\n\t\t\t}\n\t\t});\n\t\treq.on(\"end\", () => resolve(body));\n\t\treq.on(\"error\", reject);\n\t});\n}\n\nfunction isInside(root: string, file: string): boolean {\n\tconst rel = path.relative(root, file);\n\treturn !!rel && !rel.startsWith(\"..\") && !path.isAbsolute(rel);\n}\n\nasync function isFile(file: string): Promise<boolean> {\n\ttry {\n\t\treturn (await fs.promises.stat(file)).isFile();\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nconst CONTENT_TYPES: Record<string, string> = {\n\t\".png\": \"image/png\",\n\t\".jpg\": \"image/jpeg\",\n\t\".jpeg\": \"image/jpeg\",\n\t\".webp\": \"image/webp\",\n\t\".gif\": \"image/gif\",\n\t\".svg\": \"image/svg+xml\",\n\t\".mp4\": \"video/mp4\",\n\t\".mov\": \"video/quicktime\",\n\t\".webm\": \"video/webm\",\n\t\".mp3\": \"audio/mpeg\",\n\t\".wav\": \"audio/wav\",\n\t\".m4a\": \"audio/mp4\",\n\t\".ogg\": \"audio/ogg\",\n\t\".ttf\": \"font/ttf\",\n\t\".otf\": \"font/otf\",\n\t\".woff\": \"font/woff\",\n\t\".woff2\": \"font/woff2\",\n\t\".json\": \"application/json\",\n\t\".js\": \"text/javascript; charset=utf-8\",\n\t\".mjs\": \"text/javascript; charset=utf-8\",\n\t\".wasm\": \"application/wasm\",\n\t\".onnx\": \"application/octet-stream\",\n\t\".srt\": \"text/plain; charset=utf-8\",\n\t\".txt\": \"text/plain; charset=utf-8\",\n};\n\n/**\n * Sends the page to the bucket for a pinned asset that isn't on disk. The\n * bucket must allow this page's origin (CORS): the engine reads media with\n * `fetch`. True when it answered.\n */\nasync function redirectToBucket(\n\tres: http.ServerResponse,\n\tassets: AssetLocator,\n\tfile: string,\n\tunreadable: Set<string>,\n\tsay: (line: string) => void,\n): Promise<boolean> {\n\tif (!assets.entry(file)) return false;\n\tconst url = await assets.url(file);\n\tif (url) {\n\t\tres.writeHead(302, { location: url, \"cache-control\": \"no-store\" });\n\t\tres.end();\n\t\treturn true;\n\t}\n\tconst rel = path.relative(assets.root, file);\n\tif (!unreadable.has(rel)) {\n\t\tunreadable.add(rel);\n\t\tsay(\n\t\t\t`${rel} is pinned on a private remote and there are no keys here: set its FRAMEFIELDS_STORAGE_* keys, give the remote a publicUrl, or \\`npx framefields assets pull\\``,\n\t\t);\n\t}\n\treturn false;\n}\n\n/** Serves a file, with byte ranges so video can seek without loading it whole. */\nasync function sendFile(\n\treq: http.IncomingMessage,\n\tres: http.ServerResponse,\n\tfile: string,\n) {\n\tconst { size } = await fs.promises.stat(file);\n\tconst type =\n\t\tCONTENT_TYPES[path.extname(file).toLowerCase()] ??\n\t\t\"application/octet-stream\";\n\tconst range = /^bytes=(\\d*)-(\\d*)$/.exec(req.headers.range ?? \"\");\n\tlet start = 0;\n\tlet end = size - 1;\n\tif (range && (range[1] || range[2])) {\n\t\tif (range[1]) {\n\t\t\tstart = Number(range[1]);\n\t\t\tif (range[2]) end = Math.min(end, Number(range[2]));\n\t\t} else {\n\t\t\tstart = Math.max(0, size - Number(range[2]));\n\t\t}\n\t\tif (start > end || start >= size) {\n\t\t\tres.writeHead(416, { \"content-range\": `bytes */${size}` });\n\t\t\tres.end();\n\t\t\treturn;\n\t\t}\n\t}\n\tres.writeHead(range ? 206 : 200, {\n\t\t\"content-type\": type,\n\t\t\"content-length\": end - start + 1,\n\t\t\"accept-ranges\": \"bytes\",\n\t\t\"cache-control\": \"no-cache\",\n\t\t...(range && { \"content-range\": `bytes ${start}-${end}/${size}` }),\n\t});\n\tif (req.method === \"HEAD\") {\n\t\tres.end();\n\t\treturn;\n\t}\n\tfs.createReadStream(file, { start, end }).pipe(res);\n}\n\nasync function resolveMeta(\n\ttarget: PreviewTarget,\n\ttitle: string,\n\tsession: string,\n\tnotesDir: string | null,\n): Promise<PreviewMeta> {\n\tconst fps = target.fps ?? 24;\n\tconst durationMs =\n\t\ttarget.durationMs ?? (await target.computeDuration?.()) ?? 5000;\n\treturn {\n\t\tsession,\n\t\tnotesDir,\n\t\ttitle,\n\t\tfps,\n\t\tframeCount: Math.max(1, Math.round((durationMs / 1000) * fps)),\n\t\twidth: target.width ?? 1920,\n\t\theight: target.height ?? 1080,\n\t};\n}\n\n/** Same project, same port: 41000–41899, from the working directory. */\nfunction stablePort(): number {\n\tconst digest = createHash(\"sha1\").update(process.cwd()).digest();\n\treturn 41000 + (digest.readUInt16BE(0) % 900);\n}\n\n/** Asks a preview already on `port` to shut down; false if something else is there. */\nasync function requestTakeover(host: string, port: number): Promise<boolean> {\n\t// A fresh connection (no agent): a pooled keep-alive socket may point at a\n\t// server that is already gone.\n\tconst ok = await new Promise<boolean>((resolve) => {\n\t\tconst req = http.request(\n\t\t\t{\n\t\t\t\thost,\n\t\t\t\tport,\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tpath: \"/takeover\",\n\t\t\t\tagent: false,\n\t\t\t\theaders: { [TAKEOVER_HEADER]: \"1\" },\n\t\t\t\ttimeout: 1500,\n\t\t\t},\n\t\t\t(res) => {\n\t\t\t\tres.resume();\n\t\t\t\tresolve(res.statusCode === 200);\n\t\t\t},\n\t\t);\n\t\treq.on(\"timeout\", () => req.destroy());\n\t\treq.on(\"error\", () => resolve(false));\n\t\treq.end();\n\t});\n\t// Give it a moment to release the socket.\n\tif (ok) await new Promise((r) => setTimeout(r, 150));\n\treturn ok;\n}\n\n/** Max absolute amplitude of both channels per bin, 0..1. */\nfunction sendJson(res: http.ServerResponse, status: number, body: unknown) {\n\tres.writeHead(status, {\n\t\t\"content-type\": \"application/json\",\n\t\t\"cache-control\": \"no-store\",\n\t});\n\tres.end(JSON.stringify(body));\n}\n\nfunction openBrowser(url: string) {\n\tconst [cmd, args] =\n\t\tprocess.platform === \"darwin\"\n\t\t\t? [\"open\", [url]]\n\t\t\t: process.platform === \"win32\"\n\t\t\t\t? [\"cmd\", [\"/c\", \"start\", \"\", url]]\n\t\t\t\t: [\"xdg-open\", [url]];\n\ttry {\n\t\tspawn(cmd, args, { stdio: \"ignore\", detached: true })\n\t\t\t.on(\"error\", () => {})\n\t\t\t.unref();\n\t} catch {}\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as k,b as l,c as m,d as n}from"./chunk-2CECZN67.mjs";import{a as j}from"./chunk-
|
|
1
|
+
import{a as k,b as l,c as m,d as n}from"./chunk-2CECZN67.mjs";import{a as j}from"./chunk-N2VISDTG.mjs";import{a as g}from"./chunk-VMXJZ4KQ.mjs";import{a as b}from"./chunk-CYM75INB.mjs";import{a as c}from"./chunk-R4H52Q7S.mjs";import{a as d}from"./chunk-PD2HKW2S.mjs";import{a as e}from"./chunk-RYSV44B4.mjs";import{a as f}from"./chunk-SV3ZNZQU.mjs";import{a as h}from"./chunk-UVEDFIPN.mjs";import{e as i}from"./chunk-FRJPXWAG.mjs";import"./chunk-HOV6DDBZ.mjs";import"./chunk-RECQ74RP.mjs";import"./chunk-UE7Y65C3.mjs";import"./chunk-6FULVULN.mjs";import"./chunk-XOF6UTZX.mjs";import{a}from"./chunk-LZNHGH56.mjs";import"./chunk-UZTA2EUU.mjs";import"./chunk-GXFYRMJZ.mjs";import"./chunk-77MT33VR.mjs";import"./chunk-ULOFDFHJ.mjs";import"./chunk-Y4NIR77Z.mjs";export{a as WebGPUAudioProcessor,d as audioFadeRenderer,h as audioSignalExtractorRenderer,b as compressorRenderer,c as delayRenderer,n as encodeStereoWav,i as mixAudioTracks,l as mixSfxInto,e as noiseGateRenderer,f as parametricEqRenderer,k as renderSfx,g as reverbRenderer,m as softLimit,j as stereoPanningRenderer};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{w as P,x as V}from"./chunk-7M5LE6EM.mjs";import{c as I}from"./chunk-FRJPXWAG.mjs";import{n as S}from"./chunk-HOV6DDBZ.mjs";import{a as x,b as y,d as D,f as m}from"./chunk-UE7Y65C3.mjs";import{b,j as p,v as T}from"./chunk-64L4GUOF.mjs";import{b as g}from"./chunk-354GX6IB.mjs";import{g as h}from"./chunk-Y4NIR77Z.mjs";h();var f=class{constructor(e,t){this.url=e;this.isHeadless=t}url;isHeadless;input=null;initPromise=null;iterator=null;iteratorStartTimestamp=-1;currentFetchId=0;fetchLock=Promise.resolve();destroyed=!1;async init(){if(!this.input)return this.initPromise?this.initPromise:(this.destroyed=!1,this.initPromise=(async()=>{try{let e=await m.acquire(this.url);this.destroyed?m.release(this.url):this.input=e}catch(e){throw this.initPromise=null,e}})(),this.initPromise)}destroy(){this.destroyed=!0,this.iterator?.return(),this.input&&(m.release(this.url),this.input=null),this.initPromise=null,this.onDestroy()}},w=class extends f{canvasSink=null;videoSampleSink=null;currentFrame=null;nextFrame=null;currentSample=null;nextSample=null;headlessQueue=[];backwardFrames=[];backwardBytes=0;backwardEnd;lastHeadlessTimestamp;isDecoding=!1;decodeBuffer=null;nextRequest=null;async durationSec(){await this.init();let e=await this.input?.getPrimaryVideoTrack();if(!e)throw new Error(`No video track in ${this.url}`);return e.computeDuration()}pushHeadlessQueue(e){let t=this.headlessQueue.findIndex(r=>r.timestamp>e.timestamp);t===-1?this.headlessQueue.push(e):this.headlessQueue.splice(t,0,e)}async fillHeadlessQueue(e){if(this.iterator)for(;this.headlessQueue.length<2;){let t=await this.iterator.next();if(this.currentFetchId!==e){t.value?.close?.();return}if(t.done||!t.value)break;this.pushHeadlessQueue(t.value)}}async closeIterator(e){if(e)try{await e.return()}catch{}}resetSinks(){let e=this.iterator;if(this.iterator=null,e)try{e.return()}catch{}this.canvasSink=null,this.videoSampleSink=null,this.currentFrame=null,this.nextFrame=null,this.currentSample?.close(),this.currentSample=null,this.nextSample?.close(),this.nextSample=null;for(let t of this.headlessQueue)try{t.close()}catch{}this.headlessQueue=[],this.clearBackwardFrames(),this.lastHeadlessTimestamp=void 0,this.decodeBuffer=null,this.iteratorStartTimestamp=-1}clearBackwardFrames(){for(let e of this.backwardFrames)e.close();this.backwardFrames=[],this.backwardBytes=0,this.backwardEnd=void 0}async copyHeadlessSample(e,t){let r=e.displayWidth,i=e.displayHeight,a=r*i*4,s=this.decodeBuffer;return(!s||s.length!==a)&&(s=new Uint8Array(a),this.decodeBuffer=s),await e.copyTo(s,{format:"RGBA"}),this.currentFetchId!==t||this.destroyed||!this.input?null:(g(s),{width:r,height:i,buffer:s})}cachedBackwardFrame(e){let t,r=-1;for(let[a,s]of this.backwardFrames.entries()){if(s.timestamp>e+1e-4)break;t=s,r=a}let i=this.backwardFrames[r+1]?.timestamp??this.backwardEnd;if(!(!t||!(t.duration>0)||e+1e-4>=Math.min(t.timestamp+t.duration,i??1/0)))return t}async fillBackwardFrames(e,t){if(!this.videoSampleSink)return;await this.closeIterator(this.iterator),this.iterator=null;for(let i of this.headlessQueue)i.close();this.headlessQueue=[],this.clearBackwardFrames();let r=this.videoSampleSink.samples(Math.max(0,e-.5));try{for await(let i of r){if(i.timestamp>e+1e-4&&(this.backwardEnd=i.timestamp),this.destroyed||this.currentFetchId!==t||i.timestamp>e+1e-4){i.close();break}let a=i.displayWidth*i.displayHeight*4;if(a>48*1024*1024){i.close();break}for(this.backwardFrames.push(i),this.backwardBytes+=a;this.backwardBytes>48*1024*1024&&this.backwardFrames.length>1;){let s=this.backwardFrames.shift();s&&(this.backwardBytes-=s.displayWidth*s.displayHeight*4,s.close())}}}finally{await this.closeIterator(r)}}onDestroy(){this.resetSinks()}async startIterator(e,t){if(!this.canvasSink)return;let r=Math.max(0,Number.isFinite(e)?e:0),i=this.iterator;if(this.iterator=null,await this.closeIterator(i),this.currentFetchId!==t||this.destroyed)return;this.iterator=this.canvasSink.canvases(r);let a=await this.iterator.next();if(this.currentFetchId!==t||this.destroyed)return;this.currentFrame=a.value??null;let s=await this.iterator.next();this.currentFetchId!==t||this.destroyed||(this.nextFrame=s.value??null,this.iteratorStartTimestamp=r)}async startIteratorHeadless(e,t){if(!this.videoSampleSink)return;let r=Math.max(0,Number.isFinite(e)?e:0),i=this.iterator;if(this.iterator=null,await this.closeIterator(i),this.currentFetchId!==t||this.destroyed)return;for(let s of this.headlessQueue)s.close();this.headlessQueue=[];let a=Math.max(0,r-.1);this.iterator=this.videoSampleSink.samples(a),await this.fillHeadlessQueue(t),!(this.currentFetchId!==t||this.destroyed)&&(this.iteratorStartTimestamp=r)}async getFrame(e){if(await this.init(),this.destroyed||!this.input)return null;let t=Math.max(0,Number.isFinite(e)?e:0);return this.isDecoding?(this.nextRequest&&this.nextRequest.resolve(null),new Promise((r,i)=>{this.nextRequest={timestampSec:t,resolve:r,reject:i}})):this.executeDecode(t)}async executeDecode(e){this.isDecoding=!0;try{return await this.performDecode(e)}finally{if(this.isDecoding=!1,this.nextRequest){let t=this.nextRequest;this.nextRequest=null,this.executeDecode(t.timestampSec).then(t.resolve).catch(t.reject)}}}async performDecodeHeadless(e,t){if(!this.videoSampleSink){if(this.destroyed||!this.input)return null;let d=await this.input.getPrimaryVideoTrack();if(!d)return null;this.videoSampleSink=new x(d)}let r=1e-4,i=1,a=.01,s=this.lastHeadlessTimestamp!==void 0&&e<this.lastHeadlessTimestamp-1e-4;this.lastHeadlessTimestamp=e;let o=this.cachedBackwardFrame(e);if(!o&&s&&(await this.fillBackwardFrames(e,t),o=this.cachedBackwardFrame(e)),o)return this.copyHeadlessSample(o,t);if((!this.iterator||e<this.iteratorStartTimestamp-r||this.headlessQueue.length>0&&e<this.headlessQueue[0].timestamp-a||this.headlessQueue.length>0&&e-this.headlessQueue[0].timestamp>i)&&(await this.startIteratorHeadless(e,t),this.currentFetchId!==t||this.destroyed||!this.input))return null;for(;;){if(await this.fillHeadlessQueue(t),this.currentFetchId!==t||this.destroyed||!this.input)return null;if(this.headlessQueue.length>=2&&this.headlessQueue[1].timestamp<=e+r){this.headlessQueue.shift()?.close();continue}break}let n=this.headlessQueue[0];return n?this.copyHeadlessSample(n,t):null}async performDecodeInternal(e){this.currentFetchId++;let t=this.currentFetchId;if(this.isHeadless)return this.performDecodeHeadless(e,t);if(!this.canvasSink){if(this.destroyed||!this.input)return null;let o=await this.input.getPrimaryVideoTrack();if(!o)return null;this.canvasSink=new y(o,{poolSize:3,alpha:!0})}let r=1e-4;if((!this.iterator||e<this.iteratorStartTimestamp-r||this.currentFrame&&e<this.currentFrame.timestamp-.01||this.currentFrame&&e-this.currentFrame.timestamp>1)&&(await this.startIterator(e,t),this.currentFetchId!==t||this.destroyed||!this.input))return null;for(;this.nextFrame&&this.nextFrame.timestamp<=e+r;)if(this.currentFrame=this.nextFrame,this.nextFrame=null,this.iterator){let o=await this.iterator.next();if(this.currentFetchId!==t||this.destroyed||!this.input)return null;this.nextFrame=o.value??null}if(!this.currentFrame)return null;let s=this.currentFrame.canvas;return{width:s.width,height:s.height,canvas:s}}async performDecode(e){let t=Math.max(0,Number.isFinite(e)?e:0);try{return await this.performDecodeInternal(t)}catch(r){let i=r;if(i?.name==="InputDisposedError"||i?.message?.includes("disposed")||this.destroyed||!this.input)return null;this.resetSinks();try{if(!this.isHeadless&&this.input&&!this.destroyed){let a=await this.input.getPrimaryVideoTrack();if(a&&!this.destroyed)return this.canvasSink=new y(a,{poolSize:3,alpha:!0,decoderOptions:{hardwareAcceleration:"prefer-software"}}),await this.performDecodeInternal(t)}}catch{}throw r}}},v=class extends f{audioSink=null;onDestroy(){this.audioSink=null}async startIterator(e,t){this.audioSink&&(this.iterator?.return(),this.iterator=this.audioSink.buffers(e),this.iteratorStartTimestamp=e)}async*getBuffers(e){try{if(await this.init(),this.destroyed||!this.input)return;if(!this.audioSink){let i=await this.input.getPrimaryAudioTrack();if(!i)return;this.audioSink=new D(i)}(!this.iterator||Math.abs(e-this.iteratorStartTimestamp)>.05)&&(this.currentFetchId++,await this.startIterator(e,this.currentFetchId));let r=this.currentFetchId;if(!this.iterator)return;for await(let i of this.iterator){if(this.currentFetchId!==r||this.destroyed||!this.input)break;this.iteratorStartTimestamp=i.timestamp+i.duration,yield i}}catch(t){let r=t;if(r?.name==="InputDisposedError"||r?.message?.includes("disposed")||this.destroyed||!this.input)return;throw t}finally{let t=this.currentFetchId;this.currentFetchId===t&&(this.iterator=null)}}},k=class{videoDecoders=new Map;audioDecoders=new Map;lastAccessed=new Map;ticks=0;lastTick=new Map;touchKey(e){this.lastAccessed.set(e,Date.now()),this.lastTick.set(e,this.ticks)}dropKey(e){let t=this.videoDecoders.get(e);if(t){try{t.destroy()}catch{}this.videoDecoders.delete(e)}let r=this.audioDecoders.get(e);if(r){try{r.destroy()}catch{}this.audioDecoders.delete(e)}this.lastAccessed.delete(e),this.lastTick.delete(e)}tick(e=120){this.ticks++;for(let[t,r]of this.lastTick)this.ticks-r>e&&this.dropKey(t)}destroy(){for(let e of this.videoDecoders.values())try{e.destroy()}catch{}this.videoDecoders.clear();for(let e of this.audioDecoders.values())try{e.destroy()}catch{}this.audioDecoders.clear(),this.lastAccessed.clear(),this.lastTick.clear()}get size(){return this.videoDecoders.size+this.audioDecoders.size}prune(e=3e5){let t=Date.now();for(let[r,i]of this.lastAccessed.entries())t-i>e&&this.dropKey(r)}getVideo(e,t,r){let i=`${e}-${t}-${r??"default"}`,a=this.videoDecoders.get(i);return a||(a=new w(e,t),this.videoDecoders.set(i,a)),this.touchKey(i),this.prune(),a}getAudio(e,t){let r=`${e}-${t??"default"}`,i=this.audioDecoders.get(r);return i||(i=new v(e,!1),this.audioDecoders.set(r,i)),this.touchKey(r),this.prune(),i}clearNode(e){let t=[...this.videoDecoders.keys(),...this.audioDecoders.keys()];for(let r of t)r.includes(e)&&this.dropKey(r)}},j=new k;h();async function A(c,e){if(typeof window<"u"&&typeof FontFace<"u"){let t=typeof e=="string"?e:"its registered bytes";try{let i=await new FontFace(c,typeof e=="string"?`url(${e})`:e).load();document.fonts.add(i),await document.fonts.ready}catch(r){console.warn(`[preloadFont] Failed to load font "${c}" from ${t}:`,r)}}else typeof e=="string"&&await b(c,e)}h();var M=!1;async function O(c,e,t=(r,i)=>console.warn(`[framefields] ${i}`,r)){let r=new Set,i=new Map;(function s(o){let n=o.operation;if(n){let d=n.op==="text"||n.kind==="text"||n.op==="caption"||n.kind==="caption"||typeof n.text=="string",u=n.fontFamily||(d?"Inter":"");if(u&&r.add(u),d&&typeof n.text=="string"){let l=n.fontSize??48;for(let B of T(n.text))i.set(`${B}-${l}`,l)}}for(let d of o.children??[])s(d)})(c);let a=[];for(let s of r){let o=P(s),n=V(s),d=typeof o=="string"?o:"its registered bytes";a.push(A(s,o).catch(u=>t(u,`font "${s}" from ${d}`))),e&&a.push(p.preloadSlugFont(e,s,n).catch(u=>t(u,`Slug font "${s}" from ${n}`)))}if(e&&i.size>0){!p.hasEmojiFont()&&!M&&(M=!0,console.warn("[framefields] Text uses emoji but no emoji font is registered, so exports draw nothing for them. Run `npx framefields fonts add emoji` and call the generated `registerFonts()`, or register a color emoji font (e.g. Noto Color Emoji) with FontManager.registerEmojiFont(path). `comp.lint()` names the layers (missing_emoji_font)."));for(let[s,o]of i){let n=s.slice(0,s.lastIndexOf("-"));a.push(p.preloadEmoji(e,n,o).catch(d=>{p.hasEmojiFont()&&t(d,`emoji "${n}"`)}))}}await Promise.all(a)}h();function $(c){if(c.operation?.op==="source")return I(c).sourceUrl;for(let e of c.children??[]){let t=$(e);if(t)return t}}async function Y(c,e){let t=async r=>{let i=r.operation;if(i?.op==="CompositorLayer"&&i.timeRemap&&i.durationFrames){let a=$(r);if(!a)throw new Error(`Time remap on '${i.id}' has no media source`);let s=[],o=await m.acquire(a);try{let d=i.dataType==="Video"?await o.getPrimaryVideoTrack():null,u=i.dataType==="Video"&&(i.muted||i.volume===0)?null:await o.getPrimaryAudioTrack();if(i.dataType==="Video"&&!d)throw new Error(`Time remap on '${i.id}': source has no video track`);if(i.dataType==="Audio"&&!u)throw new Error(`Time remap on '${i.id}': source has no audio track`);for(let l of[d,u])l&&s.push({first:await o.getFirstTimestamp([l]),last:await l.computeDuration()})}finally{m.release(a)}let n=d=>{for(let{first:u,last:l}of s)if(d.min<u-1e-8||d.max>l+1e-8||d.maxIncluded&&d.max>=l-1e-8)throw new Error(`Time remap on '${i.id}' requests source ${d.min.toFixed(6)}\u2013${d.max.toFixed(6)}s outside ${u.toFixed(6)}\u2013${l.toFixed(6)}s`)};i.timeRemap.kind==="recorded"?i.timeRemap.recorder.checkRange(n):n(S(i.timeRemap,0,i.durationFrames,e))}for(let a of r.children??[])await t(a)};await t(c)}export{j as a,A as b,O as c,Y as d};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{a as $e,b as lt,c as ut,d as dt,e as ft,f as ht,g as mt,h as gt,i as xt,j as yt,k as bt,l as wt,n as St}from"./chunk-UKZVUYB6.mjs";import{b as Ge}from"./chunk-P4Y3PMJF.mjs";import{e as at}from"./chunk-GXFYRMJZ.mjs";import{h as st,i as ct,j as L,x as be}from"./chunk-64L4GUOF.mjs";import{a as de}from"./chunk-354GX6IB.mjs";import{f as Re,g as we}from"./chunk-Y4NIR77Z.mjs";we();function Et(t){return typeof t=="object"&&t!==null&&typeof t.get=="function"}function Fe(t,n){return Et(t)?t.get(n):t}function Ct(t,n){return t.map(e=>{let i={...e,text:String(Fe(e.text,n)??"")};return e.fontSize!==void 0&&(i.fontSize=Number(Fe(e.fontSize,n))),e.fill!==void 0&&(i.fill=String(Fe(e.fill,n))),e.opacity!==void 0&&(i.opacity=Number(Fe(e.opacity,n))),e.mark&&(i.mark={...e.mark,background:String(Fe(e.mark.background,n))}),i})}function zt(t){let n="";for(let e of t)n+=typeof e.text=="string"?e.text:"";return n}function Mt(t,n){let e=[],i=0;for(let[r,c]of t.entries()){if(i>=n.length)break;let d=typeof c.text=="string"?c.text.length:0,u=r===t.length-1?n.length:i+d;u>i&&e.push({...c,text:n.slice(i,u)}),i=u}return e}we();function Xt(t,n,e,i,r=0,c=0){let d=at.getChannelSamples(n,"primary");if(d&&d.length>0){let m=i>0?i:24,C=e/m,h=r!==0?c*r/(2*Math.PI):0,k=Math.max(0,C+h),a=Math.max(0,Math.min(d.length-1,Math.round(k*m)));return Number(d[a])||0}if(!t)return 0;let u=t[n];if(u==null)return 0;if(typeof u=="number")return u;if(typeof u=="function")return Number(u(e,i))||0;if(typeof u=="object"){let m=u;if(typeof m.get=="function"){let C=i>0?e/i:0,h=r!==0?c*r/(2*Math.PI):0;return Number(m.get({frame:e,fps:i,time:C+h}))||0}if(Array.isArray(m.samples)||ArrayBuffer.isView(m.samples)){let C=m.samples,h=Math.max(0,Math.min(C.length-1,Math.round(e)));return Number(C[h])||0}}return 0}function It(t){return t<.5?2*t*t:1-(-2*t+2)**2/2}function Yt(t){let n=0;for(let e=0;e<t.length;e++)n=t.charCodeAt(e)+((n<<5)-n);return()=>{let e=Math.sin(n++)*1e4;return e-Math.floor(e)}}function Gt(t,n){let e=Yt(n),i=Array.from({length:t},(r,c)=>c);for(let r=i.length-1;r>0;r--){let c=Math.floor(e()*(r+1)),d=i[r];i[r]=i[c],i[c]=d}return i}function jt(t,n,e){let i=`${n}-${e}`,r=L.emojiTextureCache.get(i);if(r)return r;let c=4,d=Math.max(16,Math.ceil(e*1.5*c)),u=Math.ceil(d/4)*4;if(!!globalThis.__IS_HEADLESS_RENDERER__||typeof window>"u"||typeof globalThis.document>"u")return L.hasEmojiFont()&&console.warn(`[getOrCreateEmojiTexture] Emoji "${n}" not preloaded. Returning fallback dummy texture.`),r=t.createTexture({label:`Emoji-Dummy-${n}`,size:[1,1],format:"rgba8unorm",usage:GPUTextureUsage.TEXTURE_BINDING}),L.emojiTextureCache.set(i,r),r;let C=new OffscreenCanvas(u,u),h=C.getContext("2d");return h&&(h.font=`${e*c}px "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", sans-serif`,h.textBaseline="middle",h.textAlign="center",h.fillText(n,u/2,u/2)),r=t.createTexture({label:`Emoji-${n}`,size:[u,u],format:"rgba8unorm",usage:GPUTextureUsage.TEXTURE_BINDING|GPUTextureUsage.COPY_DST|GPUTextureUsage.RENDER_ATTACHMENT}),t.queue.copyExternalImageToTexture({source:C},{texture:r,premultipliedAlpha:!0},[u,u]),L.emojiTextureCache.set(i,r),r}function je(t,n,e,i,r,c,d){if(!d)return{p_in:1,p_out:1};let u=1;if(e>0){let C=e*.5,h=c>1?e*.5/(c-1):0,k=r*h,a=k+C;t<k?u=0:t>a?u=1:u=(t-k)/(a-k)}let m=1;if(i>0){let C=n-i;if(t>C){let h=i*.5,k=c>1?i*.5/(c-1):0,a=C+r*k,A=a+h;t<a?m=1:t>A?m=0:m=1-(t-a)/(A-a)}}return{p_in:u,p_out:m}}function _e(t,n,e,i,r,c,d,u,m,C,h,k,a){let A=e?It(t):t,w=e?It(n):n,v=A*w;if(A<=0&&i!=="blur"||w<=0&&r!=="blur")return{opacity:0,transX:0,transY:0,rot:0,scale:1,blurAmount:0,visible:!1};let X=k;if(i==="fade"||i==="slideUp"||i==="blur"?X*=A:i==="appear"&&A<.5&&(X=0),r==="fade"||r==="slideUp"||r==="blur"?X*=w:r==="appear"&&w<.5&&(X=0),X<.001)return{opacity:0,transX:0,transY:0,rot:0,scale:1,blurAmount:0,visible:!1};let j=0,P=0,H=0,ee=1;i==="slideUp"&&d<m&&(P+=(1-A)*30),r==="slideUp"&&d>u-C&&(P-=(1-w)*30);let oe=a?1:Math.PI/180;c==="stack"?(ee=.8+.2*v,H=(1-v)*15*oe):c==="wave"?P+=10*Math.sin(d*.005-h*.5):c==="wiggle"?(j+=5*Math.sin(d*.008+h*1.7),P+=5*Math.cos(d*.006+h*1.3),H+=4*Math.sin(d*.005+h*2.1)*oe):c==="shuffle"&&(j+=3*Math.sin(d*.015+h*3.1),P+=3*Math.cos(d*.012+h*2.3),H+=8*Math.sin(d*.02+h*1.1)*oe);let x=0;return i==="blur"&&(x=Math.max(x,(1-A)*20)),r==="blur"&&(x=Math.max(x,(1-w)*20)),{opacity:X,transX:j,transY:P,rot:H,scale:ee,blurAmount:x,visible:!0}}var _t={linear:t=>t,none:t=>t,smoothstep:t=>t,"power1.out":t=>1-(1-t)**2,"quad.out":t=>1-(1-t)**2,"power1.in":t=>t**2,"quad.in":t=>t**2,"power1.inout":t=>t<.5?2*t*t:1-(-2*t+2)**2/2,"quad.inout":t=>t<.5?2*t*t:1-(-2*t+2)**2/2,"power2.out":t=>1-(1-t)**3,"cubic.out":t=>1-(1-t)**3,"power2.in":t=>t**3,"cubic.in":t=>t**3,"power2.inout":t=>t<.5?4*t**3:1-(-2*t+2)**3/2,"cubic.inout":t=>t<.5?4*t**3:1-(-2*t+2)**3/2,"power3.out":t=>1-(1-t)**4,"quart.out":t=>1-(1-t)**4,"power3.in":t=>t**4,"quart.in":t=>t**4,"power3.inout":t=>t<.5?8*t**4:1-(-2*t+2)**4/2,"quart.inout":t=>t<.5?8*t**4:1-(-2*t+2)**4/2,"power4.out":t=>1-(1-t)**5,"quint.out":t=>1-(1-t)**5,"power4.in":t=>t**5,"quint.in":t=>t**5,"sine.out":t=>Math.sin(t*Math.PI/2),"sine.in":t=>1-Math.cos(t*Math.PI/2),"sine.inout":t=>-(Math.cos(Math.PI*t)-1)/2,"expo.out":t=>t===1?1:1-2**(-10*t),"back.out":t=>1+2.70158*(t-1)**3+1.70158*(t-1)**2};function Ot(t,n){let e=Math.max(0,Math.min(1,n));if(!t)return e;let i=_t[t.toLowerCase().trim()];return i?i(e):e}function Ht(t,n,e){if(e<=0)return 0;let i=e>1?n/(e-1):.5,r=t.offset??0,c=(t.rangeStart??0)+r,d=(t.rangeEnd??1)+r,u=0;d<=c?u=i>=c?1:0:u=Math.max(0,Math.min(1,(i-c)/(d-c)));let m=u*u*(3-2*u);return Ot(t.easing??"power2.out",m)}function kt(t,n,e,i,r,c){let d=Math.max(1,e.charsCount??1),u=Math.max(1,e.wordsCount??1),m=Math.max(1,e.linesCount??1),C=t.selectors??[];if(C.length===0)return 1;let h=0;for(let k=0;k<C.length;k++){let a=C[k],A=0;if(a.type==="range"){let w=d,v=n.charIndex??0;a.basedOn==="words"?(w=u,v=n.wordIndex??0):a.basedOn==="lines"&&(w=m,v=n.lineIndex??0),a.randomizeOrder&&(v=bt(w,a.randomSeed??12345)[v%w]??v);let X=a.units==="index"?v:w>1?v/(w-1):.5,j=a.start??t.rangeStart??0,P=a.end??t.rangeEnd??(a.units==="index"?w:1),H=a.offset??t.offset??0;A=xt(X,j,P,H,a.shape??"smooth",a.easeHigh??0,a.easeLow??0)*(a.amount??1)}else if(a.type==="wiggly"){let w=c>0?r/c:0,v=(a.temporalPhaseDeg??0)*Math.PI/180,X=(a.spatialPhaseDeg??0)*Math.PI/180,j=w*(a.wigglesPerSecond??2)*2*Math.PI+v+X,P=yt(a.randomSeed??42,n.charIndex??0,j,a.correlation??.5),H=a.minAmount??-1,ee=a.maxAmount??1;A=H+(P*.5+.5)*(ee-H)}else if(a.type==="signal")A=Xt(i,a.signalId,r,c,a.phaseSpread??0,n.charIndex??0)*(a.multiplier??1)+(a.offset??0);else if(a.type==="expression"){let w=a,v=c>0?r/c:0,X={charIndex:n.charIndex??0,wordIndex:n.wordIndex??0,lineIndex:n.lineIndex??0,totalChars:d,totalWords:u,totalLines:m,frame:r,fps:c,time:v,signals:i};A=ft(w.expression,X)*(w.amount??1)}k===0?h=A:h=wt(h,A,a.mode??"add")}return h}function At(t,n,e,i,r=!1,c=0,d=24,u){if(!t||t.length===0)return i;let{transX:m,transY:C,rot:h,scale:k,scaleX:a,scaleY:A,opacity:w,blurAmount:v}=i,X=i.transZ??0,j=i.color,P=i.chromaticShift??0,H=i.dissolveProgress??0,ee=i.vfxParam??0,oe=i.skewM01??0,x=i.skewM10??0,fe=i.anchorConfig,le=i.fillOpacity??1,re=i.strokeWidth,U=i.strokeColor,ue=i.longShadow,he=i.lineSpacing,Z=i.lineAnchor,me=r?1:Math.PI/180,z=Math.max(1,e.charsCount??1),K=Math.max(1,e.wordsCount??1),ae=Math.max(1,e.linesCount??1);for(let ne of t)if("selectors"in ne&&Array.isArray(ne.selectors)){let ie=ne,l=ie.props??{},te=kt(ie,n,{charsCount:z,wordsCount:K,linesCount:ae},u,c,d),f=te,Y=1,q=1;if(l.spring){let b=(n.charIndex??0)*.035,D=Math.max(0,(d>0?c/d:0)-b);q=$e(D,l.spring.stiffness,l.spring.damping,l.spring.mass,l.spring.initialVelocity),Y=1-q,f=te*Y}if(f!==0||te!==0){if(l.x!==void 0&&(m+=l.x*f),l.y!==void 0&&(C+=l.y*f),l.scale!==void 0&&(k*=1+(l.scale-1)*f),l.scaleX!==void 0&&(a*=1+(l.scaleX-1)*f),l.scaleY!==void 0&&(A*=1+(l.scaleY-1)*f),l.rotation!==void 0&&(h+=l.rotation*me*f),l.rotationX!==void 0){let b=l.rotationX*f*Math.PI/180;A*=Math.max(.08,Math.abs(Math.cos(b)))}if(l.rotationY!==void 0){let b=l.rotationY*f*Math.PI/180;a*=Math.max(.08,Math.abs(Math.cos(b)))}if(l.rotationZ!==void 0&&(h+=l.rotationZ*me*f),l.translateZ!==void 0&&(X+=l.translateZ*f),l.skew!==void 0){let[,b,D]=lt(l.skew*f,l.skewAxis??0);oe+=b,x+=D}if(l.formation!==void 0){let b=ht(n.charIndex??0,z,l.formation),D=e.layoutCenter?.x??0,Q=e.layoutCenter?.y??0,xe=n.x??0,We=n.y??0,Ee=D+b.x,Xe=Q+b.y;if(m+=(Ee-xe)*f,C+=(Xe-We)*f,X+=b.z*f,b.rotX){let ye=b.rotX*f*Math.PI/180;A*=Math.max(.5,Math.abs(Math.cos(ye)))}if(b.rotY){let ye=b.rotY*f*Math.PI/180;a*=Math.max(.5,Math.abs(Math.cos(ye)))}b.rotZ&&(h+=b.rotZ*Math.PI/180*f),b.depthOpacity!==void 0&&(w*=1+(b.depthOpacity-1)*f)}if(l.anchor!==void 0&&(fe=l.anchor),l.opacity!==void 0&&(w*=1+(l.opacity-1)*f),l.blur!==void 0&&(v=Math.max(v,l.blur*f)),l.chromaticGlitch!==void 0){let b=(l.chromaticGlitch.redShiftX??0)*Math.max(0,f),D=l.chromaticGlitch.sliceProbability??.35,Q=Math.sin(c*17.13+(n.charIndex??0)*43.17),xe=Math.abs(Q-Math.floor(Q));xe<D&&(b+=(l.chromaticGlitch.sliceAmplitude??12)*(xe*2-1)),P+=b}if(l.particleDissolve!==void 0&&(H=Math.max(H,(l.particleDissolve.progress??0)*(l.spring?q:Math.max(0,f)))),l.longShadow!==void 0&&(ue=l.longShadow),l.audioSpectrum!==void 0){let b=gt(n.charIndex??0,z,l.audioSpectrum,u,c,d);l.audioSpectrum.scaleAxis==="scaleY"?A*=b:l.audioSpectrum.scaleAxis==="scaleX"?a*=b:k*=b}if(l.stroke!==void 0&&(l.stroke.fillOpacity!==void 0&&(le*=1-f+l.stroke.fillOpacity*f),l.stroke.strokeWidth!==void 0&&(re=(l.stroke.strokeWidth??0)*f),l.stroke.strokeColor&&(U=l.stroke.strokeColor)),l.lineSpacing!==void 0&&(he=l.lineSpacing*(l.spring?q:f),Z=l.lineAnchor??0),l.fillColor){let b=de(l.fillColor),D=j??{r:1,g:1,b:1,a:1},Q=te;j={r:D.r*(1-Q)+b.r*Q,g:D.g*(1-Q)+b.g*Q,b:D.b*(1-Q)+b.b*Q,a:D.a*(1-Q)+b.a*Q}}}}else{let ie=ne,l=n.charIndex??n.unitIndex??0,te=Math.max(1,e.charsCount??1);ie.unit==="word"?(l=n.wordIndex??0,te=Math.max(1,e.wordsCount??1)):ie.unit==="line"&&(l=n.lineIndex??0,te=Math.max(1,e.linesCount??1));let f=Ht(ie,l,te),Y=ie.transform;if(!Y||f<=0)continue;Y.x!==void 0&&(m+=Y.x*f),Y.y!==void 0&&(C+=Y.y*f),Y.rotation!==void 0&&(h+=Y.rotation*me*f),Y.scale!==void 0&&(k*=1+(Y.scale-1)*f),Y.scaleX!==void 0&&(a*=1+(Y.scaleX-1)*f);let q=1+((Y.scaleY??1)-1)*f;if(Y.rotationX){let b=Y.rotationX*f*Math.PI/180;q*=Math.cos(b)}if(A*=q,Y.opacity!==void 0&&(w*=1+(Y.opacity-1)*f),Y.blur!==void 0&&(v=Math.max(v,Y.blur*f)),Y.color){let b=de(Y.color),D=j??{r:1,g:1,b:1,a:1};j={r:D.r*(1-f)+b.r*f,g:D.g*(1-f)+b.g*f,b:D.b*(1-f)+b.b*f,a:D.a*(1-f)+b.a*f}}}return{transX:m,transY:C,transZ:X,rot:h,scale:k,scaleX:a,scaleY:A,opacity:Math.max(0,Math.min(1,w)),blurAmount:v,color:j,chromaticShift:P,dissolveProgress:H,vfxParam:ee,skewM01:oe,skewM10:x,anchorConfig:fe,fillOpacity:Math.max(0,Math.min(1,le)),strokeWidth:re,strokeColor:U,longShadow:ue,lineSpacing:he,lineAnchor:Z}}function pt(t,n,e){let i=e.fontFamily??"Inter";if(L.isFailed(i)&&(i="Inter"),L.isFailed(i))return;let r=e.fontSize??48,c=L.getFont(i,e.fontWeight,t.device,void 0,r,e.variableAxes),d=e.stroke&&e.strokeWidth&&e.strokeWidth>0,u=null;if(c&&d&&t.device){let o=e.strokeAlign==="outside"?2:1,y=(e.strokeWidth??0)*o*(c.unitsPerEm/r);u=L.getFont(i,e.fontWeight,t.device,y,r,e.variableAxes)??null}if(!c?.curvesTex||d&&!u?.curvesTex){let o=Ge(i);L.preloadSlugFont(t.device,i,o,void 0,e.renderId).catch(s=>{console.warn(`[drawParagraphNode] Failed to load Slug font for ${i}:`,s)});return}let m=e.letterSpacing??0,C=e.lineHeight!==void 0&&e.lineHeight<10?e.lineHeight*r:e.lineHeight??r*1.2,h=e.align??"start",k=e.direction??"auto",a=e.isCaption?0:e.textBackgroundColor?e.padding??0:0,A=e.text,w;if(e.typewriter){let o=e.fps??24,s=e.durationMs!==void 0?Math.round(e.durationMs/1e3*o):60;w=St(e.text,e.frame??0,0,s,e.typewriter.cadence,e.typewriter.scramble,e.typewriter.cursor?.blinkFrequency??2,e.typewriter.granularity??"character",e.textProgress),A=w.currentDisplayString}let v=w&&e.spans?.length?Mt(Ct(e.spans),A):e.spans,X=e.pathOptions?{...e.pathOptions,firstMargin:e.firstMargin!==void 0?e.firstMargin:e.pathOptions.firstMargin,lastMargin:e.lastMargin!==void 0?e.lastMargin:e.pathOptions.lastMargin,baselineShift:e.baselineShift!==void 0?e.baselineShift:e.pathOptions.baselineShift}:void 0,j,P;e.width!=null?(j=Math.max(0,e.width-a*2),P=be.measure(A,c,r,m,C,j,v,k)):(P=be.measure(A,c,r,m,C,void 0,v,k),j=P.width);let H=e.isCaption?e.dstRect.width:j,ee=e.animation?.applyBy??"word",x=(h==="left"||h==="start"&&!P.layout?.hasRtl)&&(H===void 0||H===j)&&!X&&ee==="word"&&!!P?.layout&&P.layout?P.layout:be.layout(A,c,r,m,C,j,h,ee,H,X,v,k),fe=e.verticalAlign??"top",le=e.height!=null&&!X&&!e.isCaption?e.height-(P.height+a*2):0,re={...e.dstRect,y:e.dstRect.y+(fe==="middle"?le/2:fe==="bottom"?le:0)},U=e.animation,ue=e.fontWeight==="bold"||typeof e.fontWeight=="number"&&e.fontWeight>=600||typeof e.fontWeight=="string"&&parseInt(e.fontWeight,10)>=600,he="hasTrueWeight"in c&&!!c.hasTrueWeight,Z=c;if(ue&&!he&&t.device){let o=ct(e.fontWeight),s=Math.max(0,(o-400)/500)*.055*r;if(s>0){let y=s*(c.unitsPerEm/r),S=L.getFont(i,400,t.device,y,r,e.variableAxes);S?.curvesTex&&(Z=S)}}let me=x.glyphs.length,z=e.elapsedMs!==void 0?e.elapsedMs:e.frame!==void 0&&e.fps!==void 0?e.frame/e.fps*1e3:0,K=e.durationMs!==void 0?e.durationMs:3e3,ae=e.isCaption?U?.in==="none"||!U?.in?0:K:U?.entranceMs?U.entranceMs:U?.in==="none"||!U?.in?0:K,ne=e.isCaption?0:U?.exitMs?U.exitMs:U?.out==="none"||!U?.out?0:K,ie=U?.in||"none",l=U?.out||"none",te=U?.kinetic||"none",f=U?.smoothing!==!1,Y=0;for(let o of x.glyphs)!o.isSpace&&o.unitIndex>Y&&(Y=o.unitIndex);let q=Y+1,b=te==="shuffle"||ie==="shuffle"?Gt(q,e.text):Array.from({length:q},(o,s)=>s);if(e.textBackgroundColor){let o=e.strokeRadius??e.borderRadius??8,s=0,y=e.opacity??1;if(e.isVideoMode===!1)s=y;else{for(let S of x.glyphs){if(S.isSpace)continue;let I=S.unitIndex,T=b[I]??I,{p_in:M,p_out:g}=je(z,K,ae,ne,T,q,!0),_=_e(M,g,f,ie,l,te,z,K,ae,ne,T,y,!1);_.visible&&_.opacity>s&&(s=_.opacity)}for(let S of x.emojis){let I=e.animation?.applyBy==="line"?0:Math.floor(S.x/(r+m)),T=b[I%q]??0,{p_in:M,p_out:g}=je(z,K,ae,ne,T,q,!0),_=_e(M,g,f,ie,l,te,z,K,ae,ne,T,y,!0);_.visible&&_.opacity>s&&(s=_.opacity)}}if(s>.001){let S=e.width!=null?e.width:P.width+a*2,I=e.height!=null?e.height:P.height+a*2;t.renderer.drawRRect(n,{rect:{x:e.dstRect.x,y:e.dstRect.y,width:S,height:I},rx:o,ry:o},e.textBackgroundColor,{opacity:s})}}if(x.marks&&x.marks.length>0)for(let o of x.marks){let s=o.borderRadius??4;t.renderer.drawRRect(n,{rect:{x:e.dstRect.x+a+o.x,y:re.y+a+o.y,width:o.width,height:o.height},rx:s,ry:s},o.background,{opacity:e.opacity??1})}let D=de(e.color??"white"),Q=de(e.highlightColor??"yellow"),xe=new Float32Array(x.glyphs.length),We=0,Ee=0;if(x.glyphs.length>0){let o=x.glyphs.filter(s=>!s.isSpace);o.length>0&&(We=(o[0].x+o[o.length-1].x)/2,Ee=(o[0].y+o[o.length-1].y)/2)}let Xe={charsCount:x.charsCount,wordsCount:x.wordsCount,linesCount:x.linesCount,layoutCenter:{x:We,y:Ee}};if(e.animators&&e.animators.length>0){for(let o of e.animators)if("selectors"in o&&Array.isArray(o.selectors)){let s=o;if(s.props?.tracking!==void 0&&s.props.tracking!==0){let y=s.props.tracking,S=new Map;for(let I=0;I<x.glyphs.length;I++){let M=x.glyphs[I].lineIndex??0,g=S.get(M);g||(g=[],S.set(M,g)),g.push(I)}for(let I of S.values()){let T=new Map;for(let F of I){let O=x.glyphs[F].charIndex,$=T.get(O);$?$.push(F):T.set(O,[F])}let M=Array.from(T.values()),g=F=>{let O=x.glyphs[F[0]];return O.clusterX??O.x};M.sort((F,O)=>g(F)-g(O));let _=[];for(let F of M){let O=x.glyphs[F[0]],$=kt(s,O,Xe,e.signals,e.frame??0,e.fps??24);_.push($)}let G=0;for(let F=0;F<M.length-1;F++)G+=y*_[F];let p=x.glyphs[I[0]]?.baseRtl??!1,R=h==="start"?p?"right":"left":h==="end"?p?"left":"right":h,N=0;for(let F=0;F<M.length;F++){let O=N;R==="center"?O-=G*.5:R==="right"&&(O-=G);for(let $ of M[F])xe[$]+=O;F<M.length-1&&(N+=y*_[F])}}}}}let ye=new Float32Array(Math.max(1024,me*24)),Ke=(o,s)=>s.cp.glyphId!==void 0&&(s.font??c).fontFamily===o.fontFamily,Qe=new Set,vt=o=>{if(Qe.has(o))return;Qe.add(o);let s=[];for(let y of x.glyphs){let S=y.cp.glyphId;S!==void 0&&(y.font??c)!==o&&Ke(o,y)&&!o.glyphs?.has(S)&&s.push(S)}s.length>0&&st.ensureGlyphs(o,s)},Pt=(o,s)=>{if((s.font??c)===o)return s.cp;if(Ke(o,s)){let y=o.glyphs?.get(s.cp.glyphId);if(y)return y}return o.codePoints.get(s.cp.codePoint)||s.cp},Se=(o,s,y=0,S=0,I,T)=>{let M=me*24;ye.length<M&&(ye=new Float32Array(M));let g=ye,_=0;vt(o);for(let G=0;G<x.glyphs.length;G++){let p=x.glyphs[G];if(p.isSpace||T&&!T(p))continue;let R=Pt(o,p);if(!R||R.width===0||R.height===0)continue;let N=p.unitIndex,F=b[N]??N,O=e.opacity??1,$=0,J=0,se=0,V=1,Ce=I!==void 0?I:0;if(e.isVideoMode!==!1){let{p_in:W,p_out:E}=je(z,K,ae,ne,F,q,!0),B=_e(W,E,f,ie,l,te,z,K,ae,ne,F,e.opacity??1,!1);if(!B.visible)continue;O=B.opacity,$=B.transX,J=B.transY,se=B.rot,V=B.scale,I===void 0&&(Ce=B.blurAmount)}let Me=1,Ue=1,tt,De=0,nt=0,it=0,ot=0,Ne=0,Le=0,Be,rt=1;if(e.animators&&e.animators.length>0){let W=At(e.animators,p,Xe,{transX:$,transY:J,rot:se,scale:V,scaleX:Me,scaleY:Ue,opacity:O,blurAmount:Ce,color:D},!1,e.frame??0,e.fps??24,e.signals);$=W.transX,J=W.transY,se=W.rot,V=W.scale,Me=W.scaleX,Ue=W.scaleY,O=W.opacity,I===void 0&&(Ce=W.blurAmount),W.color&&(tt=W.color),W.lineSpacing!==void 0&&(J+=dt(p.lineIndex,x.linesCount,W.lineSpacing,W.lineAnchor??0)),De=W.transZ??0,nt=W.chromaticShift??0,it=W.dissolveProgress??0,ot=W.vfxParam??0,Ne=W.skewM01??0,Le=W.skewM10??0,Be=W.anchorConfig,rt=W.fillOpacity??1}$+=xe[G];let ze=p.x;if(e.marquee){let W=(e.frame??0)/(e.fps??24),E=x.glyphs.length>0?x.glyphs[x.glyphs.length-1].x+r:e.dstRect.width;ze=mt(p.x,e.dstRect.width,E,e.marquee,W)}let Rt=e.highlightWordIndex!==void 0&&p.wordIndex===e.highlightWordIndex,Ft=p.fill?de(p.fill):void 0,Ye=tt??s??(Rt?Q:Ft??D);(W=>{let E=_*24,B=p.fontScale??r/o.unitsPerEm,Ie=R.width*B/2*Me,Ae=R.height*B/2*Ue;if(De!==0){let ce=Math.max(100,1200-De),ge=ce>1?1200/ce:1;Ie*=ge,Ae*=ge}let ke=se+(p.pathTangentAngleRad??0),pe,Te;if(p.pathTangentAngleRad!==void 0){let ve=R.bearingX*B+Ie,ce=-R.bearingY*B+Ae,ge=Math.cos(ke),Pe=Math.sin(ke),Ve=ve*ge-ce*Pe,qe=ve*Pe+ce*ge;pe=ze+Ve+e.dstRect.x+a+y+W,Te=p.y+qe+re.y+a+S}else pe=ze+R.bearingX*B+Ie+e.dstRect.x+a+y+W,Te=p.y-R.bearingY*B+Ae+re.y+a+S;if(Be){let ve={width:R.width*B,height:R.height*B,ascender:(o.ascender??800)*B,descender:(o.descender??-200)*B},ce=ut(ve,Be),ge=Math.cos(ke),Pe=Math.sin(ke),Ve=ce.px*(1-ge*V)+ce.py*(Pe*V),qe=-ce.px*(Pe*V)+ce.py*(1-ge*V);pe+=Ve,Te+=qe}(Ne!==0||Le!==0)&&(pe+=Ae*Ne,Te+=Ie*Le),g[E+0]=Ie,g[E+1]=Ae,g[E+2]=pe,g[E+3]=Te,g[E+4]=R.width,g[E+5]=R.height,g[E+6]=R.width/R.bandDimX,g[E+7]=R.height/R.bandDimY,g[E+8]=R.bandCount-1,g[E+9]=R.bandCount-1,g[E+10]=R.bandsTexCoordX,g[E+11]=R.bandsTexCoordY,g[E+12]=ke,g[E+13]=V,g[E+14]=$,g[E+15]=J,g[E+16]=Ye.r,g[E+17]=Ye.g,g[E+18]=Ye.b;let Wt=p.opacity??1;g[E+19]=Ye.a*O*rt*Wt,g[E+20]=Ce,g[E+21]=nt,g[E+22]=it,g[E+23]=ot,_++})(0)}return{data:g,visibleCount:_}};if(e.shadows&&e.shadows.length>0)for(let o of e.shadows){let s=de(o.color),y=o.offset?.x??0,S=o.offset?.y??0,I=o.blurRadius??0,{data:T,visibleCount:M}=Se(Z,s,y,S,I);if(M>0){let g=T.subarray(0,M*24),G=e.fontStyle==="italic"?.21:0;t.renderer.slugPipeline.draw(n,t.renderer.getTransformStack(),Z,g,M,o.color,t.renderer.getSurfaceWidth(),t.renderer.getSurfaceHeight(),{opacity:e.opacity??1,transform:e.matrix,customParam:G})}}let Oe=e.animators?.find(o=>"props"in o&&o.props?.longShadow&&(o.props?.longShadow?.length??0)>0);if(Oe&&"props"in Oe){let o=Oe,s=o.props?.longShadow;if(s){let y=o.props?.spring,S=s.length,I=s.angle;if(y){let G=(e.frame??0)/(e.fps??30),p=$e(G,y.stiffness,y.damping,y.mass,y.initialVelocity);S=Math.max(0,s.length*p),I=s.angle+(1-Math.min(1,p))*25}else{let p=1-(1-Math.min(1,(e.frame??0)/36))**3;S=s.length*p}let T=I*Math.PI/180,M=Math.max(4,Math.min(64,s.steps??16)),g=S/M,_=de(s.color??"#000000");for(let G=1;G<=M;G++){let p=G*g,R=p*Math.cos(T),N=p*Math.sin(T),F=G/M,O=(s.startOpacity??.8)*(1-F)+(s.endOpacity??0)*F,$={..._,a:_.a*O},{data:J,visibleCount:se}=Se(c,$,R,N,0);if(se>0){let V=J.subarray(0,se*24),Me=e.fontStyle==="italic"?.21:0;t.renderer.slugPipeline.draw(n,t.renderer.getTransformStack(),Z,V,se,s.color??"black",t.renderer.getSurfaceWidth(),t.renderer.getSurfaceHeight(),{opacity:(e.opacity??1)*O,transform:e.matrix,customParam:Me})}}}}if(d&&u){let o=de(e.stroke),{data:s,visibleCount:y}=Se(u,o);if(y>0){let S=s.subarray(0,y*24),T=e.fontStyle==="italic"?.21:0;t.renderer.slugPipeline.draw(n,t.renderer.getTransformStack(),u,S,y,e.stroke??"#000000",t.renderer.getSurfaceWidth(),t.renderer.getSurfaceHeight(),{opacity:e.opacity??1,transform:e.matrix,customParam:T})}}let He=new Set,Je=!1;for(let o of x.glyphs)o.font&&o.font!==Z&&(He.add(o.font),Je=!0);let et=e.fontStyle==="italic"?.21:0;if(Je){He.add(Z);for(let o of He){if(t.device&&!o.curvesTex&&L.ensureTextures(t.device,o),!o.curvesTex)continue;let{data:s,visibleCount:y}=Se(o,void 0,0,0,void 0,S=>(S.font??Z)===o);if(y>0){let S=s.subarray(0,y*24);t.renderer.slugPipeline.draw(n,t.renderer.getTransformStack(),o,S,y,e.color??"white",t.renderer.getSurfaceWidth(),t.renderer.getSurfaceHeight(),{opacity:e.opacity??1,transform:e.matrix,customParam:et})}}}else{let{data:o,visibleCount:s}=Se(Z);if(s>0){let y=o.subarray(0,s*24);t.renderer.slugPipeline.draw(n,t.renderer.getTransformStack(),Z,y,s,e.color??"white",t.renderer.getSurfaceWidth(),t.renderer.getSurfaceHeight(),{opacity:e.opacity??1,transform:e.matrix,customParam:et})}}for(let o of x.emojis){let s=e.animation?.applyBy==="line"?0:Math.floor(o.x/(r+m)),y=b[s%q]??0,S=e.opacity??1,I=0,T=0,M=0,g=1;if(e.isVideoMode!==!1){let{p_in:J,p_out:se}=je(z,K,ae,ne,y,q,!0),V=_e(J,se,f,ie,l,te,z,K,ae,ne,y,e.opacity??1,!0);if(!V.visible)continue;S=V.opacity,I=V.transX,T=V.transY,M=V.rot,g=V.scale}if(e.animators&&e.animators.length>0){let J=At(e.animators,{charIndex:s,wordIndex:0,lineIndex:o.lineIndex},{charsCount:x.charsCount,wordsCount:x.wordsCount,linesCount:x.linesCount},{transX:I,transY:T,rot:M,scale:g,scaleX:1,scaleY:1,opacity:S,blurAmount:0},!0);I=J.transX,T=J.transY,M=J.rot,g=J.scale,S=J.opacity}let _=jt(t.device,o.char,o.size),G=o.size*1.15,p=(G-o.size)/2,R={x:e.dstRect.x+o.x+a-p,y:re.y+o.y+a-p,width:G,height:G},N=new DOMMatrix,F=R.x+G/2,O=R.y+G/2;N=N.translate(F,O),(I!==0||T!==0)&&(N=N.translate(I,T)),M!==0&&(N=N.rotate(M)),g!==1&&(N=N.scale(g)),N=N.translate(-F,-O);let $=e.matrix?e.matrix.multiply(N):N;t.renderer.drawTextureRegion(n,_,{x:0,y:0,width:_.width,height:_.height},R,{opacity:S,transform:$})}if(e.typewriter?.cursor?.enabled&&w&&w.isCursorVisible&&(!e.typewriter.cursor.hideOnComplete||w.visibleChars<e.text.length)){let o=e.typewriter.cursor,s=o.color??e.color??"white",y=o.width??2.5,S=o.spacing??3,I=e.dstRect.x+a,T=re.y+a;if(x.glyphs.length>0){let M=x.glyphs[x.glyphs.length-1],g=r/c.unitsPerEm,_=M.clusterAdvance??(M.cp?.advanceWidth||0)*g,G=M.clusterX??M.x,p=o.style==="underscore"||o.style==="block"?r*.5:y;I=e.dstRect.x+a+(M.rtl?G-S-p:G+_+S),T=M.y+re.y+a}o.style==="underscore"?t.renderer.drawRect(n,{x:I,y:T,width:r*.5,height:y},s,0,{opacity:e.opacity??1}):o.style==="block"?t.renderer.drawRect(n,{x:I,y:T-r*.8,width:r*.5,height:r},s,0,{opacity:e.opacity??1}):t.renderer.drawRect(n,{x:I,y:T-r*.8,width:y,height:r},s,0,{opacity:e.opacity??1})}}we();function Tt(t){let e=t.trim().replace(",",".").split(":");if(e.length<2||e.length>3)throw new Error(`Invalid timestamp: ${t}`);if(e.length===2){let[d,u]=e;return parseInt(d,10)*60+parseFloat(u)}let[i,r,c]=e;return parseInt(i,10)*3600+parseInt(r,10)*60+parseFloat(c)}function Ut(t){let n=t.replace(/\r\n/g,`
|
|
1
|
+
import{a as $e,b as lt,c as ut,d as dt,e as ft,f as ht,g as mt,h as gt,i as xt,j as yt,k as bt,l as wt,n as St}from"./chunk-7M5LE6EM.mjs";import{b as Ge}from"./chunk-FRJPXWAG.mjs";import{e as at}from"./chunk-GXFYRMJZ.mjs";import{h as st,i as ct,j as L,x as be}from"./chunk-64L4GUOF.mjs";import{a as de}from"./chunk-354GX6IB.mjs";import{f as Re,g as we}from"./chunk-Y4NIR77Z.mjs";we();function Et(t){return typeof t=="object"&&t!==null&&typeof t.get=="function"}function Fe(t,n){return Et(t)?t.get(n):t}function Ct(t,n){return t.map(e=>{let i={...e,text:String(Fe(e.text,n)??"")};return e.fontSize!==void 0&&(i.fontSize=Number(Fe(e.fontSize,n))),e.fill!==void 0&&(i.fill=String(Fe(e.fill,n))),e.opacity!==void 0&&(i.opacity=Number(Fe(e.opacity,n))),e.mark&&(i.mark={...e.mark,background:String(Fe(e.mark.background,n))}),i})}function zt(t){let n="";for(let e of t)n+=typeof e.text=="string"?e.text:"";return n}function Mt(t,n){let e=[],i=0;for(let[r,c]of t.entries()){if(i>=n.length)break;let d=typeof c.text=="string"?c.text.length:0,u=r===t.length-1?n.length:i+d;u>i&&e.push({...c,text:n.slice(i,u)}),i=u}return e}we();function Xt(t,n,e,i,r=0,c=0){let d=at.getChannelSamples(n,"primary");if(d&&d.length>0){let m=i>0?i:24,C=e/m,h=r!==0?c*r/(2*Math.PI):0,k=Math.max(0,C+h),a=Math.max(0,Math.min(d.length-1,Math.round(k*m)));return Number(d[a])||0}if(!t)return 0;let u=t[n];if(u==null)return 0;if(typeof u=="number")return u;if(typeof u=="function")return Number(u(e,i))||0;if(typeof u=="object"){let m=u;if(typeof m.get=="function"){let C=i>0?e/i:0,h=r!==0?c*r/(2*Math.PI):0;return Number(m.get({frame:e,fps:i,time:C+h}))||0}if(Array.isArray(m.samples)||ArrayBuffer.isView(m.samples)){let C=m.samples,h=Math.max(0,Math.min(C.length-1,Math.round(e)));return Number(C[h])||0}}return 0}function It(t){return t<.5?2*t*t:1-(-2*t+2)**2/2}function Yt(t){let n=0;for(let e=0;e<t.length;e++)n=t.charCodeAt(e)+((n<<5)-n);return()=>{let e=Math.sin(n++)*1e4;return e-Math.floor(e)}}function Gt(t,n){let e=Yt(n),i=Array.from({length:t},(r,c)=>c);for(let r=i.length-1;r>0;r--){let c=Math.floor(e()*(r+1)),d=i[r];i[r]=i[c],i[c]=d}return i}function jt(t,n,e){let i=`${n}-${e}`,r=L.emojiTextureCache.get(i);if(r)return r;let c=4,d=Math.max(16,Math.ceil(e*1.5*c)),u=Math.ceil(d/4)*4;if(!!globalThis.__IS_HEADLESS_RENDERER__||typeof window>"u"||typeof globalThis.document>"u")return L.hasEmojiFont()&&console.warn(`[getOrCreateEmojiTexture] Emoji "${n}" not preloaded. Returning fallback dummy texture.`),r=t.createTexture({label:`Emoji-Dummy-${n}`,size:[1,1],format:"rgba8unorm",usage:GPUTextureUsage.TEXTURE_BINDING}),L.emojiTextureCache.set(i,r),r;let C=new OffscreenCanvas(u,u),h=C.getContext("2d");return h&&(h.font=`${e*c}px "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", sans-serif`,h.textBaseline="middle",h.textAlign="center",h.fillText(n,u/2,u/2)),r=t.createTexture({label:`Emoji-${n}`,size:[u,u],format:"rgba8unorm",usage:GPUTextureUsage.TEXTURE_BINDING|GPUTextureUsage.COPY_DST|GPUTextureUsage.RENDER_ATTACHMENT}),t.queue.copyExternalImageToTexture({source:C},{texture:r,premultipliedAlpha:!0},[u,u]),L.emojiTextureCache.set(i,r),r}function je(t,n,e,i,r,c,d){if(!d)return{p_in:1,p_out:1};let u=1;if(e>0){let C=e*.5,h=c>1?e*.5/(c-1):0,k=r*h,a=k+C;t<k?u=0:t>a?u=1:u=(t-k)/(a-k)}let m=1;if(i>0){let C=n-i;if(t>C){let h=i*.5,k=c>1?i*.5/(c-1):0,a=C+r*k,A=a+h;t<a?m=1:t>A?m=0:m=1-(t-a)/(A-a)}}return{p_in:u,p_out:m}}function _e(t,n,e,i,r,c,d,u,m,C,h,k,a){let A=e?It(t):t,w=e?It(n):n,v=A*w;if(A<=0&&i!=="blur"||w<=0&&r!=="blur")return{opacity:0,transX:0,transY:0,rot:0,scale:1,blurAmount:0,visible:!1};let X=k;if(i==="fade"||i==="slideUp"||i==="blur"?X*=A:i==="appear"&&A<.5&&(X=0),r==="fade"||r==="slideUp"||r==="blur"?X*=w:r==="appear"&&w<.5&&(X=0),X<.001)return{opacity:0,transX:0,transY:0,rot:0,scale:1,blurAmount:0,visible:!1};let j=0,P=0,H=0,ee=1;i==="slideUp"&&d<m&&(P+=(1-A)*30),r==="slideUp"&&d>u-C&&(P-=(1-w)*30);let oe=a?1:Math.PI/180;c==="stack"?(ee=.8+.2*v,H=(1-v)*15*oe):c==="wave"?P+=10*Math.sin(d*.005-h*.5):c==="wiggle"?(j+=5*Math.sin(d*.008+h*1.7),P+=5*Math.cos(d*.006+h*1.3),H+=4*Math.sin(d*.005+h*2.1)*oe):c==="shuffle"&&(j+=3*Math.sin(d*.015+h*3.1),P+=3*Math.cos(d*.012+h*2.3),H+=8*Math.sin(d*.02+h*1.1)*oe);let x=0;return i==="blur"&&(x=Math.max(x,(1-A)*20)),r==="blur"&&(x=Math.max(x,(1-w)*20)),{opacity:X,transX:j,transY:P,rot:H,scale:ee,blurAmount:x,visible:!0}}var _t={linear:t=>t,none:t=>t,smoothstep:t=>t,"power1.out":t=>1-(1-t)**2,"quad.out":t=>1-(1-t)**2,"power1.in":t=>t**2,"quad.in":t=>t**2,"power1.inout":t=>t<.5?2*t*t:1-(-2*t+2)**2/2,"quad.inout":t=>t<.5?2*t*t:1-(-2*t+2)**2/2,"power2.out":t=>1-(1-t)**3,"cubic.out":t=>1-(1-t)**3,"power2.in":t=>t**3,"cubic.in":t=>t**3,"power2.inout":t=>t<.5?4*t**3:1-(-2*t+2)**3/2,"cubic.inout":t=>t<.5?4*t**3:1-(-2*t+2)**3/2,"power3.out":t=>1-(1-t)**4,"quart.out":t=>1-(1-t)**4,"power3.in":t=>t**4,"quart.in":t=>t**4,"power3.inout":t=>t<.5?8*t**4:1-(-2*t+2)**4/2,"quart.inout":t=>t<.5?8*t**4:1-(-2*t+2)**4/2,"power4.out":t=>1-(1-t)**5,"quint.out":t=>1-(1-t)**5,"power4.in":t=>t**5,"quint.in":t=>t**5,"sine.out":t=>Math.sin(t*Math.PI/2),"sine.in":t=>1-Math.cos(t*Math.PI/2),"sine.inout":t=>-(Math.cos(Math.PI*t)-1)/2,"expo.out":t=>t===1?1:1-2**(-10*t),"back.out":t=>1+2.70158*(t-1)**3+1.70158*(t-1)**2};function Ot(t,n){let e=Math.max(0,Math.min(1,n));if(!t)return e;let i=_t[t.toLowerCase().trim()];return i?i(e):e}function Ht(t,n,e){if(e<=0)return 0;let i=e>1?n/(e-1):.5,r=t.offset??0,c=(t.rangeStart??0)+r,d=(t.rangeEnd??1)+r,u=0;d<=c?u=i>=c?1:0:u=Math.max(0,Math.min(1,(i-c)/(d-c)));let m=u*u*(3-2*u);return Ot(t.easing??"power2.out",m)}function kt(t,n,e,i,r,c){let d=Math.max(1,e.charsCount??1),u=Math.max(1,e.wordsCount??1),m=Math.max(1,e.linesCount??1),C=t.selectors??[];if(C.length===0)return 1;let h=0;for(let k=0;k<C.length;k++){let a=C[k],A=0;if(a.type==="range"){let w=d,v=n.charIndex??0;a.basedOn==="words"?(w=u,v=n.wordIndex??0):a.basedOn==="lines"&&(w=m,v=n.lineIndex??0),a.randomizeOrder&&(v=bt(w,a.randomSeed??12345)[v%w]??v);let X=a.units==="index"?v:w>1?v/(w-1):.5,j=a.start??t.rangeStart??0,P=a.end??t.rangeEnd??(a.units==="index"?w:1),H=a.offset??t.offset??0;A=xt(X,j,P,H,a.shape??"smooth",a.easeHigh??0,a.easeLow??0)*(a.amount??1)}else if(a.type==="wiggly"){let w=c>0?r/c:0,v=(a.temporalPhaseDeg??0)*Math.PI/180,X=(a.spatialPhaseDeg??0)*Math.PI/180,j=w*(a.wigglesPerSecond??2)*2*Math.PI+v+X,P=yt(a.randomSeed??42,n.charIndex??0,j,a.correlation??.5),H=a.minAmount??-1,ee=a.maxAmount??1;A=H+(P*.5+.5)*(ee-H)}else if(a.type==="signal")A=Xt(i,a.signalId,r,c,a.phaseSpread??0,n.charIndex??0)*(a.multiplier??1)+(a.offset??0);else if(a.type==="expression"){let w=a,v=c>0?r/c:0,X={charIndex:n.charIndex??0,wordIndex:n.wordIndex??0,lineIndex:n.lineIndex??0,totalChars:d,totalWords:u,totalLines:m,frame:r,fps:c,time:v,signals:i};A=ft(w.expression,X)*(w.amount??1)}k===0?h=A:h=wt(h,A,a.mode??"add")}return h}function At(t,n,e,i,r=!1,c=0,d=24,u){if(!t||t.length===0)return i;let{transX:m,transY:C,rot:h,scale:k,scaleX:a,scaleY:A,opacity:w,blurAmount:v}=i,X=i.transZ??0,j=i.color,P=i.chromaticShift??0,H=i.dissolveProgress??0,ee=i.vfxParam??0,oe=i.skewM01??0,x=i.skewM10??0,fe=i.anchorConfig,le=i.fillOpacity??1,re=i.strokeWidth,U=i.strokeColor,ue=i.longShadow,he=i.lineSpacing,Z=i.lineAnchor,me=r?1:Math.PI/180,z=Math.max(1,e.charsCount??1),K=Math.max(1,e.wordsCount??1),ae=Math.max(1,e.linesCount??1);for(let ne of t)if("selectors"in ne&&Array.isArray(ne.selectors)){let ie=ne,l=ie.props??{},te=kt(ie,n,{charsCount:z,wordsCount:K,linesCount:ae},u,c,d),f=te,Y=1,q=1;if(l.spring){let b=(n.charIndex??0)*.035,D=Math.max(0,(d>0?c/d:0)-b);q=$e(D,l.spring.stiffness,l.spring.damping,l.spring.mass,l.spring.initialVelocity),Y=1-q,f=te*Y}if(f!==0||te!==0){if(l.x!==void 0&&(m+=l.x*f),l.y!==void 0&&(C+=l.y*f),l.scale!==void 0&&(k*=1+(l.scale-1)*f),l.scaleX!==void 0&&(a*=1+(l.scaleX-1)*f),l.scaleY!==void 0&&(A*=1+(l.scaleY-1)*f),l.rotation!==void 0&&(h+=l.rotation*me*f),l.rotationX!==void 0){let b=l.rotationX*f*Math.PI/180;A*=Math.max(.08,Math.abs(Math.cos(b)))}if(l.rotationY!==void 0){let b=l.rotationY*f*Math.PI/180;a*=Math.max(.08,Math.abs(Math.cos(b)))}if(l.rotationZ!==void 0&&(h+=l.rotationZ*me*f),l.translateZ!==void 0&&(X+=l.translateZ*f),l.skew!==void 0){let[,b,D]=lt(l.skew*f,l.skewAxis??0);oe+=b,x+=D}if(l.formation!==void 0){let b=ht(n.charIndex??0,z,l.formation),D=e.layoutCenter?.x??0,Q=e.layoutCenter?.y??0,xe=n.x??0,We=n.y??0,Ee=D+b.x,Xe=Q+b.y;if(m+=(Ee-xe)*f,C+=(Xe-We)*f,X+=b.z*f,b.rotX){let ye=b.rotX*f*Math.PI/180;A*=Math.max(.5,Math.abs(Math.cos(ye)))}if(b.rotY){let ye=b.rotY*f*Math.PI/180;a*=Math.max(.5,Math.abs(Math.cos(ye)))}b.rotZ&&(h+=b.rotZ*Math.PI/180*f),b.depthOpacity!==void 0&&(w*=1+(b.depthOpacity-1)*f)}if(l.anchor!==void 0&&(fe=l.anchor),l.opacity!==void 0&&(w*=1+(l.opacity-1)*f),l.blur!==void 0&&(v=Math.max(v,l.blur*f)),l.chromaticGlitch!==void 0){let b=(l.chromaticGlitch.redShiftX??0)*Math.max(0,f),D=l.chromaticGlitch.sliceProbability??.35,Q=Math.sin(c*17.13+(n.charIndex??0)*43.17),xe=Math.abs(Q-Math.floor(Q));xe<D&&(b+=(l.chromaticGlitch.sliceAmplitude??12)*(xe*2-1)),P+=b}if(l.particleDissolve!==void 0&&(H=Math.max(H,(l.particleDissolve.progress??0)*(l.spring?q:Math.max(0,f)))),l.longShadow!==void 0&&(ue=l.longShadow),l.audioSpectrum!==void 0){let b=gt(n.charIndex??0,z,l.audioSpectrum,u,c,d);l.audioSpectrum.scaleAxis==="scaleY"?A*=b:l.audioSpectrum.scaleAxis==="scaleX"?a*=b:k*=b}if(l.stroke!==void 0&&(l.stroke.fillOpacity!==void 0&&(le*=1-f+l.stroke.fillOpacity*f),l.stroke.strokeWidth!==void 0&&(re=(l.stroke.strokeWidth??0)*f),l.stroke.strokeColor&&(U=l.stroke.strokeColor)),l.lineSpacing!==void 0&&(he=l.lineSpacing*(l.spring?q:f),Z=l.lineAnchor??0),l.fillColor){let b=de(l.fillColor),D=j??{r:1,g:1,b:1,a:1},Q=te;j={r:D.r*(1-Q)+b.r*Q,g:D.g*(1-Q)+b.g*Q,b:D.b*(1-Q)+b.b*Q,a:D.a*(1-Q)+b.a*Q}}}}else{let ie=ne,l=n.charIndex??n.unitIndex??0,te=Math.max(1,e.charsCount??1);ie.unit==="word"?(l=n.wordIndex??0,te=Math.max(1,e.wordsCount??1)):ie.unit==="line"&&(l=n.lineIndex??0,te=Math.max(1,e.linesCount??1));let f=Ht(ie,l,te),Y=ie.transform;if(!Y||f<=0)continue;Y.x!==void 0&&(m+=Y.x*f),Y.y!==void 0&&(C+=Y.y*f),Y.rotation!==void 0&&(h+=Y.rotation*me*f),Y.scale!==void 0&&(k*=1+(Y.scale-1)*f),Y.scaleX!==void 0&&(a*=1+(Y.scaleX-1)*f);let q=1+((Y.scaleY??1)-1)*f;if(Y.rotationX){let b=Y.rotationX*f*Math.PI/180;q*=Math.cos(b)}if(A*=q,Y.opacity!==void 0&&(w*=1+(Y.opacity-1)*f),Y.blur!==void 0&&(v=Math.max(v,Y.blur*f)),Y.color){let b=de(Y.color),D=j??{r:1,g:1,b:1,a:1};j={r:D.r*(1-f)+b.r*f,g:D.g*(1-f)+b.g*f,b:D.b*(1-f)+b.b*f,a:D.a*(1-f)+b.a*f}}}return{transX:m,transY:C,transZ:X,rot:h,scale:k,scaleX:a,scaleY:A,opacity:Math.max(0,Math.min(1,w)),blurAmount:v,color:j,chromaticShift:P,dissolveProgress:H,vfxParam:ee,skewM01:oe,skewM10:x,anchorConfig:fe,fillOpacity:Math.max(0,Math.min(1,le)),strokeWidth:re,strokeColor:U,longShadow:ue,lineSpacing:he,lineAnchor:Z}}function pt(t,n,e){let i=e.fontFamily??"Inter";if(L.isFailed(i)&&(i="Inter"),L.isFailed(i))return;let r=e.fontSize??48,c=L.getFont(i,e.fontWeight,t.device,void 0,r,e.variableAxes),d=e.stroke&&e.strokeWidth&&e.strokeWidth>0,u=null;if(c&&d&&t.device){let o=e.strokeAlign==="outside"?2:1,y=(e.strokeWidth??0)*o*(c.unitsPerEm/r);u=L.getFont(i,e.fontWeight,t.device,y,r,e.variableAxes)??null}if(!c?.curvesTex||d&&!u?.curvesTex){let o=Ge(i);L.preloadSlugFont(t.device,i,o,void 0,e.renderId).catch(s=>{console.warn(`[drawParagraphNode] Failed to load Slug font for ${i}:`,s)});return}let m=e.letterSpacing??0,C=e.lineHeight!==void 0&&e.lineHeight<10?e.lineHeight*r:e.lineHeight??r*1.2,h=e.align??"start",k=e.direction??"auto",a=e.isCaption?0:e.textBackgroundColor?e.padding??0:0,A=e.text,w;if(e.typewriter){let o=e.fps??24,s=e.durationMs!==void 0?Math.round(e.durationMs/1e3*o):60;w=St(e.text,e.frame??0,0,s,e.typewriter.cadence,e.typewriter.scramble,e.typewriter.cursor?.blinkFrequency??2,e.typewriter.granularity??"character",e.textProgress),A=w.currentDisplayString}let v=w&&e.spans?.length?Mt(Ct(e.spans),A):e.spans,X=e.pathOptions?{...e.pathOptions,firstMargin:e.firstMargin!==void 0?e.firstMargin:e.pathOptions.firstMargin,lastMargin:e.lastMargin!==void 0?e.lastMargin:e.pathOptions.lastMargin,baselineShift:e.baselineShift!==void 0?e.baselineShift:e.pathOptions.baselineShift}:void 0,j,P;e.width!=null?(j=Math.max(0,e.width-a*2),P=be.measure(A,c,r,m,C,j,v,k)):(P=be.measure(A,c,r,m,C,void 0,v,k),j=P.width);let H=e.isCaption?e.dstRect.width:j,ee=e.animation?.applyBy??"word",x=(h==="left"||h==="start"&&!P.layout?.hasRtl)&&(H===void 0||H===j)&&!X&&ee==="word"&&!!P?.layout&&P.layout?P.layout:be.layout(A,c,r,m,C,j,h,ee,H,X,v,k),fe=e.verticalAlign??"top",le=e.height!=null&&!X&&!e.isCaption?e.height-(P.height+a*2):0,re={...e.dstRect,y:e.dstRect.y+(fe==="middle"?le/2:fe==="bottom"?le:0)},U=e.animation,ue=e.fontWeight==="bold"||typeof e.fontWeight=="number"&&e.fontWeight>=600||typeof e.fontWeight=="string"&&parseInt(e.fontWeight,10)>=600,he="hasTrueWeight"in c&&!!c.hasTrueWeight,Z=c;if(ue&&!he&&t.device){let o=ct(e.fontWeight),s=Math.max(0,(o-400)/500)*.055*r;if(s>0){let y=s*(c.unitsPerEm/r),S=L.getFont(i,400,t.device,y,r,e.variableAxes);S?.curvesTex&&(Z=S)}}let me=x.glyphs.length,z=e.elapsedMs!==void 0?e.elapsedMs:e.frame!==void 0&&e.fps!==void 0?e.frame/e.fps*1e3:0,K=e.durationMs!==void 0?e.durationMs:3e3,ae=e.isCaption?U?.in==="none"||!U?.in?0:K:U?.entranceMs?U.entranceMs:U?.in==="none"||!U?.in?0:K,ne=e.isCaption?0:U?.exitMs?U.exitMs:U?.out==="none"||!U?.out?0:K,ie=U?.in||"none",l=U?.out||"none",te=U?.kinetic||"none",f=U?.smoothing!==!1,Y=0;for(let o of x.glyphs)!o.isSpace&&o.unitIndex>Y&&(Y=o.unitIndex);let q=Y+1,b=te==="shuffle"||ie==="shuffle"?Gt(q,e.text):Array.from({length:q},(o,s)=>s);if(e.textBackgroundColor){let o=e.strokeRadius??e.borderRadius??8,s=0,y=e.opacity??1;if(e.isVideoMode===!1)s=y;else{for(let S of x.glyphs){if(S.isSpace)continue;let I=S.unitIndex,T=b[I]??I,{p_in:M,p_out:g}=je(z,K,ae,ne,T,q,!0),_=_e(M,g,f,ie,l,te,z,K,ae,ne,T,y,!1);_.visible&&_.opacity>s&&(s=_.opacity)}for(let S of x.emojis){let I=e.animation?.applyBy==="line"?0:Math.floor(S.x/(r+m)),T=b[I%q]??0,{p_in:M,p_out:g}=je(z,K,ae,ne,T,q,!0),_=_e(M,g,f,ie,l,te,z,K,ae,ne,T,y,!0);_.visible&&_.opacity>s&&(s=_.opacity)}}if(s>.001){let S=e.width!=null?e.width:P.width+a*2,I=e.height!=null?e.height:P.height+a*2;t.renderer.drawRRect(n,{rect:{x:e.dstRect.x,y:e.dstRect.y,width:S,height:I},rx:o,ry:o},e.textBackgroundColor,{opacity:s})}}if(x.marks&&x.marks.length>0)for(let o of x.marks){let s=o.borderRadius??4;t.renderer.drawRRect(n,{rect:{x:e.dstRect.x+a+o.x,y:re.y+a+o.y,width:o.width,height:o.height},rx:s,ry:s},o.background,{opacity:e.opacity??1})}let D=de(e.color??"white"),Q=de(e.highlightColor??"yellow"),xe=new Float32Array(x.glyphs.length),We=0,Ee=0;if(x.glyphs.length>0){let o=x.glyphs.filter(s=>!s.isSpace);o.length>0&&(We=(o[0].x+o[o.length-1].x)/2,Ee=(o[0].y+o[o.length-1].y)/2)}let Xe={charsCount:x.charsCount,wordsCount:x.wordsCount,linesCount:x.linesCount,layoutCenter:{x:We,y:Ee}};if(e.animators&&e.animators.length>0){for(let o of e.animators)if("selectors"in o&&Array.isArray(o.selectors)){let s=o;if(s.props?.tracking!==void 0&&s.props.tracking!==0){let y=s.props.tracking,S=new Map;for(let I=0;I<x.glyphs.length;I++){let M=x.glyphs[I].lineIndex??0,g=S.get(M);g||(g=[],S.set(M,g)),g.push(I)}for(let I of S.values()){let T=new Map;for(let F of I){let O=x.glyphs[F].charIndex,$=T.get(O);$?$.push(F):T.set(O,[F])}let M=Array.from(T.values()),g=F=>{let O=x.glyphs[F[0]];return O.clusterX??O.x};M.sort((F,O)=>g(F)-g(O));let _=[];for(let F of M){let O=x.glyphs[F[0]],$=kt(s,O,Xe,e.signals,e.frame??0,e.fps??24);_.push($)}let G=0;for(let F=0;F<M.length-1;F++)G+=y*_[F];let p=x.glyphs[I[0]]?.baseRtl??!1,R=h==="start"?p?"right":"left":h==="end"?p?"left":"right":h,N=0;for(let F=0;F<M.length;F++){let O=N;R==="center"?O-=G*.5:R==="right"&&(O-=G);for(let $ of M[F])xe[$]+=O;F<M.length-1&&(N+=y*_[F])}}}}}let ye=new Float32Array(Math.max(1024,me*24)),Ke=(o,s)=>s.cp.glyphId!==void 0&&(s.font??c).fontFamily===o.fontFamily,Qe=new Set,vt=o=>{if(Qe.has(o))return;Qe.add(o);let s=[];for(let y of x.glyphs){let S=y.cp.glyphId;S!==void 0&&(y.font??c)!==o&&Ke(o,y)&&!o.glyphs?.has(S)&&s.push(S)}s.length>0&&st.ensureGlyphs(o,s)},Pt=(o,s)=>{if((s.font??c)===o)return s.cp;if(Ke(o,s)){let y=o.glyphs?.get(s.cp.glyphId);if(y)return y}return o.codePoints.get(s.cp.codePoint)||s.cp},Se=(o,s,y=0,S=0,I,T)=>{let M=me*24;ye.length<M&&(ye=new Float32Array(M));let g=ye,_=0;vt(o);for(let G=0;G<x.glyphs.length;G++){let p=x.glyphs[G];if(p.isSpace||T&&!T(p))continue;let R=Pt(o,p);if(!R||R.width===0||R.height===0)continue;let N=p.unitIndex,F=b[N]??N,O=e.opacity??1,$=0,J=0,se=0,V=1,Ce=I!==void 0?I:0;if(e.isVideoMode!==!1){let{p_in:W,p_out:E}=je(z,K,ae,ne,F,q,!0),B=_e(W,E,f,ie,l,te,z,K,ae,ne,F,e.opacity??1,!1);if(!B.visible)continue;O=B.opacity,$=B.transX,J=B.transY,se=B.rot,V=B.scale,I===void 0&&(Ce=B.blurAmount)}let Me=1,Ue=1,tt,De=0,nt=0,it=0,ot=0,Ne=0,Le=0,Be,rt=1;if(e.animators&&e.animators.length>0){let W=At(e.animators,p,Xe,{transX:$,transY:J,rot:se,scale:V,scaleX:Me,scaleY:Ue,opacity:O,blurAmount:Ce,color:D},!1,e.frame??0,e.fps??24,e.signals);$=W.transX,J=W.transY,se=W.rot,V=W.scale,Me=W.scaleX,Ue=W.scaleY,O=W.opacity,I===void 0&&(Ce=W.blurAmount),W.color&&(tt=W.color),W.lineSpacing!==void 0&&(J+=dt(p.lineIndex,x.linesCount,W.lineSpacing,W.lineAnchor??0)),De=W.transZ??0,nt=W.chromaticShift??0,it=W.dissolveProgress??0,ot=W.vfxParam??0,Ne=W.skewM01??0,Le=W.skewM10??0,Be=W.anchorConfig,rt=W.fillOpacity??1}$+=xe[G];let ze=p.x;if(e.marquee){let W=(e.frame??0)/(e.fps??24),E=x.glyphs.length>0?x.glyphs[x.glyphs.length-1].x+r:e.dstRect.width;ze=mt(p.x,e.dstRect.width,E,e.marquee,W)}let Rt=e.highlightWordIndex!==void 0&&p.wordIndex===e.highlightWordIndex,Ft=p.fill?de(p.fill):void 0,Ye=tt??s??(Rt?Q:Ft??D);(W=>{let E=_*24,B=p.fontScale??r/o.unitsPerEm,Ie=R.width*B/2*Me,Ae=R.height*B/2*Ue;if(De!==0){let ce=Math.max(100,1200-De),ge=ce>1?1200/ce:1;Ie*=ge,Ae*=ge}let ke=se+(p.pathTangentAngleRad??0),pe,Te;if(p.pathTangentAngleRad!==void 0){let ve=R.bearingX*B+Ie,ce=-R.bearingY*B+Ae,ge=Math.cos(ke),Pe=Math.sin(ke),Ve=ve*ge-ce*Pe,qe=ve*Pe+ce*ge;pe=ze+Ve+e.dstRect.x+a+y+W,Te=p.y+qe+re.y+a+S}else pe=ze+R.bearingX*B+Ie+e.dstRect.x+a+y+W,Te=p.y-R.bearingY*B+Ae+re.y+a+S;if(Be){let ve={width:R.width*B,height:R.height*B,ascender:(o.ascender??800)*B,descender:(o.descender??-200)*B},ce=ut(ve,Be),ge=Math.cos(ke),Pe=Math.sin(ke),Ve=ce.px*(1-ge*V)+ce.py*(Pe*V),qe=-ce.px*(Pe*V)+ce.py*(1-ge*V);pe+=Ve,Te+=qe}(Ne!==0||Le!==0)&&(pe+=Ae*Ne,Te+=Ie*Le),g[E+0]=Ie,g[E+1]=Ae,g[E+2]=pe,g[E+3]=Te,g[E+4]=R.width,g[E+5]=R.height,g[E+6]=R.width/R.bandDimX,g[E+7]=R.height/R.bandDimY,g[E+8]=R.bandCount-1,g[E+9]=R.bandCount-1,g[E+10]=R.bandsTexCoordX,g[E+11]=R.bandsTexCoordY,g[E+12]=ke,g[E+13]=V,g[E+14]=$,g[E+15]=J,g[E+16]=Ye.r,g[E+17]=Ye.g,g[E+18]=Ye.b;let Wt=p.opacity??1;g[E+19]=Ye.a*O*rt*Wt,g[E+20]=Ce,g[E+21]=nt,g[E+22]=it,g[E+23]=ot,_++})(0)}return{data:g,visibleCount:_}};if(e.shadows&&e.shadows.length>0)for(let o of e.shadows){let s=de(o.color),y=o.offset?.x??0,S=o.offset?.y??0,I=o.blurRadius??0,{data:T,visibleCount:M}=Se(Z,s,y,S,I);if(M>0){let g=T.subarray(0,M*24),G=e.fontStyle==="italic"?.21:0;t.renderer.slugPipeline.draw(n,t.renderer.getTransformStack(),Z,g,M,o.color,t.renderer.getSurfaceWidth(),t.renderer.getSurfaceHeight(),{opacity:e.opacity??1,transform:e.matrix,customParam:G})}}let Oe=e.animators?.find(o=>"props"in o&&o.props?.longShadow&&(o.props?.longShadow?.length??0)>0);if(Oe&&"props"in Oe){let o=Oe,s=o.props?.longShadow;if(s){let y=o.props?.spring,S=s.length,I=s.angle;if(y){let G=(e.frame??0)/(e.fps??30),p=$e(G,y.stiffness,y.damping,y.mass,y.initialVelocity);S=Math.max(0,s.length*p),I=s.angle+(1-Math.min(1,p))*25}else{let p=1-(1-Math.min(1,(e.frame??0)/36))**3;S=s.length*p}let T=I*Math.PI/180,M=Math.max(4,Math.min(64,s.steps??16)),g=S/M,_=de(s.color??"#000000");for(let G=1;G<=M;G++){let p=G*g,R=p*Math.cos(T),N=p*Math.sin(T),F=G/M,O=(s.startOpacity??.8)*(1-F)+(s.endOpacity??0)*F,$={..._,a:_.a*O},{data:J,visibleCount:se}=Se(c,$,R,N,0);if(se>0){let V=J.subarray(0,se*24),Me=e.fontStyle==="italic"?.21:0;t.renderer.slugPipeline.draw(n,t.renderer.getTransformStack(),Z,V,se,s.color??"black",t.renderer.getSurfaceWidth(),t.renderer.getSurfaceHeight(),{opacity:(e.opacity??1)*O,transform:e.matrix,customParam:Me})}}}}if(d&&u){let o=de(e.stroke),{data:s,visibleCount:y}=Se(u,o);if(y>0){let S=s.subarray(0,y*24),T=e.fontStyle==="italic"?.21:0;t.renderer.slugPipeline.draw(n,t.renderer.getTransformStack(),u,S,y,e.stroke??"#000000",t.renderer.getSurfaceWidth(),t.renderer.getSurfaceHeight(),{opacity:e.opacity??1,transform:e.matrix,customParam:T})}}let He=new Set,Je=!1;for(let o of x.glyphs)o.font&&o.font!==Z&&(He.add(o.font),Je=!0);let et=e.fontStyle==="italic"?.21:0;if(Je){He.add(Z);for(let o of He){if(t.device&&!o.curvesTex&&L.ensureTextures(t.device,o),!o.curvesTex)continue;let{data:s,visibleCount:y}=Se(o,void 0,0,0,void 0,S=>(S.font??Z)===o);if(y>0){let S=s.subarray(0,y*24);t.renderer.slugPipeline.draw(n,t.renderer.getTransformStack(),o,S,y,e.color??"white",t.renderer.getSurfaceWidth(),t.renderer.getSurfaceHeight(),{opacity:e.opacity??1,transform:e.matrix,customParam:et})}}}else{let{data:o,visibleCount:s}=Se(Z);if(s>0){let y=o.subarray(0,s*24);t.renderer.slugPipeline.draw(n,t.renderer.getTransformStack(),Z,y,s,e.color??"white",t.renderer.getSurfaceWidth(),t.renderer.getSurfaceHeight(),{opacity:e.opacity??1,transform:e.matrix,customParam:et})}}for(let o of x.emojis){let s=e.animation?.applyBy==="line"?0:Math.floor(o.x/(r+m)),y=b[s%q]??0,S=e.opacity??1,I=0,T=0,M=0,g=1;if(e.isVideoMode!==!1){let{p_in:J,p_out:se}=je(z,K,ae,ne,y,q,!0),V=_e(J,se,f,ie,l,te,z,K,ae,ne,y,e.opacity??1,!0);if(!V.visible)continue;S=V.opacity,I=V.transX,T=V.transY,M=V.rot,g=V.scale}if(e.animators&&e.animators.length>0){let J=At(e.animators,{charIndex:s,wordIndex:0,lineIndex:o.lineIndex},{charsCount:x.charsCount,wordsCount:x.wordsCount,linesCount:x.linesCount},{transX:I,transY:T,rot:M,scale:g,scaleX:1,scaleY:1,opacity:S,blurAmount:0},!0);I=J.transX,T=J.transY,M=J.rot,g=J.scale,S=J.opacity}let _=jt(t.device,o.char,o.size),G=o.size*1.15,p=(G-o.size)/2,R={x:e.dstRect.x+o.x+a-p,y:re.y+o.y+a-p,width:G,height:G},N=new DOMMatrix,F=R.x+G/2,O=R.y+G/2;N=N.translate(F,O),(I!==0||T!==0)&&(N=N.translate(I,T)),M!==0&&(N=N.rotate(M)),g!==1&&(N=N.scale(g)),N=N.translate(-F,-O);let $=e.matrix?e.matrix.multiply(N):N;t.renderer.drawTextureRegion(n,_,{x:0,y:0,width:_.width,height:_.height},R,{opacity:S,transform:$})}if(e.typewriter?.cursor?.enabled&&w&&w.isCursorVisible&&(!e.typewriter.cursor.hideOnComplete||w.visibleChars<e.text.length)){let o=e.typewriter.cursor,s=o.color??e.color??"white",y=o.width??2.5,S=o.spacing??3,I=e.dstRect.x+a,T=re.y+a;if(x.glyphs.length>0){let M=x.glyphs[x.glyphs.length-1],g=r/c.unitsPerEm,_=M.clusterAdvance??(M.cp?.advanceWidth||0)*g,G=M.clusterX??M.x,p=o.style==="underscore"||o.style==="block"?r*.5:y;I=e.dstRect.x+a+(M.rtl?G-S-p:G+_+S),T=M.y+re.y+a}o.style==="underscore"?t.renderer.drawRect(n,{x:I,y:T,width:r*.5,height:y},s,0,{opacity:e.opacity??1}):o.style==="block"?t.renderer.drawRect(n,{x:I,y:T-r*.8,width:r*.5,height:r},s,0,{opacity:e.opacity??1}):t.renderer.drawRect(n,{x:I,y:T-r*.8,width:y,height:r},s,0,{opacity:e.opacity??1})}}we();function Tt(t){let e=t.trim().replace(",",".").split(":");if(e.length<2||e.length>3)throw new Error(`Invalid timestamp: ${t}`);if(e.length===2){let[d,u]=e;return parseInt(d,10)*60+parseFloat(u)}let[i,r,c]=e;return parseInt(i,10)*3600+parseInt(r,10)*60+parseFloat(c)}function Ut(t){let n=t.replace(/\r\n/g,`
|
|
2
2
|
`).split(`
|
|
3
3
|
`),e=[];for(let i=0;i<n.length;i++){let r=n[i],c=n[i+1];if(r?.match(/([0-9]+)/)&&c?.includes(" --> ")){let d=c.split(" --> "),u=Tt(d[0]),m=Tt(d[1]);e.push({text:"",startMs:u*1e3,endMs:m*1e3,confidence:1,timestampMs:(u+m)/2*1e3})}else r?.includes(" --> ")||(r?.trim()===""?e.length>0&&(e[e.length-1].text=e[e.length-1].text.trim()):e.length>0&&(e[e.length-1].text+=r+`
|
|
4
4
|
`))}return{captions:e.map(i=>({...i,text:i.text.trimEnd()}))}}function Dt(t,n,e=!0){if(!t||t.length===0)return null;let i=t.find(r=>n>=r.startMs&&n<r.endMs);return i||(!e&&t.length>0?t[0]:null)}function un(t,n,e){if(!t||t.length===0)return 160;let i=n.fontSize??48,r=n.letterSpacing??0,c=n.lineHeight!==void 0&&n.lineHeight<10?n.lineHeight*i:n.lineHeight??i*1.2,d=n.padding??(n.background?12:0),u=e?Math.max(10,e-d*2):void 0,m=n.fontFamily??"Inter";L.isFailed(m)&&(m="Inter");let C=L.getFont(m,n.fontWeight,null,void 0,i),h=0;for(let k of t){if(!k.text)continue;let a=0;if(C&&C.codePoints){let w=be.measure(k.text,C,i,r,c,u);a=Math.ceil(w.height)}else{let w=k.text.split(`
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{a as et,b as rt,c as yt}from"./chunk-JAAFSVAG.mjs";import{c as mt,d as pt,e as A}from"./chunk-CSLSWUUT.mjs";import{b as tt}from"./chunk-
|
|
1
|
+
import{a as et,b as rt,c as yt}from"./chunk-JAAFSVAG.mjs";import{c as mt,d as pt,e as A}from"./chunk-CSLSWUUT.mjs";import{b as tt}from"./chunk-FRJPXWAG.mjs";import{e as ft}from"./chunk-GXFYRMJZ.mjs";import{B as gt,c as dt,e as xt,y as U,z as C}from"./chunk-64L4GUOF.mjs";import{a as D}from"./chunk-354GX6IB.mjs";import{g as b}from"./chunk-Y4NIR77Z.mjs";b();function Nt(s,t=180,e=12,r=1,i=0){if(s<=0)return 0;let a=Math.sqrt(Math.max(1e-4,t)/Math.max(1e-4,r)),o=e/(2*Math.sqrt(Math.max(1e-4,t*r)));if(o<1){let l=a*Math.sqrt(1-o*o),n=Math.exp(-o*a*s),u=-1,c=(i-o*a*-1)/Math.max(1e-6,l),m=n*(u*Math.cos(l*s)+c*Math.sin(l*s));return Math.min(2,Math.max(-1,1+m))}else if(Math.abs(o-1)<1e-4){let l=Math.exp(-a*s);return 1+(-1+(i-a)*s)*l}else{let l=-a*(o-Math.sqrt(o*o-1)),n=-a*(o+Math.sqrt(o*o-1)),u=(i+n)/Math.max(1e-6,l-n),c=-1-u;return 1+(u*Math.exp(l*s)+c*Math.exp(n*s))}}function jt(s,t=0){if(s===0)return[1,0,0,1];let e=s*Math.PI/180,r=t*Math.PI/180,i=Math.cos(r),a=Math.sin(r),o=Math.tan(e),l=1-o*i*a,n=o*i*i,u=-o*a*a,c=1+o*a*i;return[l,n,u,c]}function Ht(s,t){let e=t.anchorX,r=t.anchorY;switch(t.preset){case"center":e=.5,r=.5;break;case"baseline":e=.5,r=s.height>0?s.ascender/s.height:.8;break;case"bottom_left":e=0,r=1;break;case"bottom_center":e=.5,r=1;break;case"bottom_right":e=1,r=1;break;case"top_left":e=0,r=0;break;case"top_center":e=.5,r=0;break;case"top_right":e=1,r=0;break;default:e=.5,r=.5;break}let i=(e-.5)*s.width+t.offsetX,a=(r-.5)*s.height+t.offsetY;return{px:i,py:a}}function Yt(s,t,e,r=0){if(e===0||t<=1)return 0;let i=r*(t-1);return(s-i)*e}function Xt(s,t){try{let e={textIndex:t.charIndex,textTotal:Math.max(1,t.totalChars),wordIndex:t.wordIndex,wordTotal:Math.max(1,t.totalWords),lineIndex:t.lineIndex,lineTotal:Math.max(1,t.totalLines),time:t.time,frame:t.frame,fps:t.fps,sin:Math.sin,cos:Math.cos,tan:Math.tan,exp:Math.exp,log:Math.log,abs:Math.abs,min:Math.min,max:Math.max,sqrt:Math.sqrt,pow:Math.pow,floor:Math.floor,ceil:Math.ceil,round:Math.round,PI:Math.PI,clamp:(l,n,u)=>Math.max(n,Math.min(u,l)),noise:l=>{let n=Math.sin(l*12.9898+t.frame*.1)*43758.5453;return(n-Math.floor(n))*2-1},signals:t.signals??{}},r=Object.keys(e),i=Object.values(e),a=new Function(...r,`return (${s});`),o=Number(a(...i));return Number.isFinite(o)?Math.max(-1,Math.min(1,o)):0}catch{return 0}}function Qt(s,t,e){let r=Math.max(1,t),i=r>1?s/(r-1):0,a=e.totalAngle*Math.PI/180,o=i*a,l=0,n=0,u=0,c=0,m=0,h=0,d=1;switch(e.mode){case"cylinder":{let y=e.totalAngle<360?(i-.5)*a:o;e.axis==="y"?(l=e.radius*Math.sin(y),u=e.radius*Math.cos(y)-e.radius,m=y*180/Math.PI):e.axis==="x"?(n=e.radius*Math.sin(y),u=e.radius*Math.cos(y)-e.radius,c=y*180/Math.PI):(l=e.radius*Math.sin(y),n=e.radius*Math.cos(y),h=y*180/Math.PI);let g=Math.cos(y);d=Math.max(.25,(g+1)*.5);break}case"vortex":{let g=-Math.PI/2+o,P=e.radius*(1-i*.35);l=P*Math.cos(g),n=P*Math.sin(g),u=-s*e.pitch||0;let v=-.35*e.radius/(a||1),M=v*Math.cos(g)-P*Math.sin(g),G=v*Math.sin(g)+P*Math.cos(g);h=Math.atan2(G,M)*180/Math.PI,d=Math.max(.65,1-i*.35);break}case"helix":{if(e.axis==="x"){let y=e.pitch>10?e.pitch:28;l=(s-(r-1)/2)*y,n=e.radius*Math.sin(o),u=e.radius*Math.cos(o)-e.radius,c=Math.sin(o)*22,d=.75+.25*((Math.cos(o)+1)*.5)}else{let y=(i-.5)*a;l=e.radius*Math.sin(y),n=(s-(r-1)/2)*e.pitch,u=e.radius*Math.cos(y)-e.radius,m=y*180/Math.PI;let g=Math.cos(y);d=Math.max(.65,(g+1)*.5)}break}default:{u=0;break}}let p=Math.max(100,e.perspective??1200),f=p-u,x=f>1?p/f:1;return{x:l*x,y:n*x,z:u,rotX:c,rotY:m,rotZ:h,scaleMult:Math.max(.1,x),depthOpacity:d}}function Kt(s,t,e,r,i){let o=(t>0?Math.max(t,e):e)+r.repeatGap;if(o<=0)return s;let l=r.velocity*i;if(r.snap?.enabled){let n=r.snap.stopIndex??0,u=r.snap.granularity==="character"?40:120,c=n*u,m=Math.min(1,i*1.5);l=l*(1-m)+c*m}if((r.direction==="right"||r.direction==="down")&&(l=-l),r.loop){let n=(s-l)%o;return n<-e?n+o:n}return s-l}function Zt(s,t,e,r,i,a){let o=Math.max(1,t),l=o>1?s/(o-1):.5,n=Math.max(20,e.minFreqHz),u=Math.max(n+100,e.maxFreqHz),c=n*(u/n)**l,m=0,h=ft.getChannelSamples(e.signalChannel,"primary");if(h&&h.length>0){let p=a>0?a:24,f=Math.max(0,Math.min(h.length-1,Math.round(i/p*24)));m=Number(h[f])||0}else if(r){let p=r[e.signalChannel];if(typeof p=="number")m=p;else if(typeof p=="function")m=Number(p(i,a,c))||0;else if(typeof p=="object"&&p!==null){let f=p;if(typeof f.get=="function")m=Number(f.get({frame:i,fps:a,freq:c}))||0;else if(Array.isArray(f.samples)||ArrayBuffer.isView(f.samples)){let x=f.samples,y=Math.floor(l*(x.length-1));m=Number(x[y])||0}}}let d=Math.max(0,Math.min(1,m));return e.minScale+d*(e.maxScale-e.minScale)}b();function ee(s,t,e,r,i,a=0,o=0){let l=t+r,n=e+r;if(n<=l)return 0;let u=(s-l)/(n-l),c=0;switch(i){case"square":c=u>=0&&u<=1?1:0;break;case"ramp_up":c=Math.max(0,Math.min(1,u));break;case"ramp_down":c=1-Math.max(0,Math.min(1,u));break;case"triangle":{u<0||u>1?c=0:u<.5?c=u*2:c=(1-u)*2;break}case"round":{u<0||u>1?c=0:c=.5-.5*Math.cos(u*2*Math.PI);break}case"smooth":{u<=0?c=0:u>=1?c=1:c=u*u*(3-2*u);break}}if(c>0&&c<1){if(a!==0&&c>.5){let m=(c-.5)*2,h=a>0?1+a*3:1/(1-a*3);c=.5+.5*m**h}if(o!==0&&c<=.5){let m=c*2,h=o>0?1+o*3:1/(1-o*3);c=.5*m**h}}return Math.max(0,Math.min(1,c))}function re(s,t,e,r){let i=(t*(1-r)+r)*12.9898+e+s*78.233,a=Math.sin(i)*43758.5453;return(a-Math.floor(a))*2-1}function ie(s,t){let e=t,r=()=>(e=(e*9301+49297)%233280,e/233280),i=Array.from({length:s},(a,o)=>o);for(let a=i.length-1;a>0;a--){let o=Math.floor(r()*(a+1)),l=i[a];i[a]=i[o],i[o]=l}return i}function ne(s,t,e){switch(e){case"add":return Math.max(0,Math.min(1,s+t));case"subtract":return Math.max(0,Math.min(1,s-t));case"intersect":return s*t;case"min":return Math.min(s,t);case"max":return Math.max(s,t);case"difference":return Math.abs(s-t)}}b();var Pt={ascii:"!@#$%^&*()_+{}[]:;<>,.?",alphanumeric:"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",matrix:"010101<>[]{}+=*~#%&!?/\\:;|X0123456789",binary:"01",hex:"0123456789ABCDEF"};function kt(s,t){return s==="custom"&&t&&t.length>0?t:Pt[s]??Pt.alphanumeric}function se(s,t,e,r,i={mode:"human",jitter:.35,commaPauseMultiplier:3,sentencePauseMultiplier:5.5,paragraphPauseMultiplier:7},a={enabled:!1,charset:"alphanumeric",scrambleFramesPerChar:8,highlightProbability:.25},o=2,l="character",n){if(n===void 0&&t<e)return{visibleChars:0,currentDisplayString:"",isCursorVisible:!1};let u=n!==void 0?Math.max(0,Math.min(1,n)):Math.min(1,(t-e)/Math.max(1,r)),c=n!==void 0?u*r:t-e,m=s.length,h=Math.floor(u*m);if(l==="word"){let f=s.split(/(\s+)/),x=[];f.forEach((g,P)=>{g.trim().length>0&&x.push(P)});let y=Math.min(x.length,Math.floor(u*(x.length+1)));if(y<=0)h=0;else{let g=x[Math.min(x.length-1,y-1)];h=f.slice(0,g+1).join("").length}}else if(l==="line"){let f=s.split(`
|
|
2
2
|
`),x=Math.min(f.length,Math.floor(u*(f.length+1)));h=f.slice(0,x).join(`
|
|
3
3
|
`).length}else if(i.mode==="human"&&u<1){let f=0,x=0,y=r/Math.max(1,m);for(let g=0;g<m;g++){let P=s[g],v=1,M=i.commaPauseMultiplier??3,G=i.sentencePauseMultiplier??5.5,B=i.paragraphPauseMultiplier??7,R=i.jitter??.35;P===","||P===";"?v=M:P==="."||P==="!"||P==="?"?v=G:P===`
|
|
4
4
|
`&&(v=B);let L=1+Math.sin(g*17.3+42)*.5*R,w=y*v*L;if(x+=w,x<=c)f=g+1;else break}h=Math.min(m,f)}let d=s.slice(0,h);if(a.enabled&&h<m&&h>0){let f=kt(a.charset,a.customCharset),x=Math.min(h,Math.max(1,a.scrambleFramesPerChar>4?3:2)),y="";for(let g=0;g<x;g++){let P=Math.floor(Math.abs(Math.sin(t*7+g*13))*f.length)%f.length;y+=f[P]}d=d.slice(0,d.length-x)+y}let p=!0;if(o>0){let f=Math.max(1,Math.round(30/(2*o)));p=Math.floor(c/f)%2===0}return{visibleChars:h,currentDisplayString:d,isCursorVisible:p}}b();b();var bt=`
|